word2vecで実践するAKB48と乃木坂46における「自由」

#はじめに
Pythonのライブラリのword2vecを使って、AKB48と乃木坂46の歌詞をそれぞれ学習することで、AKB48と乃木坂46それぞれの歌詞における「自由」と類似する言葉を調べてみました。ここでは主に６つのプログラムによって処理を行っています。最初に歌ネット（ https://www.uta-net.com/ ）からAKB48と乃木坂46のそれぞれの歌詞を取得するために楽曲のタイトルとIDを取得しています。次にそのIDを使って、楽曲ごとの歌詞を取得しています。その後、MeCabを使って形態素解析を行い、ストップワード処理をしています。その次にどのような言葉が多く使われているかを確認するために形態素解析したデータを集計しています。そして、形態素解析したデータをword2vecを使って学習モデルを作成しています。最後に、学習モデルから「自由」に類似する言葉を抽出してみました。

#このプログラムを実行するために必要な環境
BeautifulSoup、gensimなどのライブラリがインストールされている。（anacondaなどのパッケージを推奨、gensimのみ別途インストールが必要）

mecabをpythonで使用できる環境が整っていて、かつ、mecab-ipadic-NEologdなどの辞書が入っている。

ここで紹介しているプログラムはコマンドラインの「python ファイル名.py」で実行できます。

#プログラムの主な流れ
この分析は以下のAKB48と乃木坂46それぞれについて、６つのプログラムを順を追って実行していくことで処理できます。

- 歌ネットからAKB48と乃木坂46それぞれの楽曲のIDとタイトルをスクレイピングする。
- 歌ネットからAKB48と乃木坂46それぞれの歌詞をスクレイピングする。
- スクレイピングした歌詞を形態素解析する。
- 形態素解析したテキストデータを集計してみる。
- word2vecで形態素解析したテキストデータから学習モデルを作成する。
- 学習したモデルから「自由」に類似した言葉を抽出する。

#AKB48における「自由」の類似語
最初に、AKB48における「自由」の類似語を抽出してみたいと思います。ここでの処理は、先ほどの説明の通り、歌ネットからIDとタイトルを取得し、歌詞を取得し、形態素解析を行って、word2vecで学習モデルを作成するといった流れとなっています。

##AKB48の歌詞をスクレイピング
今回の分析で学習用に使用するデータとしては、これまで発表されてきたAKB48のすべての楽曲の歌詞を使いたいと思います。歌ネットでは、アーティストごとの楽曲の歌詞があるので、ここから歌詞をスクレイピングします。歌ネットのサイトを見てみると、歌詞の掲載されているページはIDで作られているので、その楽曲のIDを取得するために、最初にAKB48の楽曲が一覧できるページから楽曲のIDとタイトルをスクレイピングします。そのIDを使って楽曲ごとの歌詞を取得することができます。

###AKB48の楽曲タイトルのIDを取得
以下のプログラムでは、歌ネットのうちAKB48の楽曲のみを絞ったURLを使って、楽曲のIDと楽曲のタイトルをスクレイピングしています。取得したデータをakb48_id.csvというファイルで書き出すまでの処理を行っています。

```python:scraping_akb48_id.py
# -*- coding:utf-8 -*-
import csv
import requests
import codecs
from bs4 import BeautifulSoup

f = codecs.open('akb48_id.csv', 'w', 'utf-8')
f.write("code,title" "\n")

target_url = 'https://www.uta-net.com/search/?Aselect=1&Bselect=3&Keyword=AKB48&sort=&pnum={0}'

for i in range(1, 4):
    r = requests.get(target_url.format(i))
    req = requests.Request(r)
    soup = BeautifulSoup(r.text, 'html5lib')
    codes = soup.find_all('td',{'class':'side td1'})
    titles = soup.find_all('td',{'class':'side td1'})


    for code, title in zip(codes, titles):
        print(code.find('a').attrs['href'][6:].replace("/", ''), title.text)
        f.write(str(code.find('a').attrs['href'][6:].replace("/", '')) + ',' + title.text + "\n")


f.close()
```

###AKB48の楽曲タイトルのIDから歌詞を取得
先ほどのプログラムで取得した楽曲のIDを使用して、歌詞のテキストデータをスクレイピングしています。取得したテキストデータは、akb48_lyrics.csvというファイルで書き出しています。

```python:scraping_akb48_lyrics.py
# -*- coding:utf-8 -*-
import csv
import requests
import codecs
from bs4 import BeautifulSoup
import pandas as pd

f = codecs.open('akb48_lyrics.csv', 'w', 'utf-8')
f.write("lyrics" "\n")

target_url = 'https://www.uta-net.com/song/{0}/'

akb48_01 = pd.read_csv('akb48_id.csv',dtype = 'object')
akb48_02 = akb48_01["code"].values.tolist()

for i in akb48_02:
    r = requests.get(target_url.format(i))
    req = requests.Request(r)
    soup = BeautifulSoup(r.text, 'html5lib')
    lyrics = soup.find_all('div',{'id':'kashi_area'})

    for lyric in lyrics:

        print(lyric.text.replace(",", ''))

        f.write(str(lyric.text.replace(",", '') + "\n"))


f.close()
```
##AKB48の歌詞を形態素解析
次に、取得した歌詞のテキストデータを形態素解析して、word2vecのモデル生成に対応したデータにします。word2vecで学習する場合には、単語スペース単語という形式になっている必要があります。なので、形態素解析したデータがスペース区切りとなるような処理を行っています。ストップワード処理としては、名詞、形容詞、動詞、副詞のみを取り出す処理としています。形態素解析したデータは、akb48_wakati.txtというファイルで出力しています。

```python:akb48_mecab.py
import MeCab

lyrics = open('akb48_lyrics.csv', 'r')
text = lyrics.readlines()

def extractKeyword(line):
    tagger = MeCab.Tagger('-Ochasen')
    tagger.parse('')
    node = tagger.parseToNode(line)
    keywords = []
    while node:
        if node.feature.split(",")[0] == u"名詞":
            keywords.append(node.surface)
        elif node.feature.split(",")[0] == u"形容詞":
             keywords.append(node.surface)
        elif node.feature.split(",")[0] == u"動詞":
             keywords.append(node.surface)
        elif node.feature.split(",")[0] == u"副詞":
             keywords.append(node.surface)
        node = node.next
    return keywords

import codecs
import re
f = codecs.open('akb48_wakati.txt', 'w', 'utf-8')
single =  r"^[ぁ-ん]$"

for line in text:
    kekka = extractKeyword(line)
    wakati = ' '.join(kekka)
    re_wakati1 = wakati.split()

    for line2 in re_wakati1:
        if re.match(single,line2):
            re_wakati2 = ""
        elif len(line2.encode('utf-8')) < 3:
            re_wakati2 = ""
        else:
            re_wakati2 = line2
        print(re_wakati2,end=" ")
        f.write(re_wakati2)
        f.write(" ")
    print("\n")
    f.write("\n")
f.close()
```

##形態素解析したテキストデータの集計（AKB48）
ここでの処理は、今回の分析には直接は関係ないので、飛ばしても大丈夫です。一応、形態素解析したテキストがどのような言葉が実際に多く使われているのかを確認するためにやっています。今回調べようと思っている「自由」という言葉も75回使われていることが確認できます。この集計の処理については、pandasを使うなどすればもっと見やすく出力することができると思います。

```python:akb48_count.py
f = open('akb48_wakati.txt')
lines2 = f.readlines()
f.close()

import codecs
f = codecs.open('akb48_count.txt', 'w', 'utf-8')

words =[]
for line in lines2:
    line3 = line.replace(" ", "\n")
    f.write(line3)
f.close()

f = open('akb48_count.txt')
lines2 = f.read()
f.close()

lines3 = lines2.split()

import collections
words = collections.Counter(lines3)

print(words.most_common())
```
##word2vecで歌詞からモデル作成（AKB48）
ここからが学習に関するプログラムとなります。先ほど行った形態素解析をしたスペース区切りのテキストデータを使ってword2vecで学習モデルを作成しています。size、min_count、windowなどのパラメーターによって学習結果が異なってきますので、何度かパラメーターを変更してみたりすることをお勧めします。作成した学習モデルは、akb48.modelというファイルとして保存しています。

```python:akb48_word2vec.py
from gensim.models import word2vec
import logging

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
sentences = word2vec.LineSentence('akb48_wakati.txt')

model = word2vec.Word2Vec(sentences, size=100, min_count=5, window=10, hs=1, sg=1, seed=100)
model.save("akb48.model")
```
##「自由」に類似する単語を抽出（AKB48）
最後に、先ほど作成した学習モデルを使用して、ここでは「自由」という言葉に類似する単語を抽出しています。topnを指定すると上位の類似する単語が出力されます。

```python:akb48_word2vec_model.py
from gensim.models import word2vec

model = word2vec.Word2Vec.load("akb48.model")
results = model.wv.most_similar(positive=["自由"], topn=20)
for result in results:
    print(result)
```
##出力結果（AKB48）

```
世界 0.5644636154174805
風に乗って 0.5584157705307007
パラダイス 0.5396431088447571
アイヤ 0.5267466902732849
飛ぶ 0.521303117275238
Halloween 0.5185834765434265
飛べる 0.5173347592353821
踊れ 0.4997383952140808
縛ら 0.4945579767227173
いい 0.4936122000217438
リスク 0.49195727705955505
ルール 0.4813954532146454
アイヤイヤイ 0.472299188375473
奪わ 0.45674586296081543
ない 0.4543856084346771
馬鹿げ 0.4531818926334381
持つ 0.4521175026893616
てる 0.44793424010276794
鳴り響く 0.436395525932312
遥か 0.4335517883300781
```

#乃木坂46における「自由」の類似語
次に、乃木坂46における「自由」の類似語を抽出してみたいと思います。ここでの処理は、先ほどのAKB48の時に行った処理と同様に、歌ネットからIDとタイトルを取得し、歌詞を取得し、形態素解析を行って、word2vecで学習モデルを作成するといった流れとなっています。

##乃木坂46の歌詞をスクレイピング
今回の分析で学習用に使用するデータとしては、これまで発表されてきた乃木坂46のすべての楽曲の歌詞を使いたいと思います。歌ネットでは、アーティストごとの楽曲の歌詞があるので、ここから歌詞をスクレイピングします。歌ネットのサイトを見てみると、歌詞の掲載されているページはIDで作られているので、その楽曲のIDを取得するために、最初に乃木坂46の楽曲が一覧できるページから楽曲のIDとタイトルをスクレイピングします。そのIDを使って楽曲ごとの歌詞を取得することができます。

###乃木坂46の楽曲タイトルのIDを取得
以下のプログラムでは、歌ネットのうち乃木坂46の楽曲のみを絞ったURLを使って、楽曲のIDと楽曲のタイトルをスクレイピングしています。取得したデータをnogi46_id.csvというファイルで書き出すまでの処理を行っています。

```python:scraping_nogizaka46_id.py
# -*- coding:utf-8 -*-
import csv
import requests
import codecs
from bs4 import BeautifulSoup

f = codecs.open('nogi46_id.csv', 'w', 'utf-8')
f.write("code,title" "\n")

target_url = 'https://www.uta-net.com/search/?Keyword=%E4%B9%83%E6%9C%A8%E5%9D%8246&x=0&y=0&Aselect=1&Bselect={0}'

for i in range(1, 2):
    r = requests.get(target_url.format(i))         #requestsを使って、webから取得
    req = requests.Request(r)
    soup = BeautifulSoup(r.text, 'html5lib') #要素を抽出
    codes = soup.find_all('td',{'class':'side td1'})
    titles = soup.find_all('td',{'class':'side td1'})


    for code, title in zip(codes, titles):
        print(code.find('a').attrs['href'][6:].replace("/", ''), title.text)
        f.write(str(code.find('a').attrs['href'][6:].replace("/", '')) + ',' + title.text + "\n")


f.close()
```

###乃木坂46の楽曲タイトルのIDから歌詞を取得
先ほどのプログラムで取得した楽曲のIDを使用して、歌詞のテキストデータをスクレイピングしています。取得したテキストデータは、nogi46_lyrics.csvというファイルで書き出しています。

```python:scraping_nogizaka46_lyrics.py
# -*- coding:utf-8 -*-
import csv
import requests
import codecs
from bs4 import BeautifulSoup
import pandas as pd

f = codecs.open('nogi46_lyrics.csv', 'w', 'utf-8')
f.write("lyrics" "\n")

target_url = 'https://www.uta-net.com/song/{0}/'

nogi46_01 = pd.read_csv('nogi46_id.csv',dtype = 'object')
nogi46_02 = nogi46_01["code"].values.tolist()

for i in nogi46_02:
    r = requests.get(target_url.format(i))         #requestsを使って、webから取得
    req = requests.Request(r)
    soup = BeautifulSoup(r.text, 'html5lib') #要素を抽出
    lyrics = soup.find_all('div',{'id':'kashi_area'})

    for lyric in lyrics:

        print(lyric.text.replace(",", ''))

        f.write(str(lyric.text.replace(",", '') + "\n"))


f.close()
```

##乃木坂46の歌詞を形態素解析
次に、取得した歌詞のテキストデータを形態素解析して、word2vecのモデル生成に対応したデータにします。word2vecで学習する場合には、単語スペース単語という形式になっている必要があります。なので、形態素解析したデータがスペース区切りとなるような処理を行っています。ストップワード処理としては、名詞、形容詞、動詞、副詞のみを取り出す処理としています。形態素解析したデータは、nogi46_wakati.txtというファイルで出力しています。

```python:nogizaka46_mecab.py
import MeCab

lyrics = open('nogi46_lyrics.csv', 'r')
text = lyrics.readlines()

def extractKeyword(line):
    tagger = MeCab.Tagger('-Ochasen')
    tagger.parse('')
    node = tagger.parseToNode(line)
    keywords = []
    while node:
        if node.feature.split(",")[0] == u"名詞":
            keywords.append(node.surface)
        elif node.feature.split(",")[0] == u"形容詞":
             keywords.append(node.surface)
        elif node.feature.split(",")[0] == u"動詞":
             keywords.append(node.surface)
        elif node.feature.split(",")[0] == u"副詞":
             keywords.append(node.surface)
        node = node.next
    return keywords

import codecs
import re
f = codecs.open('nogi46_wakati.txt', 'w', 'utf-8')
single =  r"^[ぁ-ん]$"

for line in text:
    kekka = extractKeyword(line)
    wakati = ' '.join(kekka)
    re_wakati1 = wakati.split()

    for line2 in re_wakati1:
        if re.match(single,line2):
            re_wakati2 = ""
        elif len(line2.encode('utf-8')) < 3:
            re_wakati2 = ""
        else:
            re_wakati2 = line2
        print(re_wakati2,end=" ")
        f.write(re_wakati2)
        f.write(" ")
    print("\n")
    f.write("\n")
f.close()
```

##形態素解析したテキストデータの集計（乃木坂46）
ここでの処理は、今回の分析には直接は関係ないので、飛ばしても大丈夫です。一応、形態素解析したテキストがどのような言葉が実際に多く使われているのかを確認するためにやっています。今回調べようと思っている「自由」という言葉も60回使われていることが確認できます。この集計の処理については、pandasを使うなどすればもっと見やすく出力することができると思います。

```python:nogizaka46_count.py
f = open('nogi46_wakati.txt')
lines2 = f.readlines()
f.close()

import codecs
f = codecs.open('nogizaka46_count.txt', 'w', 'utf-8')

words =[]
for line in lines2:
    line3 = line.replace(" ", "\n")
    f.write(line3)
f.close()

f = open('nogizaka46_count.txt')
lines2 = f.read()
f.close()

lines3 = lines2.split()

import collections
words = collections.Counter(lines3)

print(words.most_common())
```

##word2vecで歌詞からモデル作成（乃木坂46）
ここからが学習に関するプログラムとなります。先ほど行った形態素解析をしたスペース区切りのテキストデータを使ってword2vecで学習モデルを作成しています。size、min_count、windowなどのパラメーターによって学習結果が異なってきますので、何度かパラメーターを変更してみたりすることをお勧めします。作成した学習モデルは、nogizaka46.modelというファイルとして保存しています。


```python:nogizaka46_word2vec.py
from gensim.models import word2vec
import logging

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
sentences = word2vec.LineSentence('nogi46_wakati.txt')

model = word2vec.Word2Vec(sentences, size=100, min_count=5, window=10, hs=1, sg=1, seed=100)
model.save("nogizaka46.model")
```

##「自由」に類似する単語を抽出
最後に、先ほど作成した学習モデルを使用して、ここでは「自由」という言葉に類似する単語を抽出しています。topnを指定すると上位の類似する単語が出力されます。


```python:nogizaka46_word2vec_model.py
from gensim.models import word2vec

model = word2vec.Word2Vec.load("nogizaka46.model")
results = model.wv.most_similar(positive=["自由"], topn=20)
for result in results:
    print(result)
```

##出力結果（乃木坂46）

```
特権 0.6652380228042603
脱い 0.6220737099647522
なろ 0.5961438417434692
始める 0.5454341173171997
国境 0.45137685537338257
見上げ 0.44773566722869873
Get 0.4456521272659302
必要 0.44296208024024963
仲間たち 0.4364272952079773
受けて 0.4297247529029846
進め 0.4280410706996918
空 0.4277403652667999
若さ 0.422186940908432
ボーダー 0.42211493849754333
制服 0.4201713502407074
ディスコティック 0.4199380874633789
ノック 0.41815412044525146
拓け 0.4154769778251648
起き 0.412681519985199
ガールズトーク 0.40724512934684753
```
#分析結果
AKB48は、「世界」、「風に乗って」、「パラダイス」など、メッセージ的な印象で、乃木坂46は、「特権」、「国境」、「ボーダー」など、俯瞰的な印象の言葉が目立った。

|AKB48|コサイン類似度|乃木坂46|コサイン類似度|
|:----------------:|:-----------------:|:-----------------:|:-----------------:|
|世界|0.5644636154174805|特権|0.6652380228042603|
|風に乗って|0.5584157705307007|脱い|0.6220737099647522|
|パラダイス|0.5396431088447571|なろ|0.5961438417434692|
|アイヤ|0.5267466902732849|始める|0.5454341173171997|
|飛ぶ|0.521303117275238|国境|0.45137685537338257|
|Halloween|0.5185834765434265|見上げ|0.44773566722869873|
|飛べる|0.5173347592353821|Get|0.4456521272659302|
|踊れ|0.4997383952140808|必要|0.44296208024024963|
|縛ら|0.4945579767227173|仲間たち|0.4364272952079773|
|いい|0.4936122000217438|受けて|0.4297247529029846|
|リスク|0.49195727705955505|進め|0.4280410706996918|
|ルール|0.4813954532146454|空|0.4277403652667999|
|アイヤイヤイ|0.472299188375473|若さ|0.422186940908432|
|奪わ|0.45674586296081543|ボーダー|0.42211493849754333|
|ない|0.4543856084346771|制服|0.4201713502407074|
|馬鹿げ|0.4531818926334381|ディスコティック|0.4199380874633789|
|持つ|0.4521175026893616|ノック|0.41815412044525146|
|てる|0.44793424010276794|拓け|0.4154769778251648|
|鳴り響く|0.436395525932312|起き|0.412681519985199|
|遥か|0.4335517883300781|ガールズトーク|0.40724512934684753|

#参考
[「OK word2vec ! "マジ卍"の意味を教えて」 Pythonでword2vec実践してみた](http://www.randpy.tokyo/entry/python_word2vec)