LoginSignup
6
3

More than 3 years have passed since last update.

先生、人生相談です。TF-IDFで歌詞の分析はできますか?(ヨルシカらしさを求めて Part.1)

Last updated at Posted at 2020-12-04

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が出るかもしれないし出ないかもしれないです。気持ち的には頑張って出してヨルシカの布教をしたいなぁと思っています。

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3