データサイエンティスト見習い達の日常

社会人1年目と学生の2人のデータサイエンティスト見習いの成果物

B'zの歌詞をPythonと機械学習で分析してみた 〜Word 2 Vec編〜

1. 本Part概要

こんにちは。pira_ninoです。
早速のお知らせなのですが、本編から超絶優秀な友人のtaijest君も編集に加わってもらっています。これに伴いブログのタイトルも若干変更しました。

さて本題に戻ります。 前PartではB'zの歌詞を「LDA」を用いた曲のクラスタリングを行いました。
皆様のおかげで週間のランキングで11位に載りました!!ありがとうございます!!

pira-nino.hatenablog.com

blog.hatenablog.com

本Partでは最近流行りの「Word 2 Vec」を用いて単語の意味の分析を行なっていきます。
目標としましては、B'zの歌詞を用いて「きれい」に意味が近い単語は何かや「あなたと恋するためには僕には何が必要か」といった分析を行っていきます。

f:id:pira_nino:20180812125217p:plain
Word 2 Vecを用いた分析の目標

2. Word 2 Vecとは

早速、本章はtaijestが書きます。

2.1イメージ

今回用いるWord2vecについて、簡単な説明をします。Word2vecは、「単語の意味をベクトルで表現する」ためのモデルです。

単語の意味をベクトルで表現、というと難しそうに聞こえますが、例えば新聞データにWord2vecを適用した例を以下に示します。

f:id:taijest:20180812133402p:plain
新聞データにWord 2 Vecを適用したイメージ

単語に割り当てられたベクトルから次のようなことが解釈できます。

  • 1番目の要素は、「イチロー」「テレビ」「サッカー」「大谷翔平」で大きくなっていることから、「スポーツ」に関連した要素であると想定できる
  • 2番目の要素は、「テレビ」「鉄腕アトム」で大きくなっていることから、「機械」に関連した要素であると想定できる
  • 3番目の要素は、…

といったように、単語のベクトル表現では、単語の意味をベクトルの各要素に分散します。*1このような性質から、このベクトル表現は「分散的意味表現」と呼ばれます。

単語の意味をベクトルで表現するので、当然、意味の近い単語は似たようなベクトルになります。つまり、単語ベクトル間のcos類似度を用いて、意味の近い単語を検出することができるのです

さらに注目されている単語ベクトルの性質として、意味の足し算、引き算ができるというものがあります。この性質については、以下のような有名な例があります。

「王様」 ー 「男」 + 「女」 = 「女王」

これは、「王様」から「男」の要素を引いて「女」の要素を足したら「女王」になる、という直感的にも納得できる例です。言い換えれば、「男」でいう「王様」は「女」でいう「〇〇」だ という単語の対応づけを行っているともいえます。

このような性質を持つことから、単語ベクトルは単語の数値表現として注目を浴びています*2

2.2 理論

理論は難しいので、興味のある方のみ読んでください。汗

では、単語ベクトルをどうやって決定すれば良いのでしょうか。コンピュータは単語の意味を理解できないし、人間が手作業で決めていくしかない…?

そんなことはありません。単語ベクトルの学習モデルであるWord 2 vecは、ある仮定をおいて単語ベクトルをコンピュータに学習させることに成功しました。(以下、論文のリンク)

https://arxiv.org/pdf/1301.3781.pdf

その仮定とは、「同じ文脈で出現する単語は意味が類似している」というものです。例えば、以下のような文を考えましょう。

私の家では〇〇を飼う予定だ。

〇〇に入る単語として、何を思い浮かべましたか?おそらく、「犬」や「猫」と答える人が多いでしょう。「ハムスター」という人もいるかもしれません。この場合、これらの単語は「ペット」という共通の意味に基づいて文脈に出現します。

Word2vecのネットワークの一つであるSkip-gramモデルでは、上記の例では以下のような周辺単語を予測するニューラルネットワークを学習します。

f:id:taijest:20180812134746p:plain

先述したとおり、〇〇には「犬」や「猫」などの単語が入ることが想定されますが、その際の出力が同一であることに着目してください。同じ出力を予測するのであれば、中間層のベクトルも似たものになるはず。つまり、学習されたネットワークにおいて、中間層のベクトルを単語ベクトルとするのです*3

このモデルを数式で考えてみましょう。入力は単語に対応したone-hotベクトルです。入力層から中間層への射影は以下の式で計算されます。

f:id:taijest:20180811230914p:plain

同様に、中間層から出力層への射影は以下の式で計算されます。

f:id:taijest:20180811225806p:plain

出力層ではソフトマックス関数による活性化を行うので、以下の式で出力単語の予測確率を算出します。

f:id:taijest:20180812124558p:plain

この式は、重み行列の列ベクトルと行ベクトルを用いて以下のように書き換えられます。

f:id:taijest:20180812124607p:plain

学習では、この確率が1に近づくように重み行列を更新していきます*4
入力単語の情報を中間層への重み行列に、周辺の単語の情報を出力層への重み行列に学習させていく、というイメージです。

実際には、効率的に学習を行うために、ニューラルネットワークの代わりにロジスティック回帰が用いられる*5ことがほとんどですが、予測のための中間表現に着目するというスタンスは変わりません。

モデル式やパラメータの学習については、以下に詳しく記載されています。

https://arxiv.org/pdf/1411.2738.pdf

また、初学者向けの参考サイトとしては以下のリンクが役に立つと思います。

www.randpy.tokyo

qiita.com

2.3 TF-IDF・LDAとの比較

単語をベクトルで表現していることに加え使用している特徴量の比較を簡単に述べますと・・・

  • TF-IDF:単語の頻度のみ
  • LDA:単語の頻度+単語の共起
  • Word 2 Vec:文脈を考慮

となります。上記に加え単語をベクトルで表すことで類似度の計算など直感的な操作が可能が故に、様々な人が扱えるので流行っていると考えられます。

3. 歌詞データへのWord 2 Vecの適用

さて「単語の意味をベクトルで表現する」Word 2 VecをB'zの歌詞データに適用し、単語の意味の分析を行っていきます。

3.1 Word 2 Vecの学習

まず、データ入手編で入手した曲ごとの歌詞データを格納したData Frameが手元にあることを前提とします。

f:id:pira_nino:20180727130908p:plain
歌詞抽出をしたDataFrame(歌詞部分はモザイクかけてます)

なんとなんとgensimを使うと数行でmodelの作成ができます。。。

radimrehurek.com

ちなみに私のmacbookで2分弱で約330曲の大きさのデータを学習できます。

from gensim.models import word2vec

sent = list(df_all["Lyrics"])

model = word2vec.Word2Vec(sent,
                          size=100,  # ベクトルの次元
                          min_count=3,  # 最小3回以上出ている単語のみの使用
                          window=5,  # window幅
                          iter=4000,  # 学習の繰り返し数
                          seed=2018
                         )

パラメータのチューニングの件ですが、taijest曰く以下のような勘所でチューニングを行うと良いらしいです。

  • ベクトルの次元数
    • 大規模(Wikipediaなど・ユニークな語彙数:数万):数百次元
    • 中規模(新聞などの数カテゴリな文書・ユニークな語彙数:数千):50〜200次元
    • 小規模(あるカテゴリの文書・ユニークな語彙数:数百):10〜50次元
  • window幅:日本語ならデータセットに依存せず5くらい。段落単位くらいの広い解釈をしたいならば15
  • 学習率:とりあえず0.025(デフォルト)
  • 微妙な結果が得られた際の対応
    • 単語間の類似度が低い:次元数増やす
    • 学習率:減らす
    • これでもうまくいかない:エポック数増やす

「微妙な結果」の基準は特になく個人の好みなのでご注意。

もちろんモデルの保存、読み込みもできます。
一度学習したモデルを保存しておくことで、同じ学習を2度することを回避できます。

model.save("bz.model")
model= word2vec.Word2Vec.load("bz.model")

3.2 単語の類似度

ここから先は(比較的)機械学習の知識がなくても読めます

B'z的にある単語と類似度の高い単語は何かということを分析していきます。
理論的には、学習で得られた各単語のベクトル間の類似度*6を測ることで解釈を行います。

word = "きれい"
# wordと類似度の近い20単語と類似度を取得
results = model.wv.most_similar(positive=word,topn=20)  
# DataFrameに格納し表示
df_result = pd.DataFrame(results,columns=["Word","Sim"])
print("___{}___".format(word))
display(df_result.T)

上記で得られた「きれい」と類似度が近い単語がこちら。

きれい

Rank 1 2 3 4 5
単語 思い出し 流す 苦しい 満たさ
類似度 0.37 0.28 0.27 0.26 0.26

それっぽいきれいなことは思い出すことでもあるが苦しいものでもある。深いですね。

興味深い結果として「あなた」と「オマエ」に類似度が高い単語の結果を示します。

あなた

Rank 1 2 3 4 5
単語 知ら きみ 落ちる 青い
類似度 0.54 0.29 0.27 0.27 0.26

オマエ

Rank 1 2 3 4 5
単語 最高 悔い きる truth 敏感
類似度 0.29 0.28 0.28 0.28 0.27

あなた」は「」に近いのは直感的に納得がいきます。
しかし「オマエ」は「最高」らしいです。

Wikipediaを用いて学習したモデル*7など、いわゆる一般的なコーパスで学習されたモデルは数多く公開されていますが、このようなモデルを用いると「オマエ」は「君」に近くなることが予想されます。
しかし、B'zの歌詞のみを用いると「オマエ」は「最高」といったように通常のコーパスではあり得ない結果が得られる ことが今回の分析の興味深い点です。

このような感じで行った他単語の結果をいくつか載せさて頂きます。

Rank 1 2 3 4 5
単語 ゆけ まま 思う 痛み
類似度 0.30 0.30 0.30 0.30 0.29

世界

Rank 1 2 3 4 5
単語 事件 トラブル 冷たく
類似度 0.31 0.27 0.26 0.26 0.25

Rank 1 2 3 4 5
単語 まばゆい 広げ banzai cal l 歌う
類似度 0.47 0.36 0.34 0.33 0.31

作者の感想
* 「声」は「嘘」と「痛み」に近いので暗い意味なのか。。。確かに苦しい時にあなたの声を求めている感はB'z的にある。
* 「世界」は「歌」。。。カッコイイ。。。でも事件笑
* 「翼」は「Banzai」に近いらしいが、「Banzai」という曲に「翼」という単語出ていないので、意味的に近いとWord 2 Vecが学習したのは興味深い。

3.3 単語の足し算・引き算

Word 2 Vecでは単語をベクトルで表現していることで単語同士の足し算・引き算ができます。これの代表的な例として・・・

  1. 「奥さん」ー「彼女」で彼女から奥さんにランクアップするには?を分析した例
    ainow.ai

  2. Wikipediaで学習したモデルで「レディーガガ」ー「アメリカ人」+「日本人」=「???」で 日本版レディーガガを分析した例

https://arxiv.org/pdf/1507.07998.pdf

が挙げられます。ちなみに上記論文曰く、日本版のレディーガガは浜崎あゆみらしいです。

さて、本題に戻りB'zバージョンで単語の足し算・引き算を行っていきます。
A + B = C + ??? ⇔ A + B - C = ???であることに留意して以下のようなコードを書きます。

# 恋+あなた=僕+???の例
results = model.most_similar(positive=[u"恋",u"あなた"], negative=[u"僕"])
df_result = pd.DataFrame(results,columns=["Word","Sim"])
display(df_result.T)

あなた+恋=僕+???

Rank 1 2 3 4 5
単語 笑う 微熱 数え 思い出 隙間
類似度 0.32 0.29 0.28 0.28 0.28

「僕が笑う」と「あなたとの恋」になるらしいです。それっぽいですね!!!微熱はなんとなく分かりますが隙間はよく分かりませんね。

単語の類似度の時と同様にいくつかの結果を載せます。

夏+恋=春+???

Rank 1 2 3 4 5
単語 輝い out 忙しい 強がっ 輝き
類似度 0.30 0.28 0.28 0.27 0.26

季節で比較するのはあるあるですね。 「夏の恋」は「春の輝き」らしいです。

恋+未来=あなた+???

Rank 1 2 3 4 5
単語 待て super 人生 song
類似度 0.35 0.31 0.28 0.28 0.27

恋+過去=あなた+???

Rank 1 2 3 4 5
単語 ゆえ 場面 さめ 情熱 仕事
類似度 0.37 0.31 0.30 0.29 0.28

「あなたを待つ」「あなたの未来」「あなたの人生」は未来への恋
「あなたの場面」「あなたの情熱」「あなたの仕事」は過去の恋
未来と過去でこんなに違うのですね。
過去の恋は情熱的であったが仕事などで忙しく思い出と化したのでしょうね。
未来に恋したい人はsongを聞きながら待ちましょう。

3.4 曲のマッピング

LDA編と同様に曲のマッピングを行います。
今回は、曲ごとに出現する単語の平均ベクトルを特徴量(100次元)とします。

# 曲数×ベクトル次元の配列に各曲の平均ベクトルを格納
song_vec = np.zeros((df_all.shape[0],100))

for i,song in tqdm(enumerate(text_list)):
    print(df_all.SongName[i])
    feature_vec=np.zeros(100)
    num_words=0
    for word in song:  # 各曲の単語ごとに
        try:
            feature_vec += model.wv[word]
            num_words += 1
        except:   # 分析の対象外としている単語の時
            print("{}_is_not_in_dic".format(word))
    feature_vec = feature_vec / num_words  # 得られた単語のベクトルの和を単語数で割る
    song_vec[i] = feature_vec

t-SNEを用いて圧縮したものを可視化するコードはTF-IDF編と同様なのでご参照ください。

ちなみにLDA編で「「泣いて泣いて泣きやんだら」と「パーフェクトライフ」の類似度が高いと出ているが曲の意味は一切違う」とのコメントを頂いたのでWord 2 Vecの特徴量を用いて類似度を測ってみました。
結果、「泣いて泣いて泣きやんだら」から見た類似度ランキングで「パーフェクトライフ」は約70位とそこまで類似度は高くないとの結果が得られました。加えて、上位には「ONE」「ONE」「傷心」が出現し、(個人的に)意味的にも似ている曲が上位にきていると思います。
このように、文脈を考慮したWord 2 Vecを用いた本modelはなかなかの完成度であることが推測できます。

本題に戻り、曲のマッピングを行います。 ここでは、LDA編で得られた所属トピック別に色をつけた図と、発売年別に色をつけた図の2つを示させて頂きます。

f:id:pira_nino:20180811200045p:plain
Word 2 Vecを特徴量とした曲のマッピング(LDAの所属トピックで色付け)
f:id:pira_nino:20180811195741p:plain
Word 2 Vecを特徴量とした曲のマッピング(発売年で色付け)

今回もいい感じですね。 まず、トピック別の色付けの図を見ますと、同じ色同士の曲が固まっていないことが分かります。
このことから、LDAで得られたトピックはWord 2 Vecを用いた特徴量と相関性が弱いということが伺えます。
上記での「泣いて泣いて泣きやんだら」の例で若干触れましたが、LDAと異なりWord 2 Vecでは意味的に似てそうな曲の類似度を高く得られていることがわかっております。
このように単語の文脈を考慮したWord 2 Vecは非常に強力なモデルであることがLDAとの比較でも再確認できました。

次に発売年で色付けした図を見ますと、古い曲(青や緑)は一定の箇所に固まっており一方、最近の曲(オレンジや赤)は散らばっていることが分かります。
このことから、昔に比べ最近は多様な意味を持った曲を作成していることが伺えます。

4. Doc 2 Vecを用いた分析

4.1 Doc 2 Vec とは

簡単に言うと「文書もベクトル化して似たような文書・単語をベクトル的に近づけるように学習するモデル」です。

deepage.net

https://medium.com/scaleabout/a-gentle-introduction-to-doc2vec-db3e8c0cce5e

4.2 歌詞データへのDoc 2 Vecの適用

案の定gensimにDoc 2 Vecありました。

radimrehurek.com

Woerd 2 Vecと違う点は、["文書n",Tag]というTaggedDocumenという型でinputを作ることです。今回はTagには素直に曲名を入れました。

from gensim.models.doc2vec import Doc2Vec
from gensim.models.doc2vec import TaggedDocument

# 入力の作成
training_code = []
for song in df_all.iterrows():
     training_code.append(TaggedDocument(words=song[1]["Lyrics"].split(" "), tags=[str(song[1].SongName)]))

# modelの学習
model_doc = models.Doc2Vec(
    documents=training_code,
    size=100,
    min_count=3,
    iter=4000
)

4.3 曲のマッピング

単語の考察はちょっと書きたいことあるので先にお手軽な曲のマッピングを行います。 恒例のt-SNEを用いて2次元に圧縮して可視化します。

f:id:pira_nino:20180811204848p:plain
Doc 2 Vecを特徴量とした曲のマッピング(発売年で色付け)

なんか丸くなった。。。かなり強引な解釈ですが、曲間の特徴は一気に変化することがなく徐々に変化し一周回って円になる(?)ということが考えられます。
確かに曲によって意味する内容は変わりますが、明らかな外れ値的な曲がなく、ある曲とある曲の間には曲があり、ということを繰り返すことで円になったのと思われます。
加えて、中心には昔の曲(青)が固まっていることから昔の曲は意味的に特殊であり他の曲と接することなく中心に密に固まったでのは、ということも推測されます*8

4.4 単語の類似度(Word 2 Vecとの比較)

Doc 2 Vecでも単語の類似度が計算できます。 ここで「」という単語に着目しWord 2 Vec との比較を行います。

Word 2 Vec による「恋」と類似度が高い単語

Rank 1 2 3 4 5
単語 強がっ 思っ 一緒 思う 輝い
類似度 0.36 0.30 0.30 0.28 0.27

Doc 2 Vec による「恋」と類似度が高い単語

Rank 1 2 3 4 5
単語 一緒 歓び 切り 行き先 別れ
類似度 0.36 0.35 0.28 0.28 0.27

Word 2 Vec、Doc 2 Vec共になんだか似たような単語が出てるとはいえ、どっちが良いかは定性的な解釈の勝負になります。
例えば、「恋」はB'z的に「強がり」(Word 2 Vec)なのか「歓び」(Doc 2 Vec)なのか
個人的に思ったことは、単語の前後関係だけでは「強がりな恋」というフレーズはB'zっぽいと思いましたが、「歓びな恋」には違和感を感じました。
しかし、「恋」の歌は最終的に「あなたがいる歓び」的な曲に多いので、曲全体を加味すると遠回しに「恋=歓び」との解釈でDoc 2 Vec では学習されたのではと推測されます。

このように曲同士の類似度を考慮しつつ単語の意味空間を学習してくDoc 2 Vecでは、より概念的な単語の意味空間が学習されているのではと推測されます。

では、どっちがいいか?ワカラナイ・・・・

5. 最後に

本Partでは、Word 2 Vecを用いて単語の意味の解釈を行いました。
個人的には、「あなた」は「君」に近いが「オマエ」は「最高」という結果が出た際には、テンション上がりました。

さて、今後についてなのですがコメントに「アーティスト別比較」をやって欲しいと多数意見を頂きしたが、実はこちらを既に着々と進めておりました。
ということで、次回はtaijest君主導でアーティスト別比較を行っていきます。 次回作をお待ちください。(その前にちょい見せ)

f:id:pira_nino:20180811214842p:plain

*1:実際には、上記のような概念は要素の組み合わせで表現される場合がほとんどであり、解釈は困難です

*2:自然言語処理のタスクで、単語ベクトルをモデルへの入力としている場合が多いです

*3:このとき、「犬」「猫」「ハムスター」などは似たようなベクトルになります

*4:誤差逆伝播法で学習を行います。

*5:ネガティブサンプリングと呼ばれています

*6:デフォルトはコサイン類似度

*7:有名どころですと、東北大の乾研究室が公開しているモデル(http://www.cl.ecei.tohoku.ac.jp/~m-suzuki/jawiki_vector/)など多くのモデルが公開されています

*8:古参ファンが「最近の曲は変わった」とよく言いますが、今回の分析でこれは正しいことが証明されました。