2.4 Affine

2.4.1 定式化

Affineレイヤの入力と出力の関係は以下のようになります。まずは、時系列データでない場合を考えます。

\mathbf{Y} = \mathbf{X}\cdot\mathbf{W} + \mathbf{B}

ここでバッチ数がN、入力ノード数がn、出力ノード数がmのとき、各行列の次元は次のようになります。

\mathbf{Y}: (N, m),\ \mathbf{X}: (N, n),\ \mathbf{W}: (n, m),\ \mathbf{B}: (m,)

具体的に、N=3, n=2, m=4 の時を書き下します。

\left[\begin{matrix}y_{11} & y_{12} & y_{13} & y_{14} \\ y_{21} & y_{22} & y_{23} & y_{24} \\ y_{31} & y_{32} & y_{33} & y_{34}\end{matrix}\right]=\left[\begin{matrix}x_{11} & x_{12} \\ x_{21} & x_{22} \\ x_{31} & x_{32}\end{matrix}\right]\cdot\left[\begin{matrix}w_{11} & w_{12} & w_{13} & w_{14} \\ w_{21} & w_{22} & w_{23} & w_{24}\end{matrix}\right] \\ +\left[\begin{matrix}1\\1\\1\end{matrix}\right]\cdot\left[\begin{matrix}b_{1} & b_{2} & b_{3} & b_{4}\end{matrix}\right]

ここでは、 \partial L/\partial \mathbf{X} を導出します。\partial L/\partial \mathbf{W}\partial L/\partial \mathbf{B}も同様です。

スカラー関数が分子で、ベクトル・行列が分母のとき、その微分の次元は分母のそれに等しくなります。例えば、入力のバッチ数が3、入力のノード数が2のとき、

\frac{\partial L}{\partial \mathbf{X}} = \left[\begin{matrix}\partial L/\partial x_{11} & \partial L/\partial x_{12} \\ \partial L/\partial x_{21} & \partial L/\partial x_{22} \\ \partial L/\partial x_{31} & \partial L/\partial x_{32}\end{matrix}\right]

となります。連鎖率、

\frac{\partial L}{\partial x_{ij}} = \sum_{i'j'}\frac{\partial L}{\partial y_{i'j'}} \frac{\partial y_{i'j'}}{\partial x_{ij}}

において、

\frac{\partial y_{i'j'}}{\partial x_{ij}} = \delta_{ii'}w_{jj'}

より、

\frac{\partial L}{\partial x_{ij}} = \sum_{j'}\frac{\partial L}{\partial y_{ij'}} w_{jj'} \rightarrow \frac{\partial L}{\partial \mathbf{X}} = \frac{\partial L}{\partial \mathbf{Y}}\cdot \mathbf{W}^\mathrm{T}

となります。他もまとめると、

\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}}
\frac{\partial L}{\partial \mathbf{B}} = \mathbf{1}\cdot\frac{\partial L}{\partial \mathbf{Y}}

形状を確認しておきます。時系列データでない場合、

N, L, M = 2, 3, 4

[37] 2019-06-12 20:01:03 (15.7ms) python3 (656ms)

パラメータ 形状 具体例
\mathbf{X} (N, L) (2,3)
\mathbf{W} (L, M) (3, 4)
\mathbf{B} (M,) (4,)
\mathbf{Y} (N, M) (2, 4)

勾配確認を行っていきます。損失パラメータのgrad_variablesは勾配が計算された変数を返すイタレータです。

import numpy as np
from ivory.core.model import sequential

net = [("input", L), ("affine", M, "softmax_cross_entropy")]
model = sequential(net)

[49] 2019-06-12 20:01:03 (15.6ms) python3 (823ms)

x = np.random.randn(N, L)
t = np.random.randint(0, M, N)
model.set_data(x, t)
model.forward()
model.backward()

[50] 2019-06-12 20:01:03 (15.6ms) python3 (839ms)

for v in model.grad_variables:
    print(v, model.gradient_error(v))
    print(v.grad)
    print(model.numerical_gradient(v))

[51] 2019-06-12 20:01:03 (24.1ms) python3 (863ms)

<Variable(['Affine.1.x'], (3,)) at 0x16e3255fcc0> 1.0581289936380707e-06
[[-0.92125754  0.42084399  1.05086333]
 [ 0.31468884  0.64493255  0.48903929]]
[[-0.92125566  0.42084313  1.05086119]
 [ 0.31468852  0.6449319   0.48903879]]
<Variable(['Affine.1.W'], (3, 4)) at 0x16e3255fb38> 1.6137863323636085e-05
[[-0.01774635 -0.02255213 -0.01391566  0.05421414]
 [-0.34253322  0.26866443  0.30600875 -0.23213997]
 [ 0.01108177  0.14966886  0.11936157 -0.28011219]]
[[-0.01774398 -0.02255246 -0.01391586  0.05422303]
 [-0.34248766  0.26866856  0.3059679  -0.23214336]
 [ 0.01108027  0.14969338  0.11934556 -0.2801581 ]]
<Variable(['Affine.1.b'], (4,)) at 0x16e3255f828> 6.31001326967251e-07
[-0.416861    0.38937673  0.42335592 -0.39587165]
[-0.4168606   0.38937607  0.4233553  -0.39587077]

2.4.2 時系列データの場合

形状を確認おきます。

N, T, L, M = 2, 3, 4, 5

[52] 2019-06-12 20:01:03 (11.0ms) python3 (874ms)

パラメータ 形状 具体例
\mathbf{X} (N, T, L) (2,3,4)
\mathbf{W} (L, M) (4, 5)
\mathbf{B} (M,) (5,)
\mathbf{Y} (N, T, M) (2,3, 5)
net = [("input", L), ("affine", M, "softmax_cross_entropy")]
model = sequential(net)
affine = model.layers[0]

[66] 2019-06-12 20:01:04 (8.04ms) python3 (1.03s)

時系列データの場合でも順伝搬ではこれまで通りです。

x = np.random.randn(N, T, L)
t = np.random.randint(0, M, (N, T))
model.set_data(x, t)
model.forward()
print(affine.y.d)

[67] 2019-06-12 20:01:04 (9.96ms) python3 (1.04s)

[[[-0.24057545  1.00166258  0.97453114 -0.88146599 -1.1743355 ]
  [-1.82209629 -0.05938507 -0.40170759  0.43994177 -0.79361739]
  [-0.79977642 -0.28900191  0.99518383 -0.66953857  0.69327197]]

 [[-2.26315687 -0.59638026 -0.96684275 -0.23594729  0.03077316]
  [-1.26589848  0.73713281 -0.4751409   1.62627378 -2.3517955 ]
  [-2.09421515 -0.54846191  0.27859446 -2.41008939  1.09818244]]]

確かめてみます。

print(x[:, 0] @ affine.W.d)
print(x[:, 1] @ affine.W.d)

[68] 2019-06-12 20:01:04 (15.6ms) python3 (1.06s)

[[-0.24057545  1.00166258  0.97453114 -0.88146599 -1.1743355 ]
 [-2.26315687 -0.59638026 -0.96684275 -0.23594729  0.03077316]]
[[-1.82209629 -0.05938507 -0.40170759  0.43994177 -0.79361739]
 [-1.26589848  0.73713281 -0.4751409   1.62627378 -2.3517955 ]]

逆伝搬を確かめるために、数値微分してみます。入力\mathbf{X}です。

print(model.numerical_gradient(affine.x.variable))

[69] 2019-06-12 20:01:04 (31.3ms) python3 (1.09s)

[[[-0.01812351  0.05977721 -0.05403869 -0.02143319]
  [-0.10241967 -0.09583436 -0.05405765 -0.09689515]
  [ 0.11068883  0.14719911  0.1909003   0.08003594]]

 [[ 0.19389327 -0.0579725  -0.13100485 -0.107474  ]
  [ 0.15650449 -0.13002491 -0.1889938  -0.08542201]
  [ 0.22985102 -0.00084506 -0.09140494 -0.11540397]]]

これは、これまでの式が使えます。

model.backward()
dy = affine.y.g
print(dy @ affine.W.d.T)

[70] 2019-06-12 20:01:04 (12.0ms) python3 (1.10s)

[[[-0.01812351  0.05977722 -0.05403871 -0.02143319]
  [-0.10241973 -0.09583441 -0.05405769 -0.09689521]
  [ 0.11068897  0.14719929  0.19090054  0.08003604]]

 [[ 0.1938938  -0.05797266 -0.13100521 -0.1074743 ]
  [ 0.15650494 -0.13002528 -0.18899435 -0.08542226]
  [ 0.22985063 -0.00084345 -0.09140662 -0.11541168]]]

重みに関しては、前述の式通りでは計算ができません。

print(model.numerical_gradient(affine.W.variable))

[71] 2019-06-12 20:01:04 (10.0ms) python3 (1.11s)

[[-0.41649798  0.04926323  0.10067122 -0.01909558  0.285599  ]
 [ 0.33644694 -0.10479273  0.21575005 -0.54295784  0.09545711]
 [-0.02451767 -0.04936397  0.05673707 -0.03463425  0.05177367]
 [ 0.18390924 -0.1403182   0.03770662  0.07056691 -0.15185405]]

同じ重みが複数回使われるので、勾配は加算されます。確かめてみます。

print(sum(x[:, k].T @ dy[:, k] for k in range(T)))

[72] 2019-06-12 20:01:04 (8.02ms) python3 (1.12s)

[[-0.41643095  0.04926097  0.10066389 -0.01909799  0.28560408]
 [ 0.3364918  -0.10479292  0.21577486 -0.5429497   0.09547597]
 [-0.02451768 -0.04936434  0.05673355 -0.03462861  0.05177708]
 [ 0.18393448 -0.14033519  0.03771515  0.07056586 -0.15188029]]

形状確認をすることによって、次式が正しい値を与えることが分かります。

print(np.tensordot(x, dy, axes=[(0, 1), (0, 1)]))

[73] 2019-06-12 20:01:04 (15.8ms) python3 (1.13s)

[[-0.41643095  0.04926097  0.10066389 -0.01909799  0.28560408]
 [ 0.3364918  -0.10479292  0.21577486 -0.5429497   0.09547597]
 [-0.02451768 -0.04936434  0.05673355 -0.03462861  0.05177708]
 [ 0.18393448 -0.14033519  0.03771515  0.07056586 -0.15188029]]

バイアスについても確認します。

print(model.numerical_gradient(affine.b.variable))
print(dy.sum(axis=(0, 1)))

[74] 2019-06-12 20:01:04 (15.7ms) python3 (1.15s)

[-0.4463153   0.0538999   0.07525906  0.07785531  0.23930101]
[-0.44631712  0.05389869  0.07525582  0.07785565  0.23930697]

実際にレイヤを使って確かめてみます。

for v in model.grad_variables:
    print(v, model.gradient_error(v))
    print(v.grad)
    print(model.numerical_gradient(v))

[75] 2019-06-12 20:01:04 (37.8ms) python3 (1.19s)

<Variable(['Affine.2.x'], (4,)) at 0x16e325811d0> 6.360969708808093e-07
[[[-0.01812351  0.05977722 -0.05403871 -0.02143319]
  [-0.10241973 -0.09583441 -0.05405769 -0.09689521]
  [ 0.11068897  0.14719929  0.19090054  0.08003604]]

 [[ 0.1938938  -0.05797266 -0.13100521 -0.1074743 ]
  [ 0.15650494 -0.13002528 -0.18899435 -0.08542226]
  [ 0.22985063 -0.00084345 -0.09140662 -0.11541168]]]
[[[-0.01812351  0.05977721 -0.05403869 -0.02143319]
  [-0.10241967 -0.09583436 -0.05405765 -0.09689515]
  [ 0.11068883  0.14719911  0.1909003   0.08003594]]

 [[ 0.19389327 -0.0579725  -0.13100485 -0.107474  ]
  [ 0.15650449 -0.13002491 -0.1889938  -0.08542201]
  [ 0.22985102 -0.00084506 -0.09140494 -0.11540397]]]
<Variable(['Affine.2.W'], (4, 5)) at 0x16e32581278> 1.359897883850235e-05
[[-0.41643095  0.04926097  0.10066389 -0.01909799  0.28560408]
 [ 0.3364918  -0.10479292  0.21577486 -0.5429497   0.09547597]
 [-0.02451768 -0.04936434  0.05673355 -0.03462861  0.05177708]
 [ 0.18393448 -0.14033519  0.03771515  0.07056586 -0.15188029]]
[[-0.41649798  0.04926323  0.10067122 -0.01909558  0.285599  ]
 [ 0.33644694 -0.10479273  0.21575005 -0.54295784  0.09545711]
 [-0.02451767 -0.04936397  0.05673707 -0.03463425  0.05177367]
 [ 0.18390924 -0.1403182   0.03770662  0.07056691 -0.15185405]]
<Variable(['Affine.2.b'], (5,)) at 0x16e325812b0> 2.5175342109662323e-06
[-0.44631712  0.05389869  0.07525582  0.07785565  0.23930697]
[-0.4463153   0.0538999   0.07525906  0.07785531  0.23930101]

実装コードの確認しておきます。

Code 2.7 Affineクラス

class Affine(Layer):
    def init(self):
        self.W = self.add_weight(self.shape).randn()
        self.b = self.add_weight(self.shape[-1:]).zeros()
        self.weight_decay = self.add_state(0)

    def forward(self):
        self.y.d = self.x.d @ self.W.d + self.b.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)
        dW = np.tensordot(self.x.d, self.y.g, axes=[axis, axis])
        self.W.g = dW + self.weight_decay.d * self.W.d
        self.b.g = self.y.g.sum(axis=axis)