1. はじめに
皆さん「ヨルシカ」をご存じですか? 私はファンクラブの会員になるほど大好きです。最近はFFBEやNEWS23にヨルシカの楽曲が起用されていることもあるので、名前を知っている人も多いのではないでしょうか? ちなみに私のおすすめ曲は「心に穴が空いた」です。「夜紛い」のアンサーソングとしての意味合いもあるので、是非とも一緒に聞いてみてください。
そこで、今回は**ヨルシカの楽曲同士で比較して、各楽曲でどういう歌詞が多く使われているのか?**について、TF-IDFで分析していこうと思います!
2. 環境
- Windows 10
- Python 3.7.9 (Anaconda)
- mecab-python3 (Ver. 1.0.3)
- BeautifulSoup4 (Ver. 4.9.3)
- scikit-learn (Ver. 0.23.2)
3. まず、TF-IDFとは?(簡単に)
tf-idfは、文書中に含まれる単語の重要度を評価する手法の1つであり、主に情報検索やトピック分析などの分野で用いられている。(Wikipediaより)
TF-IDF値は以下の式で計算されます。
tfidf(t_{a}, d_{b}) = tf(t_{a}, d_{b}) \cdot idf(t_{a})
ここで,$d_b$をある文書として,そこに出現する単語を$t_a$とします。
TF値、IDF値はそれぞれ以下の式で計算されます。
tf(t_{a}, d_{b}) = \frac{n_{a, b}}{\sum_{k} n_{k,b}} \\
idf(t_{a}) = \mathrm{log}\frac{N}{df(t_{a})}
ここで、$n_{a, b}$はある単語$t_a$の文書$d_b$内での出現回数、Nは全文書数、$df(t_{a})$は単語$t_{a}$が出現する文書の数とします。そうすることで、TF値は文書中で単語がどの程度の頻度で出現したかを、IDF値は単語がどれくらいそれぞれの文書で出現しやすいかを表現します。そして、それらを掛け合わせるたTF-IDF値は、ある単語が他の文書と比較してどれだけ重要であるかを表現できます。
4. コード
まず、歌詞検索J-Lyric.netからのアーティストページから楽曲の歌詞ページをとってきます、そして、それぞれの歌詞ページから歌詞を持ってきて、分かち書きしたテキストに変換します。
今回はbase_listに分かち書きした歌詞の文字列のリストを入れています。
import requests
import re
from bs4 import BeautifulSoup
import MeCab
import sys
from sklearn.feature_extraction.text import TfidfVectorizer
import time
DOMAIN = "https://j-lyric.net"
YORUSHIKA = "/artist/a05cf40/"
def get_soup_from_artist_page(url):
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
time.sleep(1)
return soup
def get_lyric_page_from_artist_page(soup):
"""
歌詞ページへのリンク部分のデータを取得する。
Parameters
----------
soup : bs4.BeautifulSoup
アーティストページをBSでパースしたデータ。
Returns
-------
soup.find(id="cnt").find(id="mnb").find(class_="cnt").find_all(id=re.compile("ly")) : list
歌詞へのリンク、タイトルを含むデータのリスト。
"""
return soup.find(id="cnt").find(id="mnb").find(class_="cnt").find_all(id=re.compile("ly"))
def get_lyric_path(page):
"""
歌詞ページへのパスを取得する。
Parameters
----------
page : bs4.BeautifulSoup
歌詞ページへのリンク部分のデータ。
Returns
-------
page.find(class_="ttl").find("a").get("href") : string
歌詞ページへのパス。
"""
return page.find(class_="ttl").find("a").get("href")
def get_lyric_title(page):
"""
歌詞タイトルを取得する。
Parameters
----------
page : bs4.BeautifulSoup
歌詞ページへのリンク部分のデータ。
Returns
-------
page.find(class_="ttl").find("a").contents[0] : string
歌詞のタイトル。
"""
return page.find(class_="ttl").find("a").contents[0]
def get_lyrics_textlist_from_page(lyrics_url):
"""
URLから歌詞を持ってくる。
Parameters
----------
lyrics_url : string
歌詞ページのURL。
Returns
-------
lyric_textlist : list
歌詞ページから持ってきた歌詞の文字列。
ex. [..., "誰かのために強くなれるなら", "ありがとう、悲しみよ", ...]
"""
response = requests.get(lyrics_url)
soup = BeautifulSoup(response.text, 'html.parser')
time.sleep(1)
lyric_list = soup.find(id="Lyric").contents
lyric_textlist =[x for x in lyric_list if getattr(x, 'name', None) != 'br']
# 全角空白を「、」にする
lyric_textlist =[x.replace("\u3000", "、") for x in lyric_textlist]
return lyric_textlist
def get_base_str(lyric, mecabTagger):
"""
歌詞のリストをMecabで分かち書きして、そのテキストを返す。
Parameters
----------
lyric : list
歌詞のリスト。
ex. [..., "誰かのために強くなれるなら", "ありがとう、悲しみよ", ...]
mecabTagger : MeCab.Tagger
MeCabのTagger。
Returns
-------
base_str : string
分かち書きしたテキスト。
ex. "... 誰 ため 強い なれる ありがとう 悲しみ ..."
"""
base_str = ""
for t in lyric:
node = mecabTagger.parseToNode(t)
base_str_each_line = ""
while node:
feature = node.feature.split(",")
if feature[0] != "BOS/EOS" and feature[0] != "助詞" and feature[0] != "助動詞" and feature[0] != "補助記号" and len(feature) > 8:
base = feature[7]
if "-" in base:
base = base.split("-")[0] # 原型
base_str_each_line += (base + " ") # res: "いちご 食べる "
node = node.next
base_str += base_str_each_line # res: "いちご 食べる ラーメン 美味しい ... "
return base_str
soup = get_soup_from_artist_page(DOMAIN + YORUSHIKA)
title_list = []
mecabTagger = MeCab.Tagger("-Owakati")
# アーティストページから歌詞ページを含むsoupを持ってくる
page_list = get_lyric_page_from_artist_page(soup)
base_list = []
for p in page_list:
lyric_path = get_lyric_path(p)
lyric_title = get_lyric_title(p)
lyric_url = DOMAIN + lyric_path
lyrics_text_list = get_lyrics_textlist_from_page(lyric_url)
title_list.append(lyric_title)
base_str_each_song = get_base_str(lyrics_text_list, mecabTagger)
base_list.append(base_str_each_song)
とりあえずこれで分かち書きした歌詞テキストのリストを取得できたので、これをTF‐IDFで計算していきます。
計算するコードはこちらより引用させてもらいました。
vectorizer = TfidfVectorizer(max_df=0.9) # tf-idfの計算
X = vectorizer.fit_transform(base_list)
words = vectorizer.get_feature_names()
i = 0
for doc_id, vec in zip(range(len(base_list)), X.toarray()):
print('doc_id:', doc_id)
cnt = 0
print('title: ', title_list[i])
for w_id, tfidf in sorted(enumerate(vec), key=lambda x: x[1], reverse=True):
if cnt > 10: break
lemma = words[w_id]
print('\t{0:s}: {1:f}'.format(lemma, tfidf))
cnt += 1
i += 1
これで各楽曲ごとにTF-IDF値の高い語が順に11個出力されます。
5. 結果
全部結果を載せると大変なので一部抜粋して見ていきましょう。
doc_id: 31
title: 爆弾魔
爆破: 0.736169
為る: 0.245904
吹き飛ぶ: 0.226513
爆弾: 0.226513
狡い: 0.226513
片手: 0.152953
全部: 0.150287
此の: 0.144266
青春: 0.131620
日々: 0.117569
さようなら: 0.104170
流石爆弾魔といったところでしょうか。タイトルの通り、サビでも爆破という言葉を反復しているので、値が0.73まで到達するのも納得です。(余談ですが、3rd Full Album「盗作」に収録されているRe-recording版もとても良かったので是非聞いてみてね)
doc_id: 24
title: 盗作
足りる: 0.570575
未だ: 0.356744
満たす: 0.332895
知る: 0.248869
盗む: 0.240515
美しい: 0.186341
本当: 0.134581
此の: 0.134035
ああ: 0.131671
切っ掛け: 0.120257
或る: 0.120257
わかる人にはわかるでしょう。助動詞がないのでちょっとわかりにくいですが、この曲は「足りない」「満たされない」「知りたい」と自らの欲への渇望を謳った曲なのです。改めてこうやって歌詞を見返してみるとやはり心に来るものがありますね。ヨルシカ最高。盗作はいいぞ。Music Videoも観てね。ヨルシカ - 盗作(OFFICIAL VIDEO)
doc_id: 22
title: ただ君に晴れ
大人: 0.335331
追い付く: 0.287321
ポケット: 0.230888
記憶: 0.230888
成る: 0.213277
上る: 0.207875
出す: 0.201199
咲く: 0.193791
乾く: 0.178883
俯く: 0.178883
其れ: 0.169299
Youtube再生回数1億回越えおめでとう。ただ君に晴れです。個人的には大人とか追いつくの値が高くてへぇ~~ってなりました。やっぱりサビで反復が多いとスコアに乗りやすいんですかね。こっちのMusic Videoもぜひ見てね。ヨルシカ - ただ君に晴れ (MUSIC VIDEO)
6. 気になったところ
MeCabで形態素解析した結果が一部怪しかったです。「覚束ぬままに」の「おぼつかぬ」を正しく認識できていなかったり、「マシンガン」を「マシン」と「ガン」に分けて認識していたりしました。MeCabの方で辞書とかいじればもうちょっといい感じにできるんでしょうか?
7. 最後に
今回はヨルシカの各楽曲の歌詞について調べていきました。単純な手法でしたが、結構頻度の高い語を持ってこれていたのでびっくりしました。
一応これはPart.1なので、もしかしたらPart.2が出るかもしれないし出ないかもしれないです。気持ち的には頑張って出してヨルシカの布教をしたいなぁと思っています。