Pythonでベクトル表現・演算を行う【Word2Vec】
Pythonで単語のベクトル表現・演算を行う
Word2Vecは、自然言語の単語の意味をベクトル表現する手法です。 また、以下のような意味的な演算が可能であることが有名です。
「King (王) – Masculinity (男性) + Femininity (女性) = Queen (女王)」
モチベーション
前回GiNZAを紹介しました。
このときに、単語ベクトルを見て、Word2Vecを思い出しました。
また、単語のベクトル表現について、Word2Vecを試してみようかなと思いました。 例えば、「焼く+水=煮る・茹でる」になるのかなど。 ただ、「水を入れて火にかける」という説明を学習データに含めないと、マッピングができないかもなと思っています。
そこで、今回はWord2Vecを試し、料理に関する単語についてベクトル演算が可能かを確かめてみました。
Word2Vec
主に以下のサイトを参考に試しました。
また、収集したレシピテキストをもとにWord2Vecを再学習し、調理に関する単語の演算を試しました。 今回は、Word2Vecの内容は特に調べていません。
使用方法
インストール
pip install gensim
上のコマンドで簡単にインストールができます。 また、今回は形態素解析にJanomeを用いるため、これもインストールします。
pip install janome
Word2Vec学習、ベクトル演算処理
- インポート
import json from janome.tokenizer import Tokenizer from gensim.models import word2vec
必要なモジュールをインポートします。 今回は、事前に取得しJson形式で保存したレシピデータを使うので、jsonモジュールをインポートしています。
- テキスト準備
path = 'data/recipe_chefgohan.json' df = json.load(open(path, 'r', encoding='utf-8')) # print(df) text = '' for _,r in df['recipes'].items(): # print(r['title']) start = r['title']+'を作ります。' end = r['title']+'が完成しました。' instructions = ''.join(r['instructions']).replace('\n','') text += start + instructions + end + '\n' print(text[:100])
レシピの手順テキストを使いますが、手順には料理名が含まれないので、 以下の文字列を前後に追加します。
これでちゃんとした文章になるかな?
「○○を作ります。」
「○○が完成しました。」
t = Tokenizer() def extract_words(text): tokens = t.tokenize(text) return [token.base_form for token in tokens if token.part_of_speech.split(',')[0] in['名詞', '動詞']] sentences = text.split('。') word_list = [extract_words(sentence) for sentence in sentences]
準備したテキストを形態素解析し、名詞・動詞・形容詞のみを抽出します。
これで、準備が整いました。
- Word2Vecの学習
model = word2vec.Word2Vec(word_list, vector_size=100,min_count=5,window=5,epochs=100)
学習は、1行で行えます。 引数がわからないときは、以下のコードで確認できます。
参考にしたブログと引数名が変わっていたため、この方法で確認して書き換えました。
print(help(word2vec.Word2Vec()))
結果表示
- 肉ベクトル
コード:
print(model.__dict__['wv']['肉'])
ベクトルをそのままリストで取得し、表示します。
出力:
[-0.86755484 -0.1890513 2.70156 -0.76835483 1.2123765 -1.7350413 1.2958905 -1.2852497 -0.27418274 0.02897273 -1.6959829 1.7419792 -0.9828316 -0.10096513 -0.08968378 -0.48344788 0.42379144 -0.6499651 ・・・ -1.6139066 -0.20777668 0.66221493 -1.0789928 -0.3463647 0.8860949 ]
一応ベクトルができていますね。
- 焼く
コード:
ret = model.wv.most_similar(positive=['焼く'], topn=5) for item in ret: print(item[0], item[1])
次に、ターゲットのベクトルに最も類似したベクトルを持つ単語を表示します。
出力:
焼ける 0.4931952655315399 キツネ 0.45121780037879944 トースター 0.44726747274398804 焼き上げる 0.430612713098526 返す 0.4264180660247803
うーん、この時点で怪しい。 「焼く」という単語の近くに出てくるであろう単語が出力されています。
- 焼く+水
コード:
results = model.wv.most_similar(positive=['焼く','水'], topn=5) for result in results: print(result[0])
positive
に足したい単語の文字列をリストで与えることで、ベクトルの足し算ができます。
出力:
焦げる g 70 焼き上げる テフロン
うーん。「g」「70」は水の近くにある分量だと思います。 「焼く」-「水」で焦げるだったら理解できるけど、今回は足し算だしな。
- 煮る-水
コード:
results = model.wv.most_similar(positive=['煮る'], negative=['水'], topn=5) for result in results: print(result[0])
negative
に引きたい単語の文字列をリストで与えることで、ベクトルの引き算ができます。
出力:
煮込む タイ 店 俺 蒸し器
「煮る」-「水」は「煮込む」! 今回これだけそれっぽいですね。 「水」がいくらか減るまで「煮る」=「煮込む」
ただ次の候補が「タイ」。。
- 煮る-焼く
コード:
results = model.wv.most_similar(positive=['煮る'], negative=['焼く'], topn=5) for result in results: print(result[0])
最後に、「煮る」-「焼く」。 もちろん「水」を期待してのテストです。
出力:
顆粒 だし A 煮汁 ペースト
完全敗北。 「A」は調味料や下ごしらえした食材を表すAですね。
- 肉+玉ねぎ
コード:
results = model.wv.most_similar(positive=['肉','たまねぎ'], topn=5) for result in results: print(result[0])
最後に食材を足して、何かしらの料理が出てくるかを試しました。
出力:
きゅうり 挽き肉 ひき肉 ローリエ セロリ
なんか食材いっぱい出てきた。 もしかしたら名詞のうち、食材はクラスターになっている? そうだとしたら今後使い道があるかもしれません。
結論
今回は、Word2Vecを使った調理単語ベクトル表現を試しました。 しかし、上の通り、全くうまくいきませんでした。
原因としては、Word2Vecが単語間の位置関係しか見ていないことがあると思います。
レシピ手順テキストの特徴として、以下があります。 * 主語や動詞の対象が省略されることが多い * 調理の結果を明示的に表現しないため、「煮る=水分が多い状態で加熱する」を学習できるようなデータになっていない
そのため、文章構造解析の結果をもとに、主語を補完する、 また、「煮る」とは何かがわかるデータを学習する、などが必要かもしれません。
しかし、「煮る」などは定義可能であるため、わざわざ学習せずに調理データベースを構築したほうがよいかもしれません。
それでも、かなり手間がかかる作業です。。難しい。
レシピテキスト解析特有の問題やその対応がまとめられたものなどあれば読んでみたいですね。 現状は、自然言語処理の分野もレシピ構造化についても背景が少なくて難しい。
今回用意した学習データでは全くうまくいきませんでした。 Word2Vecについて「使えた」「理解した」とは言えないので、Word2Vec学習でよく使われる、Wikipediaテキストを試したり、理論を学んだりもしてみたいと思います。