2016/12/26

B'zの歌詞について、dov2vecによる類似検索と、感情分析を行う

こんにちは、tktです。

この記事はチャットボット Advent Calendar 2016の23日目の記事です。
http://qiita.com/advent-calendar/2016/chatbot

チャットボットを立ち上げる部分は皆さんが書いてくださっているので、
私は言語理解の部分について書いてみたいと思います。

お題は「その時の気分に合わせた歌詞を返してくれるBot」です。

入力テキストを理解し、それとよく似たシチュエーション・意味の歌詞をレスポンスするシステムを
  1. 入力テキストに類似する歌詞をdov2vecで探す
  2. 入力テキストと類似歌詞を感情分析し、結果を照合する
という、2段構えの仕組みで作ってみたいと思います。
では参りましょう。



1. 頑張って歌詞を書き起こす。


データが無いと始まりません。。歌詞サイトからのコピペは不可ですので、自力でやるしかないです。
とはいえ、380曲程あるらしい(※)全曲を書き出そうとすると来年になってしまうので、
今回は初期曲から10数曲をピックアップして.txtに書き出します。
(※参考:結局のところB'zって全部で何曲あるんですか?

半日かけて作った歌詞データとフォルダ構成はこんな感じです。

root
├── bz_chat.py
└── lyrics
    ├── BREAK_THROUGH
    │   ├── LADY_GO_ROUND.txt
    │   ├── STARDUST_TRAIN.txt
    │   ├── となりでねむらせて.txt
    │   └── 今では…今なら…今も….txt
    ├── Bz
    │   ├── halftonelady.txt
    │   ├── だからその手を離して.txt
    │   └── ハートも濡れるナンバー.txt
    ├── OFF_THE_LOCK
    │   ├── safety_love.txt
    │   └── 君の中で踊りたい.txt
    ├── RISKY
    │   ├── Easy_Come,_Easy_Go!.txt
    │   ├── FRIDAY_MIDNIGHT_BLUE.txt
    │   ├── GIMME_YOUR_LOVE -不屈のLOVE_DRIVER-.txt
    │   ├── It's_Raining....txt
    │   ├── VAMPIRE_WOMAN.txt
    │   ├── 愛しい人よGood_Night....txt
    │   └── 確かなものは闇の中.txt
    └── other
        ├── 衝動.txt
        └── 恋心.txt

「It's_Raining」は名曲ですよね。「コーヒー飲めるようになったんだ…」のくだりがたまりません。
さぁ、次のステップに行きましょう。

2.データを加工する


今回は曲全体を1レコードとして食わせるのではなく、1行毎に訓練データとして食わせてみます。
しかし、歌詞サイト等の表示通りに区切っては、いまいち意味が分からなかったりするので、
ある程度文章として成立して意味が通るように、改行を消して文を繋げていきます。


3.doc2vecのモデルを作成する


doc2vecのやり方については、下記記事が非常にわかりやすいです。

Python と gensim で doc2vec を使う

まず最初に歌詞データを読み込みます。

    lyric_list = []
    cnt = 0

    os.chdir("lyrics")
    album_titles = ["Bz", "OFF_THE_LOCK", "BREAK_THROUGH", "RISKY", "other"]
    for album_title in album_titles:
        filenames = os.listdir(album_title)
        for txtfile in sorted(filenames):
            f = open(album_title + "/" + txtfile)
            lines = f.readlines()
            f.close()
            for line in lines:
                lyric_list.append([line, txtfile.replace(".txt", "") + "_" + str(cnt)])
                cnt += 1
    os.chdir("../")
次に、Mecabで分かち書きにします。今回は、「名詞」「形容詞」のみを抽出します。
def get_token_list(text):

    token_list = []
    hinshi = ["名詞", "形容詞"]

    mt = MeCab.Tagger("-Ochasen")
    mt.parse('')
    node = mt.parseToNode(text)

    while node:
        feats = node.feature.split(',')
        if feats[0] in hinshi :
            try:
                token_list.append(node.surface)
            except:
                print("err: " + str(node.surface))
        node = node.next

    return token_list
分かち書き文をdoc、「タイトル_件数」をtagとして、doc2vecモデルを作成します。
    docs = []
    for lyric_line in lyric_list:
        docs.append(TaggedDocument(get_token_list(lyric_line[0]), tags=[lyric_line[1]]))

    doc2vec_model = Doc2Vec(documents=docs, size=200, window=3, min_count=1, workers=4)
以上で、doc2vecのモデルが出来上がりました!

4.入力テキストの類似歌詞を抽出する


さて、3で作成したモデルに早速、任意の文を食わせて結果を見てみましょう。
手順は、入力テキストを分かち書き→ベクトル化→最も近い文を抽出、です。
    test_doc = "毎日毎日仕事ばっかりで本当に疲れました"
    test_docvec = doc2vec_model.infer_vector(get_token_list(test_doc))
    print("input: ", test_doc)
    for sim_doc in doc2vec_model.docvecs.most_similar([test_docvec]):
        for lyric_line in lyric_list:
            if sim_doc[0] == lyric_line[1]:
                print(str(sim_doc[1]) + ":" + lyric_line[0])
気になる結果(※上位数件抜粋)は、
input:  毎日毎日仕事ばっかりで本当に疲れました

0.8835504651069641:現実を乗車拒否できないのが辛いところ 乗られても でるだけ口聞かないようにしてるよ
0.8353495597839355:心も踊るWEEKEND 憂鬱な彼は TAXI DRIVER 客の多い夜が辛い 気持ちはまるで MONDAY MORNING BLUE
0.8242136836051941:忘れないきみがしてくれたことすべて 二人を繋いでゆくその理由を どこにいてもきっといつしょだから
input:  安西先生、彼女が欲しいです

0.9330607652664185:華やかな人眺めるのは 嫌いじゃないけれど あぁ 眠い・・・
0.9287312030792236:幸せは キャンバスの中に 好きなように描けるはずだから
0.9192379713058472:生涯 最愛のものを手に入れるまで 晴天ばかりは続かない... 体が気づいてる
う~~ん…まぁ、「っぽい」っちゃあ、「っぽい」結果ですね!!w

やはり、データ数が少ないし、文章として成り立ってない表現なんかもあったりするので、イマイチな感じになってしまうのかな。
歌詞で類似を見る場合には、素直にTF-IDFでやる方がしっくりくる結果が得られるかも。。。

続いて、感情分析と参りましょう。

5.感情分析APIを利用して、感情分析を行う


感情分析はこちらのAPIを使います。
一言コメント感情分析APIガイド
基本一言メッセージのチャットボットには結構合うんじゃないかな、、、というのが選定理由です。
HTTPリクエストを送る処理を書きます。
import urllib.request, urllib.parse
import json
def get_emotion(text):

    param = {"comment": text,}
    p = urllib.parse.urlencode(param)
    url = "http://nicomment-api.ktty1220.me/v1/single?" + p

    with urllib.request.urlopen(url) as res:
       result = res.read().decode("utf-8")
       return json.loads(result)["name"]
これを4.の出力結果に適用してみます。その結果は以下の通り。
input:  安西先生、彼女が欲しいです! , emotion:  要望

0.9227278232574463, lyric_line: 僕のこと 大好きだという 言葉にウソは 見当たらない 独りにされるのが かなり苦手な 憶病な君が選んだシステムは, emotion: 称賛
0.9017759561538696, lyric_line: もう一度呼び止め口説ける気がするほど 別れは朧気 眠ってる記憶の中で, emotion: 分類不能
0.8839508891105652, lyric_line: バンパイアウーマン ちょっとやばいよ 本気になりそうな予感がする, emotion: 期待
0.8834215998649597, lyric_line: バンパイアウーマン じつは pretty girl そろそろ君にもツキがきた, emotion: 分類不能
0.880945086479187, lyric_line: ライバルはみんなハイレベル とてもかないそうにない 体力も 財力も 競争率は激しい Yeah!, emotion: 熱狂
う~む、、完全にイマイチですね。そもそもの感情分析が上手くいっていないのが多い。。

こちらについてももうちょっとシンプルに、ポジネガ判定にかけてみる方が、良いかもしれないですね。

X.まとめ、今後


チャットボットの言語理解部を作成する目的で、doc2vecと感情分析を簡単に触ってみました。
結果は今一つでしたが、可能性は感じれたような気がします。

データをもっと増やす&加工の仕方も工夫して、再チャレンジしてみたいですが、
上記した通り、次はもっと素直に、TF-IDF&ポジネガ判定でもやってみようと思います。

0 件のコメント:

コメントを投稿