#!/usr/bin/env python # coding: utf-8 # # 第3章 word2vec # # word2vecの実装に、推論を使用する。前の章ではカウントベースの手法を使用したが、今回は推論ベースの手法を使用する。 # # ## 3.1 推論ベースの手法とニューラルネットワーク # # ### 3.1.1 カウントベースの手法の問題点 # カウントベースの手法を使うと、コーパスで扱う語彙数が非常に巨大になり、例えば100万×100万の巨大な行列を作る必要が発生してしまう。 # このような巨大な行列に対してSVDを行うことは、現実的ではない。 # # これに対して、推論ベースの手法では、ニューラルねとワークを使う場合は、ミニバッチを使って学習を行う。 # # ### 3.1.2 推論ベースの手法の概要 # # 推論ベースの手法では、例えば"You ? goodbay and I say hello."という文字列に対して、?に何が入るかを推論することになる。 # # ### 3.1.3 ニューラルネットワークにおける単語の処理方法 # # 単語のIDから、one-hotへと変換する。 # # このベクトルに行列を掛け合わせることで、ニューラルネットワークを実現する。 # ## 3.2 シンプルなword2vec # # ニューラルネットワークに対してword2vecを組み込むために、"continuous bag-of-words : CBOW"と呼ばれるモデルを使用すrる。 # # CBOWは、 # - 入力層が2つ : 入力層を指定して、その2つの単語から類推する言葉を推論する。 # - 中間層 : 各入力層の全結合による返還後の値が「平均」されたものになる。 # - 出力層が1つ : 推論された単語。単語の数だけニューロンがあり、そのニューロンは単語の「スコア」となる。したがって、Softmax関数を適用して確率を得る。 # # In[2]: import sys sys.path.append('..') import numpy as np from common.layers import MatMul c0 = np.array([[1, 0, 0, 0, 0, 0, 0]]) c1 = np.array([[0, 0, 1, 0, 0, 0, 0]]) W_in = np.random.randn(7, 3) W_out = np.random.randn(3, 7) in_layer0 = MatMul(W_in) in_layer1 = MatMul(W_in) out_layer = MatMul(W_out) h0 = in_layer0.forward(c0) h1 = in_layer1.forward(c1) h = 0.5 * (h0 + h1) s = out_layer.forward(h) print(s) # ### word2vecを実行するための手順 # # まずは、サンプルの文章からコーパスを作成する。id_to_wordは辞書に相当する。 # In[12]: import sys sys.path.append('..') from common.util import preprocess text = 'You say goodbye and I say hello.' corpus, word_to_id, id_to_word = preprocess(text) print(corpus) print(id_to_word) # 次に、作成したcorpusを元にonehotの配列を作成する。 # # 下記の`target`は推論する対象の単語(のインデックス)、`contexts`は推論する対象に対するコンテキスト(つまり隣接する単語)。 # In[14]: import sys sys.path.append('..') from common.util import preprocess, create_contexts_target, convert_one_hot def create_contexts_target(corpus, window_size=1): target = corpus[window_size:-window_size] contexts = [] for idx in range(window_size, len(corpus)-window_size): cs = [] for t in range(-window_size, window_size+1): if t == 0: continue cs.append(corpus[idx+t]) contexts.append(cs) return np.array(contexts), np.array(target) contexts, target = create_contexts_target(corpus, window_size=1) vocab_size = len(word_to_id) target = convert_one_hot(target, vocab_size) contexts = convert_one_hot(contexts, vocab_size) print(target) print(contexts) # ニューラルネットワークの実装。 # # `layer0`と`layer1`に対して重みを掛け合わせ、最後に平均を取るのが順方向の処理。 # In[15]: import sys sys.path.append('..') import numpy as np from common.layers import MatMul, SoftmaxWithLoss class SimpleCBOW: def __init__(self, vocab_size, hidden_size): V, H = vocab_size, hidden_size # 重みの初期化 W_in = 0.01 * np.random.randn(V, H).astype('f') W_out = 0.01 * np.random.randn(H, V).astype('f') # レイヤの生成 self.in_layer0 = MatMul(W_in) self.in_layer1 = MatMul(W_in) self.out_layer = MatMul(W_out) self.loss_layer = SoftmaxWithLoss() # すべての重みと勾配をリストにまとめる layers = [self.in_layer0, self.in_layer1, self.out_layer] self.params, self.grads = [], [] for layer in layers: self.params += layer.params self.grads += layer.grads # メンバ変数に単語の分散表現を設定 self.word_vecs = W_in def forward(self, contexts, target): h0 = self.in_layer0.forward(contexts[:, 0]) h1 = self.in_layer1.forward(contexts[:, 1]) h = (h0 + h1) * 0.5 score = self.out_layer.forward(h) loss = self.loss_layer.forward(score, target) return loss def backward(self, dout=1): ds = self.loss_layer.backward(dout) da = self.out_layer.backward(ds) da *= 0.5 self.in_layer1.backward(da) self.in_layer0.backward(da) return None # 実行結果。誤答率が下がっていることが分かる。 # In[17]: # coding: utf-8 import sys sys.path.append('..') # 親ディレクトリのファイルをインポートするための設定 from common.trainer import Trainer from common.optimizer import Adam from simple_cbow import SimpleCBOW from common.util import preprocess, create_contexts_target, convert_one_hot window_size = 1 hidden_size = 5 batch_size = 3 max_epoch = 1000 text = 'You say goodbye and I say hello.' corpus, word_to_id, id_to_word = preprocess(text) vocab_size = len(word_to_id) contexts, target = create_contexts_target(corpus, window_size) target = convert_one_hot(target, vocab_size) contexts = convert_one_hot(contexts, vocab_size) model = SimpleCBOW(vocab_size, hidden_size) optimizer = Adam() trainer = Trainer(model, optimizer) trainer.fit(contexts, target, max_epoch, batch_size) trainer.plot() # In[18]: word_vecs = model.word_vecs for word_id, word in id_to_word.items(): print(word, word_vecs[word_id]) # ### 3.5.2 skip-gramモデル # # skip-gramもでるはCBOWモデルと異なり、 # - skip-gram : 中央の単語(ターゲット)から、周囲に複数ある単語(コンテキスト)を推測する。 # - CBOWモデル : コンテキスト(周囲の単語)から中央の単語(ターゲット)を推測する。 # # In[ ]: