2.9 MatMul/Embeddingレイヤ

2.9.1 定式化

MatMulレイヤの順伝搬、逆伝搬は以下の通りです。

\mathbf{Y}=\mathbf{X}\cdot\mathbf{W}
\frac{\partial L}{\partial \mathbf{X}}=\frac{\partial L}{\partial \mathbf{Y}}\cdot \mathbf{W}^\mathrm{T}
\frac{\partial L}{\partial \mathbf{W}}=\mathbf{X}^\mathrm{T}\cdot \frac{\partial L}{\partial \mathbf{Y}}

まずは、MatMalレイヤの動作を確認します。形状を定義します。

N, T, L, M = 20, 3, 3, 4

[11] 2019-06-12 20:01:30 (6.00ms) python3 (325ms)

ここでNはバッチ数、Tはタイムステップ数、Lは入力の次元、Mは出力の次元です。

import numpy as np

from ivory.core.model import sequential
from ivory.common.util import convert_one_hot

net_mat = [("input", L), ("matmul", M, "softmax_cross_entropy")]
model_mat = sequential(net_mat)
mat = model_mat.layers[0]

[12] 2019-06-12 20:01:30 (13.0ms) python3 (338ms)

ランダムな入力を作成します。

x = np.random.randint(0, L, N)
t = np.random.randint(0, M, N)

[13] 2019-06-12 20:01:30 (6.00ms) python3 (344ms)

MatMulレイヤへ入力するために、one-hot表現に変換します。

model_mat.set_data(convert_one_hot(x, L), t)
model_mat.forward()
print(model_mat.loss)

[14] 2019-06-12 20:01:30 (10.0ms) python3 (354ms)

1.470152673184216

逆伝搬を比較します。

model_mat.backward()
print(mat.W.g)

[15] 2019-06-12 20:01:30 (7.07ms) python3 (361ms)

[[ 0.0467592  -0.13846478  0.05848749  0.03321808]
 [ 0.02664038  0.04141173  0.0214785  -0.08953062]
 [ 0.06250668 -0.03183299  0.01628042 -0.04695412]]

数値微分による勾配と比較します。

print(model_mat.numerical_gradient(mat.W.variable))

[16] 2019-06-12 20:01:30 (15.7ms) python3 (377ms)

[[ 0.04675993 -0.13844624  0.05847969  0.03322349]
 [ 0.02664081  0.04141239  0.02148206 -0.08954541]
 [ 0.06250772 -0.03183349  0.01628312 -0.04694793]]

時系列データを確かめます。

xs = np.random.randint(0, L, (N, T))
ts = np.random.randint(0, M, (N, T))

[17] 2019-06-12 20:01:30 (19.3ms) python3 (396ms)

MatMulレイヤへ入力するために、one-hot表現に変換します。

model_mat.set_data(convert_one_hot(xs, L), ts)
model_mat.forward()
print(model_mat.loss)

[18] 2019-06-12 20:01:30 (8.03ms) python3 (404ms)

1.6217845884211495

逆伝搬を比較します。

model_mat.backward()
print(mat.W.g)

[19] 2019-06-12 20:01:31 (7.98ms) python3 (412ms)

[[-0.04263126 -0.0317359  -0.02406147  0.09842863]
 [ 0.04414367 -0.00959807  0.01254369 -0.04708929]
 [ 0.04375557  0.00680584 -0.11976631  0.0692049 ]]

数値微分による勾配と比較します。

print(model_mat.numerical_gradient(mat.W.variable))

[20] 2019-06-12 20:01:31 (16.0ms) python3 (428ms)

[[-0.04263198 -0.03173163 -0.02405831  0.09844489]
 [ 0.04414439 -0.00959825  0.01254577 -0.04709706]
 [ 0.04375621  0.00680592 -0.11978588  0.06919558]]

次にEmbeddingレイヤを確かめます。まずは時系列でないデータです。

net_em = [("input", L), ("embedding", M, "softmax_cross_entropy")]
model_em = sequential(net_em)
em = model_em.layers[0]

[21] 2019-06-12 20:01:31 (15.7ms) python3 (444ms)

Embeddingレイヤへは、one-hot表現ではなく、ミニバッチのデータごとにスカラー値を与えます。

model_em.set_data(x, t)

[22] 2019-06-12 20:01:31 (15.6ms) python3 (459ms)

両者を比較するために重みを同じ値に設定します。変数の割り当てを変えたらモデルをビルドします。

em.share_weight_variables(mat)
model_em.build()

[23] 2019-06-12 20:01:31 (10.1ms) python3 (470ms)

<ivory.core.model.Model at 0x1982fa4ed68>

順伝搬を行います。

model_em.forward()
print(model_em.loss)

[24] 2019-06-12 20:01:31 (6.05ms) python3 (476ms)

1.4701526641845704

逆伝搬を比較します。

model_em.backward()
print(em.W.g)
print(model_em.numerical_gradient(em.W.variable))

[25] 2019-06-12 20:01:31 (9.05ms) python3 (485ms)

[[ 0.0467592  -0.13846478  0.05848749  0.03321808]
 [ 0.02664038  0.04141173  0.0214785  -0.08953062]
 [ 0.06250668 -0.03183298  0.01628043 -0.04695413]]
[[ 0.04673004 -0.13875961  0.05865097  0.0333786 ]
 [ 0.02670288  0.04196167  0.02145767 -0.08916855]
 [ 0.0629425  -0.03147125  0.01573563 -0.04673004]]

次に時系列データです。

model_em.set_data(xs, ts)
model_em.forward()
print(model_mat.loss)
print(mat.y.d[0])
print(em.y.d[0])

[26] 2019-06-12 20:01:31 (16.0ms) python3 (501ms)

1.6217776713417569
[[ 0.44147804 -0.9581278   0.66527981  1.01793158]
 [ 0.44147804 -0.9581278   0.66527981  1.01793158]
 [ 0.34931859 -0.15173322 -1.58373058  0.93963802]]
[[ 0.44147804 -0.9581278   0.6652798   1.0179316 ]
 [ 0.44147804 -0.9581278   0.6652798   1.0179316 ]
 [ 0.3493186  -0.15173322 -1.5837306   0.939738  ]]

逆伝搬を比較します。

model_em.backward()
print(mat.W.g)

[27] 2019-06-12 20:01:31 (8.04ms) python3 (509ms)

[[-0.04263127 -0.0317359  -0.02406147  0.0984286 ]
 [ 0.04414367 -0.00959807  0.01254369 -0.04708927]
 [ 0.04375557  0.00680584 -0.11976629  0.06920489]]

数値微分による勾配と比較します。

print(model_mat.numerical_gradient(mat.W.variable))

[28] 2019-06-12 20:01:31 (31.2ms) python3 (540ms)

[[-0.04263198 -0.03173163 -0.02405831  0.09844489]
 [ 0.04414439 -0.00959825  0.01254577 -0.04709706]
 [ 0.04375621  0.00680592 -0.11978588  0.06919558]]

念のため比較します。

grad = np.zeros_like(em.W.d)
np.scatter_add(grad, em.x.d, em.y.g)  # np.add.at
print(grad)
grad = np.zeros_like(em.W.d)
for t in range(em.x.d.shape[1]):
    np.scatter_add(grad, em.x.d[:, t], em.y.g[:, t])  # np.add.at
print(grad)

[29] 2019-06-12 20:01:31 (15.7ms) python3 (556ms)

[[-0.04263127 -0.0317359  -0.02406147  0.0984286 ]
 [ 0.04414367 -0.00959807  0.01254369 -0.04708927]
 [ 0.04375557  0.00680584 -0.11976629  0.06920489]]
[[-0.04263126 -0.03173589 -0.02406147  0.09842861]
 [ 0.04414367 -0.00959807  0.01254369 -0.04708929]
 [ 0.04375556  0.00680585 -0.11976632  0.0692049 ]]

以下に実装コードを示します。

Code 2.11 MatMulクラス

class MatMul(Layer):
    def init(self):
        self.W = self.add_weight(self.shape).randn()

    def forward(self):
        self.y.d = self.x.d @ self.W.d

    def backward(self):
        self.x.g = self.y.g @ self.W.d.T
        axis = (0,) if self.x.g.ndim == 2 else (0, 1)
        self.W.g = np.tensordot(self.x.d, self.y.g, axes=[axis, axis])

Code 2.12 Embeddingクラス

class Embedding(Layer):
    input_ndim = -2

    def init(self):
        self.y.shape = self.shape[-1:]
        self.W = self.add_weight(self.shape).randn()

    def forward(self):
        self.y.d = self.W.d[self.x.d]

    def backward(self):
        grad = np.zeros_like(self.W.d)
        np.scatter_add(grad, self.x.d, self.y.g)  # np.add.at
        self.W.g = grad