アラカン"BOKU"のITな日常

文系システムエンジニアの”BOKU”が勉強したこと、経験したこと、日々思うこと。

NNablaで単語のベクトル表現化処理(Word2Vec)をやってみる。(3回め)

NNablaで、Word2Vecをやってみるの3回目です。

 

この記事は長くなるので以下のように3つにわけてます。

 

1回めは、Word_to_Vector.pyで、日本語のごく小さなテキストファイルを読み込んで、とりあえず、動かしてどんな結果が得られるかを確認しました。

 

2回めは、ネットから何か適当なテキストデータをとってきて、自然言語処理をおこなって、Word2Vecで利用できるインプットデータを作るところまでやりました。

 

3回めの今回は、それを学習させて、その結果を利用して、任意の単語で類義語を取得して遊んでみようかなと思っている・・とまあ、こんな構成です。

 

ソースの修正

これ以降の説明は、以下の記事にそってインストールと環境設定をしてあることを前提にしています。

arakan-pgm-ai.hatenablog.com

 

それ以外の環境でインストールしている方は、読み替えてください。

 

まず、Activate nnabla で、nnabla環境にします。

 

IDLEを立ち上げます。

 

IDLEで、1回めのときに修正した保存したファイルを開きます。

f:id:arakan_no_boku:20171009213615j:plain

 

ここでソースを修正していきます。

 

方針は以下です。

  • 2回めで作成したテキストデータで学習する
  • チェックしたい単語を指定できるように変更する。

 

2回めで作成したテキストデータで学習する

これは、非常に簡単です。

 

def main():の下にある、data_file名を定義する部分を、保存したファイル名に書き換えるだけです。

data_file = "w2v_data.txt" 

 

うーん。

 

見出しつける必要もなかった。

 

チェックしたい単語を指定できるように変更する。

もとのソースは、読み込んだテキストデータの単語を出現順に、「max_check_words」個分表示し、それぞれに対して、類似度が高い順に5つの単語を表示する仕様です。

 

そのため、今回のように出だしが「1 ドル 8 7 セント 全部 6 0 セント 小銭 小銭 一 回 買い物 一 枚 二 枚・・」だと、1、 ドル、 8、 7、 セント・・と表示してしまいます。

 

これでは全然おもしろくないので、せめて、小説を読んで気になる単語を指定して表示させるくらいはやりたいなと考えてます。

 

まず、正規表現を使うので、importを追加します。

import re 

 

次にmain()の最後の方を以下のように変更します。

 

赤字の部分が変更点です。

#max_check_words = args.max_check_words
max_check_words = len(itow)
for i in range(max_check_words):

    # prediction
    xr.d = i
    hr.forward(clear_buffer=True)
    h = hr.d

    # similarity calculation
    w = nn.get_parameters()['e1/embed/W'].d
    s = np.sqrt*1
    w /= s.reshape*2
    similarity = w.dot(h[0]) / s[i]

    # for understanding
    if(re.match("髪|時計",itow[i])):
         output_similar_words(itow, i, similarity)

 

補足すると、まず以下をコメントアウトします。

max_check_words = args.max_check_words

 

それで、以下を追記します。

 max_check_words = len(itow)

 

このitowというdictに、出現した単語が格納されているので、lenでサイズをとることで、格納した単語の種類すべての回数分繰り返しをさせようということです。

 

実際に結果を出力しているのは、以下のファンクションです。

output_similar_words(itow, i, similarity)

 

このままにしておくと、最初から最後まですべての単語を出力してしまうので、条件をつけるために、if文の下にくくってます。

if(re.match("髪|時計",itow[i])):
     output_similar_words(itow, i, similarity)

 

itow[i]に単語がはいっているので、要するに、「髪か時計」のどちらかに一致した場合だけ出力するということにしてます。

 

ここの髪とか時計とかを、変更すると任意のキーワードで結果を出力できるというわけですね。

 

変更はこれだけです。

 

実行と結果の評価

 

上書き保存して、F5キーで実行します。

 

結果はこんな感じでした。

query_word: id=214, 髪
id=510, 駄目: 0.312214
id=610, ピュア: 0.284497
id=654, 微笑み: 0.239668
id=314, すぎ: 0.226814
id=289, スカート: 0.208866


query_word: id=223, 時計
id=67, 付き: 0.271526
id=387, 愛: 0.257016
id=26, 頬: 0.249335
id=560, 答え: 0.244989
id=505, プレゼント: 0.229143


query_word: id=405, 髪型
id=288, かぶり: 0.295162
id=401, 小さく: 0.280532
id=462, 開き: 0.247210
id=567, 申し: 0.233307
id=498, 歩み寄り: 0.231590


query_word: id=501, 髪の毛
id=299, 看板: 0.338091
id=695, やりとり: 0.284501
id=592, 叫び: 0.271762
id=34, クリスマス: 0.258493
id=465, やせ: 0.256218

 

うーん。微妙です。

 

たぶん、もとにする小説を翻訳ものにしたのも良くなかったかもしれません。

 

実際、Word2Vecは、学習させるテキストデータによって、「おーー!」と思わずうなるような結果を出すときもあれば、今回のようにイマイチな場合もあります。

 

もちろん、学習させるテキストデータの量が多いにこしたことはありませんし。

 

ただ、今回の一連の変更で、適当なテキストデータを持ってきて、Word2Vecを気楽に試せるようにはなってます。

 

いろんなソースと単語の組み合わせで、遊んで見るのも面白いんじゃないですかね。

 

ちなみに、"BOKU"が、「賢者の贈り物」で一番マシだと思った結果は、テキストデータを単語分割する時に対象とする品詞を名詞のみにしたケースでした。

 

そのときの結果がこれです。

 

キーワードは、時計|髪の毛にしてます。

query_word: id=149, 時計
id=309, 髪の毛: 0.279052
id=412, お話: 0.264193
id=358, 歓喜: 0.257908
id=89, 夫人: 0.249895
id=61, 呼び鈴: 0.240536

query_word: id=309, 髪の毛
id=340, 知恵者: 0.314990
id=149, 時計: 0.279052
id=25, 明日: 0.218193
id=88, ジェームズ・ディリンガム・ヤング: 0.214705
id=156, 向こう: 0.198949 

 

お話の筋を考えると、時計と髪の毛がお互いに近いものであると認識しているのは、なかなか良い・・まるで小説の内容を理解しているかのように見える・・と思います。

 

まとめ

NNablaのsampleの「word_to_vector.py」を、ちょこっと改造して、Word2Vecで遊んでみました。

 

実は、この単語のベクトル化は、文章解釈とか会話みたいな自然言語処理を伴うAIの処理にとって、なかなか重要な技術らしいのです。

 

たとえば、FaceBookがニュースフィードから釣り見出しを排除するために開発したと言われている「FastText」なども、このWord2Vecの超高速版みたいなものですし。

deepage.net

 

だから、とりあえず、簡単なサンプルででも感触をつかんでおくのは、悪くないと思うんですよね。

 

関連情報

NNabla関連の記事一覧はこちらです。

arakan-pgm-ai.hatenablog.com

 

ニューラルネットワークコンソール関連の記事一覧はこちらです。

arakan-pgm-ai.hatenablog.com

f:id:arakan_no_boku:20170910161122j:plain



 

*1:w * w).sum(1

*2:s.shape[0], 1