4.3 改良版word2vecの実装

モデルを構築する関数を用意します。

from ivory.common.context import np
from ivory.core.model import Model, branch

def cbow(vocab_size, window_size=5, hidden_size=100, sample_size=5, batch_size=100):
    em = [("input", 2 * window_size, vocab_size), ("embeddingmean", hidden_size)]
    ns = [("embeddingdot", vocab_size), "sigmoid_cross_entropy"]
    h = branch(em)
    losses = [branch(ns, h).loss for _ in range(sample_size + 1)]
    model = Model(losses)
    # EmbeddingDotレイヤはひとつの重みを共有します。
    v = model.weights[1].variable
    for p in model.weights[2:]:
        p.set_variable(v)
    len(v.parameters)
    # 正例と負例の正解ラベルを代入します。今後更新することがないので、`frozen`を`True`に設定します。
    v = model.data_input_variables[2]
    v.data = np.ones(batch_size, dtype=np.int32)
    v.frozen = True
    for v in model.data_input_variables[4::2]:
        v.data = np.zeros(batch_size, dtype=np.int32)
        v.frozen = True
    # 再度モデルをビルドします。
    model.build()
    # 重みを初期化します。
    model.init(std=0.01)
    return model

[1] 2019-06-12 17:46:24 (203ms) python3 (203ms)

まずは実験用のコーパスを準備します。

from ivory.common.dataset import ContextDataset

corpus = [0, 1, 2, 3, 4, 1, 2, 3, 2]
data = ContextDataset(corpus, replace=True)

[2] 2019-06-12 17:46:24 (8.04ms) python3 (211ms)

ハイパーパラメータの設定を行います。

data.set_window_size(2)
data.negative_sample_size = 2
data.batch_size = 2
hidden_size = 10

[3] 2019-06-12 17:46:24 (10.00ms) python3 (221ms)

モデルを作成します。

model = cbow(
    data.vocab_size,
    window_size=data.window_size,
    hidden_size=hidden_size,
    sample_size=data.negative_sample_size,
    batch_size=data.batch_size,
)

[4] 2019-06-12 17:46:24 (14.1ms) python3 (235ms)

モデルの確認を行います。

model.data_input_variables

[5] 2019-06-12 17:46:24 (19.0ms) python3 (254ms)

[<Variable(['EmbeddingMean.1.x'], (4,)) at 0x17483a40ef0>,
 <Variable(['EmbeddingDot.1.t'], ()) at 0x17483a40eb8>,
 <Variable(['EmbeddingDot.2.t'], ()) at 0x17483a40f28>,
 <Variable(['EmbeddingDot.3.t'], ()) at 0x17483a40fd0>]
model.frozen_input_variables

[6] 2019-06-12 17:46:25 (31.2ms) python3 (286ms)

[<Variable(['SigmoidCrossEntropy.1.t'], ()) at 0x17483a40e48>,
 <Variable(['SigmoidCrossEntropy.2.t'], ()) at 0x17483a40f98>,
 <Variable(['SigmoidCrossEntropy.3.t'], ()) at 0x17483a40f60>]
model.weight_variables

[7] 2019-06-12 17:46:25 (31.2ms) python3 (317ms)

[<Variable(['EmbeddingMean.1.W'], (5, 10)) at 0x17483a409b0>,
 <Variable(['EmbeddingDot.1.W', 'EmbeddingDot.2.W', 'EmbeddingDot.3.W'], (5, 10)) at 0x17483a409e8>]

重みの標準偏差を確認します。

for v in model.weight_variables:
    print(v.data.std())

[8] 2019-06-12 17:46:25 (31.2ms) python3 (348ms)

0.010364148
0.01014407

勾配確認のために、重みのビット精度を64ビットにします。

for v in model.weight_variables:
    v.data = v.data.astype(np.float64)

[9] 2019-06-12 17:46:25 (6.04ms) python3 (354ms)

モデルに代入してみます。

model.set_data(*data[0])
model.forward()
model.backward()
model.loss

[10] 2019-06-12 17:46:25 (9.04ms) python3 (363ms)

2.0795377012168577

数値微分による勾配確認を行います。

for v in model.grad_variables:
    print(model.gradient_error(v))
    print(v.grad[:2, :4])
    print(model.numerical_gradient(v)[:2, :4])

[11] 2019-06-12 17:46:25 (120ms) python3 (483ms)

1.4419764900359954e-10
[[ 8.71024039e-04 -8.07416993e-04  3.55431663e-04  6.89682988e-04]
 [-5.82890768e-04  4.68944837e-04  9.49804874e-04  9.89671060e-05]]
[[ 8.71023864e-04 -8.07416829e-04  3.55431591e-04  6.89682849e-04]
 [-5.82890651e-04  4.68944743e-04  9.49804684e-04  9.89670856e-05]]
1.676778286683299e-10
[[ 0.          0.          0.          0.        ]
 [ 0.00135436 -0.0003167   0.00141522  0.00261721]]
[[ 0.          0.          0.          0.        ]
 [ 0.00135436 -0.0003167   0.00141522  0.00261721]]

正しい結果が得られています。

次に、PTBデータセットを読み出します。

from ivory.utils.repository import import_module

ptb = import_module("scratch2/dataset/ptb")
corpus, word_to_id, id_to_word = ptb.load_data("train")
data = ContextDataset(corpus, window_size=5, replace=True)

[12] 2019-06-12 17:46:25 (3.97s) python3 (4.45s)

ハイパーパラメータの設定を行います。

data.negative_sample_size = 5
data.batch_size = 100
hidden_size = 100

[13] 2019-06-12 17:46:29 (5.07ms) python3 (4.46s)

モデルを作成します。

model = cbow(
    data.vocab_size,
    window_size=data.window_size,
    hidden_size=hidden_size,
    sample_size=data.negative_sample_size,
    batch_size=data.batch_size,
)

[14] 2019-06-12 17:46:29 (296ms) python3 (4.76s)

モデルに代入してみます。

model.set_data(*data[0])
model.forward()
model.backward()
model.loss

[15] 2019-06-12 17:46:29 (55.1ms) python3 (4.81s)

4.158883132934571

トレーナーに登録します。

from ivory.core.trainer import Trainer
from ivory.core.optimizer import Adam

trainer = Trainer(model, optimizer=Adam(), dataset=data, metrics=['loss'])
trainer.init(std=0.01)

[16] 2019-06-12 17:46:29 (80.0ms) python3 (4.89s)

Trainer(inputs=[(10,), (), (), (), (), (), ()], optimizer='Adam', metrics=['loss'])

訓練を実施します。

for _ in zip(range(201), trainer):
    if data.iteration % 20 == 0:
        print(data.iteration, model.loss)

[17] 2019-06-12 17:46:29 (16.6s) python3 (21.5s)

0 4.158847274780273
20 4.157372360229492
40 4.146584930419922
60 4.099747619628907
80 4.022499923706055
100 3.8987150955200187
120 3.767969474792481
140 3.7017833709716794
160 3.5168034362792966
180 3.2581941223144533
200 3.441788787841797

GPUを使ってみます。

np.context = 'gpu'
data = ContextDataset(corpus, window_size=5, replace=True)
data.negative_sample_size = 5
data.batch_size = 100

model = cbow(
    data.vocab_size,
    window_size=data.window_size,
    hidden_size=hidden_size,
    sample_size=data.negative_sample_size,
    batch_size=data.batch_size,
)

trainer = Trainer(model, optimizer=Adam(), dataset=data, metrics=['loss'])
trainer.init(std=0.01)

[18] 2019-06-12 17:46:46 (4.98s) python3 (26.5s)

Trainer(inputs=[(10,), (), (), (), (), (), ()], optimizer='Adam', metrics=['loss'])

訓練を実施します。

for _ in zip(range(201), trainer):
    if data.iteration % 20 == 0:
        print(data.iteration, model.loss)

[19] 2019-06-12 17:46:51 (4.58s) python3 (31.1s)

0 4.158863
20 4.156633
40 4.1426606
60 4.0916934
80 4.014283
100 3.9159517
120 3.726818
140 3.6841648
160 3.4984107
180 3.308152
200 3.4728296