Word2Vec の紹介
2016年 12月 17日
エキサイトとしては初の Advent Calendar 参戦です!!
こんにちはー!

ピチピチの新卒一年生

本記事では、Word2Vec を本気でご紹介したいと思います!!
というのも、実は私、学生時代は自然言語処理に関する研究をやっておりまして、Word2Vecを利用した新機能を密かに考えているのです・・・!ここに書いている時点で密かじゃなくなっていますが!
Word2Vecというと、自然言語処理や機械学習の分野でたびたび耳にする単語で、なんとなく敷居が高いイメージがあるんじゃないかなーと思います。
もちろん、技術的には非常に高度で、根本を理解しようと思えばそれなりの知識を要します。
ですが、実は簡単な解析をするだけなら結構お手軽に動かすことができてしまうんです。
この記事でお伝えしたいのは以下の2点です!
- Word2Vec を触ってみることの敷居の低さ
- Word2Vec のおもしろさ!
この記事を読んで、Word2Vecに興味を持って、実際に触ってみよう!と思ってくださる方が一人でもいらっしゃれば嬉しいです!
では、本題に入りましょう。
Word2Vec とは
Word2Vec は、テキストのコーパスを入力すると、コーパスに含まれる各単語の特徴量ベクトルを表す数値を出力します。
単語を数値化することで、単語間の類似性を調べたり、単語同士の計算(!!!)をすることができます。
具体的にどういうことができるかというと、例えばディズニーランドと類似性が高い単語を調べたり、"女王 - 女性 = ?" といった不思議な計算をすることができるわけです。 (この結果は後ほど示します!)
これを利用して、意味解析や文書分類など様々な分野に応用することができます。
Word2Vec を触ってみる
Word2Vec を簡単に利用しようと思ったら、python と gensim を用いる方法が簡単かと思います。
また、コーパスを作成するために、形態素解析器で単語の分かち書きをする必要があります。 コーパスの作成にあたって、wikipedia日本語版のdumpデータを利用します。
各種ツール等は以下のものを利用しました。
- python 2.7.12
- gensim (word2vecを含むライブラリ)
- ruby 2.3.1
- wp2txt (wikipediaのdumpデータを入力すると、プレーンテキストにして返してくれるライブラリ)
- Mecab
- ipadic,neologd (Mecabの辞書)
先ずは、wikipediaのdumpデータからコーパス(jawiki.txt)を作成します。
# wp2txtでdumpデータをプレーンテキストに
$ wp2txt -i *-pages-articles.xml.bz2
# バラバラのテキストを一つにまとめて、mecabを用いて分かち書きする(バッファサイズを大きめに取らないとエラー出ちゃいます)
$ cat jawiki-*-articles.txt > jawiki.txt
$ mecab -b 100000 -Owakati jawiki.txt
コーパスが作成できたら、いよいよ Word2Vec の出番です。
先ずは学習データを生成します。
そこそこ時間がかかります・・・
w2v_learning.py
1 # coding: utf-8
2 from gensim.models import word2vec
3 sentences = word2vec.Text8Corpus(sys.argv[1])
4
5 model = word2vec.Word2Vec(sentences, size=200, min_count=20, window=15)
6 model.save(sys.argv[1].replace('.txt','.model'))
7
8 if __name__ == '__main__':
9 print "Finish!!!"
学習時の各パラメータ
sentences | コーパス |
size | 出力するベクトルの次元数 |
min_count | この設定値より出現回数が少ない単語を除外する |
window | 共起をみるときに、この設定値内の前後の単語を対象とする |
次に、学習データを利用して、指定した単語に関連性が高い単語とその関連度を出力してみます。
model.most_similar() は、単語ベクトル間のコサイン類似度を一括計算して高い順に出力してくれる関数です。
w2v_result.py と実行結果
1 # coding: utf-8
2 from gensim.models import word2vec
3 import sys
4
5 model = word2vec.Word2Vec.load(sys.argv[1])
6
7 def similarWords(pos, neg=[]):
8 cnt = 1
9 result = model.most_similar(positive = pos, negative = neg)
10 for r in result:
11 print cnt,' ', r[0],' ', r[1]
12 cnt += 1
13
14 if __name__ == '__main__':
15 word = sys.argv[2]
16 word = unicode(word, 'utf-8')
17 similarWords([word])
$ python w2v_result.py jawiki.model ディズニーランド
1 マジック・キングダム 0.795866250992
2 香港ディズニーランド 0.788654565811
3 ディズニー・ハリウッド・スタジオ 0.782007932663
4 アトラクション 0.769073843956
5 ディズニー・カリフォルニア・アドベンチャー 0.767895102501
6 ウォルト・ディズニー・ワールド 0.760404348373
7 東京ディズニーランド 0.759293854237
8 ディズニーパーク 0.755676984787
9 ディズニーランド・パリ 0.751613974571
10 トゥーンタウン 0.732621729374
こんな感じの結果が得られます。
世界各地のディズニーランドやアトラクションなど、ディズニーランドに関連性が高い単語が列挙されていることがわかりますね!
次に、単語同士の足し算引き算をやってみます 。
単語同士の足し算または引き算を行って、最も妥当な単語を返します。
w2v_word_calc.py と実行結果
1 # coding: utf-8
2 from gensim.models import word2vec
3 import sys
4
5 model = word2vec.Word2Vec.load(sys.argv[1])
6
7 def wordAddition(pos1, pos2):
8 result = model.most_similar(positive = [pos1,pos2], topn = 1)
9 print pos1 , '+' , pos2 , '='
10 for r in result:
11 print r[0]
12
13 def wordSubtraction(pos, neg):
14 result = model.most_similar(positive = pos, negative = neg, topn = 1)
15 print pos , '-' , neg , '='
16 for r in result:
17 print r[0]
18
19 if __name__ == '__main__':
20 word1 = unicode(sys.argv[2], 'utf-8')
21 word2 = unicode(sys.argv[4], 'utf-8')
22 if sys.argv[3] == '+':
23 wordAddition(word1,word2)
24 else:
25 wordSubtraction(word1,word2)
$ python w2v_word_calc.py jawiki.model 女王 - 女性
女王 - 女性 =
大王
$ python w2v_word_calc.py jawiki.model 大王 + 女性
大王 + 女性 =
女王
女王 から 女性を引き算すると 大王 が残り、大王 に再び 女性 を足し算すると 女王 に戻る!おもしろい!
今回の例以外にも、他の単語で試してみるとおもしろい結果が得られるかもしれません。
(人生 + 結婚 = ? とか・・・)
応用編 Word2Vecをサービスに活かす
冒頭にも書きましたが、Word2Vec をサービスに活かすアイデアとして考えているものがありますので、その1つを紹介します。
私の担当サービスの1つ フレンズちゃんねる はユーザが自由に記事を書いて投稿できるサービスですが、その記事に内容を表すタグをつけることができます。
このタグはユーザが自由につけることが出来るのですが、同じような意味でも文言が違うタグがつけられることがよくあります。
例えば、「野球」と「プロ野球」などです。
同じタグがつけられている記事を関連記事として表示する機能があるのですが、タグの微妙な文言の違いなどで、表示されるべき記事が表示されないことがよくあります。
この問題を解決するために、特定のタグと意味的に近しいタグを Word2Vec を利用して抽出することで、関連記事機能を強化しようと考えています。
このアイデアを簡易的に実装したものがこちらです。
学習済みモデルとタグ一覧を与えておいて、指定された単語と近しい単語をjson形式で返します。
getRelatedtag.py
1 # coding: utf-8
2 from gensim.models import word2vec
3 import sys
4 import json
5 import codecs
6
7 # しきい値
8 THRESHOLD = 0.5
9
10 # 学習済みモデルのロード
11 model = word2vec.Word2Vec.load("jawiki.model")
12
13 def getRelatedTag(word):
14 # ちゃんねるに存在するタグ一覧を取得
15 with codecs.Open('taglist.txt', 'r') as file:
16
17 relatedtags = {}
18 for tag in file:
19 try:
20 tag = tag.replace('\n','')
21 tag = unicode(tag, 'utf-8')
22 sim = model.similarity(word, tag)
23
24 if sim > THRESHOLD:
25 relatedtags[tag] = sim
26
27 except Exception as e:
28 pass
29
30 return json.dumps(relatedtags, ensure_ascii=False)
31
32 if __name__ == '__main__':
33 word = sys.argv[1]
34 word = unicode(word, 'utf-8')
35 print getRelatedTag(word)
実行結果
$ python relatedTag.py 野球{"プロ野球": 0.7077490747918278, "イチロー": 0.54230111065593367, "バスケ": 0.5550160978529679, "野球": 1.0, "練習": 0.52382533618720439, "スポーツ": 0.57571585722699159}
この出力結果を利用することで、いままでは表示されていなかった関連する記事も表示できるようになるはずです。
終わりに
今回は wikipediaのdumpデータからコーパスを作成しましたが、例えば青空文庫のデータを用いたりツイッターのつぶやきを収集したものを用いたりすると、得られる結果の性質が変わったりして面白いと思います。
ざっと word2Vecを用いた実験を紹介してみましたが、敷居の低さは伝わったんじゃないかなと思います。敷居が低いからこそ、幅広い発展がある技術なんじゃないかなーと僕は思っています。
おもしろいアイデアが浮かんだ方は、ぜひとも今すぐ Word2Vecを触ってみてください!!!
明日の担当は、私の尊敬する先輩でチューターでもある大重さんです!
僕も度々参加させていただいている、ボードゲームを使ったエンジニア交流会についての記事です!お楽しみに!!

ぜひぜひご応募をお待ちしております!