西野カナに「恋」とは何か聞いてみた
- 自然言語処理
- Word2Vec
- Python
この記事で分かること
- 歌詞から「恋」の意味を読み解く手順
- 西野カナとaikoで「恋」の使われ方はどう違うのか
- Word2Vecで単語を足し引きするとどうなるのか
いきなりですが、みなさん恋してますか? 僕は出来てないです。というか恋が何か分かってません(笑)。 好きは分かるけど、恋って何か重い感じがする。「恋してる」何て言ったことないし、恋って何だーと思ったので、定義を調べてみると、
恋:異性に愛情を寄せること、その心。
ほほう。愛情とは?
愛情:相手にそそぐ愛の気持。
ほほう。愛とは?
愛:そのものの価値を認め、強く引きつけられる気持。
うーん….よく分からないので、よく恋をしてそうな西野カナ先生に聞いてみましょう!
1.どうやって聞くか?
会って直接!とはいかないので、今回は西野カナ先生の歌に対して自然言語処理をして、先生が恋をどのように表現しているか調べてみます。言い換えると、
「西野カナ先生に恋とは何かを聞く」 → 「西野カナ先生の歌詞から辞書を作り、恋と類似度の高い言葉は何かを解析する」
2.読者対象
- 自然言語処理で何が出来るか知りたい人
- 恋に悩んでいる人
3.使う技術
- 自然言語処理(Janome, word2vec)
- スクレイピング(BeautifulSoup)
- Python
4.データの抽出
まず始めに、こちらから西野カナの歌全167曲の歌詞をスクレイピングで抽出します。 ライブラリはBeautifulSoupを使います。
歌ネット(uta-net.com)歌詞を検索できるサイト。アーティスト名や曲名、歌い出しなどから楽曲の歌詞を探せます。uta-net.comuta-net.comimport requests
from bs4 import BeautifulSoup
base_url = "https://www.uta-net.com"
target_url = 'https://www.uta-net.com/search/?Aselect=1&Keyword=...&Bselect=3&x=0&y=0'
music_num = 167
r = requests.get(target_url)
soup = BeautifulSoup(r.text, "html.parser")
url_list = []
#曲一覧から各曲のURLを取り出してリストに入れる
for i in range(music_num):
href = soup.find_all("td", attrs={"class": "side td1"})[i].contents[0].get("href")
url_list.append(href)
kashi = ""
#曲ごとにRequestを送り歌詞を抽出する
for i in range(music_num):
target_url = base_url + url_list[i]
r = requests.get(target_url)
soup = BeautifulSoup(r.text, "html.parser")
for string in soup.find_all("div", attrs={"id": "kashi_area"})[0].strings:
kashi += string
with open('kashi.txt', mode = 'w', encoding = 'utf-8') as fw:
fw.write(kashi)これでkashi.txtに全曲の歌詞が入りました。
5.データの前処理
歌詞にはLoveやWowといった英語も含まれており、英数字、記号を正規表現で削除し、日本語のみの歌詞にします。
import re
# 英数字の削除
kashi = re.sub("[a-xA-Z0-9_]","",kashi)
# 記号の削除
kashi = re.sub("[!-/:-@[-`{-~]","",kashi)
# 空白・改行の削除
kashi = re.sub(u'\n\n', '\n', kashi)
kashi = re.sub(u'\r', '', kashi)6.形態素解析
次に歌詞データに対して形態素解析を行います。 形態素解析とは日本語を最小単位に分割し、品詞などを同定する行為です。 形態素解析なんて難しそーと思われるかもしれませんが、Janomeという形態素解析を行ってくれるライブラリがあるのでそれを使います。
def tokenize(text):
t = Tokenizer()
tokens = t.tokenize(text)
word = []
stop_word = create_stop_word()
for token in tokens:
part_of_speech = token.part_of_speech.split(",")[0]
if part_of_speech == "名詞":
if not token.surface in stop_word:
word.append(token.surface)
if part_of_speech == "動詞":
if not token.base_form in stop_word:
word.append(token.base_form)
if part_of_speech == "形容詞":
if not token.base_form in stop_word:
word.append(token.base_form)
if part_of_speech == "形容動詞":
if not token.base_form in stop_word:
word.append(token.base_form)
return word結果はこのようになります。
窓 名詞,一般,*,*,*,*,窓,マド,マド
の 助詞,連体化,*,*,*,*,の,ノ,ノ
外 名詞,一般,*,*,*,*,外,ソト,ソト
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
眺め 動詞,自立,*,*,一段,連用形,眺める,ナガメ,ナガメ
ながら 助詞,接続助詞,*,*,*,*,ながら,ナガラ,ナガラ
考え 動詞,自立,*,*,一段,連用形,考える,カンガエ,カンガエ
てる 動詞,非自立,*,*,一段,基本形,てる,テル,テル「は」や「の」といった助詞・助動詞は今回は必要ないので、形態素解析を行った後に、名詞・動詞・形容詞・形容動詞だけを取り出しています。 動詞・形容詞・形容動詞に関しては、活用されているものは基本形に直します。基本形は.base_formで取り出せます。
(例)眺め/て → 眺める/て
| 記述 | 取り出せるもの |
|---|---|
| surface | 表層形 |
| infl_type | 活用型 |
| infl_form | 活用形 |
| base_form | 原形 |
| print token.reading | 読み |
| print token.phonetic | 発音 |
他にも関係なさそうな単語を省くためストップワードリストを作成します。 ストップワードは以下を参考に、いくつか自分で追加しました。
def create_stop_word():
target_url = 'http://svn.sourceforge.jp/svnroot/slothlib/CSharp/Version1/SlothLib/NLP/Filter/StopWord/word/Japanese.txt'
r =requests.get(target_url)
soup=BeautifulSoup(r.text, "html.parser")
stop_word=str(soup).split()
#自分で追加
my_stop_word=['いる','する','させる','の','色','真夏','身体','最初','知る','られる']
stop_word.extend(my_stop_word)
return stop_wordこれで辞書が完成しました。
7.word2vecで学習
word2vecは、大量のテキストデータを解析し、各単語の意味をベクトル表現化する手法です。Word2Vecを使うことで、単語と単語の関係性を簡単に表現でき、
「王様」 – 「男」+ 「女」 = 「女王」
「パリ」 – 「フランス」 + 「日本」 = 「東京」
のような 単語同士の 演算が出来たり、単語同士の類似度を計算することができます。 では歌詞データに使われている単語の関係性をword2vecに学習させましょう。
model = word2vec.Word2Vec(sentence, size=200, min_count=4, window=4, iter=50)パラメータはそれぞれ以下を表しています。
| パラメータ | 意味 |
|---|---|
| size | ベクトルの次元数 |
| min_count | n回未満登場する単語を破棄 |
| window | 学習に使う前後の単語数 |
8.類似度の計算
さあようやく準備が出揃いました。 後は西野カナ先生が恋をどのように表現しているのかを調べるだけです。 どうやって調べるかというと、恋という単語に対して類似度の高い単語を出力します。学習したモデルに対し、
.most_similar(positive=["単語"])という風にmost_similarメソッドを使うことでその単語と類似度の高い単語が出力されます。
from gensim import corpora
from janome.tokenizer import Tokenizer
from gensim.models import word2vec
import matplotlib.pyplot as plt
from wordcloud import WordCloud
import re
import requests
from bs4 import BeautifulSoup
with open("kashi.txt", "r", encoding="utf-8") as f:
kashi = f.read()
# 英数字の削除
kashi = re.sub("[a-xA-Z0-9_]","",kashi)
# 記号の削除
kashi = re.sub("[!-/:-@[-`{-~]","",kashi)
# 空白・改行の削除
kashi = re.sub(u'\n\n', '\n', kashi)
kashi = re.sub(u'\r', '', kashi)
# 品詞を取り出し「名詞、動詞、形容詞、形容動詞」のリスト作成
def tokenize(text):
t = Tokenizer()
tokens = t.tokenize(text)
word = []
stop_word = create_stop_word()
for token in tokens:
part_of_speech = token.part_of_speech.split(",")[0]
if part_of_speech == "名詞":
if not token.surface in stop_word:
word.append(token.surface)
if part_of_speech == "動詞":
if not token.base_form in stop_word:
word.append(token.base_form)
if part_of_speech == "形容詞":
if not token.base_form in stop_word:
word.append(token.base_form)
if part_of_speech == "形容動詞":
if not token.base_form in stop_word:
word.append(token.base_form)
return word
def create_stop_word():
target_url = 'http://svn.sourceforge.jp/svnroot/slothlib/CSharp/Version1/SlothLib/NLP/Filter/StopWord/word/Japanese.txt'
r =requests.get(target_url)
soup=BeautifulSoup(r.text, "html.parser")
stop_word=str(soup).split()
#自分で追加
my_stop_word=['いる','する','させる','の','色','真夏','身体','最初','知る','られる']
stop_word.extend(my_stop_word)
return stop_word
sentence = [tokenize(kashi)]
model = word2vec.Word2Vec(sentence, size=200, min_count=4, window=4, iter=50)
print(model.wv.most_similar(positive=[u"恋"], topn=10))topnで上位表示数を指定してます。
出力結果
| 順位 | 単語 | 類似度 |
|---|---|---|
| 1 | 追いかける | 0.8969742059707642 |
| 2 | 待てる | 0.8759297728538513 |
| 3 | 生まれ変わる | 0.8688281178474426 |
| 4 | 押す | 0.8627504110336304 |
| 5 | つらい | 0.8446334004402161 |
| 6 | 失う | 0.8399721384048462 |
| 7 | 逃す | 0.8399657011032104 |
| 8 | 送る | 0.833713710308075 |
| 9 | 為 | 0.8328136801719666 |
| 10 | 噂 | 0.830684244632721 |
一番近かったのは「追いかける」でした。
西野先生いわく、恋とは追いかけるものらしいです。 てっきり「震える」が出ると思ったら出ませんでしたね。
これらの結果から無理やり定義すると
恋:追いかけて、時には待ち、つらい思いもするが生まれ変われるもの
という結果になりました。 なるほど、さすが西野先生。納得しました! せっかくなのでaiko先生にも聞いてみたいと思います。
出力結果
| 順位 | 単語 | 類似度 |
|---|---|---|
| 1 | 堕ちる | 0.9168707132339478 |
| 2 | さよなら | 0.915056347846984 |
| 3 | 全て | 0.9063875675201416 |
| 4 | 嫌い | 0.9030067920684814 |
| 5 | 怒る | 0.889123797416687 |
| 6 | 並べる | 0.8881044983863831 |
| 7 | 上げる | 0.8861263990402222 |
| 8 | 我慢 | 0.885418713092804 |
| 9 | 勇気 | 0.8848025798797607 |
| 10 | 痺れる | 0.8845231533050537 |
「さよなら」「嫌い」「怒る」「我慢」等。 aiko先生は恋はわりとネガティブなモノと考えているみたいですね。
9.おまけ
恋についてもっと知りたくなったので、いろいろな演算を行っていましょう! 演算はpositiveに足す単語を、negativeに引く単語をいれることで出来ます。
model.wv.most_similar(positive=[u"単語1", u"単語2"], negative=[u"単語3"], topn=10)この場合 単語1 + 単語2 – 単語3 になります。
心
恋という漢字の下に心がつくように、恋とは心あってのものだと思います。 では心のない恋とは存在するのでしょうか?恋の概念から心の概念を引いてみました。
model.wv.most_similar(positive=[u"恋"], negative=["心"], topn=3)| 順位 | 単語 | 類似度 |
|---|---|---|
| 1 | 友達 | 0.7440136075019836 |
| 2 | れる | 0.7216039896011353 |
| 3 | 思う | 0.7072246074676514 |
友達になっちゃいました。心が動かないとただの友達ということでしょうか。
嘘
恋に関して嘘ついたことありませんか?好きでもないのに、好きって言ってみたり。←特に男性! 自分の気持ちに嘘ついたり。 西野先生いわく、恋で嘘つくことは・・・
model.wv.most_similar(positive=[u"恋", u"嘘"], topn=3)| 順位 | 単語 | 類似度 |
|---|---|---|
| 1 | 仕方 | 0.9253218173980713 |
| 2 | 辛い | 0.922753095626831 |
| 3 | 追いかける | 0.9220635890960693 |
仕方ないみたいです笑
10.まとめ
- 西野カナの歌詞では、「恋」に最も近い言葉は「追いかける」だった
- 結果をつなぐと「追いかけて、時には待ち、つらい思いもするが生まれ変われるもの」になる
- aikoでは「堕ちる」「さよなら」「嫌い」が上位で、恋の捉え方がかなり違う
- 恋から心を引くと「友達」、恋に嘘を足すと「仕方」が出てくる
パラメーターによって結果にばらつきがでたので、 今後はパラメーターを変えて結果がどう変わっていくかを調査できればなと思います。