カルチャーとデータサイエンス

ビジネスの意思決定は無理でも、せめてカルチャーはデータサイエンスしたい

向井秀徳は結局「諸行無常」しか言ってないんじゃないか?をデータサイエンス ~自然言語処理編~

はじめに

ちょうど記事書いてたら逆噴射バンドの東京公演当たった・・

f:id:fyiwdwytm:20191013152328p:plain

この画像の生成を行うところまでを頑張ります。

当記事の目的

向井秀徳が結局「諸行無常」しか言っていない気がしていたのでこの際はっきりさせようと思います。

本テーマは2部構成でお送りします。テーマが思いついたらまた記事を足します。

  1. 歌詞取得編 →
  2. 自然言語処理編 → この記事

この記事は自然言語処理編です。前の記事で作った歌詞データのcsvを読み込んで、単語の分解や、どの単語を最も多く使用しているか?を分析したいと思います。

参考元

前の記事と同様に、以下のサイトを参考にさせていただきました。

qiita.com

加えて、上記Qiitaの元記事?が以下の記事に当たるようで、後半部分は一部参考にしています。

pira-nino.hatenablog.com

また、自然言語処理に関する言葉の定義は以下サイトから引用しています。

www.nltk.org

準備

実装方法の定義

言語は前の記事と同じくPythonを使用します。

加えて形態素解析としてMeCabを使用します。形態素解析の意味は下記を参照してください。

形態素とは意味を持つ最小の言語単位であり、形態素解析とは与えられた文を形態素単位に区切り、各形態素に品詞などの情報を付与する処理である。形態素の定義はもともと欧米語の言語学から来たものであるため、まちまちであり確立されていないが、日本語形態素解析の立場から言えば、辞書に記載されている見出し語を形態素と見なし、その単位に分割すると同時に、見出し語にひもづく品詞や標準形などを形態素に付与する処理であると言える。したがって、必要以上に単語や形態素の区別に気をつける必要はない。 www.nltk.org

英語であれば形態素の区切りとして" "(空白)が使用されますが、日本語の場合はそのような分かち書きがなされない言語です。更に同じ単語でも位置や句読点の違いから意味が異なる場合があります。

Mecabはその単語を区切るための処理エンジンと、区切る際に参照する辞書を一緒に提供する機能、と考えてください。

環境構築

形態素解析器のインストール

筆者は分析前から既にMecabの環境構築を済ませていましたが、これから環境構築を行う場合は以下を参考にMecabやその他必要なパッケージをインストールします。

fresopiya.com (2019年時点でおそらく最新の手順書?)

PythonからMecabを使用する場合、Pythonバインディングを行うためmecab-python3のインストールが必要となります。

またWeb用語等の新しい言葉を使用する場合には、辞書mecab-ipadic-NEologdも合わせてインストールしておくと便利です。

■Matplotlib, Seabornの日本語化

qiita.com

歌詞をSeabornの棒グラフで表示する際に、グラフ描画のパッケージであるMatplotlib及びSeabornを使用します。

一方これらのパッケージはそのまま日本語を載せると文字化けしてしまうので、上記URLを参考に日本語対応できるようにします。

(MatplotlibおよびSeabornのインストールがまだの人はpip installconda installしましょう!Anacondaを使っている人はすでにインストールされているはずです。)

実装

コードを実装していきます。前記事と同じくJupyter Notebookにて実装します。

最初の手順でMecabの機能をサンプル文で検証し、その後に前回作成した歌詞データcsvを読み込ませて検証します。

形態素解析を行った後は歌詞に出てくる単語の頻度をランキング化し、棒グラフやWordCloud(後述)で描画・分析します。

1. 形態素解析器の検証

はじめにMecabの使い方もかねて、試し書きのコードを実装します。

import MeCab

# 1. 形態素解析器をインスタンス化し、parseToNodeメソッドでオブジェクトに処理結果を格納
text = "繰り返される諸行無常 よみがえる性的衝動"
mecabTagger = MeCab.Tagger("-Ochasen -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd/") # 辞書:mecab-ipadic-neologdを使用
node = mecabTagger.parseToNode(text)

#2. 表層形(surface)と素性(feature)を格納するデータフレームを作成
surface_and_feature = pd.DataFrame()
surface = []
feature = []

#3. nodeオブジェクトのアトリビュートから表層形、素性を抽出
while node:
    surface.append(node.surface)
    feature.append(node.feature)
    node = node.next
    
surface_and_feature['surface'] = surface
surface_and_feature['feature'] = feature

surface_and_feature

f:id:fyiwdwytm:20191013151517p:plain

形態素解析器をmecabTaggerという形でインスタンス化します。その後、形態素解析を行うメソッドparseToNode()を実行し、結果をnodeオブジェクトに格納しています。

形態素解析の結果、nodeオブジェクトには2つのアトリビュートが格納されます。

  1. 表層形(surface): 単語そのもの。文中において文字列として出現する形式
  2. 素性(feature): 単語の情報のリスト

なおfeatureという単語について、機械学習界隈では「特徴量」と訳すことが多いですが、自然言語処理では「素性(そせい)」ということが多いそうです。

次に、各単語の素性には複数の情報がリストで格納されているので、更に確認します。

text = "繰り返される諸行無常 よみがえる性的衝動"
mecabTagger = MeCab.Tagger("-Ochasen -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd/")
node = mecabTagger.parseToNode(text)

# 素性(feature)のリストの中身(品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音)をデータフレームに格納
features = pd.DataFrame(columns=["pos","pos1","pos2","pos3","ctype","cform","base","read","pronounce"])
posses = pd.DataFrame
list = []
while node:
    tmp = pd.Series(node.feature.split(','), index=features.columns)
    features = features.append(tmp, ignore_index=True)
    node = node.next

辞書mecab-ipadic-neologdを使用する場合、素性(feature)の中身には品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音の8つがリストとして格納されます。

ちなみにデータフレームの先頭と末尾にあった「BOS/EOS」ですが、はnodeの先頭と末尾をそのまま表す値だそうです(要確認)。

Mecabの使い方を確認したところで、本番の処理を記載していきます。

2. パッケージインポート

必要なパッケージをインポートします。

import MeCab #自然言語処理
import numpy as np #行列演算
import matplotlib.pyplot as plt #グラフ描画
import pandas as pd #データフレーム操作
import re #正規表現
import seaborn as sns #グラフ描画
from PIL import Image #WordCloud使用で必要な依存パッケージ
from wordcloud import WordCloud #WordCloudを描画
%matplotlib inline 

WordCloudとはこの記事の冒頭に載せた画像のような、文字をマッピングした画像を生成するパッケージです。

3. 関数(形態素解析用)の定義

# 関数① get_word_list
def get_word_list(lyric_list):
    m = MeCab.Tagger ("-Ochasen -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd/")
    lines = []
    for text in lyric_list:
        keitaiso = []
        m.parse('')
        node = m.parseToNode(re.sub('\u3000',' ',text)) #補足①
        while node:
            #辞書に形態素を入れていく
            tmp = {}
            tmp['surface'] = node.surface
            tmp['base'] = node.feature.split(',')[-3] #原形(base)
            tmp['pos'] = node.feature.split(',')[0] #品詞(pos)
            tmp['pos1'] = node.feature.split(',')[1] #品詞再分類(pos1)
            
            #文頭、文末を表すBOS/EOSは省く
            if 'BOS/EOS' not in tmp['pos']:
                keitaiso.append(tmp)
                
            node = node.next
        lines.append(keitaiso)
    
    #名詞・動詞・形容詞の場合は原形、それ以外の品詞で活用が存在しない場合は表層形をリストに格納する
    word_list = [] 
    for line in lines:
        for keitaiso in line:
            if (keitaiso['pos'] == '名詞') |\
                (keitaiso['pos'] == '動詞') |\
                (keitaiso['pos'] == '形容詞') :
                if not keitaiso['base'] == '*' :
                    word_list.append(keitaiso['base'])
                else: 
                    word_list.append(keitaiso['surface'])

    return word_list

関数① get_word_list - 複数の曲の歌詞のリストを受け取って、単語のリストに変換します。 - 動詞「笑う」という単語をとっても、「笑わない」「笑いたい」「笑う」など複数の活用形が考えられます。これらを単語に切ったとき、「笑わ」「笑い」「笑う」は全て「笑う」と扱うのが望ましいです。なので、単語を切った後、原形(動詞であれば終止形)があるものは全て原形をリストに格納します。

補足① メソッドre.sub - re.sub(正規表現パターン,置換先文字列,処理対象文字列)で使用し、正規表現で指定した文字列を別の文字列に置換します。 - \u3000はunicodeで全角スペースを表します。歌詞の取得時に全角スペースがエンコードされて残っているのを取り除く処理、ということです。

4. 歌詞の形態素解析・データフレーム化

# csvインポート
mukai_lyrics = pd.read_csv("mukai_lyrics.csv")

# 関数get_word_listによる文字列リストword_listの生成
word_list = get_word_list(mukai_lyrics.Lyric.tolist())

# word_listから、単語とその出現頻度をマッピングしたデータフレームwords_dfの生成
word_freq = pd.Series(word_list).value_counts() #pandasのSeriesに変換してvalue_counts()
words_df = pd.DataFrame({'noun' : word_freq.index,
             'noun_count' : word_freq.tolist()})

words_df.head(10)

f:id:fyiwdwytm:20191013154159p:plain

前回の記事で作成した歌詞データのcsvから歌詞のカラムLyricsを指定し、先程作成した関数を通してリストword_listを生成します。

word_listには各歌詞に含まれる単語が1次元のリストとして格納されています。重複された単語をカウントするために、単語と出現回数をマッピングしたデータフレームwords_dfを生成します。

出力結果を見ると、既に一番使用している単語は「俺」であるという身も蓋もない結果が出ていますが、「する」「いる」「れる」など、単語自体には歌詞の意味が入っていないであろう単語が気になります。

次の項にてこれらの単語をストップワード(stop words)として除外します。

5. 特定の単語(stop words)の除外

stop_words = ['いる','する','れる','てる','なる','られる','よう','の','いく','ん','せる','いい','ない','ある','しまう','さ']
droplist = []
for i in stop_words:
    droplist.append(words_df[words_df.noun == i].index[0])

words_df = words_df.drop(droplist).reset_index(drop = True)

words_df.head(10)

ストップワードは記事冒頭で参考リンクにあげている記事で使用されたものをそのまま使用しました。

f:id:fyiwdwytm:20191013154837p:plain

笑う・狂うが動詞で最も頻度が多い、というのは納得の結果ではないでしょうか。

6. 検証①棒グラフ

words_df_50 =words_df[0:50]

f, ax = plt.subplots(figsize=(18, 6))
sns.set(font="IPAexGothic")
sns.barplot(x=words_df_50['noun'], y=words_df_50['noun_count'])
plt.xticks(rotation='90')
plt.xlabel('単語リスト', fontsize=15)
plt.ylabel('出現頻度', fontsize=15)
plt.title('向井秀徳の歌詞に含まれる単語の出現回数', fontsize=15);

f:id:fyiwdwytm:20191013160218p:plain

ランキング1~50位をグラフ化しました。

記事タイトルにもあった「諸行無常」は20位ぐらいに落ち着いています。

それ以外の考察は8. 考察の項でまとめて実施します。

7. 検証②WordCloud

# WordCloudを描画する関数の実装
def draw_wordcloud(df,col_name_noun,col_name_quant,fig_title):
    word_freq_dict = {}
    for i, v in df.iterrows(): #単語とその頻度をデータフレームから辞書化
        word_freq_dict[v[col_name_noun]] = v[col_name_quant]
    fpath = "/System/Library/Fonts/ヒラギノ角ゴシック W3.ttc"
    
    # WordCloudをインスタンス化
    wordcloud = WordCloud(background_color='white',
                        font_path = fpath,
                          min_font_size=15,
                         max_font_size=200,
                         width=1000,
                         height=1000
                         )
    wordcloud.generate_from_frequencies(word_freq_dict) #補足①
    plt.figure(figsize=[20,20])
    plt.imshow(wordcloud,interpolation='bilinear')
    plt.axis("off")
    plt.title(fig_title,fontsize=25)
    
draw_wordcloud(words_df, 'noun','noun_count','向井秀徳Word Cloud')

f:id:fyiwdwytm:20191013152328p:plain

補足① メソッドwordcloud.generate_from_frequencies()

  • 関数の中ではインポートしたクラスWordCloudインスタンス化し、その後メソッドgenerate_from_frequenciesを呼び出しています。
  • メソッドgenerate_from_frequenciesはkey: 単語名、value: 出現回数となる辞書word_freq_dictを引数とし、出現回数が大きくなるほど文字が大きくなるWordCloud画像を生成します。
  • 生成した画像はplt.imshowにてプロットします。

WordCouldから分かる情報は「なんとなくこの単語はいっぱい登場している」ぐらいのものですが、より直感的でわかりやすいですね。

8. 考察

棒グラフやデータフレームから、改めて分かる情報を考察します。

f:id:fyiwdwytm:20191013160218p:plain

前提条件として、あくまで歌ネットから取得した歌詞ベースで取得しているので、繰り返しの省略などが入っているなど、実際に歌っている回数や他の歌詞サイトと差異が発生している可能性があります。あくまで歌ネットベースでの考察とご理解ください。

もうちょっと聴き込んでいればマシな考察ができるんでしょうけど。。文脈まで分かる方は是非ご自身でコードを実装して推察してみてください。

1. 一番使用している動詞は「笑う」

  • 「俺」の次に登場回数が多く、合計75回です。
  • もっと物騒な単語が多いのかと思えば、全体を見ると意外と朗らかな歌詞を歌っているのかもしれないですね。3位は「狂う」ですけど

2. 「街」「都市」など、大きめの市町村をテーマにしている歌詞が何曲かある

  • 「街」が同率3位(44回)、「都市」が7位(35回)です。福岡の久留米市か、もしくは東京にインスパイアされているのでしょうか。
  • 向井秀徳といえば「冷凍都市」という言葉のイメージが強いですが、「冷凍」だけで見ると18位(24回)なので、「冷凍都市」のセットよりも「都市」だけで多く使用していることが分かります。

3. 「性的」より「衝動」の方が出現回数が多い

  • 「よみがえる性的衝動」という言葉を分解すると、よみがえる=40位(17回)、性的=21位(23回)、衝動=11位(29回)です。
  • 「性的衝動」という言葉はほぼセットで使用されていることが読み取れますが、それよりも多い頻度で「衝動」という言葉だけで使われているときがあるようです。
  • そりゃ「性的」だけ出していたら歌詞的に浮くでしょうけど...「衝動」とセットにして口馴染みのある言葉にする向井秀徳のワードセンスはすごいですね。

4. 「パンツ」だけで16回言っている

  • 「サイボーグのオバケ」で15回言っているはずなので、後1回はどこに...
  • ちなみに「下着」は114位(10)回です。これも「黒い下着」がほぼ占めていると考えられます。


サイボーグのオバケ

Todo

  • sns.barplotへのデータラベル追加(何件あったかの数値をグラフ上に表示する。ax.annotateを使おうとしたがどうやら軸名に日本語を使っているせいでエラーになる・・?
  • sklearn.TfidfVectorizerによる単語のベクトル化。単語の出現頻度をTF-IDFというロジックによって頻度・希少性を元にベクトル化が可能です。単語同士による組み合わせ(「冷凍」と「都市」)のクラスタリングや、機械学習と組み合わせれば「向井秀徳っぽい文章」のモデル化とその判定器を作ることができるかもしれません。
  • LDAおよびWord2Vecによる単語のベクトル化、及びクラスタリング

ailaby.com

おわりに

この記事も本来は前処理程度のことしかやっておらず、本来であればTodoの通りTF-IDFやWord2Vecを用いた単語のベクトル化まで行うのが自然言語処理・・と言いたいところです。が、まだ学習が間に合っておらず一旦ここまでとします。

先人の方々が既にこのあたり実装されているようなので、参考にさせていただきつつ実装を進めようと思います。

pira-nino.hatenablog.com

おまけ

f:id:fyiwdwytm:20191013184222p:plain


Number Girl - Num-Ami-Dabutz