B'zの歌詞をPythonと機械学習で分析してみた 〜前処理編〜
1. 本Part概要
前Partでは「歌詞データの入手」と「前処理の必要性」について話しました。
本Partでは「実際にどのような前処理をしたか」について話していきます。
2. 前処理の概要
先に今回行った前処理の流れについて書かせて頂きます。
- mecabを用いて「名詞・動詞・形容詞」の抽出
- nltk, sklearnの英語のstopwordリストを用いて英語の不用語の削除
あくまでも上記は本データへの前処理の一例であって、絶対的なものではないです。
今回対象とする歌詞データは日本語・英語が混在しているちょっと特殊な文書データとなっております。
そこで、mecabは英単語を全て名詞と判定する性質を用いて、先にmecabで品詞を絞って、後に英語の前処理を施しました*1。
3. mecabを用いた前処理
3.1 mecabとは
簡単にいうと「日本語文を単語ごとに区切ってくれて、しかも品詞判定してくれる」便利なパッケージです。正確にはこれらの作業は「わかち書き」と呼ばれます。
インストールの仕方や簡単な使い方は以下のリンクを参考にしてください。
例えば、以下のような感じで単語のわかち書きができます。
#辞書指定はお好みで # tagger = MeCab.Tagger('') tagger = MeCab.Tagger('/usr/local/lib/mecab/dic/mecab-ipadic-neologd/') tagger.parse("")#バグ回避 result = tagger.parse("今日の天気は晴れである。It is sunny today.") print(result) 今日 名詞,副詞可能,*,*,*,*,今日,キョウ,キョー の 助詞,連体化,*,*,*,*,の,ノ,ノ 天気 名詞,一般,*,*,*,*,天気,テンキ,テンキ は 助詞,係助詞,*,*,*,*,は,ハ,ワ 晴れ 名詞,一般,*,*,*,*,晴れ,ハレ,ハレ で 助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ ある 助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル 。 記号,句点,*,*,*,*,。,。,。 It 名詞,一般,*,*,*,*,* is 名詞,一般,*,*,*,*,* sunny 名詞,一般,*,*,*,*,* today 名詞,一般,*,*,*,*,* . 名詞,サ変接続,*,*,*,*,* EOS
有能ですね。見てお分かりの通り、英語は名詞判定されてしまうのでご注意を。*2
3.2 名詞・動詞・形容詞のみの抽出
NLPでは「名詞・動詞・形容詞以外は大して意味を持っていないので削除」する方針で前処理を行うことが一般的に知られています。
そこで、曲ごとに必要な品詞の単語のみを取り出すコードを書きます*3。
詳細な文法等は以下のリンク等を参考にしてください。
詳細情報の取得 parse()の代わりにparseToNode()を使うと形態素の詳細情報が得られます。parseToNode()は先頭のノード(形態素情報)を返し、surfaceで表層形、featureで形態素情報を取得できます。両方とも文字列です。featureは , で区切られているのでsplit()などで分割して必要な情報を抽出します。
WindowsでMeCab Pythonを使う - 人工知能に関する断創録
def get_dokuritsugo_by_mecab(text): tagger = MeCab.Tagger('/usr/local/lib/mecab/dic/mecab-ipadic-neologd/') tagger.parse('') node = tagger.parseToNode(text) word_list = [] while node: pos = node.feature.split(",")[0] if pos in ["名詞", "動詞", "形容詞"]: # 対象とする品詞 word = node.surface word_list.append(word) node = node.next return " ".join(word_list) df_all["mecabで前処理"]=df_all["Lyric"].apply(lambda x : get_dokuritsugo_by_mecab(x))
さて、こちらで前処理された某曲の歌詞がこちらになります。 と曲のサンプルを載せたいのですが、ちょっと色々怖いので勘弁してください。*4
4. 英語の前処理
さて、次に英語の前処理を行います。
英語の前処理の目的は"it"や"is"といった意味のない単語を削除することです。
前述でも若干触れましたが、実はこのような単語は"stop words"と呼ばれ公開されています。
公開されているstop wordsリストで有名なものは以下の2つです。
from sklearn.feature_extraction import stop_words stop_words_sklearn=stop_words.ENGLISH_STOP_WORDS import nltk nltk.download('stopwords') stop_words_nltk = nltk.corpus.stopwords.words('english')
どっちがいいかということで比較を行うと、sklearn
は318語、nltk
は179単語となっており、nltk
の方が基本的な単語のリストになっております。
今回は、力技で両方ともくっつけてstop wordリストを作成します。
stop_words_nltk.extend(stop_words_sklearn) stop_words_all=stop_words_nltk
リストにリストを足すときに、append
ではうまくいかないことは想像がつくと思いますが、extend
でうまくいきます。
(ちょっと躓いたのでシェア)
さらに実はなのですが、日本語のstop wordsリストを公開してくれているサイトも存在します。
slothlib_path = 'http://svn.sourceforge.jp/svnroot/slothlib/CSharp/Version1/SlothLib/NLP/Filter/StopWord/word/Japanese.txt' slothlib_file = urllib.request.urlopen(slothlib_path) slothlib_stopwords = [line.decode("utf-8").strip() for line in slothlib_file] slothlib_stopwords = [ss for ss in slothlib_stopwords if not ss==u'']
今回はこれを使うと「時間」や「彼女」などB'zっぽい単語も消えてしまうので、あえて使いません。
さて本題に戻り、stop wordsの削除をするコードを書きます。
上記のstop wordsの削除を行っても微妙な単語がいくつか残っていたので(本当はもっと上手くやりたいが)手動で削除。
import string import re add_words=["'",u"それ",u"てる",u"よう",u"こと",u"の",u"し",u"い",u"ん",u"さ",u"て",u"せ",u"れ"] stop_words_all.extend(add_words) def del_eng_stop_words(text): #!マークなども消したいので正規表現で空文字に置換 regex = re.compile('[' +re.escape(string.punctuation) + '0-9\\r\\t\\n]') sample = regex.sub("", text) #小文字にして、stop wordsに入っていなければリストに追加 words = [w.lower() for w in sample.split(" ") \ if not w.lower() in stop_words_all] words=' '.join(words) return words df_all["英語の削除"]=list(df_all["mecabで前処理"].apply(lambda x: del_eng_stop_words(x)))
5. 前処理の効果の確認
5.1 高頻出単語の比較
前処理の効果を見るために、出現頻度が高い単語の比較を行います*5
上図を見ると前処理後は「君」「僕」「love」といったそれっぽい単語が高頻度で出現していることが見てとれます。
このことからいい感じに前処理できているということができます。
5.2 Word Cloudを用いた可視化
データ入手編でもお話ししたWord Cloudで再度可視化します。
前Partで作成した図に比べ凄くそれっぽいカッコイイ図が生成されました。
個人的には満足なのでひとまず前処理はこちらで終了です。
6. 最後に
本Partでは諸々の前処理について書きました。
次Partでは、いよいよ「機械学習を用いた分析〜TF-IDF編〜」を書いていきます。
B'zの歌詞をPythonと機械学習で分析してみた 〜データ入手編〜
1. 本Part概要
こんにちは。pira-ninoです。
本Partでは、B'zの「歌詞データの入手」について書かさせて頂きます。
分析の概要については、「イントロ編」を参照してください。
2. 歌詞データの入手
前Part(分析結果の公開について)で書いた通りなのですが、ちょっとデータ入手については色々不安点がありますので、あえて生コードは載せません。(いきなりで申し訳ない。。。)
以下のリンク*1を参考に、「歌詞」・「曲名」・「発売年」をカラムに持ったDataFrameを作成します。
あまりにも解説がないのはアレなので、何をしたのかを以下の箇条書きで説明します。
- 曲一覧のページに対して、
urllib.request
でhtmlを引っ張ってくる - 取ってきたhtmlを元に
BeautySoup
オブジェクトを作る soup.find_all<href=re.compile("正規表現")
でhrefが正規表現に引っかかる部分のみ取って来れるので、これを駆使して各曲名と歌詞が書いてあるURLを取ってくる- そのURLのhtmlを元に歌詞を取ってくる。
- ついでにamazonへのリンクがあるので、それを元にCDの発売日を取ってくる
また、BeautySoupに関しては以下のURLが初心者向けで為になります。
さらに、発売日が「:1991-11-2」といった形で抽出されるので軽く前処理をし、「年」のみの抽出を行います。
def get_year(moji): return int((moji.split(":")[1]).split("-")[0]) df["Year"]=list(df["Sales_Date"].apply(lambda x: get_year(x)))
これらを行うことで得られるDataFrameはこのようになります。(キューピー3分クッキング方式)
ちなみに、334曲の歌詞が上記の作業でDataFrameに格納されていると思います。改めてですが、すごい曲数ですね。。。
3. 前処理の前に一回集計
自然言語処理(NLP:Natural Language Processing)の分野ではいわゆる「不用語」の削除を前処理として行うのが定石となっております。
例えば「句読点」や「助詞」は頻度的には多く出現するが、特に意味を持っていないので予め削除すべきということは明白です。
さらにこの問題は日本語特有の問題ではなく、英語でも、"It"や"Is"など明らかな不用語はstopwordと呼ばれ削除することも定石となっております。
そこで、ここではあえて不用語削除前の単語の分析を行うことで「前処理の必要性」を感じたいと思います。
3.1 単語頻度の集計
単語頻度を集計する際にはcollections.Counter
が非常に便利です。
これに一個の長い単語のリストを入れると、その要素と頻度が簡単に求められます。
from collections import Counter text = ' '.join(df_all["Lyric"]) #各曲を長い一つのリストに統合 words = [w for w in text.split(" ")] #textをスペース区切りでリストに格納 word_count = Counter(words)
これで得られた(単語、頻度)の組み合わせをDataFrameに入れて頻度上位20単語を可視化します。 何かとぶつかるmatplotlibの日本語表示の問題は各自の環境に合わせて解決とよく言われるが、私はこれで解決。
import matplotlib from matplotlib.font_manager import FontProperties font_path = '/Library/Fonts/TakaoPGothic.ttf' font_prop = FontProperties(fname=font_path) import matplotlib.pyplot as plt %matplotlib inline #DataFrameに格納 df_count = pd.DataFrame.from_dict(word_count,orient='index').reset_index() df_count.columns=["word","count"] df_count.sort_values("count",ascending=False,inplace=True) df_count=df_count.iloc[1:,:] #可視化 plt.figure(figsize = (30,20)) ax=df_count.iloc[:20,:].plot.bar() ax.set_ylabel("Frequency",fontsize=15) ax.set_xticklabels( df_count.iloc[:20,:]["word"], fontdict = { "fontproperties": font_prop, 'fontsize':10, } ) ax.legend_.remove() plt.show()
これで出力された図がこちら。
"to"とか"it"などが出ていることから、なんとなく残念な結果が出ていることが分かります。
さらに、単語頻度を詳細に見て見ると\u3000
って文字のせいで日本語がうまく区切られていないことが見られます。この\u3000
は全角スペースのことで文字コードの処理で何かとよく見る文字です。
3.2 Word Cloudを用いて可視化
Word Cloudはネットでよく見る単語をかっこよく表示しているツール(?)です。
結構かっこよく見えますが、色も大きさも含め実は「単語の頻度の情報」しか情報量を持っていない図となっております。色は気にしなくて大丈夫です。大きさだけ見てればいいです。
何はともあれ可視化して見ましょう。
from PIL import Image from wordcloud import WordCloud, STOPWORDS font_path = '/Library/Fonts/TakaoPGothic.ttf' #単語の頻度をCounterに text = ' '.join(df_all["Lyric"]) #各曲を長い一つのリストに統合 words = [w for w in text.split(" ")] #textをスペース区切りでリストに格納 word_count = Counter(words) #Word Cloudを用いて可視化 bz_mask = np.array(Image.open("bz.png"))#適当に持ってきた写真。背景が白で黒い部分に文字が載る。 wc_bz = WordCloud( background_color="white", max_words=3000, max_font_size=70, mask=bz_mask, font_path=font_path ) # Counterを引き渡す wc_bz.generate_from_frequencies(word_count) plt.figure(figsize = (21,12)) sns.set_style("whitegrid") plt.imshow(wc_bz, interpolation="bilinear") plt.axis("off") # plt.savefig(''前処理前の単語のword_cloud.png") plt.show()
再度ですが「単語の頻度以外の情報量がない図」なのですが、とにかくカッコイイ図ですね。
カッコイイとはいえ結構ゴミ単語が見受けられることから「前処理の必要性」を感じていただけたでしょうか?
4. 最後に
本Partでは、歌詞をスクレイピングしてきてDataFrameに納めました。
さらに、集計を行うことで前処理の必要性を確認しました。
次Partでは「どのような前処理をしたか」について話していきたいと思います。
*1:このサイトを見つけた時、「神」と思ったのと同時に「世の中似たようなことしたい人がいるんだなぁ」と思いました。