スクレイピング
機械学習
python3
word2vec
ニューラル

「米津玄師どこにも行けない説」をword2vecと歌詞を駆使して「どこへ行きたい」か探ってみた

これは1人のアーティストに追ったドキュメンタリー記事です。

皆さんは米津玄師(ボカロ名義:ハチ)さんをご存知ですか?
わからないよ〜という方、昨年大ヒットしたアニメ映画「打ち上げ花火、下から見るか? 横から見るか?」はどうですか?

そう!つい口ずさんでしまうあのメロディーです♪その映画の主題歌である「打上花火」の作詞作曲・ボーカルを担当しているのが米津玄師さんになります。
女性ラッパーで有名なDAOKOさんとコラボして歌った素晴らしい楽曲でした。

ボーカロイドやダンスなど様々な舞台で活躍している米津玄師さんですが、自身の楽曲の歌詞についてSNSで話題になっています。

LOSER
->もうどこにも行けやしないのに夢見てお休み

パンダヒーロー
->どこにも行けない

Mrs.Pumpkinの滑稽な夢
->どこへ行こうか?

恋と灼熱
->何処にも行けない私をどうする?

メトロノーム
->どこへ行くのかな

スクリーンショット 2018-07-03 15.40.08.png
やけになってカートに乗る米津玄師さん

Oh...紹介したのはほんの一部に過ぎません。
twitterで調べてみたところ、23曲でどこにも行けていないという情報が得られました。

さらに調査してみると、

2017.6.21リリース「ピースサイン」より「ピースサイン」
->遠くへ行け遠くへ行けと僕の中で誰かが歌う

2017.11.1リリース「BOOTLEG」より「砂の惑星 (+初音ミク)」
->どこへも行けなくて堕落衛星

あー!せっかく行きかけたのに!!僕の中の誰かどうした!!!

ご覧の通り、なかなか一歩を踏み出すことができない米津玄師さん。
そこで私は1つの疑問を抱きました。

「一体彼はどこに行きたいんだ?」

ここまで読んできた読者の皆さんはきっと考えが一致していることでしょう。

今回は「僕の中の誰か」が「遠く(の特定された場所へ)行け」と伝えたと仮定し、word2vecと歌詞を組み合わせて真相を探ってみます!

はじめに

一体なぜword2vecと歌詞を組み合わせたのか、気になりますよね?(気にならない?)
理由を挙げると、

  • 本人と話す機会がない
    • SNSで聞きにくい(見ず知らずの私が「あなたはどこへ行きたいんだ」なんて聞けない)
    • 地理的な制約(沖縄に住んでいるため)
    • 連絡先がない
  • 歌詞から作曲者の伝えたい想いを汲み取ることができそう
  • 調査単語に近似する単語が調べられる
    • 複数の単語の組み合わせも可能
  • 純粋に彼がどこに行きたいか興味がある
  • 言語処理の勉強に役立つはず

他にもブログやラジオなどソースは豊富にありますが、まずは歌詞だけで結果が得られるのか検証してみます。決してめんどくさいわけじゃないよ。
皆さんもどのような場所に行きたいか想像しながら読み進めていきましょう!

次のセクションから技術的なアプローチがゴリゴリ続きます。

word2vecってなに?

単語の意味や文法などをニューラルネットワークを介し、ベクトルで表現することができるライブラリのことです。
例えば、

  • 対象単語から前後の文の推測が行える
    • [食べる] -> 木工用ボンドを[食べる]のが日課。
  • 対象単語のコサイン類似度が近似する単語を探し出す
    • [歌手] -> [シンガーソングライター], [ミュージシャン], [ギタリスト]
  • 単語同士の計算(加算と減算)が行える
    • [王様] - [男] + [女] = [女王]

※いずれも学習した単語に限る
これを使用することで「どこへ行く」を単語に分割してベクトル化し、近似する単語を調べることができます。

環境

  • macOS High Sierra 10.13.5
  • Python 3.6.5
  • mecab 0.996
  • janome 0.3.6

プロセス

4つのPythonスクリプトを作成して検証を行います。

  1. scraping.py...歌詞のスクレイピングを行う
  2. wakati.py...歌詞を単語ごとに分ける(分かち書きを行う)
  3. gen_model.py...分かち書きした歌詞をベクトルに変換する
  4. load_model.py...モデルの評価を行う

歌詞のスクレイピング

歌詞情報サイト歌ネット(utanet)から米津玄師さんの全73曲(2018.7.2現在)の歌詞を取得しました。
サーバに負荷を与えないため、1つの歌詞ごとに1秒待機するようにしています。今回は73曲なので1分ちょっとで終了しました。

※以前、マルコフ連鎖を使って名(迷?)言を作らせてみたに掲載したscraping.pyを関数化し、汎用性を高めたものです。

scraping.py
import os
import re
import bs4
import time
import requests
import pprint

def load(url):
    res = requests.get(url)
    res.raise_for_status()

    return res.text

def pickup_tag(html, find_tag):
    soup = bs4.BeautifulSoup(str(html), 'html.parser')
    paragraphs = soup.find_all(find_tag)

    return paragraphs

def parse(html):
    soup = bs4.BeautifulSoup(str(html), 'html.parser')
    # htmlタグの排除
    kashi_row = soup.getText()
    kashi_row = kashi_row.replace('\n', '')
    kashi_row = kashi_row.replace(' ', '')

    # 英数字の排除
    kashi_row = re.sub(r'[a-zA-Z0-9]', '', kashi_row)
    # 記号の排除
    kashi_row = re.sub(r'[ <>♪`‘’“”・…_!?!-/:-@[-`{-~]', '', kashi_row)
    # 注意書きの排除
    kashi = re.sub(r'注意:.+', '', kashi_row)

    return kashi

def main():
    with open('yonedu_kashi.txt', 'a') as f:
        # アーティストページのアドレス
        url = f'https://www.uta-net.com/artist/12795/'
        # 曲ページの先頭アドレス
        base_url = f'https://www.uta-net.com'

        # ページの取得
        html = load(url)

        # 曲ごとのurlを格納
        musics_url = []
        # 歌詞を格納
        kashis = ''

        """ 曲のurlを取得 """
        # td要素の取り出し
        for td in pickup_tag(html, 'td'):
            # a要素の取り出し
            for a in pickup_tag(td, 'a'):
                # href属性にsongを含むか
                if 'song' in a.get('href'): 
                    # urlを配列に追加
                    musics_url.append(base_url + a.get('href'))
        # pprint.pprint(musics_url)

        """ 歌詞の取得 """
        for i, page in enumerate(musics_url):
            print('{}曲目:{}'.format(i + 1, page))
            html = load(page)
            for div in pickup_tag(html, 'div'):
                # id検索がうまく行えなかった為、一度strにキャスト
                div = str(div)
                # 歌詞が格納されているdiv要素か
                if r'itemprop="text"' in div:
                    # 不要なデータを取り除く
                    kashi = parse(div)

                    print(kashi, end = '\n\n')
                    # 歌詞を1つにまとめる
                    kashis += kashi + '\n'

                    # 1秒待機
                    time.sleep(1)
                    break
        # 歌詞の書き込み
        f.write(kashis)

if __name__ == '__main__':
    main()

実行すると同ディレクトリにyonedu_kashi.txtが作成されます。

スクリーンショット 2018-07-03 13.06.10.png

分かち書きを行う

分かち書きとは、英語のように単語の間に区切り(スペースや改行)を入れて表現することを指します。
試しに私の好きなOrionの一部を分かち書きしてみましょう。

分かち書き
# befor
神様どうか声を聞かせて、ほんのちょっとでいいから。

# after
神様_どうか_声_を_聞か_せ_て_、_ほんの_ちょっと_で_いい_から_。

分かりやすくアンダースコアで区切りました。これら1つ1つに分割した単語のことを形態素と呼びます。
これでコンピュータにどこまでが単語なのか判別させることが可能です。

形態素解析ライブラリ「mecab」

そんな分かち書きですが、pythonには形態素を解析するためのツールがいくつか存在します。
最初にmecabを使ってターミナルから実行してみましょう。

terminal
$mecab -b 100000 -Owakati yonedu_kashi.txt -o yonedu_kashi_mecab.txt

早速yonedu_kashi_mecab.txtにあるlemonの歌詞を確認します。

yonedu_kashi_mecab.txtの一部
夢 なら ば どれほど よかっ た でしょ う 未だに あなた の こと を 夢 に みる

でしょ」や「」、「よかっ」など一部望まない結果が出ましたね。
このままベクトル化してしまうと結果に悪影響が出る可能性(調査単語とコサイン類似度が近似する恐れ)があります。
先の歌詞をmecabコマンドを叩いて詳細を確認しましょう。

terminal
$mecab
夢ならばどれほどよかったでしょう未だにあなたのことを夢にみる
夢 名詞,一般,*,*,*,*,夢,ユメ,ユメ
なら  助動詞,*,*,*,特殊・ダ,仮定形,だ,ナラ,ナラ
ば 助詞,接続助詞,*,*,*,*,ば,バ,バ
どれほど    副詞,一般,*,*,*,*,どれほど,ドレホド,ドレホド
よかっ   形容詞,自立,*,*,形容詞・アウオ段,連用タ接続,よい,ヨカッ,ヨカッ
た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
でしょ   助動詞,*,*,*,特殊・デス,未然形,です,デショ,デショ
う 助動詞,*,*,*,不変化型,基本形,う,ウ,ウ
未だに   副詞,一般,*,*,*,*,未だに,イマダニ,イマダニ
あなた   名詞,代名詞,一般,*,*,*,あなた,アナタ,アナタ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
こと  名詞,非自立,一般,*,*,*,こと,コト,コト
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
夢 名詞,一般,*,*,*,*,夢,ユメ,ユメ
に 助詞,格助詞,一般,*,*,*,に,ニ,ニ
みる  動詞,自立,*,*,一段,基本形,みる,ミル,ミル

望まない形態素は「助動詞(でしょ)」や「助詞(の)」などの品詞でした。
また、「動詞(よかっ)」は活用された形になっています。これも基本形に戻した方が使い勝手が良くなる(1つの「よい」としてまとまる)のでプログラムを組んで変更する処理を行なっていきます。

janomeを使って分かち書きを行う

次はjanomeという同じく形態素を解析するライブラリを使って分かち書きを行います。
基本的にmecabと変わらないですが、他のライブラリも触ってみたかったので挑戦してみました。

ついでにストップワード(結果に左右されるあまり意味のない形態素)もcreate_stop_word関数で取り除いています。

wakati.py
from gensim.models import word2vec
from janome.tokenizer import Tokenizer
import re
import pprint

def wakati_write(results):
    # モデルファイルに書き出す
    wakachi_file = "yonedu_kashi.wakati"
    with open(wakachi_file, 'w', encoding = 'utf-8') as fp:
        # 改行コードを入れて書き込んでいく
        fp.write("\n".join(results))

def create_stop_word():
    stop_word=['「', '」', 'じゅう', 'そこら', 'れる', 'くい']

    return stop_word

def token(lines):
    # 形態素解析の宣言
    t = Tokenizer()
    results = []

    # ストップワードの定義
    stop_word = create_stop_word()

    """ 解析を行う """
    for line in lines:
        # 形態素に分割
        tokens = t.tokenize(line)
        r = []
        # 形態素を1つずつ取り出す
        for token in tokens:
            # 品詞が基本形か
            if token.base_form == '*':
                w = token.surface
            else:
                # 活用前の単語を渡す
                w = token.base_form
            # 品詞情報
            ps = token.part_of_speech
            # 品詞の種類を取り出す
            hinshi = ps.split(',')[0]
            # 主要な品詞のみ追加する
            if hinshi in ['名詞', '形容詞', '動詞', '記号']:
                # ストップワードか
                if not w in stop_word:
                    r.append(w)
        # スペースを入れて連結し、前後のスペースを排除
        rl = (" ".join(r)).strip()

        results.append(rl)

    return results

def main():
    # 歌詞の読み込み
    bindata = open("yonedu_kashi.txt", 'rb').read()
    kashi = bindata.decode('utf-8')

    # 曲ごとに区切る
    lines = kashi.split('\n')

    # 分かち書きを行う
    results = token(lines)
    # 結果を保存
    wakati_write(results)

if __name__ == '__main__':
    main()

mecabの分かち書きと比較した結果がこちらです。スッキリまとめることができましたね!

分かち書きの比較
# mecab
夢 なら ば どれほど よかっ た でしょ う 未だに あなた の こと を 夢 に みる

# janome(主要な品詞のみ・ストップワード除外)
夢 よい あなた こと 夢 みる

分かち書きした歌詞をベクトルに変換する

word2vecを実行するモデルを作成します。
今回の目的は「どこへ行く」かを知ることなので、Skip-gram(対象単語のコサイン類似度が近似する単語を探し出す)というニューラルネットワークを使用して学習を行いました。

gen_model.py
from gensim.models import word2vec

# データの読み込み
data = word2vec.Text8Corpus("yonedu_kashi.wakati")
# word2vecのモデル作成
model = word2vec.Word2Vec(data, size = 300, min_count = 2, window = 4, iter = 50)

# モデルの保存
model.save("yonedu_kashi.model")

使用したパラメータの詳細はこちら。

パラメータ名 説明
size ベクトルの次元数
min_count n回未満登場する形態素を破棄
window 学習に使う前後の単語数
iter 学習の反復回数

モデルの評価

先に作成したモデルを読み込んで実行します。

load_model.py
import pprint
from gensim.models import word2vec

def kashi_vec(posi_words, nega_words):
    if len(posi_words) > 0: print("positive={}".format(posi_words))
    if len(nega_words) > 0: print("negative={}".format(nega_words))

    # モデルデータの読み込み
    model = word2vec.Word2Vec.load('yonedu_kashi.model')
    # 結果の出力
    pprint.pprint(model.most_similar(positive = posi_words, negative = nega_words, topn = 10))

def main():
    posi_words = ["何かしらの単語", "何かしらの単語"]
    nega_words = ["何かしらの単語", "何かしらの単語"]

    # コサイン類似度の高い形態素トップ10を出力
    kashi_vec(posi_words, nega_words)

if __name__ == '__main__':
    main()

調べたい単語をposi_wordsリストに、減算を行う単語はnega_wordsリストに格納します。
例えば、[王様] - [男] + [女]を実現するにはこのように記述しましょう。

load_model.py(main関数内)
posi_words = ["王様", "女"]
nega_words = ["男"]

彼はどこへ行きたいのか、その真相は

まず「どこへ行く」か知りたいので、mecabを使って形態素に分けます。

terminal
$mecab
どこへ行く
どこ  名詞,代名詞,一般,*,*,*,どこ,ドコ,ドコ
へ 助詞,格助詞,一般,*,*,*,へ,ヘ,エ
行く  動詞,自立,*,*,五段・カ行促音便,基本形,行く,イク,イク

「助詞(へ)」は取り除き、「動詞(行く)」は基本形なのでそのまま使うことができますね。
posi_wordsリストに「どこ」と「行く」を追加しましょう。

load_model.py(main関数内)
posi_words = ["どこ", "行く"]
nega_words = []    # なにも入れなくてOK

# コサイン類似度の高い単語 トップ10を出力
kashi_vec(posi_words, nega_words)

長らくお待たせしました...load_model.pyを実行し、米津玄師さんがどこへ行きたいか確認します!

「どこ」と「行く」に近似するコサイン類似度の単語たち

歌詞から分析したトップ10の項目はこちら。

terminal
$python load_model.py 
positive=['どこ', '行く']
[('ダーリン', 0.9938626289367676),
 ('引きずる', 0.9915787577629089),
 ('となり', 0.9894084930419922),
 ('寄り添う', 0.988717794418335),
 ('寄る辺', 0.9885412454605103),
 ('思える', 0.9877473711967468),
 ('旅', 0.9876766800880432),
 ('沈む', 0.9872937202453613),
 ('ため', 0.9870020151138306),
 ('当て', 0.986397385597229)]

10項目のうち、上位3項目について詳しく見ていきましょう!

米津玄師さんの行きたい(どこか)トップ3

コサイン類似度が高いトップ3の単語
1位: 'ダーリン' 0.9938626289367676 <-コサイン類似度
2位: '引きずる' 0.9915787577629089
3位: 'となり' 0.9894084930419922

皆さんの予想は的中しましたか?
私は「あなた」や「君」がランクインするのかと予想しましたが、浅はかな考えでした。

それはさておき、堂々たる1位を獲得した「ダーリン」の意味を調べてみたところ、

最愛の人、(呼びかけで)あなたの意味。英語では男女の区別はないが、日本では女性から男性の恋人(もしくは夫)の意味で使われることが多い。
引用元: ダーリン - Wikipedia

どうやら愛する人を呼ぶ際に使う言葉のようです。
米津玄師さんは「愛する人」に関係する場所に行きたい(となりに居たい)と推測できますね。

2位の「引きずる」も非常に気になります。過去に何か関連するのでしょうか...

トップ3の単語が含まれる歌詞

それぞれの単語が使用されている楽曲と歌詞を見てみましょう。

1位: ダーリン
->これから僕らはどこ行こう? ねえダーリン何処だろうときっと
引用元: 歌ネット(utanet) - 米津玄師(Blue Jasmine)

2位: 引きずる
->探し求めた感情が どこにも見つからず 途方にくれた正午に
->このまま行こう あの日の思いを 引きずりながらそれでも行こ
引用元: 歌ネット(utanet) - 米津玄師(Neon Sign)

3位: となり
1曲目.かいじゅうのマーチ
->信じられる心があるだけ あなたのとなりで眠りたい
2曲目.Blue Jasmine
->となりにあなたがいるなら それだけで特別なんだ
引用元: 歌ネット(utanet) - 米津玄師(かいじゅうのマーチ), 歌ネット(utanet) - 米津玄師(Blue Jasmine)

いずれの楽曲も愛や別れ、想いを綴った歌詞が印象的でした。

「どこ」と「行く」を使用した回数

全楽曲内で使われた「どこ」と「行く」の集計がこちらになります。

単語 回数 楽曲数 1曲あたり平均
どこ 52回 33曲 1.6回
行く 87回 35曲 2.5回

どこかへ行きたい気持ちがひしひしと伝わってきますね...
約半数の楽曲で使われているからすごい。

彼もこの説を知っていた!

実は昨年、米津玄師さん本人がtwitterで言及していました。

これから開花するたくさんの花が楽しみですね!(根が深いだけに)

執筆後に気づいたこと

ブログに情報がないか確認してみると、半分答えのような発言を見つけます。

・「何処にもいけない」という感覚が自分の中に大きく内在していて、よく歌詞に使ってしまうし使おうとしてしまう。自分でもこれが何なのかよくわかってなかったけれど、改めて考えてみると、どうやら不可逆な時間の流れに於いて使っているらしい。と思う。やっぱりわからない
引用元: 投げっぱなしジャーマン | 米津玄師 official site「REISSUE RECORDS」

ふ、不可逆な時間の流れだと...流石に歌詞にない単語は見つけられないorz
最後にやっぱりわからないって言ってるので「代わりに探ってみた」ということにしておきましょう(

歌詞以外のソースも考慮する必要がありますね。めんどくさいは結果につながるよ。

おわりに

一体彼はどこへ行きたいのでしょうか...今回の結果は「ダーリン(愛する人)」が有力でしたが、結局のところ真実は米津玄師さん本人しか分かりません。
いつかどこかへ行った時に会えたら聞いてみたい(笑)

他にも説がないか探してみると、

  • BUMP OF CHICKEN迷いまくる説
  • L'Arc〜en〜Ciel降り注ぎまくる説
  • 西野カナ会いたくて震える説

まだまだ面白そうな説がありますね。この記事が評判になったらこれらも挑戦するかも?

以上、今回はword2vecと歌詞を組み合わせて「どこに行きたい」か探ってみました。
これからさらに活躍・発展していくことに期待です!