b/2019/ikeuchi/learning
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
|
ログイン
]
開始行:
#contents
*参考文献 : ゼロから作るDeep Learning ❷ ―自然言語処理編 [...
本書で使用するソースコード : https://github.com/oreilly-j...
**実装ルール(P.14) [#u349df6c]
-ニューラルネットワークの各層はクラスとして実装する
-ニューラルネットワークの各処理はレイヤ(層)として実装する...
-層はメソッドとして forward() と backward() を持つ
--forward : 順伝播
--backward : 逆伝播
-層はインスタンス変数として params と grads を持つ
--params : 重み、バイアスなどのパラメータをリストとして持つ
--grads : params に対応する形で各パラメータの勾配をリスト...
*学習計画 [#z2961a4d]
-参考文献の3, 4章を理解する
--理解した内容をwikiにまとめる
--提示されるコードは実際に動かしてみて、動作を確認する
*3章 word2vec [#oa42830e]
**3.1 推論ベースの手法とニューラルネットワーク [#ie9e31b3]
単語の分散表現 : 単語をベクトルとして表したもののことを言...
この分散表現を得る手法は大きく二つに分けられる。
-カウントベース : ある単語に注目して、その周りにどういう...
-推論ベース : コンテキストからターゲットを推測する
どちらも分布仮説に基づいている。
***3.1.1 カウントベース手法の問題点 [#c5b368d6]
-1 : コーパスで扱う語彙数が100万と巨大な時に計算量が膨大...
-2 : カウントベースでは、学習データを一度にまとめて処理す...
その他にも推論ベースの方が魅力的な点がある
***3.1.2 推論ベース手法の概要 [#va0f1203]
コンテキストが与えられた時にターゲットを推測する手法であ...
you ? say goodbye and I say hello
の?をコンテキスト'you', 'say'から推測するということである...
***3.1.3 ニューラルネットワークにおける単語の処理方法 [#a...
単語を'you', 'say'など、そのままでは扱えないので「固定長...
コーパスを "you say goodbye and I say hello." とすると、...
これでニューラルネットワークで単語を扱えるようになった。
**3.2 シンプルなword2vec [#y5a51cb7]
コンテキストからターゲットを推測するモデルにword2vecで提...
***3.2.1 CBOWモデルの推論処理 [#v6899959]
以下、推測の対象とするコーパスは、"you say goodbye and I ...
単語はone-hot表現でベクトルに変換する。
CBOW の基本的な構造は次の様なものである。
|層の種類|出力サイズ|h
|入力層|1 * 7|
|MatMul層|1 * 3|
|出力層|1 * 7|
-入力層 : CBOWモデルでは入力層がコンテキストの数だけ存在...
-MatMul層: 入力データと重みの行列積を行う層。バイアスはな...
--MatMul層への入力は、コンテキストと重みの行列積の平均で...
u = (X1 @ W + X2 @ W) / 2
と表される(@は行列積を表している)。
***3.2.2 CBOWモデルの学習 [#ded09938]
出力層に活性化関数 soft-max関数 を用いることで、各単語の...
***3.2.3 word2vecの重みと分散表現 [#j5be6d44]
CBOWモデルの重みは二つあるが、MatMul層と出力層の重みを用...
**3.3 学習データの準備 [#w1b4fc95]
word2vecの学習を行うにあたって、学習データの準備を行う。"...
***3.3.1 コンテキストとターゲット [#n2da8893]
i.コーパスのテキストを単語IDへ変換
その為にpreprocess()関数を実装する。
#pre{{
def preprocess(text):
text = text.lower()
text = text.replace('.', ' .')
words = text.split(' ')
word_to_id = {}
id_to_word = {}
for word in words:
if word not in word_to_id:
new_id = len(word_to_id)
word_to_id[word] = new_id
id_to_word[new_id] = word
corpus = np.array([word_to_id[w] for w in words])
return corpus, word_to_id, id_to_word
}}
これを次のように実行すると
text = "You say goodbye and I say hello."
corpus, word_to_id, id_to_word = preprocess(text)
print(corpus)
print(word_to_id)
print(id_to_word)
次のような結果が得られる
[0 1 2 3 4 1 5 6]
{'you': 0, 'say': 1, 'goodbye': 2, 'and': 3, 'i': 4, 'he...
{0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: ...
これによりコーパスをIDで表すことができた。
ii.単語IDで表されたコーパスからコンテキストとターゲットを...
#pre{{
def create_contexts_target(corpus, window_size=1):
'''コンテキストとターゲットの作成
:param corpus: コーパス(単語IDのリスト)
:param window_size: ウィンドウサイズ(ウィンドウサイ...
:return:
'''
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, win...
print(contexts)
print(target)
このような結果が得られる
[[0 2]
[1 3]
[2 4]
[3 1]
[4 5]
[1 6]]
[1 2 3 4 1 5]
コンテキストを2つ持たない、つまり端の単語はターゲットから...
***3.3.2 one-hot表現への変換 [#w2d5b6db]
上記で作成したコンテキストとターゲットをone-hot表現へ変換
#pre{{
def convert_one_hot(corpus, vocab_size):
'''one-hot表現への変換
:param corpus: 単語IDのリスト(1次元もしくは2次元のNu...
:param vocab_size: 語彙数
:return: one-hot表現(2次元もしくは3次元のNumPy配列)
'''
N = corpus.shape[0]
if corpus.ndim == 1:
one_hot = np.zeros((N, vocab_size), dtype=np.int32)
for idx, word_id in enumerate(corpus):
one_hot[idx, word_id] = 1
elif corpus.ndim == 2:
C = corpus.shape[1]
one_hot = np.zeros((N, C, vocab_size), dtype=np.i...
for idx_0, word_ids in enumerate(corpus):
for idx_1, word_id in enumerate(word_ids):
one_hot[idx_0, idx_1, word_id] = 1
return one_hot
}}
これを次のように実行すると
vocab_size = len(word_to_id)
target = convert_one_hot(target, vocab_size)
contexts = convert_one_hot(contexts, vocab_size)
print("contexts\n",contexts)
print("target\n", target)
このような結果が得られる
contexts
[[[1 0 0 0 0 0 0]
[0 0 1 0 0 0 0]]
[[0 1 0 0 0 0 0]
[0 0 0 1 0 0 0]]
[[0 0 1 0 0 0 0]
[0 0 0 0 1 0 0]]
[[0 0 0 1 0 0 0]
[0 1 0 0 0 0 0]]
[[0 0 0 0 1 0 0]
[0 0 0 0 0 1 0]]
[[0 1 0 0 0 0 0]
[0 0 0 0 0 0 1]]]
target
[[0 1 0 0 0 0 0]
[0 0 1 0 0 0 0]
[0 0 0 1 0 0 0]
[0 0 0 0 1 0 0]
[0 1 0 0 0 0 0]
[0 0 0 0 0 1 0]]
**3.4 CBOWモデルの実装 [#ace0b7d6]
本書では、ニューラルネットワークの処理をレイヤ(層)で表す...
#pre{{
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.ou...
self.params, self.grads = [], []
for layer in layers:
self.params += layer.params
self.grads += layer.grads
# メンバ変数に単語の分散表現を設定
self.word_vecs = W_in
}}
#pre{{
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
}}
#pre{{
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
}}
***3.4.1 学習コードの実装 [#w80587dd]
#pre{{
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_targe...
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_...
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()
word_vecs = model.word_vecs
for word_id, word in id_to_word.items():
print(word, word_vecs[word_id])
}}
実行結果
#pre{{
you [ 0.816448 -1.5548781 -0.97975504 0.8965743 -1.2...
say [-1.14312 -1.1699724 1.1750078 -1.1562544 0.1...
goodbye [ 1.1648722 -0.18298058 -0.9638767 1.0863249 ...
and [-1.0530106 -0.01240244 1.1135145 -1.1372522 -1.9...
i [ 1.1653302 -0.1588453 -0.97213495 1.1066818 -0.438...
hello [ 0.81998146 -1.5668002 -0.9753178 0.9042221 -1...
. [-0.89470506 -1.4097029 0.89925027 -0.8334046 1.640...
}}
中間層のニューロン数は5(コード中のhidden_sizeが中間層のニ...
また、学習を重ねるごとに交差エントロピーが減少しているこ...
&ref(./スクリーンショット 2019-09-11 23.15.22.png,50%);
**3.5 word2vecに関する補足 [#ff19aecf]
CBOWモデルを「確率」の視点からもう一度見ていく
***3.5.1 CBOWモデルと確率 [#pa1be4bd]
単語列を&mathjax{ w_1, w_2, ... , w_t};と表すと、CBOWは次...
#mathjax(P(w_t | w_{t-1}, w_{t+1}));
損失関数は
#mathjax( L = -\frac{1}{T} \sum_{t=1}^{T} \log P(w_t | w_...
と表される。
***3.5.2 skip-gramモデル [#r557a25c]
CBOW モデルの他にskip-gramモデルもword2vecでは提案されて...
skip-gram モデルは、CBOW モデルでのターゲットとコンテキス...
***3.5.3 カウントベース v.s. 推論ベース [#m9f9de0e]
++語彙に新しい単語を追加するケースで、単語の分散表現の更...
>
カウントベース : ゼロから計算を行う必要がある。共起行列を...
<
>
推論ベース : これまでに学習した重みを初期値として再学習で...
<
++単語の分散表現の性質や制度
>
カウントベース : 分散表現の性質は、単語の類似性が捉えられる
<
>
推論ベース : 分散表現の性質は、単語の類似性に加えて、複雑...
<
精度的には二つの手法には優劣がつけられない
**3.6 まとめ [#p34a39db]
CBOWは基本的に二層のニューラルネットワークでとてもシンプ...
*4章 word2vecの高速化 [#z3feca29]
前章で実装したword2vecのままでは、語彙数が増えることに伴...
++Embeddingレイヤの実装
++Negative Sampling という損失関数の導入
を行う。これにより、本物のword2vecが得られる。
**4.1 word2vecの改良1 [#d1fb74dc]
具体的に次の二箇所の計算がボトルネックとなる。
++入力層のone-hot表現と重み行列の積による計算
++中間層と重み行列の積およびSoftmaxレイヤの計算
一つ目の問題点はone-hot表現を扱っていることによって、コー...
二つ目の問題点は行列の積の計算とSoftmaxの計算です。行列積...
***4.1.1 Embeddingレイヤ [#j2726229]
one-hot表現はその単語を表す要素が1で、その他は0のベクトル...
***4.1.2 Embeddingレイヤの実装 [#cd934f0c]
class Embedding:
def __init__(self, W):
self.params = [W]
self.grads = [np.zeros_like(W)]
self.idx = None
def forward(self, idx):
W, = self.params
self.idx = idx
out = W[idx]
return out
idxには抽出する行のインデックスを配列として格納する。そし...
def backward(self, dout):
dW, = self.grads
dW[...] = 0
np.add.at(dW, self.idx, dout) #dWのself.idxにdout...
return None
forwardで重みの特定の行を抜き出すだけの処理なので、それを...
**4.2 word2vecの改良2 [#jac33393]
残る問題点は、中間層以降の行列の積とSoftmaxレイヤの計算で...
この解決策として、NegativeSamplingと呼ばれる手法を用いる...
***4.2.1 中間層以降の計算の問題点 [#ud57eb0a]
++語彙数が100万、中間層のニューロンが100の時を考えると、...
++上記と同じ条件でSoftmaxの計算を考えてみると、Softmaxの...
#mathjax( \begin{align} z_k &= \frac{\exp(y_k)}{\sum_{j=1...
大変
***4.2.2 多値分類から二値分類へ [#u3de7c81]
NegativeSamplingの手法の説明をしていく。この手法のキーと...
そのために問題の形を変えてあげる必要がある。具体的には、...
この時のCBOWの処理は次の様になる。語彙数が100万、中間層の...
***4.2.3 シグモイド関数と交差エントロピー誤差 [#j785c6ba]
多値分類から二値分類の問題に変わったので、Softmax関数の代...
Sigmoid関数
#mathjax( y = \frac{1}{1+\exp(-x)} )
交差エントロピー
#mathjax( L = -(t\log y - (1-t)\log ( 1 - y)) )
***4.2.4 多値分類から二値分類へ(実装編) [#ue5cf43c]
#pre{{
import sys
sys.path.append('..')
from common.np import * # import numpy as np
from common.layers import Embedding, SigmoidWithLoss
import collections
class EmbeddingDot:
def __init__(self, W):
self.embed = Embedding(W) #4.1.2 で実装したEmbedd...
self.params = self.embed.params
self.grads = self.embed.grads
self.cache = None
def forward(self, h, idx):
target_W = self.embed.forward(idx)
out = np.sum(target_W * h, axis=1) #内積を計算
self.cache = (h, target_W)
return out
def backward(self, dout):
h, target_W = self.cache
dout = dout.reshape(dout.shape[0], 1)
dtarget_W = dout * h
self.embed.backward(dtarget_W)
dh = dout * target_W
return dh
}}
前回実装したEmbedding層を二値分類の処理に対応させたもの。...
***4.2.5 Negative Sampling [#d468ad56]
これまでの議論で解くべき問題を二値分類に変換することがで...
以上をまとめると、損失関数 Negative Sampling は正例につい...
***4.2.6 Negative Sampling のサンプリング手法 [#v843e38f]
負例のサンプリングの手法を説明する。ただランダムにサンプ...
&mathjax{P(w_i)}; が &mathjax{i}; 番目の単語の確率を表す...
#mathjax(P'(w_i) = \frac{P(w_i)^{0.75} }{ \sum_{j}^{n} P(...
0.75乗することで出現確率の低い単語を見捨てないようにしま...
#pre{{
class UnigramSampler:
def __init__(self, corpus, power, sample_size):
self.sample_size = sample_size
self.vocab_size = None
self.word_p = None
counts = collections.Counter()
for word_id in corpus:
counts[word_id] += 1
vocab_size = len(counts)
self.vocab_size = vocab_size
self.word_p = np.zeros(vocab_size)
for i in range(vocab_size):
self.word_p[i] = counts[i]
self.word_p = np.power(self.word_p, power)
self.word_p /= np.sum(self.word_p)
def get_negative_sample(self, target):
batch_size = target.shape[0]
if not GPU:
negative_sample = np.zeros((batch_size, self....
for i in range(batch_size):
p = self.word_p.copy()
target_idx = target[i]
p[target_idx] = 0
p /= p.sum()
negative_sample[i, :] = np.random.choice(...
else:
# GPU(cupy)で計算するときは、速度を優先
# 負例にターゲットが含まれるケースがある
negative_sample = np.random.choice(self.vocab...
replace=Tr...
return negative_sample
}}
***4.2.7 Negative Sampling の実装 [#c2911813]
#pre{{
class NegativeSamplingLoss:
def __init__(self, W, corpus, power=0.75, sample_size...
self.sample_size = sample_size
self.sampler = UnigramSampler(corpus, power, samp...
self.loss_layers = [SigmoidWithLoss() for _ in ra...
self.embed_dot_layers = [EmbeddingDot(W) for _ in...
self.params, self.grads = [], []
for layer in self.embed_dot_layers:
self.params += layer.params
self.grads += layer.grads
}}
イニシャライズの各引数
W : 出力側の重み
corpus : 単語IDのリスト
power : 確率分布の累乗の値(4.2.6 で説明した確率分布の0.75...
sample_size : 負例のサンプルサイズ
負例をサンプリングする為のクラスUnigramSamplerをメンバ変...
#pre{{
def forward(self, h, target):
batch_size = target.shape[0]
negative_sample = self.sampler.get_negative_sampl...
# 正例のフォワード
score = self.embed_dot_layers[0].forward(h, target)
correct_label = np.ones(batch_size, dtype=np.int32)
loss = self.loss_layers[0].forward(score, correct...
# 負例のフォワード
negative_label = np.zeros(batch_size, dtype=np.in...
for i in range(self.sample_size):
negative_target = negative_sample[:, i]
score = self.embed_dot_layers[1 + i].forward(...
loss += self.loss_layers[1 + i].forward(score...
return loss
}}
negative_sample にサンプリングした負例を代入。そして正例...
#pre{{
def backward(self, dout=1):
dh = 0
for l0, l1 in zip(self.loss_layers, self.embed_do...
dscore = l0.backward(dout)
dh += l1.backward(dscore)
return dh
}}
逆伝播は各レイヤの逆伝播のメソッド backward() を呼ぶ処理。
**4.3 改良版word2vecの学習 [#fdce0403]
Embeddingレイヤ、続いてNegative Sampling という手法につい...
***4.3.1 CBOWモデルの実装 [#d9a7e22c]
#pre{{
import sys
sys.path.append('..')
from common.np import * # import numpy as np
from common.layers import Embedding
from ch04.negative_sampling_layer import NegativeSampling...
class CBOW:
def __init__(self, vocab_size, hidden_size, window_si...
V, H = vocab_size, hidden_size
# 重みの初期化
W_in = 0.01 * np.random.randn(V, H).astype('f')
W_out = 0.01 * np.random.randn(V, H).astype('f')
# レイヤの生成
self.in_layers = []
for i in range(2 * window_size):
layer = Embedding(W_in) # Embeddingレイヤを...
self.in_layers.append(layer)
self.ns_loss = NegativeSamplingLoss(W_out, corpus...
# すべての重みと勾配をリストにまとめる
layers = self.in_layers + [self.ns_loss]
self.params, self.grads = [], []
for layer in layers:
self.params += layer.params
self.grads += layer.grads
# メンバ変数に単語の分散表現を設定
self.word_vecs = W_in
}}
vocab_size : 語彙数
hidden_size : 中間層のニューロン数
corpus : 単語IDのリスト
window_size : ターゲットの周囲の単語をどれだけコンテキス...
Embeddingレイヤを 2 * window_size だけ作成し、それをメン...
NegativeSamplingLoss クラスをメンバ変数 ns_loss で保持。
#pre{{
def forward(self, contexts, target):
h = 0
for i, layer in enumerate(self.in_layers):
h += layer.forward(contexts[:, i])
h *= 1 / len(self.in_layers)
loss = self.ns_loss.forward(h, target)
return loss
}}
#pre{{
def backward(self, dout=1):
dout = self.ns_loss.backward(dout)
dout *= 1 / len(self.in_layers)
for layer in self.in_layers:
layer.backward(dout)
return None
}}
***4.3.2 CBOWモデルの学習コード [#yc658cd0]
CBOWモデルの学習を実装する。ウインドウサイズを5, 隠れ層の...
#pre{{
import sys
sys.path.append('..')
import numpy as np
from common import config
# GPUで実行する場合は、下記のコメントアウトを消去(要cupy)
# ===============================================
# config.GPU = True
# ===============================================
import pickle
from common.trainer import Trainer
from common.optimizer import Adam
from cbow import CBOW
from skip_gram import SkipGram
from common.util import create_contexts_target, to_cpu, t...
from dataset import ptb
# ハイパーパラメータの設定
window_size = 5
hidden_size = 100
batch_size = 100
max_epoch = 10
# データの読み込み
corpus, word_to_id, id_to_word = ptb.load_data('train')
vocab_size = len(word_to_id)
contexts, target = create_contexts_target(corpus, window_...
if config.GPU:
contexts, target = to_gpu(contexts), to_gpu(target)
# モデルなどの生成
model = CBOW(vocab_size, hidden_size, window_size, corpus)
# model = SkipGram(vocab_size, hidden_size, window_size, ...
optimizer = Adam()
trainer = Trainer(model, optimizer)
# 学習開始
trainer.fit(contexts, target, max_epoch, batch_size)
trainer.plot()
# 後ほど利用できるように、必要なデータを保存
word_vecs = model.word_vecs
if config.GPU:
word_vecs = to_cpu(word_vecs)
params = {}
params['word_vecs'] = word_vecs.astype(np.float16)
params['word_to_id'] = word_to_id
params['id_to_word'] = id_to_word
pkl_file = 'cbow_params.pkl' # or 'skipgram_params.pkl'
with open(pkl_file, 'wb') as f:
pickle.dump(params, f, -1)
}}
学習結果
PTBデータセット : データサイズ = 929,589, 語彙数 = 10,000...
バッチサイズ = 100, iteration数 = 9295, epoch数 = 10
実行時間 : 3.9[h]
&ref(./cbowlearning2.png,50%);
***4.3.3 CBOWモデルの評価 [#g7d49bcc]
先の実験で得られた単語の分散表現を評価してみる。
#pre{{
import sys
sys.path.append('..')
from common.util import most_similar, analogy
import pickle
pkl_file = 'cbow_params.pkl'
# pkl_file = 'skipgram_params.pkl'
with open(pkl_file, 'rb') as f:
params = pickle.load(f)
word_vecs = params['word_vecs']
word_to_id = params['word_to_id']
id_to_word = params['id_to_word']
# most similar task
querys = ['you', 'year', 'car', 'toyota']
for query in querys:
most_similar(query, word_to_id, id_to_word, word_vecs...
# analogy task
print('-'*50)
analogy('king', 'man', 'queen', word_to_id, id_to_word, ...
analogy('take', 'took', 'go', word_to_id, id_to_word, wo...
analogy('car', 'cars', 'child', word_to_id, id_to_word, ...
analogy('good', 'better', 'bad', word_to_id, id_to_word,...
}}
実行結果
#pre{{
[query] you
we: 0.73046875
i: 0.71142578125
your: 0.609375
weird: 0.6064453125
us: 0.57763671875
[query] year
month: 0.8623046875
week: 0.79541015625
summer: 0.78564453125
spring: 0.76708984375
decade: 0.70556640625
[query] car
truck: 0.6708984375
window: 0.62939453125
luxury: 0.625
auto: 0.62158203125
cars: 0.583984375
[query] toyota
seita: 0.65478515625
honda: 0.646484375
engines: 0.63427734375
marathon: 0.62890625
mazda: 0.6181640625
}}
これはqueryに指定した単語ともっとも近い単語を上から5つ表...
#pre{{
[analogy] king:man = queen:?
woman: 5.1640625
hacker: 5.09765625
text: 5.01953125
a.m: 4.71484375
amendment: 4.7109375
}}
これは 「man → woman」ベクトルと「king → ?」ベクトルが一...
#pre{{
[analogy] take:took = go:?
eurodollars: 5.60546875
were: 4.54296875
're: 4.4609375
went: 4.45703125
a.m: 4.2265625
[analogy] car:cars = child:?
a.m: 6.98046875
rape: 5.95703125
children: 5.2109375
daffynition: 4.85546875
incest: 4.85546875
[analogy] good:better = bad:?
rather: 5.453125
more: 5.2265625
less: 4.9453125
greater: 4.30859375
worse: 4.171875
}}
類推問題の結果はとても良いものに見えるがこれはうまく解け...
**4.4 word2vecに関する残りのテーマ [#s6b782da]
***4.4.1 word2vecを使ったアプリケーションの例 [#qbda54c6]
word2vecで得られた単語の分散表現は転移学習に用いる。これ...
自然言語のタスクの例 : テキスト分類、文書クラスタリング、...
また、単語の分散表現を用いて文章を固定長のベクトルに変換...
***4.4.2 単語ベクトルの評価方法 [#j4b937a0]
単語の分散表現を用いて、何らかの処理をしたいわけです。そ...
++単語の分散表現を得る為の学習
++その分散表現を用いて、ある処理のシステムを学習させる
と二段階の評価が必要がある。さらにその二つのシステムにお...
これは大変なので、単語の分散表現の評価はこの上に乗せるシ...
しかし、単語の分散表現の良さが目的とするシステムにどれだ...
**4.5 まとめ [#ta4b7558]
本章ではword2vecの高速化をテーマとして扱った。「すべて」...
word2vecの思想は、音声、画像、動画などにも応用されている...
*補足 [#vb270894]
**PTB(Penn Treebank)データセット(P.86~) [#z233586b]
PTBコーパスでは一つの文が一行ごとに保存されているが、本書...
PTBコーパスを理解するために本書から提供されている ch02/sh...
#pre{{
import sys
sys.path.append('..')
from dataset import ptb
corpus, word_to_id, id_to_word = ptb.load_data('train')
print('corpus size:', len(corpus))
print('corpus[:30]:', corpus[:30])
print()
print('id_to_word[0]:', id_to_word[0])
print('id_to_word[1]:', id_to_word[1])
print('id_to_word[2]:', id_to_word[2])
print()
print("word_to_id['car']:", word_to_id['car'])
print("word_to_id['happy']:", word_to_id['happy'])
print("word_to_id['lexus']:", word_to_id['lexus'])
}}
実行結果
#pre{{
In [71]: %run show_ptb.py ...
corpus size: 929589
corpus[:30]: [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 1...
24 25 26 27 28 29]
id_to_word[0]: aer
id_to_word[1]: banknote
id_to_word[2]: berlitz
word_to_id['car']: 3856
word_to_id['happy']: 4428
word_to_id['lexus']: 7426
}}
また、ipython上で以下の様に実行してみた。
#pre{{
In [72]: from dataset import ptb ...
In [73]: corpus, word_to_id, id_to_word = ptb.load_data('...
In [74]: a = word_to_id.keys() ...
In [75]: len(a) ...
Out[75]: 10000
}}
これより、PTBのコーパスサイズは、929,589 で各単語はIDに変...
*応用 [#m7095758]
**word2vec を用いたアプリケーション [#p3745933]
***文章を固定長のベクトルへ変換する手法 [#i5e924f8]
-bag-of-words(BoW) : 文章の各単語を分散表現に変換し、それ...
-CNN : テキスト系列内の有益な n-グラム、あるいはギャップ...
-RNN : bag-of-wordsとは違い、順序を考慮したモデル。難しい...
bag-of-words とかの具体的な実装方法はネットじゃなくて図書...
***MLPなど比較的単純なシステムで処理できる問題 [#g9bcc253]
-ロジスティック回帰で2値分類問題
--レビューに対する感情分類(positive か negativeか)
-MLPで多値分類
--トピック分類(これは政治の記事、これはスポーツの記事、と...
**PTB以外のデータセット [#o6918193]
文章に対する処理がしたいなら当然新たなデータセットが必要。
-[[機械学習に使える日本語のデータセット13個>https://qiita...
-[[自然言語処理に使えるデータセットまとめ(25個)>https://l...
**word2vecの実験 [#b5ae2277]
***扱うデータセット [#z4a2c73c]
[[Large Movie Review Dataset>http://ai.stanford.edu/~amaa...
肯定、否定のラベル付けをされた英語のIMDB映画レビューのデ...
Kerasから提供されているライブラリを用いてこのデータセット...
***word2vec の学習 [#m0d761b9]
IMDBレビューの肯定/否定の2値識別問題に取り組むにあたって...
++前回PTBコーパスで学習した単語の分散表現を流用する(転移...
++IMDBレビューの学習データを用いて、レビューを単語レベル...
とりあえず i の方法での実験を行おうと考えている。その為に...
-[[上へまいります>b/2019/ikeuchi/learning#gc08b923]]
終了行:
#contents
*参考文献 : ゼロから作るDeep Learning ❷ ―自然言語処理編 [...
本書で使用するソースコード : https://github.com/oreilly-j...
**実装ルール(P.14) [#u349df6c]
-ニューラルネットワークの各層はクラスとして実装する
-ニューラルネットワークの各処理はレイヤ(層)として実装する...
-層はメソッドとして forward() と backward() を持つ
--forward : 順伝播
--backward : 逆伝播
-層はインスタンス変数として params と grads を持つ
--params : 重み、バイアスなどのパラメータをリストとして持つ
--grads : params に対応する形で各パラメータの勾配をリスト...
*学習計画 [#z2961a4d]
-参考文献の3, 4章を理解する
--理解した内容をwikiにまとめる
--提示されるコードは実際に動かしてみて、動作を確認する
*3章 word2vec [#oa42830e]
**3.1 推論ベースの手法とニューラルネットワーク [#ie9e31b3]
単語の分散表現 : 単語をベクトルとして表したもののことを言...
この分散表現を得る手法は大きく二つに分けられる。
-カウントベース : ある単語に注目して、その周りにどういう...
-推論ベース : コンテキストからターゲットを推測する
どちらも分布仮説に基づいている。
***3.1.1 カウントベース手法の問題点 [#c5b368d6]
-1 : コーパスで扱う語彙数が100万と巨大な時に計算量が膨大...
-2 : カウントベースでは、学習データを一度にまとめて処理す...
その他にも推論ベースの方が魅力的な点がある
***3.1.2 推論ベース手法の概要 [#va0f1203]
コンテキストが与えられた時にターゲットを推測する手法であ...
you ? say goodbye and I say hello
の?をコンテキスト'you', 'say'から推測するということである...
***3.1.3 ニューラルネットワークにおける単語の処理方法 [#a...
単語を'you', 'say'など、そのままでは扱えないので「固定長...
コーパスを "you say goodbye and I say hello." とすると、...
これでニューラルネットワークで単語を扱えるようになった。
**3.2 シンプルなword2vec [#y5a51cb7]
コンテキストからターゲットを推測するモデルにword2vecで提...
***3.2.1 CBOWモデルの推論処理 [#v6899959]
以下、推測の対象とするコーパスは、"you say goodbye and I ...
単語はone-hot表現でベクトルに変換する。
CBOW の基本的な構造は次の様なものである。
|層の種類|出力サイズ|h
|入力層|1 * 7|
|MatMul層|1 * 3|
|出力層|1 * 7|
-入力層 : CBOWモデルでは入力層がコンテキストの数だけ存在...
-MatMul層: 入力データと重みの行列積を行う層。バイアスはな...
--MatMul層への入力は、コンテキストと重みの行列積の平均で...
u = (X1 @ W + X2 @ W) / 2
と表される(@は行列積を表している)。
***3.2.2 CBOWモデルの学習 [#ded09938]
出力層に活性化関数 soft-max関数 を用いることで、各単語の...
***3.2.3 word2vecの重みと分散表現 [#j5be6d44]
CBOWモデルの重みは二つあるが、MatMul層と出力層の重みを用...
**3.3 学習データの準備 [#w1b4fc95]
word2vecの学習を行うにあたって、学習データの準備を行う。"...
***3.3.1 コンテキストとターゲット [#n2da8893]
i.コーパスのテキストを単語IDへ変換
その為にpreprocess()関数を実装する。
#pre{{
def preprocess(text):
text = text.lower()
text = text.replace('.', ' .')
words = text.split(' ')
word_to_id = {}
id_to_word = {}
for word in words:
if word not in word_to_id:
new_id = len(word_to_id)
word_to_id[word] = new_id
id_to_word[new_id] = word
corpus = np.array([word_to_id[w] for w in words])
return corpus, word_to_id, id_to_word
}}
これを次のように実行すると
text = "You say goodbye and I say hello."
corpus, word_to_id, id_to_word = preprocess(text)
print(corpus)
print(word_to_id)
print(id_to_word)
次のような結果が得られる
[0 1 2 3 4 1 5 6]
{'you': 0, 'say': 1, 'goodbye': 2, 'and': 3, 'i': 4, 'he...
{0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: ...
これによりコーパスをIDで表すことができた。
ii.単語IDで表されたコーパスからコンテキストとターゲットを...
#pre{{
def create_contexts_target(corpus, window_size=1):
'''コンテキストとターゲットの作成
:param corpus: コーパス(単語IDのリスト)
:param window_size: ウィンドウサイズ(ウィンドウサイ...
:return:
'''
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, win...
print(contexts)
print(target)
このような結果が得られる
[[0 2]
[1 3]
[2 4]
[3 1]
[4 5]
[1 6]]
[1 2 3 4 1 5]
コンテキストを2つ持たない、つまり端の単語はターゲットから...
***3.3.2 one-hot表現への変換 [#w2d5b6db]
上記で作成したコンテキストとターゲットをone-hot表現へ変換
#pre{{
def convert_one_hot(corpus, vocab_size):
'''one-hot表現への変換
:param corpus: 単語IDのリスト(1次元もしくは2次元のNu...
:param vocab_size: 語彙数
:return: one-hot表現(2次元もしくは3次元のNumPy配列)
'''
N = corpus.shape[0]
if corpus.ndim == 1:
one_hot = np.zeros((N, vocab_size), dtype=np.int32)
for idx, word_id in enumerate(corpus):
one_hot[idx, word_id] = 1
elif corpus.ndim == 2:
C = corpus.shape[1]
one_hot = np.zeros((N, C, vocab_size), dtype=np.i...
for idx_0, word_ids in enumerate(corpus):
for idx_1, word_id in enumerate(word_ids):
one_hot[idx_0, idx_1, word_id] = 1
return one_hot
}}
これを次のように実行すると
vocab_size = len(word_to_id)
target = convert_one_hot(target, vocab_size)
contexts = convert_one_hot(contexts, vocab_size)
print("contexts\n",contexts)
print("target\n", target)
このような結果が得られる
contexts
[[[1 0 0 0 0 0 0]
[0 0 1 0 0 0 0]]
[[0 1 0 0 0 0 0]
[0 0 0 1 0 0 0]]
[[0 0 1 0 0 0 0]
[0 0 0 0 1 0 0]]
[[0 0 0 1 0 0 0]
[0 1 0 0 0 0 0]]
[[0 0 0 0 1 0 0]
[0 0 0 0 0 1 0]]
[[0 1 0 0 0 0 0]
[0 0 0 0 0 0 1]]]
target
[[0 1 0 0 0 0 0]
[0 0 1 0 0 0 0]
[0 0 0 1 0 0 0]
[0 0 0 0 1 0 0]
[0 1 0 0 0 0 0]
[0 0 0 0 0 1 0]]
**3.4 CBOWモデルの実装 [#ace0b7d6]
本書では、ニューラルネットワークの処理をレイヤ(層)で表す...
#pre{{
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.ou...
self.params, self.grads = [], []
for layer in layers:
self.params += layer.params
self.grads += layer.grads
# メンバ変数に単語の分散表現を設定
self.word_vecs = W_in
}}
#pre{{
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
}}
#pre{{
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
}}
***3.4.1 学習コードの実装 [#w80587dd]
#pre{{
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_targe...
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_...
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()
word_vecs = model.word_vecs
for word_id, word in id_to_word.items():
print(word, word_vecs[word_id])
}}
実行結果
#pre{{
you [ 0.816448 -1.5548781 -0.97975504 0.8965743 -1.2...
say [-1.14312 -1.1699724 1.1750078 -1.1562544 0.1...
goodbye [ 1.1648722 -0.18298058 -0.9638767 1.0863249 ...
and [-1.0530106 -0.01240244 1.1135145 -1.1372522 -1.9...
i [ 1.1653302 -0.1588453 -0.97213495 1.1066818 -0.438...
hello [ 0.81998146 -1.5668002 -0.9753178 0.9042221 -1...
. [-0.89470506 -1.4097029 0.89925027 -0.8334046 1.640...
}}
中間層のニューロン数は5(コード中のhidden_sizeが中間層のニ...
また、学習を重ねるごとに交差エントロピーが減少しているこ...
&ref(./スクリーンショット 2019-09-11 23.15.22.png,50%);
**3.5 word2vecに関する補足 [#ff19aecf]
CBOWモデルを「確率」の視点からもう一度見ていく
***3.5.1 CBOWモデルと確率 [#pa1be4bd]
単語列を&mathjax{ w_1, w_2, ... , w_t};と表すと、CBOWは次...
#mathjax(P(w_t | w_{t-1}, w_{t+1}));
損失関数は
#mathjax( L = -\frac{1}{T} \sum_{t=1}^{T} \log P(w_t | w_...
と表される。
***3.5.2 skip-gramモデル [#r557a25c]
CBOW モデルの他にskip-gramモデルもword2vecでは提案されて...
skip-gram モデルは、CBOW モデルでのターゲットとコンテキス...
***3.5.3 カウントベース v.s. 推論ベース [#m9f9de0e]
++語彙に新しい単語を追加するケースで、単語の分散表現の更...
>
カウントベース : ゼロから計算を行う必要がある。共起行列を...
<
>
推論ベース : これまでに学習した重みを初期値として再学習で...
<
++単語の分散表現の性質や制度
>
カウントベース : 分散表現の性質は、単語の類似性が捉えられる
<
>
推論ベース : 分散表現の性質は、単語の類似性に加えて、複雑...
<
精度的には二つの手法には優劣がつけられない
**3.6 まとめ [#p34a39db]
CBOWは基本的に二層のニューラルネットワークでとてもシンプ...
*4章 word2vecの高速化 [#z3feca29]
前章で実装したword2vecのままでは、語彙数が増えることに伴...
++Embeddingレイヤの実装
++Negative Sampling という損失関数の導入
を行う。これにより、本物のword2vecが得られる。
**4.1 word2vecの改良1 [#d1fb74dc]
具体的に次の二箇所の計算がボトルネックとなる。
++入力層のone-hot表現と重み行列の積による計算
++中間層と重み行列の積およびSoftmaxレイヤの計算
一つ目の問題点はone-hot表現を扱っていることによって、コー...
二つ目の問題点は行列の積の計算とSoftmaxの計算です。行列積...
***4.1.1 Embeddingレイヤ [#j2726229]
one-hot表現はその単語を表す要素が1で、その他は0のベクトル...
***4.1.2 Embeddingレイヤの実装 [#cd934f0c]
class Embedding:
def __init__(self, W):
self.params = [W]
self.grads = [np.zeros_like(W)]
self.idx = None
def forward(self, idx):
W, = self.params
self.idx = idx
out = W[idx]
return out
idxには抽出する行のインデックスを配列として格納する。そし...
def backward(self, dout):
dW, = self.grads
dW[...] = 0
np.add.at(dW, self.idx, dout) #dWのself.idxにdout...
return None
forwardで重みの特定の行を抜き出すだけの処理なので、それを...
**4.2 word2vecの改良2 [#jac33393]
残る問題点は、中間層以降の行列の積とSoftmaxレイヤの計算で...
この解決策として、NegativeSamplingと呼ばれる手法を用いる...
***4.2.1 中間層以降の計算の問題点 [#ud57eb0a]
++語彙数が100万、中間層のニューロンが100の時を考えると、...
++上記と同じ条件でSoftmaxの計算を考えてみると、Softmaxの...
#mathjax( \begin{align} z_k &= \frac{\exp(y_k)}{\sum_{j=1...
大変
***4.2.2 多値分類から二値分類へ [#u3de7c81]
NegativeSamplingの手法の説明をしていく。この手法のキーと...
そのために問題の形を変えてあげる必要がある。具体的には、...
この時のCBOWの処理は次の様になる。語彙数が100万、中間層の...
***4.2.3 シグモイド関数と交差エントロピー誤差 [#j785c6ba]
多値分類から二値分類の問題に変わったので、Softmax関数の代...
Sigmoid関数
#mathjax( y = \frac{1}{1+\exp(-x)} )
交差エントロピー
#mathjax( L = -(t\log y - (1-t)\log ( 1 - y)) )
***4.2.4 多値分類から二値分類へ(実装編) [#ue5cf43c]
#pre{{
import sys
sys.path.append('..')
from common.np import * # import numpy as np
from common.layers import Embedding, SigmoidWithLoss
import collections
class EmbeddingDot:
def __init__(self, W):
self.embed = Embedding(W) #4.1.2 で実装したEmbedd...
self.params = self.embed.params
self.grads = self.embed.grads
self.cache = None
def forward(self, h, idx):
target_W = self.embed.forward(idx)
out = np.sum(target_W * h, axis=1) #内積を計算
self.cache = (h, target_W)
return out
def backward(self, dout):
h, target_W = self.cache
dout = dout.reshape(dout.shape[0], 1)
dtarget_W = dout * h
self.embed.backward(dtarget_W)
dh = dout * target_W
return dh
}}
前回実装したEmbedding層を二値分類の処理に対応させたもの。...
***4.2.5 Negative Sampling [#d468ad56]
これまでの議論で解くべき問題を二値分類に変換することがで...
以上をまとめると、損失関数 Negative Sampling は正例につい...
***4.2.6 Negative Sampling のサンプリング手法 [#v843e38f]
負例のサンプリングの手法を説明する。ただランダムにサンプ...
&mathjax{P(w_i)}; が &mathjax{i}; 番目の単語の確率を表す...
#mathjax(P'(w_i) = \frac{P(w_i)^{0.75} }{ \sum_{j}^{n} P(...
0.75乗することで出現確率の低い単語を見捨てないようにしま...
#pre{{
class UnigramSampler:
def __init__(self, corpus, power, sample_size):
self.sample_size = sample_size
self.vocab_size = None
self.word_p = None
counts = collections.Counter()
for word_id in corpus:
counts[word_id] += 1
vocab_size = len(counts)
self.vocab_size = vocab_size
self.word_p = np.zeros(vocab_size)
for i in range(vocab_size):
self.word_p[i] = counts[i]
self.word_p = np.power(self.word_p, power)
self.word_p /= np.sum(self.word_p)
def get_negative_sample(self, target):
batch_size = target.shape[0]
if not GPU:
negative_sample = np.zeros((batch_size, self....
for i in range(batch_size):
p = self.word_p.copy()
target_idx = target[i]
p[target_idx] = 0
p /= p.sum()
negative_sample[i, :] = np.random.choice(...
else:
# GPU(cupy)で計算するときは、速度を優先
# 負例にターゲットが含まれるケースがある
negative_sample = np.random.choice(self.vocab...
replace=Tr...
return negative_sample
}}
***4.2.7 Negative Sampling の実装 [#c2911813]
#pre{{
class NegativeSamplingLoss:
def __init__(self, W, corpus, power=0.75, sample_size...
self.sample_size = sample_size
self.sampler = UnigramSampler(corpus, power, samp...
self.loss_layers = [SigmoidWithLoss() for _ in ra...
self.embed_dot_layers = [EmbeddingDot(W) for _ in...
self.params, self.grads = [], []
for layer in self.embed_dot_layers:
self.params += layer.params
self.grads += layer.grads
}}
イニシャライズの各引数
W : 出力側の重み
corpus : 単語IDのリスト
power : 確率分布の累乗の値(4.2.6 で説明した確率分布の0.75...
sample_size : 負例のサンプルサイズ
負例をサンプリングする為のクラスUnigramSamplerをメンバ変...
#pre{{
def forward(self, h, target):
batch_size = target.shape[0]
negative_sample = self.sampler.get_negative_sampl...
# 正例のフォワード
score = self.embed_dot_layers[0].forward(h, target)
correct_label = np.ones(batch_size, dtype=np.int32)
loss = self.loss_layers[0].forward(score, correct...
# 負例のフォワード
negative_label = np.zeros(batch_size, dtype=np.in...
for i in range(self.sample_size):
negative_target = negative_sample[:, i]
score = self.embed_dot_layers[1 + i].forward(...
loss += self.loss_layers[1 + i].forward(score...
return loss
}}
negative_sample にサンプリングした負例を代入。そして正例...
#pre{{
def backward(self, dout=1):
dh = 0
for l0, l1 in zip(self.loss_layers, self.embed_do...
dscore = l0.backward(dout)
dh += l1.backward(dscore)
return dh
}}
逆伝播は各レイヤの逆伝播のメソッド backward() を呼ぶ処理。
**4.3 改良版word2vecの学習 [#fdce0403]
Embeddingレイヤ、続いてNegative Sampling という手法につい...
***4.3.1 CBOWモデルの実装 [#d9a7e22c]
#pre{{
import sys
sys.path.append('..')
from common.np import * # import numpy as np
from common.layers import Embedding
from ch04.negative_sampling_layer import NegativeSampling...
class CBOW:
def __init__(self, vocab_size, hidden_size, window_si...
V, H = vocab_size, hidden_size
# 重みの初期化
W_in = 0.01 * np.random.randn(V, H).astype('f')
W_out = 0.01 * np.random.randn(V, H).astype('f')
# レイヤの生成
self.in_layers = []
for i in range(2 * window_size):
layer = Embedding(W_in) # Embeddingレイヤを...
self.in_layers.append(layer)
self.ns_loss = NegativeSamplingLoss(W_out, corpus...
# すべての重みと勾配をリストにまとめる
layers = self.in_layers + [self.ns_loss]
self.params, self.grads = [], []
for layer in layers:
self.params += layer.params
self.grads += layer.grads
# メンバ変数に単語の分散表現を設定
self.word_vecs = W_in
}}
vocab_size : 語彙数
hidden_size : 中間層のニューロン数
corpus : 単語IDのリスト
window_size : ターゲットの周囲の単語をどれだけコンテキス...
Embeddingレイヤを 2 * window_size だけ作成し、それをメン...
NegativeSamplingLoss クラスをメンバ変数 ns_loss で保持。
#pre{{
def forward(self, contexts, target):
h = 0
for i, layer in enumerate(self.in_layers):
h += layer.forward(contexts[:, i])
h *= 1 / len(self.in_layers)
loss = self.ns_loss.forward(h, target)
return loss
}}
#pre{{
def backward(self, dout=1):
dout = self.ns_loss.backward(dout)
dout *= 1 / len(self.in_layers)
for layer in self.in_layers:
layer.backward(dout)
return None
}}
***4.3.2 CBOWモデルの学習コード [#yc658cd0]
CBOWモデルの学習を実装する。ウインドウサイズを5, 隠れ層の...
#pre{{
import sys
sys.path.append('..')
import numpy as np
from common import config
# GPUで実行する場合は、下記のコメントアウトを消去(要cupy)
# ===============================================
# config.GPU = True
# ===============================================
import pickle
from common.trainer import Trainer
from common.optimizer import Adam
from cbow import CBOW
from skip_gram import SkipGram
from common.util import create_contexts_target, to_cpu, t...
from dataset import ptb
# ハイパーパラメータの設定
window_size = 5
hidden_size = 100
batch_size = 100
max_epoch = 10
# データの読み込み
corpus, word_to_id, id_to_word = ptb.load_data('train')
vocab_size = len(word_to_id)
contexts, target = create_contexts_target(corpus, window_...
if config.GPU:
contexts, target = to_gpu(contexts), to_gpu(target)
# モデルなどの生成
model = CBOW(vocab_size, hidden_size, window_size, corpus)
# model = SkipGram(vocab_size, hidden_size, window_size, ...
optimizer = Adam()
trainer = Trainer(model, optimizer)
# 学習開始
trainer.fit(contexts, target, max_epoch, batch_size)
trainer.plot()
# 後ほど利用できるように、必要なデータを保存
word_vecs = model.word_vecs
if config.GPU:
word_vecs = to_cpu(word_vecs)
params = {}
params['word_vecs'] = word_vecs.astype(np.float16)
params['word_to_id'] = word_to_id
params['id_to_word'] = id_to_word
pkl_file = 'cbow_params.pkl' # or 'skipgram_params.pkl'
with open(pkl_file, 'wb') as f:
pickle.dump(params, f, -1)
}}
学習結果
PTBデータセット : データサイズ = 929,589, 語彙数 = 10,000...
バッチサイズ = 100, iteration数 = 9295, epoch数 = 10
実行時間 : 3.9[h]
&ref(./cbowlearning2.png,50%);
***4.3.3 CBOWモデルの評価 [#g7d49bcc]
先の実験で得られた単語の分散表現を評価してみる。
#pre{{
import sys
sys.path.append('..')
from common.util import most_similar, analogy
import pickle
pkl_file = 'cbow_params.pkl'
# pkl_file = 'skipgram_params.pkl'
with open(pkl_file, 'rb') as f:
params = pickle.load(f)
word_vecs = params['word_vecs']
word_to_id = params['word_to_id']
id_to_word = params['id_to_word']
# most similar task
querys = ['you', 'year', 'car', 'toyota']
for query in querys:
most_similar(query, word_to_id, id_to_word, word_vecs...
# analogy task
print('-'*50)
analogy('king', 'man', 'queen', word_to_id, id_to_word, ...
analogy('take', 'took', 'go', word_to_id, id_to_word, wo...
analogy('car', 'cars', 'child', word_to_id, id_to_word, ...
analogy('good', 'better', 'bad', word_to_id, id_to_word,...
}}
実行結果
#pre{{
[query] you
we: 0.73046875
i: 0.71142578125
your: 0.609375
weird: 0.6064453125
us: 0.57763671875
[query] year
month: 0.8623046875
week: 0.79541015625
summer: 0.78564453125
spring: 0.76708984375
decade: 0.70556640625
[query] car
truck: 0.6708984375
window: 0.62939453125
luxury: 0.625
auto: 0.62158203125
cars: 0.583984375
[query] toyota
seita: 0.65478515625
honda: 0.646484375
engines: 0.63427734375
marathon: 0.62890625
mazda: 0.6181640625
}}
これはqueryに指定した単語ともっとも近い単語を上から5つ表...
#pre{{
[analogy] king:man = queen:?
woman: 5.1640625
hacker: 5.09765625
text: 5.01953125
a.m: 4.71484375
amendment: 4.7109375
}}
これは 「man → woman」ベクトルと「king → ?」ベクトルが一...
#pre{{
[analogy] take:took = go:?
eurodollars: 5.60546875
were: 4.54296875
're: 4.4609375
went: 4.45703125
a.m: 4.2265625
[analogy] car:cars = child:?
a.m: 6.98046875
rape: 5.95703125
children: 5.2109375
daffynition: 4.85546875
incest: 4.85546875
[analogy] good:better = bad:?
rather: 5.453125
more: 5.2265625
less: 4.9453125
greater: 4.30859375
worse: 4.171875
}}
類推問題の結果はとても良いものに見えるがこれはうまく解け...
**4.4 word2vecに関する残りのテーマ [#s6b782da]
***4.4.1 word2vecを使ったアプリケーションの例 [#qbda54c6]
word2vecで得られた単語の分散表現は転移学習に用いる。これ...
自然言語のタスクの例 : テキスト分類、文書クラスタリング、...
また、単語の分散表現を用いて文章を固定長のベクトルに変換...
***4.4.2 単語ベクトルの評価方法 [#j4b937a0]
単語の分散表現を用いて、何らかの処理をしたいわけです。そ...
++単語の分散表現を得る為の学習
++その分散表現を用いて、ある処理のシステムを学習させる
と二段階の評価が必要がある。さらにその二つのシステムにお...
これは大変なので、単語の分散表現の評価はこの上に乗せるシ...
しかし、単語の分散表現の良さが目的とするシステムにどれだ...
**4.5 まとめ [#ta4b7558]
本章ではword2vecの高速化をテーマとして扱った。「すべて」...
word2vecの思想は、音声、画像、動画などにも応用されている...
*補足 [#vb270894]
**PTB(Penn Treebank)データセット(P.86~) [#z233586b]
PTBコーパスでは一つの文が一行ごとに保存されているが、本書...
PTBコーパスを理解するために本書から提供されている ch02/sh...
#pre{{
import sys
sys.path.append('..')
from dataset import ptb
corpus, word_to_id, id_to_word = ptb.load_data('train')
print('corpus size:', len(corpus))
print('corpus[:30]:', corpus[:30])
print()
print('id_to_word[0]:', id_to_word[0])
print('id_to_word[1]:', id_to_word[1])
print('id_to_word[2]:', id_to_word[2])
print()
print("word_to_id['car']:", word_to_id['car'])
print("word_to_id['happy']:", word_to_id['happy'])
print("word_to_id['lexus']:", word_to_id['lexus'])
}}
実行結果
#pre{{
In [71]: %run show_ptb.py ...
corpus size: 929589
corpus[:30]: [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 1...
24 25 26 27 28 29]
id_to_word[0]: aer
id_to_word[1]: banknote
id_to_word[2]: berlitz
word_to_id['car']: 3856
word_to_id['happy']: 4428
word_to_id['lexus']: 7426
}}
また、ipython上で以下の様に実行してみた。
#pre{{
In [72]: from dataset import ptb ...
In [73]: corpus, word_to_id, id_to_word = ptb.load_data('...
In [74]: a = word_to_id.keys() ...
In [75]: len(a) ...
Out[75]: 10000
}}
これより、PTBのコーパスサイズは、929,589 で各単語はIDに変...
*応用 [#m7095758]
**word2vec を用いたアプリケーション [#p3745933]
***文章を固定長のベクトルへ変換する手法 [#i5e924f8]
-bag-of-words(BoW) : 文章の各単語を分散表現に変換し、それ...
-CNN : テキスト系列内の有益な n-グラム、あるいはギャップ...
-RNN : bag-of-wordsとは違い、順序を考慮したモデル。難しい...
bag-of-words とかの具体的な実装方法はネットじゃなくて図書...
***MLPなど比較的単純なシステムで処理できる問題 [#g9bcc253]
-ロジスティック回帰で2値分類問題
--レビューに対する感情分類(positive か negativeか)
-MLPで多値分類
--トピック分類(これは政治の記事、これはスポーツの記事、と...
**PTB以外のデータセット [#o6918193]
文章に対する処理がしたいなら当然新たなデータセットが必要。
-[[機械学習に使える日本語のデータセット13個>https://qiita...
-[[自然言語処理に使えるデータセットまとめ(25個)>https://l...
**word2vecの実験 [#b5ae2277]
***扱うデータセット [#z4a2c73c]
[[Large Movie Review Dataset>http://ai.stanford.edu/~amaa...
肯定、否定のラベル付けをされた英語のIMDB映画レビューのデ...
Kerasから提供されているライブラリを用いてこのデータセット...
***word2vec の学習 [#m0d761b9]
IMDBレビューの肯定/否定の2値識別問題に取り組むにあたって...
++前回PTBコーパスで学習した単語の分散表現を流用する(転移...
++IMDBレビューの学習データを用いて、レビューを単語レベル...
とりあえず i の方法での実験を行おうと考えている。その為に...
-[[上へまいります>b/2019/ikeuchi/learning#gc08b923]]
ページ名: