Help us understand the problem. What is going on with this article?

エレファントカシマシをdoc2vecで分析

More than 1 year has passed since last update.

はじめに

 私が10年近く追いかけ続けている日本最高峰ロックバンド《エレファントカシマシ》。結成は1981年だそうで、40年近く活動していれば昔と今では随分曲調なんかも違うはず。今回は古くは1988年に最初にリリースされたアルバム『デーデ』から最新2018年の『Wake Up』まで集めうる限り全曲の歌詞をdoc2vecで分類し、それぞれの曲の類似度など調べてみます。

doc2vecとは

 その名の通り、文書をベクトル化(document-to-vector)することで文書の分散表現を得る手法です。2つの文書ベクトル間のコサインを取ることで類似度(完全に同じならば+1、完全に異なっていれば-1)を定量的に評価することができます。一からこのアルゴリズムを実装するとなると骨が折れますが、pythonのgensimパッケージを用いることで簡単に取り扱えます。

環境

OS: macOS Mojave 10.14.2
Python: 3.6.5
MeCab: 0.996
< 主に使うライブラリ >
pandas: 0.23.1
mecab-python3: 0.7
gensim: 3.6.0
< 可視化用 >
numpy: 1.16.0
sklearn: 0.19.1
umap-learn: 0.3.7

使用するライブラリのインポート
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import MeCab
import umap
from sklearn.preprocessing import LabelEncoder
from gensim.models.doc2vec import Doc2Vec, TaggedDocument

1. 歌詞の取得

 歌詞掲載サイトから歌詞をありったけスクレイピングしてCSVに保存する(コードはこちら scraping.py)。BeautifulSoup

2. MeCabで前処理

 上で保存したcsvをpandasで読み込み、doc2vecモデルに渡す歌詞をMeCabで分かち書きする。簡単に単語分割しただけのものを用意したが、本当はここからさらに正規化とストップワード除去もするべきだと思う(めんどうくさい)。

前処理
# csvファイルを読み込み、歌詞を抜き出す
df = pd.read_csv('../data/elekashi.csv')
lyrics = df.Lyrics.tolist()

# MeCabで分かち書き
mecab = MeCab.Tagger('-Ochasen')
mecab.parse('')
documents = []
for i, l in enumerate(lyrics):
    words = []
    node = mecab.parseToNode(l)
    while node:
        if len(node.surface) > 0:
            words.append(node.surface)
        node = node.next
    documents.append(TaggedDocument(words, [i]))

3. doc2vecモデルの構築・学習

 gensimDoc2Vecモデルを学習させる。

doc2vec
model = Doc2Vec(documents=documents,
                vector_size=200,
                window=10,
                min_count=2,
                epochs=50)

# これ以上モデルの更新をしない場合は次の記述をしておく
model.delete_temporary_training_data(keep_doctags_vectors=True, keep_inference=True)

# modelの保存
model.save('doc2vec.model')

学習回数epochsは20以下だと曲どうしのコサイン類似度がほとんど-1か0になり、100以上だとどの曲も大差が無くなるような結果になったので今回は50を採用しました。

4. 結果の確認

 好きな曲の名前を渡すと類似度の高い曲/低い曲を表示してくれる関数print_similar_songs()を定義してやる。

結果を表示するための関数
# データフレームにおける、指定した曲のインデックスを取得
def get_index(song_name):
    return df[df.Title==song_name].index[0]

# 指定した曲とcos類似度が高い順に曲を並べたリストを取得
def get_similar_songs(song_name):
    return model.docvecs.most_similar(get_index(song_name), topn=len(df))

# 指定した曲とcos類似度の高い上位(下位)N曲を曲名・類似度・リリース日の順に表示
def print_similar_songs(song_name, N=10, top=True):
    if top:
        lst = get_similar_songs(song_name)[:N]
    else:
        lst = get_similar_songs(song_name)[-N:]

    for i, similarity in lst:
        print('{}, {}, {}'.format(df.iloc[i].Title,
                                  similarity,
                                  df.iloc[i].ReleaseDate))

以上で準備が整ったので、2017年にリリースされたこれまでの集大成とも言える名曲『風と共に』で確認してみます。

>>> song = '風と共に'
>>> print('----- 上位10曲 -----')
>>> print_similar_songs(song)
>>> print('\n----- 下位10曲 -----')
>>> print_similar_songs(song, top=False)

----- 上位10 -----
夢を追う旅人, 0.6038109660148621, 2016-08-03
いつか見た夢を, 0.5994913578033447, 2010-10-20
桜の花、舞い上がる道を, 0.5884137153625488, 2008-03-05
今を歌え, 0.5861414670944214, 2017-11-08
自由, 0.5756574869155884, 2018-06-06
to you, 0.5693894624710083, 2009-03-18
幸せよ、この指にとまれ, 0.5595215559005737, 2010-05-12
ジョニーの彷徨, 0.545758843421936, 2009-04-29
なからん, 0.5440325736999512, 2015-11-18
恋人よ, 0.5416057109832764, 1999-12-08

----- 下位10 -----
so many people, -0.12663136422634125, 2000-01-26
平成理想主義, -0.13520459830760956, 2004-09-29
ふわふわ, -0.13689041137695312, 2002-03-27
理想の朝, -0.15985427796840668, 2006-03-29
遁生, -0.16307081282138824, 1990-09-01
地元のダンナ, -0.16500194370746613, 2006-03-29
DEAD OR ALIVE, -0.1774204820394516, 2002-12-26
浮世の姿, -0.19684182107448578, 1993-05-21
ひまつぶし人生, -0.2009955644607544, 1992-04-08
どこへ?, -0.2093726396560669, 2003-07-16

 類似度の高いものから見ていくと同じアルバム『Wake Up』に収録された「夢を追う旅人」「今を歌え」や直近に出た「幸せよ、この指にとまれ」「桜の花、舞い上がる道を」など心の奥底から湧き上がるパワーを感じさせる曲が目立つ。対して類似度の低い曲は「ふわふわ」だとか「遁生」だとか、20年前人生が何も見えず煙草ふかしてやさぐれていた頃が垣間見えるのが印象深い。
 ちなみに「今宵の月のように」で試すと、上位は「孤独な旅人」や「風に吹かれて」のように同時期に出た一般ウケしそう・テレビで流してもスタジオが凍りつかなさそうな曲が類似度が高かった反面、低い曲には「生命賛歌」「コール アンド レスポンス」「ガストロンジャー」など尖りに尖ったラインナップ。これだけでもモデルの出来は大いに満足。曲配信サービスのレコメンドエンジンは「今宵の月のように」を聴いたら「ガストロンジャー」を次に勧めるくらい、ユーザーの目玉が飛び出て内臓がひっくり返るような体験をさせてほしいですね。

文書ベクトルの可視化

なかなか満足のゆく結果が得られたのでUMAPで可視化してみる。

# 文書ベクトルを numpy.ndarray 形式に保存
vecs = []
for tagged in documents:
    vecs.append(model.infer_vector(tagged[0]))
vecs = np.array(vecs)

# UMAPで次元削減
v_embedded = umap.UMAP().fit_transform(vecs)

# スキャッタープロット
# それぞれの点はリリース日ごとに色分け
le = LabelEncoder()
label = le.fit_transform(df.ReleaseDate.astype(str))
plt.figure(figsize=(10, 10))
plt.scatter(x=v_embedded[:, 0],
            y=v_embedded[:, 1],
            c=label)

結果
download-2.png

もっと綺麗にクラスタが分かれるかと期待していたけれどそう甘くはなかった。ここは収録された曲の調子が平均して極端に別れてそうな2つのアルバム

  • 『生活』(1990年リリース、厭世系)
  • 『Wake Up』(2018年リリース、パワー系)

を比べてみる。

# それぞれのアルバムに収録曲のリスト
seikatsu = ['男は行く', '凡人−散歩き−', 'too fine life', '偶成(ぐうせい)',
            '遁生', '月の夜', '晩秋の一夜']
wakeup = ['Wake Up', 'Easy Go', '風と共に', '夢を追う旅人', '神様俺を', 'RESTART',
          '自由', 'i am hungry', '今を歌え', '旅立ちの朝', 'いつもの顔で', 'オレを生きる']
all_titles = seikatsu + wakeup

# データフレームにおける、上記の曲のインデックスを取得
seikatsu_indices = df[df.Title.isin(seikatsu)].index.tolist()
wakeup_indices = df[df.Title.isin(wakeup)].index.tolist()
all_indices = seikatsu_indices + wakeup_indices

# プロット
plt.figure(figsize=(5, 5))
plt.scatter(x=v_embedded[seikatsu_indices, 0],
            y=v_embedded[seikatsu_indices, 1],
            label='生活')
plt.scatter(x=v_embedded[wakeup_indices, 0],
            y=v_embedded[wakeup_indices, 1],
            label='Wake Up')
for i, txt in enumerate(all_titles):
    plt.annotate(txt, (v_embedded[all_indices[i], 0], v_embedded[all_indices[i], 1]))
plt.legend(loc='upper right')

download.png

目視でも2つのクラスタが別れているのが確認できる。「男は行く」は”お前らのことなんか知ったことではない、俺は俺の道を行く”といった曲調なので右下に固まってる厭世観全開の「遁生」グループよりも、生きるパワーをみなぎらせたオレンジ側に近いのかも。

終わりに

 今回のコードは GitHub に上げてありますので、レポジトリをクローンすればコマンドライン上で好きな曲の類似度計算を実行できます。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした