F@N Ad-Tech Blog

株式会社ファンコミュニケーションズ nend・nex8・viidleのエンジニア・技術ブログ

Word2Vecで「役者/監督」+「映画」を演算する

こんにちは。ファンコミュニケーションズのr_nakayamaです。

さて、今日のblogは、
Word2Vecを使って、「役者/監督」+「 映画」をしまくる、というものです。

もう2、3年前からになるのかもしれませんが、Word2Vecがすごいと
噂かねがね聞いておりながら試していなかったものなので、今回遊んでみました。

では、なぜ映画が題材なのか、ということですが、
筆者は映画がわりかし好きなもので、色々と映画のレビューを使っては、
映画の分析を行えないかと試行錯誤しているからです。

余談ですが、今年秋始めごろ、映画「ダンケルク[*1]」のジャパンプレミアで、
クリストファーノーラン監督を生で拝見してきました。めちゃ嬉しかったです。

【目次】

 

Word2Vecで「役者/監督」+「映画」を演算

さて、本題に入ります。

今回のブログでは、とってもおしゃれですてきな Word2Vec を使ってみたい!ということで
大好きな映画を題材として「役者/監督」+「映画」を調べています。

概要

映画のレビューサイトから、「役者/監督名」がキャストに含まれる映画のレビューを集め、
その集めたレビューから、Word2Vecを使い、
「役者/監督 」という概念に「映画」という概念を加算してみると
どんなワードが現れるかを調べ、
そしてそのワードはしっくりくるのか!?ということを検証します。

今回使わせていただいたレビューサイト : みんなのシネマレビュー
www.jtnews.jp

Word2Vecがとってもおしゃれですてきだと思う理由

筆者がWord2Vecを魅力的だと感じている理由ですが、以下の理由になります。

  • 特に難しくない
  • 言葉の演算が直感的
  • なんか哲学的
特に難しくない

Word2Vecが、特に引っかかるところもなく遊べるということは
下のコードを回すだけなのでおわかりいただけるかと思います。

言葉の演算が直感的

Word2Vecの概要の説明でよく使われる演算に、
「フランス」ー「パリ」+「東京」=「日本」
があるのですが、言葉の演算がとても直感的だと思います。おもしろい!

deepage.net

なんか哲学的

青空文庫などのデータを使って、さまざまな作家の作品を丸々読み込んで、
「人間」= ... というのを調べた方もいらっしゃるようですが、
芥川龍之介の作品を読み込んだ結果、「人間」=「金持」
夏目漱石の作品を読み込んだ結果、「人間」=「矛盾」
ロマン・ロワンの作品を読み込んだ結果、「人間」=「民主」
だなんて、哲学的で素敵だと思いませんか!

tjo.hatenablog.com

実装

さて、まず、Word2Vecで言葉の演算を行うには、
学習させるための、まとまった文章を用意しなければいけません。
今回は、上のレビューサイトをクロールして文章を用意しました!
クロールのためのコードなのですが、割愛させていただきますm(_ _)m

私はクロールしたときにひとつひとつのレビューをリストに保存していきましたので、
(文章のまとまりがリストである必要はないです。)
リストから単語区切りをしていく流れで実装しています。

1. レビューを格納したリストを用意

(他サイトのレビューをそのまま載せるのはNGなので割愛m(_ _)m)

2. 用意したreviewsを、単語で区切り、ファイルに保存

さて、単語を区切って、Word2Vecに食わせるようのtextファイルを作成します。
ポイントは、単語はスペースで区切り、一文一文は改行で区切ることです!
保存する単語ですが、今回は名詞と形容詞に限定して保存しています。
理由は、そうしたほうが結果がきれいだからです。。。(適当ですみません)
名詞と形容詞に限定せずに結果を出すとどうなるかは少し下で。。。

import MeCab
import codecs

# 単語で区切る
tagger = MeCab.Tagger('-Ochasen')
tagger.parse('')

all_data = []
for review in review_list:
    node = tagger.parseToNode(review)
    data = []
    
    while node:
        if (node.feature.split(',')[0] == "名詞") | (node.feature.split(',')[0] == "形容詞"):
            data.append(node.surface)
        node = node.next

    # 単語の区切りはスペースで。
    data = ' '.join(data)
    all_data.append(data)

# ファイルに保存
f = codecs.open("wakati_text.txt", 'w', 'utf-8')
for data in all_data:
    # 一文の区切りは改行で。
    f.write(data + '\n')
f.close()
単語の分割方法

Word2Vecは、単語の区切り方次第で、結果が大きく変わります。
例では、chasenというMecabのデフォルトで入っている辞書で形態素解析を行っています。
ちなみに、-Oは-O(utput format)ということらしいです。。。
単語の分割方法はミソだと思うので、また後日詳しくまとめようと思います!
・単語分割に関する参考ページ
https://engineering.linecorp.com/ja/blog/detail/109
https://qiita.com/quvo/items/9ef250d58971eadf6e1a

名詞と形容詞だけ抜き出す作業をしなかった時

「ノーラン」+「映画」でこんな演算してきます。

  1. 監督 ( 類似度:0.922781 )
  2. 未見 ( 類似度:0.889720 )
  3. 157 ( 類似度:0.886434 )
  4. ♪ ( 類似度:0.879061 )
  5. やっぱり ( 類似度:0.876450 )
  6. 143 ( 類似度:0.875560 )
  7. 151 ( 類似度:0.873607 )
  8. 苦手 ( 類似度:0.872930 )
  9. 105 ( 類似度:0.872208 )
  10. いやぁ ( 類似度:0.870364 )

音符あたりなかなかじわじわきますよね。
(名詞と形容詞だけ抜き出す作業をした結果は下で。)

3. ファイルを読み込んで、modelを作成

from gensim.models import word2vec

# ファイルを読み込んで、modelを作成
data = word2vec.Text8Corpus('wakati_text.txt')

# size      : 出力するベクトルの次元数
# min_count : この数値よりも登場回数が少ない単語は無視する
# window    : 一つの単語に対してこの数値分だけ前後をチェックする
# これらのパラメータを変えていってチューニングをしたり。。。
model = word2vec.Word2Vec(data, size=300, min_count=10, window=10)

4. 色々試す

# vocabularyの確認
[Input]
 model.wv.vocab

[Out]
 'ラストシーン': <gensim.models.keyedvectors.Vocab at 0x7fde7d719518>,
 'シンプル': <gensim.models.keyedvectors.Vocab at 0x7fde7d454f98>,
 '予告編': <gensim.models.keyedvectors.Vocab at 0x7fde7d3e9400>,
 '我々': <gensim.models.keyedvectors.Vocab at 0x7fde7d6c97b8>,
 'なし': <gensim.models.keyedvectors.Vocab at 0x7fde7d456048>,
 'ライジング': <gensim.models.keyedvectors.Vocab at 0x7fde7d6c9898>, ...

# 演算を行う
# most_similar関数、「エマワトソン」+「映画」 = 〇〇の上位n位まで出してくれる
model.most_similar(positive=['エマワトソン', '映画'], topn = 100)

結果発表

さて、早速結果発表。
most_similar関数をつかって、「役者/監督」+「映画」を計算し、
類似度の高かった上位10ワードを載せています。
一度出してみた結果だとあまりしっくりこなかったので、
model = word2vec.Word2Vec(data, size=300, ,min_count=10, window=10)
のパラメータを変えていってそれぞれのワードの類似度を合算して、
上位10位を今回の結果とします!!!

# 類似度を足していくための辞書を用意
test = {}

# 一度目:ベクトルサイズを1000に。
model = word2vec.Word2Vec(data, size=1000, min_count=10, window=10)
out = model.most_similar(positive=['ノーラン', '映画'], topn = 300)
for i in out:
    print(i[0], '( 類似度:{:04f}'.format(i[1]),')')
    if i[0] not in test.keys():
        test[i[0]] = i[1]
    else:
        test[i[0]] += i[1]

# 二度目:ベクトルサイズを10000に。
model = word2vec.Word2Vec(data, size=10000, min_count=10, window=10)
out = model.most_similar(positive=['ノーラン', '映画'], topn = 300)
for i in out:
    print(i[0], '( 類似度:{:04f}'.format(i[1]),')')
    if i[0] not in test.keys():
        test[i[0]] = i[1]
    else:
        test[i[0]] += i[1]

# 上の作業を繰り返す。。。


今回は調べたうち、私の大好きな「ステイサム」「エマワトソン」、
「クリストファーノーラン監督」の結果をご紹介します。

「ステイサム」+「映画」

トランスポーターシリーズでおなじみのイギリス紳士 ステイサム [*2]

1 位 :  派手
2 位 :  最近
3 位 :  迫力
4 位 :  作り
5 位 :  予想
6 位 :  無理
7 位 :  嫌い
8 位 :  シリーズ
9 位 :  ヒロイン
10 位 :  主役

【考察】
1位「派手」、3位「迫力」、6位「無理」、7位「嫌い」。。。
ステイサムっぽいですね。。。(?)

「エマ(ワトソン)」+「映画」

ハリーポッターシリーズ ハーマイオニー役でおなじみのエマワトソン [*3]

1 位 :  内容
2 位 :  映像
3 位 :  ストーリー
4 位 :  学校
5 位 :  何
6 位 :  展開
7 位 :  世界
8 位 :  印象
9 位 :  魔法
10 位 :  ため

【考察】
9位「魔法」が出現しました。「ハリー・ポッター シリーズ」でしょうか。
15位に「ハーマイオニー」などもあったことから、
エマワトソンといえば、「ハリー・ポッター シリーズ」でハーマイオニー役を演じた
という印象が強いのだろうな、ということが伺えるかと思います。

「ノーラン」+「映画」

さて、最後に「インセプション」「インターステラー」でおなじみノーラン監督 [*4]

1 位 :  長い
2 位 :  よかっ
3 位 :  迫力
4 位 :  10
5 位 :  マジック
6 位 :  良かっ
7 位 :  満足
8 位 :  なかっ
9 位 :  正直
10 位 :  編集

【考察】
んー、微妙!!!

まとめ

「役者/監督」+「映画」という演算の結果、
しっくりくるものは少なかったかなと思います。。。
ステイサムのような決まった役を演じる方だと、うまくいくのかもしれません。
単語の区切り方や辞書、パラメータを変えたり、データを増やしてみたりして、
さらに工夫してみて楽しみたいと思います。

最後に

弊社では随時インターン生を募集しています。
詳細は最近インターンに来てくれたs_itoくんのブログで!

tech-blog.fancs.com