ENGINEER BLOG

ENGINEER BLOG

Word2Vecを試す (3)

こんにちは。コンサルティングサービス本部、芳賀です。
最終回の今回は、モデルの作成とJupyterを使っての実際のモデルとのインタラクションの様子をお伝えします。
コーパス内の単語の出現位置や頻度だけを頼りに、ここまで単語間の関係が学習できることに驚くと思います。

今回、モデルの作成も含め、すべての作業は Jupyter notebook 上で実施しています。
掲載している notebook は、弊社のカンファレンスで使用したものもとにを一部改変したものです。


モデルを構築します

環境によりますが、数時間要します。待ちましょう。

モデル作成手順の詳細な解説(モデルを調整するパラメータなど)は、gensimのドキュメントを参照してください。
今回はデフォルトの設定でモデルを作成しています。

In [1]:
from gensim.models import word2vec
In [2]:
jawiki_sentences = word2vec.Text8Corpus('data/jawiki.wakati.txt')
In [3]:
jawiki_model = word2vec.Word2Vec(jawiki_sentences,workers=4)
In [17]:
jawiki_model.save('data/jawki_model')

今回はブログ用に特別にモデルの変数名を日本語にしています。
したがって、これ以降登場する「ウィキペディア」と「商品マスタ」は、文字列ではなく、変数名です。

In [18]:
ウィキペディア=jawiki_model

また、比較用に、書籍を説明する文章のカラムを社内の商品マスターから抽出し、モデルにします。(事前作成したものをロードします)
なお、こちらのモデルは読者の方は用意できません。ご容赦ください。モデル作成手順はwikipedia日本語版をコーパスとする場合と同様です。ご自身で青空文庫から文章を適当にダウンロードしてコーパスとしたり、ご自身のSNSから文章を抽出してコーパスにしたりすると新しい発見があるかもしれません。

In [20]:
商品マスタ=word2vec.Word2Vec.load('data/naiyo_joho_model_20160715')

準備ができました。さっそく触ってみましょう。

単語が200次元ベクトルに変換された様子をみてみます。

In [22]:
ウィキペディア['東京']
Out[22]:
array([ 0.02766896,  0.13300112,  0.10585292,  0.29200196, -0.51379174,
       -0.26279065, -0.3761799 , -0.22302115, -0.20107685, -0.01863858,
       -0.01092686,  0.46746945, -0.27543142,  0.16208133,  0.37406415,
        0.31500113,  0.23568238,  0.12708855,  0.00923837,  0.09240662,
        0.19932362, -0.25703019, -0.07693946,  0.31057099, -0.10192768,
        0.19385937,  0.04281049, -0.08178736, -0.23916037,  0.34120628,
        0.07312653,  0.20073345,  0.26171887,  0.24137218, -0.37859371,
       -0.04917977,  0.50534201, -0.28246707, -0.10503445, -0.09931532,
        0.32209334,  0.05187452, -0.04353952, -0.16787396, -0.26921749,
       -0.22309114,  0.25342059,  0.21478592,  0.06887484, -0.21540873,
        0.41865644,  0.1803377 ,  0.08541498, -0.03592011,  0.41215912,
        0.1468574 , -0.09644897,  0.43917358,  0.0573376 ,  0.04770743,
        0.10973065,  0.05365396, -0.34046513, -0.20521615, -0.00851355,
        0.41605097, -0.04838128,  0.34625554, -0.05094474,  0.13113861,
        0.17295271,  0.29826364, -0.03518726, -0.04515481,  0.21845268,
       -0.20610964,  0.03242953, -0.17421858,  0.39253581,  0.1335925 ,
       -0.05300309, -0.13887092,  0.07792427, -0.31373355,  0.29161048,
        0.55255312,  0.08157756,  0.3103376 ,  0.7018711 ,  0.20268874,
       -0.47434926, -0.37137747,  0.11310391, -0.02582842,  0.45049638,
       -0.21544816,  0.0416888 , -0.08589543,  0.16383864,  0.01146561], dtype=float32)

「東京」という単語が200次元ベクトルになり numpy の配列に格納されていることがわかります。
単なる数値の羅列を眺めても理解はできないので、各種メソッドを試してみます。

most_similar メソッドを試す

ある単語に類似する単語のリストを返します。

ウィキペディアの中で、「東京」に類似した単語はなんでしょう?

In [23]:
ウィキペディア.most_similar('東京')
Out[23]:
[('目黒', 0.816143274307251),
 ('世田谷', 0.8112450838088989),
 ('墨田', 0.792610764503479),
 ('文京', 0.786091148853302),
 ('練馬', 0.7789212465286255),
 ('杉並', 0.7788006663322449),
 ('品川', 0.775039553642273),
 ('都', 0.7697024941444397),
 ('渋谷', 0.7644315958023071),
 ('大阪', 0.7545528411865234)]

「東京」が登場する文脈で、それを適当な区の名前に置き換えても自然ということでしょうか。うーむ。

地名以外もやってみましょう。

In [24]:
ウィキペディア.most_similar('ハンバーガー')
Out[24]:
[('ハンバーグ', 0.8245341181755066),
 ('ソフトクリーム', 0.8025467395782471),
 ('オムライス', 0.7948653697967529),
 ('カップラーメン', 0.7943527698516846),
 ('チキン', 0.7722241878509521),
 ('ケーキ', 0.7707080245018005),
 ('クレープ', 0.7706681489944458),
 ('ピザ', 0.7698202729225159),
 ('ステーキ', 0.7676940560340881),
 ('パスタ', 0.767647385597229)]

それっぽいラインナップ

In [25]:
ウィキペディア.most_similar('カレーライス')
Out[25]:
[('オムライス', 0.8668258786201477),
 ('冷やし中華', 0.8451029062271118),
 ('ホットケーキ', 0.836061418056488),
 ('カップラーメン', 0.8316356539726257),
 ('パスタ', 0.8310901522636414),
 ('ソフトクリーム', 0.826958179473877),
 ('グラタン', 0.8264805674552917),
 ('シュークリーム', 0.8263536691665649),
 ('梅干し', 0.8252124786376953),
 ('チャーハン', 0.8203301429748535)]

これも、それっぽいですね。 しかし、「福神漬」を差し置いて9位に「梅干し」がランクイン。
福神漬はどこへ行ったのでしょう。

In [27]:
ウィキペディア.most_similar('福神漬')
Out[27]:
[('チヂミ', 0.7690371870994568),
 ('ピラルク', 0.7307997941970825),
 ('トウチ', 0.7277759313583374),
 ('ナシクニン', 0.7276380062103271),
 ('ブルグール', 0.724781334400177),
 ('ギョウザ', 0.7234683632850647),
 ('ゲーン', 0.7213473320007324),
 ('ソマイ', 0.7210976481437683),
 ('カイエンペッパー', 0.7207245826721191),
 ('ケチャップマニス', 0.7198642492294312)]

ウィキペディアでは、「カレーライス」と「福神漬」は全然違う文脈の世界に存在するようです。

書籍の商品マスタ由来のモデルでも試してみます。

In [29]:
商品マスタ.most_similar('東京')
Out[29]:
[('京都', 0.7880042791366577),
 ('都', 0.7717466354370117),
 ('名古屋', 0.7284942269325256),
 ('神戸', 0.7046212553977966),
 ('横浜', 0.699553370475769),
 ('大阪', 0.6966103315353394),
 ('map', 0.6773609519004822),
 ('マップ', 0.6755198240280151),
 ('全国', 0.6692596673965454),
 ('多摩', 0.6683148145675659)]

同じ「東京」でもウィキペディアモデルとは違うリストが返ってきます。
どうやらこちらのモデルは、旅行ガイドや地図を紹介する文章の影響を強く受けているようです。

「パリ」も試してみます。

In [30]:
商品マスタ.most_similar('パリ')
Out[30]:
[('ロンドン', 0.8447970747947693),
 ('北欧', 0.7298220992088318),
 ('上海', 0.7198860049247742),
 ('プラハ', 0.7111126780509949),
 ('ソウル', 0.7034491896629333),
 ('フランス', 0.6970089673995972),
 ('北京', 0.6908043622970581),
 ('ミラノ', 0.6862040758132935),
 ('ハワイ', 0.6860457062721252),
 ('スイス', 0.6844651699066162)]

これは、思わず旅に出たくなるラインナップ。
シリーズ物の旅行ガイドブックのタイトル「○○○○○○ パリ」「○○○○○○ ロンドン」などがSkip-gramにはまるんでしょう。

「ドイツ」を試します。

In [31]:
商品マスタ.most_similar('ドイツ')
Out[31]:
[('スペイン', 0.8871545791625977),
 ('ロシア', 0.862625002861023),
 ('文法', 0.8309294581413269),
 ('フランス語', 0.8305643796920776),
 ('イタリア', 0.8092654943466187),
 ('会話', 0.7939123511314392),
 ('語', 0.7931591272354126),
 ('ポルトガル', 0.7721288800239563),
 ('アラビア', 0.7441087365150452),
 ('タガログ', 0.7402811050415039)]

いきなり、語学のテキストになってしまいました。国名だからでしょうか?

「フランス」もみてみましょう。

In [32]:
商品マスタ.most_similar('フランス')
Out[32]:
[('イギリス', 0.8103082180023193),
 ('イタリア', 0.7607895731925964),
 ('アメリカ', 0.7370178699493408),
 ('ドイツ', 0.6986533999443054),
 ('パリ', 0.6970089673995972),
 ('英国', 0.6917133927345276),
 ('ロシア', 0.6799556016921997),
 ('オランダ', 0.6540017127990723),
 ('スペイン', 0.6521366834640503),
 ('中国', 0.650036633014679)]

ドイツほどの語学臭はないようです。

形容詞も試します

In [34]:
ウィキペディア.most_similar('高い')
Out[34]:
[('低い', 0.916477620601654),
 ('高く', 0.8358142971992493),
 ('低く', 0.7885122299194336),
 ('大きい', 0.7814016342163086),
 ('少ない', 0.7634713053703308),
 ('乏しい', 0.7512435913085938),
 ('比較的', 0.7462846040725708),
 ('ある程度', 0.744866669178009),
 ('増す', 0.7412347793579102),
 ('総じて', 0.736081063747406)]

「高い」の対義語「低い」がトップ。関連性が高く、「高い」と置換してもほとんどの場合意味のある文章になります。

今回はword2vecに入力する前に、原形に変換したりはしていないのですが、「高い」と「高く」の関連性に Word2Vecは気付きました。

In [36]:
ウィキペディア.most_similar('恥ずかしい')
Out[36]:
[('気持ちいい', 0.7770894169807434),
 ('つまらない', 0.7612548470497131),
 ('すごく', 0.7581580281257629),
 ('可愛い', 0.7522638440132141),
 ('腹立たしい', 0.7521724700927734),
 ('かっこよく', 0.7477181553840637),
 ('疲れる', 0.7436439394950867),
 ('とにかく', 0.742212176322937),
 ('凄く', 0.7396305799484253),
 ('凄い', 0.7381633520126343)]

どうなんでしょう。

動詞もみてみましょう

In [37]:
ウィキペディア.most_similar('食べる')
Out[37]:
[('食す', 0.9019774198532104),
 ('飲む', 0.8885045647621155),
 ('食する', 0.8614270091056824),
 ('煮込む', 0.8086380958557129),
 ('摂る', 0.8046443462371826),
 ('混ぜる', 0.8035823106765747),
 ('焼く', 0.7918040752410889),
 ('釣る', 0.7875957489013672),
 ('食べ', 0.7812197208404541),
 ('作る', 0.7712804675102234)]

いい線いってます。

お待ちかねの A + B – C = X

ロンドン + フランス – パリ = X

パリ → フランス
ロンドン → ?

パリにとってのフランスは、ロンドンにとって何? なお、ベクトルの加算は可換なので、パリにとってのロンドンは、フランスにとっての何?と理解してもかまいません。

In [38]:
ウィキペディア.most_similar(positive=['ロンドン','フランス'], negative=['パリ'])
Out[38]:
[('イギリス', 0.8048648834228516),
 ('カナダ', 0.7888961434364319),
 ('スイス', 0.7299706339836121),
 ('オーストラリア', 0.724111795425415),
 ('アルゼンチン', 0.7208001017570496),
 ('メキシコ', 0.7062230110168457),
 ('北アイルランド', 0.7061851024627686),
 ('アメリカ', 0.705697774887085),
 ('ノルウェー', 0.7050607204437256),
 ('ジャマイカ', 0.703565776348114)]
In [39]:
商品マスタ.most_similar(positive=['ロンドン','フランス'],negative=['パリ'])
Out[39]:
[('イギリス', 0.7595692276954651),
 ('イタリア', 0.7105359435081482),
 ('英国', 0.6935348510742188),
 ('アメリカ', 0.6751421689987183),
 ('オランダ', 0.6313244104385376),
 ('ロシア', 0.6289608478546143),
 ('ドイツ', 0.6177889108657837),
 ('スペイン', 0.5951469540596008),
 ('スイス', 0.5944533944129944),
 ('ポルトガル', 0.5904580950737)]

どちらのモデルも良好な結果を返しています。

こんなのもいけますよ。

女 → スカート
男 → ?

コーパス内の単語の位置関係だけを頼りにここまで意味を汲み取れます。辞書無しで。

In [70]:
ウィキペディア.most_similar(positive=['スカート','男'],negative=['女'])
Out[70]:
[('ズボン', 0.84205162525177),
 ('上着', 0.8038914799690247),
 ('長袖', 0.7932732701301575),
 ('ノースリーブ', 0.7930992841720581),
 ('ネクタイ', 0.7886382341384888),
 ('マフラー', 0.7866106033325195),
 ('フリル', 0.7812436819076538),
 ('紺色', 0.7800796627998352),
 ('バンダナ', 0.7782864570617676),
 ('口元', 0.7780355215072632)]

イタリア + パリ – フランス

フランス → パリ
イタリア → ?

これは初回の記事のイラストにもしていますが、実はうまくいきません。
やってみましょう。

In [40]:
ウィキペディア.most_similar(positive=['イタリア','パリ'],negative=['フランス'])
Out[40]:
[('ミラノ', 0.82990962266922),
 ('アムステルダム', 0.8059395551681519),
 ('ブカレスト', 0.7985496520996094),
 ('ウィーン', 0.7839888334274292),
 ('プラハ', 0.7817593812942505),
 ('チューリヒ', 0.7749707698822021),
 ('ブリュッセル', 0.7641240358352661),
 ('ザグレブ', 0.7623311281204224),
 ('マドリッド', 0.7563279867172241),
 ('トビリシ', 0.7556824088096619)]
In [41]:
商品マスタ.most_similar(positive=['イタリア','パリ'],negative=['フランス'])
Out[41]:
[('ロンドン', 0.7489010691642761),
 ('上海', 0.7273936867713928),
 ('ソウル', 0.7065390348434448),
 ('ハワイ', 0.6977462768554688),
 ('北京', 0.678214967250824),
 ('ロサンゼルス', 0.672370970249176),
 ('バリ島', 0.6664369106292725),
 ('香港', 0.6601269841194153),
 ('ホノルル', 0.6591138243675232),
 ('ラスベガス', 0.6507853865623474)]

ミラノやロンドンがトップに来てしまいます

その訳は?

In [42]:
ウィキペディア.most_similar('ローマ')
Out[42]:
[('エルサレム', 0.816376805305481),
 ('エトルリア', 0.7731591463088989),
 ('ラヴェンナ', 0.7594192028045654),
 ('ビザンティン', 0.7448341846466064),
 ('アヴィニョン', 0.7424214482307434),
 ('エフェソス', 0.7393876910209656),
 ('ビザンチン', 0.7339200973510742),
 ('ボヘミア', 0.7303676009178162),
 ('スタロ・ナゴリチャネ', 0.7297236919403076),
 ('ローマ帝国', 0.7296479344367981)]

ローマは他の国の首都とは文脈が違う

  • ローマ帝国
  • ローマ法王
  • ローマ神話

など、他の国の首都とは使われる文脈が大きく異なります。

そこで、次に、 仲間はずれ探しをしてみます。

仲間はずれ探し ( doesnt_match メソッド )

引数に与えたリストに含まれる単語の中から、それらの平均からもっとも遠い単語を返します。

In [43]:
ウィキペディア.doesnt_match(['ローマ','パリ','ロンドン','ニューヨーク'])
Out[43]:
'ローマ'

やはり、ローマが仲間はずれでした。

ローマは他の首都とは使われる文脈が異なるように、実は、日本の都道府県では東京が特別であることがわかります。
みてみましょう。

In [47]:
ウィキペディア.doesnt_match(['東京','神奈川','千葉','埼玉'])
Out[47]:
'東京'

やはり、「東京」が仲間はずれでした。

東京のかわりに茨城をいれてみると?

In [50]:
ウィキペディア.doesnt_match(['茨城','神奈川','千葉','埼玉'])
Out[50]:
'茨城'

現実は時に非情です。

In [51]:
ウィキペディア.doesnt_match(['東京','神奈川','千葉','埼玉','カレーライス'])
Out[51]:
'カレーライス'

都道府県の中ではもっとも個性的な東京も、他県との隔たりはカレーライスほどではないようです。

関東6県の「東京」との類似度

実際、東京と一番遠いのは何県かみてみましょう

In [53]:
for pref in ['東京','神奈川','千葉','埼玉','茨城','栃木','群馬']:
    print(pref,'\t',ウィキペディア.similarity('東京',pref))
東京 	 1.0
神奈川 	 0.509930309937
千葉 	 0.407990118647
埼玉 	 0.38136527742
茨城 	 0.268230849295
栃木 	 0.271866302947
群馬 	 0.240678413719

群馬でした。 茨城ごめんなさい・・・。

ここに大阪も参加させてみます。

In [56]:
for pref in ['東京','神奈川','千葉','埼玉','茨城','栃木','群馬','大阪']:
    print(pref,'\t',ウィキペディア.similarity('東京',pref))
東京 	 1.0
神奈川 	 0.509930309937
千葉 	 0.407990118647
埼玉 	 0.38136527742
茨城 	 0.268230849295
栃木 	 0.271866302947
群馬 	 0.240678413719
大阪 	 0.754552816184

地理的には距離がある大阪ですが、文脈上の距離は関東6県よりも東京に近いようです。

カレーライスも参戦させます。

In [61]:
for pref in ['東京','神奈川','千葉','埼玉','茨城','栃木','群馬','大阪','カレーライス']:
    print(pref,'\t',ウィキペディア.similarity('東京',pref))
東京 	 1.0
神奈川 	 0.509930309937
千葉 	 0.407990118647
埼玉 	 0.38136527742
茨城 	 0.268230849295
栃木 	 0.271866302947
群馬 	 0.240678413719
大阪 	 0.754552816184
カレーライス 	 0.0340320143222

カレーライスの類似度は一桁落ちます。
東京>群馬>>>>>>>>>>カレーライス
くらいの距離感??

商品マスタのほうがウィキペディアよりも優れる例

文書ファイルのサイズも含まれる単語の種類もウィキペディアのほうが10倍程度大きく、多くの一般的な問に対して、ウィキペディア由来のモデルのほうが良好な結果を示します。 しかし、用途によっては、書籍の内容を説明する文章由来のモデルのほうが良好な結果を返します。

夏目 → 漱石

芥川 → ?

In [57]:
ウィキペディア.most_similar(positive=['芥川','漱石'],negative=['夏目'])
Out[57]:
[('正岡子規', 0.5950794816017151),
 ('芥川賞', 0.5939810276031494),
 ('荷風', 0.5742967128753662),
 ('詩歌', 0.5628352761268616),
 ('随筆', 0.5620007514953613),
 ('康成', 0.5484417676925659),
 ('短歌', 0.5447999238967896),
 ('ゲーテ', 0.5447625517845154),
 ('ミステリ', 0.5404539704322815),
 ('ノンフィクション', 0.5370290875434875)]
In [58]:
商品マスタ.most_similar(positive=['芥川','漱石'],negative=['夏目'])
Out[58]:
[('龍之介', 0.6412351131439209),
 ('正岡子規', 0.6400415301322937),
 ('牧水', 0.6273407340049744),
 ('志賀', 0.6155011057853699),
 ('シュトルム', 0.6094892024993896),
 ('谷崎', 0.5964627265930176),
 ('国木田独歩', 0.5948587656021118),
 ('利一', 0.5942226052284241),
 ('横光', 0.5736669301986694),
 ('魯迅', 0.5727154016494751)]

餅は餅屋といったところでしょうか。
書名や著者に関連する情報の場合、精度が高い傾向にあります。

さいごに

今回は、EC2上でWord2Vecをためしてみました。 モデルの作成には多少時間がかかりますがそれでも、この実験にかかるAWSの料金はほんの数百円です。(うまくスポットを使うと百円以下かも)
作業用のデータをEphemeralに置くためにm4ではなくm3を使い、インスタンス停止時は作業用のディレクトリ以下をs3に保管しています。

思い立ったらすぐに試せるのがクラウドの良さです。

みなさんも、さまざまな文章をWord2Vecに食べさせて面白い結果を得てください!

次回は、また違うライブラリを試したいと思います。

In [ ]: