2.11 EmbeddingMean

Embeddingレイヤの入力は、バッチ数分のラベル値でした。よって、EmbeddingMeanレイヤへは、(バッチ数, 入力数)のラベルとなります。

Embeddingレイヤのforwardメソッドは以下の通りでした。

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

まずはこれを拡張します。形状を(バッチ数、入力数、入力の次元、出力の次元)とします。

import numpy as np

N, I, L, M = 2, 3, 5, 3

[1] 2019-06-12 20:09:37 (5.98ms) python3 (5.98ms)

重みは、

w = np.random.randn(L, M)
print(w)

[2] 2019-06-12 20:09:37 (31.3ms) python3 (37.3ms)

[[ 0.3342486  -1.23969602 -0.71566716]
 [-0.94152091 -0.666604    1.02151093]
 [ 0.76440342 -0.66294955  0.23458379]
 [-0.87572989 -0.09520326 -0.14725043]
 [ 1.44295605  1.82698823 -0.52967815]]

入力は、

x = np.random.randint(0, L, (N, I))
print(x)

[3] 2019-06-12 20:09:37 (15.6ms) python3 (52.9ms)

[[0 1 0]
 [4 3 4]]

各入力に対する出力は、

print(w[x.T])

[4] 2019-06-12 20:09:37 (11.0ms) python3 (63.9ms)

[[[ 0.3342486  -1.23969602 -0.71566716]
  [ 1.44295605  1.82698823 -0.52967815]]

 [[-0.94152091 -0.666604    1.02151093]
  [-0.87572989 -0.09520326 -0.14725043]]

 [[ 0.3342486  -1.23969602 -0.71566716]
  [ 1.44295605  1.82698823 -0.52967815]]]

となり、複数の入力に対する出力を平均化するので、

print(w[x.T].sum(axis=0) / I)

[5] 2019-06-12 20:09:37 (7.01ms) python3 (70.9ms)

[[-0.0910079  -1.04866535 -0.1366078 ]
 [ 0.67006074  1.18625773 -0.40220225]]

となりますが、同じことは、

print(w[x].sum(axis=1) / I)

[6] 2019-06-12 20:09:37 (5.95ms) python3 (76.9ms)

[[-0.0910079  -1.04866535 -0.1366078 ]
 [ 0.67006074  1.18625773 -0.40220225]]

でもできます。以上が順伝搬になります。ここまでを実装します。

from ivory.core.layer import Layer

class EmbeddingMean(Layer):
    def init(self):
        self.W = self.add_weight(self.shape[1:]).randn()

    def forward(self):
        self.y.d = self.W.d[self.x.d].sum(axis=1) / self.shape[0]

[7] 2019-06-12 20:09:37 (185ms) python3 (261ms)

すでにあるMatMulMeanレイヤと比較します。

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

N, I, L, M = 20, 10, 4, 3
net_mat = [("input", I, L), ("matmulmean", M, "softmax_cross_entropy")]
model_mat = sequential(net_mat)
net_em = [("input", I, L), ("embeddingmean", M, "softmax_cross_entropy")]
model_em = sequential(net_em)

[8] 2019-06-12 20:09:37 (11.0ms) python3 (272ms)

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

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

[9] 2019-06-12 20:09:37 (21.0ms) python3 (294ms)

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

model_mat.set_data(convert_one_hot(x, L), t)

[10] 2019-06-12 20:09:37 (15.6ms) python3 (309ms)

Embeddingレイヤへは、そのまま入力します。

model_em.set_data(x, t)

[11] 2019-06-12 20:09:37 (15.7ms) python3 (325ms)

両者を比較するために重みを同じ値に設定します。

mat, em = model_mat.layers[0], model_em.layers[0]
em.share_weight_variables(mat)

[12] 2019-06-12 20:09:37 (9.06ms) python3 (334ms)

[<Variable(['MatMulMean.1.W', 'EmbeddingMean.1.W'], (4, 3)) at 0x1c3413b49b0>]

順伝搬を比較します。

model_mat.forward()
model_em.forward()
model_mat.loss, model_em.loss

[13] 2019-06-12 20:09:37 (14.1ms) python3 (348ms)

(1.1459859498179688, 1.1459858894348145)

次に逆伝搬を実装します。ここでも勾配確認のテクニックを使います。数値微分による勾配を求めます。

model_em.numerical_gradient(em.W.variable)

[14] 2019-06-12 20:09:38 (10.3ms) python3 (358ms)

array([[ 0.06580353, -0.04482269, -0.02145767],
       [ 0.02384186, -0.00762939, -0.01573563],
       [ 0.02527237, -0.01049042, -0.01382828],
       [ 0.04720688, -0.02098083, -0.02574921]], dtype=float32)

EmbeddingMeanレイヤの出力の勾配を求めます。

try:
    model_em.backward()
except AttributeError:  # まだ、backwardメソッドを定義していないため
    pass

[15] 2019-06-12 20:09:38 (8.03ms) python3 (366ms)

Embeddingレイヤの逆伝搬と同じことをしますが、形状を合わせるために転置します。

grad = np.zeros_like(em.W.d)
np.add.at(grad, em.x.d.T, em.y.g)
grad /= em.shape[0]
grad

[16] 2019-06-12 20:09:38 (21.9ms) python3 (388ms)

array([[ 0.06625388, -0.04501684, -0.02123704],
       [ 0.02375701, -0.0081978 , -0.01555922],
       [ 0.02524898, -0.01056978, -0.01467921],
       [ 0.04702323, -0.02069013, -0.0263331 ]], dtype=float32)

一致が確認できました。最終的な実装は以下のようになります。

Code 2.14 EmbeddingMeanクラス

class EmbeddingMean(Layer):
    input_ndim = -2

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

    def forward(self):
        self.y.d = self.W.d[self.x.d].sum(axis=1) / self.shape[0]

    def backward(self):
        grad = np.zeros_like(self.W.d)
        np.scatter_add(grad, self.x.d.T, self.y.g)  # np.add.at
        grad /= self.shape[0]
        self.W.g = grad

MatMulMeanクラスの代わりに使ってみます。

N, I, L, M = 20, 10, 4, 3
net_mat = [("input", I, L), ("matmulmean", M, "softmax_cross_entropy")]
model_mat = sequential(net_mat)
net_em = [("input", I, L), ("embeddingmean", M, "softmax_cross_entropy")]
model_em = sequential(net_em)

[19] 2019-06-12 20:09:38 (14.0ms) python3 (457ms)

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

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

[20] 2019-06-12 20:09:38 (7.00ms) python3 (464ms)

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

model_mat.set_data(convert_one_hot(x, L), t)

[21] 2019-06-12 20:09:38 (7.02ms) python3 (471ms)

Embeddingレイヤへは、そのまま入力します。

model_em.set_data(x, t)

[22] 2019-06-12 20:09:38 (31.3ms) python3 (503ms)

両者を比較するために重みを同じ値に設定します。

mat, em = model_mat.layers[0], model_em.layers[0]
em.share_weight_variables(mat)

[23] 2019-06-12 20:09:38 (15.7ms) python3 (518ms)

[<Variable(['MatMulMean.2.W', 'EmbeddingMean.2.W'], (4, 3)) at 0x1c3413dd080>]

順伝搬を比較します。

model_mat.forward()
model_em.forward()
model_mat.loss, model_em.loss

[24] 2019-06-12 20:09:38 (8.99ms) python3 (527ms)

(1.2578249141870912, 1.2578248977661133)

逆伝搬を比較します。

model_mat.backward()
model_em.backward()
mat.W.g, em.W.g

[25] 2019-06-12 20:09:38 (8.05ms) python3 (535ms)

(array([[-0.06191752, -0.0654019 ,  0.12731942],
        [-0.01291479, -0.11174937,  0.12466416],
        [ 0.00272195, -0.113053  ,  0.11033104],
        [-0.00093432, -0.08884086,  0.08977518]]),
 array([[-0.06191752, -0.0654019 ,  0.12731942],
        [-0.01291479, -0.11174937,  0.12466416],
        [ 0.00272195, -0.113053  ,  0.11033104],
        [-0.00093432, -0.08884086,  0.08977518]]))

一致が確認できました。