誤差逆伝播法

計算グラフからの逆伝播

$y = f(x)$ という計算がある。逆方向には $E$ が入力されると $\displaystyle E \frac{\partial y}{\partial x}$ が出力される。 たとえば $y = f(x) = x^2$ であれば、 $\displaystyle \frac{\partial y}{\partial x} = 2x$ となる。

$\quad z = t^2\\ \quad t = x + y \quad\quad\quad (5.1)$
のように2つの式で表現することができる。

$\displaystyle \frac{\partial z}{\partial x} = \frac{\partial z}{\partial t} \frac{\partial t}{\partial x} \\ \quad\quad\quad = 2t \times 1 \\ \quad\quad\quad = 2(x+y) \quad\quad\quad\quad (5.4)$

 layer_naive.py class MulLayer: def __init__(self): self.x = None self.y = None def forward(self, x, y): self.x = x self.y = y out = x * y return out def backward(self, dout): dx = dout * self.y dy = dout * self.x return dx, dy class AddLayer: def __init__(self): pass def forward(self, x, y): out = x + y return out def backward(self, dout): dx = dout * 1 dy = dout * 1 return dx, dy
 buy_apple.py apple = 100 apple_num = 2 tax = 1.1 mul_apple_layer = MulLayer() mul_tax_layer = MulLayer() # # forward # apple_price = mul_apple_layer.forward(apple, apple_num) # 200 price = mul_tax_layer.forward(apple_price, tax) # 220 print(price) # 220 # #backward # dprice = 1 dapple_price, dtax = mul_tax_layer.backward(dprice) dapple, dapple_num = mul_apple_layer.backward(dapple_price) print(dapple, dapple_num, dtax) # 2.2 110 200

ReLUレイヤ

$\displaystyle $$y = \left\{ \begin{matrix} x && (x \gt 0) \\ 0 && (x \leqq 0) \end{matrix} \right. \quad\quad\quad\quad (5.7)$$$
$\displaystyle $$\frac{\partial y}{\partial x} = \left\{ \begin{matrix} 1 && (x \gt 0) \\ 0 && (x \leqq 0) \end{matrix} \right. \quad\quad\quad\quad (5.8)$$$

 relu.py class Relu: def __init__(self): self.mask = None def forward(self, x): self.mask = (x <= 0) out = x.copy() out[self.mask] = 0 return out def backward(self, dout): dout[self.mask] = 0 dx = dout return dx # # example # x = np.array([[1.0, -0.5], [-2.0, 3.0]]) mask = (x <= 0) print(mask) # [[False, True], # [True, False]] out = x.copy() out[mask] = 0 print(out) # [[1.0, 0.0], # [0.0, 3.0]]

sigmoidレイヤ

sigmoid関数は次の通り。

$\displaystyle h(x) = \frac{1}{1+ e^{-x}} \quad\quad\quad\quad (5.9)$

この関数を数ステップに分解して計算の伝播を考える。

$\displaystyle p = -x \\ q = e^p \\ r = 1 + q \\ y = \frac{1}{r} \\$
1. ステップ1: $\displaystyle y = \frac{1}{r}$

2. $\displaystyle \frac{\partial y}{\partial r} = - \frac{1}{r^2} \\ \quad\quad = -y^2 \quad\quad\quad (5.10)$

式 (5.10) より、逆伝播のときは、上流の値に対して $-y^2$ を乗算して下流へ伝播する。

3. ステップ2: $\displaystyle r = 1 + q$

4. $\displaystyle r = 1 + q \\ \frac{\partial r}{\partial q} = 1$

したがって、上流の値をそのまま下流に流せばよい。

5. ステップ3: $\displaystyle q = e^p$

6. $\displaystyle \frac{\partial q}{\partial p} = e^p$

上流の値に $e^p = e^{-x}$ を乗算して下流に流す。

7. ステップ4: $\displaystyle p = -x$

8. $\displaystyle \frac{\partial p}{\partial x} = -1$

上流の値に $-1$ を乗算して下流に流す。

$\displaystyle \frac{\partial y}{\partial x} = \frac{\partial y}{\partial r} \frac{\partial r}{\partial q} \frac{\partial q}{\partial p} \frac{\partial p}{\partial x} = (-y^2) \cdot 1 \cdot e^p \cdot (-1) = y^2 e^{-x} \\ = y^2 (\frac{1}{y} - 1) \quad\quad (\because y = \frac{1}{1+e^{-x}} より e^{-x} = \frac{1}{y}-1) \\ = y (1 - y)$

[自分へのメモ] 単に合成関数の微分の説明をしているだけ。

 sigmoid.py class Sigmoid: def __init__(self): self.out = None def forward(self, x): out = 1 / (1 + np.exp(-x)) self.out = out return out def backward(self, dout): dx = dout * (1.0 - self.out) * self.out return dx

Affineレイヤ

$\displaystyle Y = X \cdot W + B \\ \displaystyle \frac{\partial L}{\partial X} = \frac{\partial L}{\partial Y} \cdot W^T \\ \displaystyle \frac{\partial L}{\partial W} = X^T \cdot \frac{\partial L}{\partial Y} \quad\quad\quad (5.13)$

$L$ はスカラーであり、$X$ や $W$ は行列である。 $\displaystyle \frac{\partial L}{\partial X}$ とは、 スカラー $L$ を行列 $X$ の各要素 $x_i$ で微分して、元の行列 $X$ と同じ形に並べたものである。

$\displaystyle X = (x_0, x_1, \cdots, x_n ) \\ \displaystyle \frac{\partial L}{\partial X} = (\frac{\partial L}{\partial x_0}, \frac{\partial L}{\partial x_1}, \cdots, \frac{\partial L}{\partial x_n} ) \quad\quad\quad (5.15)$

バッチ版 Affine レイヤ

 affine_batch.py class Affine: def __init__(self, W, b): self.W = w self.b = b self.x = None self.dW = None self.db = None def forward(self, x): self.x = x out = np.dot(x, self.W) + self.b return out def backward(self, dout): dx = np.dot(dout, self.W.T) self.dW = np.dot(self.x.T, dout) self.db = np.sum(dout, axis=0) return dx # # sample # X_dot_W = np.array([[0, 0, 0], [10, 10, 10]]) B = np.arary([1, 2, 3]) pritn(X_dot_W + B) #[[ 1, 2, 3 ], # [ 11, 12, 13 ]] dY = np.array([[1, 2, 3], [4, 5, 6]]) dB = np.sum(dY, axis=0) print(dB) #[ 5, 7, 9 ]

Softmax-with-Loss レイヤ

ソフトマックス関数は、入力された値を正規化して出力する。

$\displaystyle \left( \begin{matrix} a_1 \\ a_2 \\ a_3 \end{matrix} \right) \rightarrow Softmax \rightarrow \displaystyle \left( \begin{matrix} y_1 \\ y_2 \\ y_3 \end{matrix} \right) + \left( \begin{matrix} t_1 \\ t_2 \\ t_3 \end{matrix} \right) \rightarrow \begin{matrix} Cross \\ Entropy \\ Error \end{matrix} \rightarrow L$

このとき、逆伝播は次のようになる。

$\displaystyle \left( \begin{matrix} y_1 - t_1 \\ y_2 - t_2 \\ y_3 - t_3 \end{matrix} \right) \leftarrow Softmax \leftarrow \begin{matrix} Cross \\ Entropy \\ Error \end{matrix} \leftarrow l$
 softmax_with_loss.py class SoftmaxWithLoss: def __init__(self): self.loss = None self.y = None # output self.t = None # training data (one-hot vector) def forward(self, x, t): self.t = t self.y = softmax(x) self.loss = cross_entropy_error(self.y, self.t) return self.loss def backward(self, dout=1): batch_size = self.t.shape[0] dx = (self.y - self.t) / batch_size return dx

誤差逆伝播法の実装

2層のニューラルネットワークを実装する。

OrderedDict は順番付きの辞書であり、順伝播では追加した順にレイヤのforward()関数を呼び出し、 逆伝播では追加した逆順にレイヤのbackward()関数を呼び出せばよい。

誤差逆伝播法を使った学習

 train_neuralnet.py import sys, os sys.path.append(os.pardir) import numpy as np from dataset.mnist import load_mnist from two_layer_net import TwoLayerNet (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True) network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10) iters_num = 10000 train_size = x_train.shape[0] batch_size = 100 learning_rate = 0.1 train_loss_list = [] train_acc_list = [] test_acc_list = [] iter_per_epoch = max(train_size / batch_size, 1) for i in range(iters_num): batch_mask = np.random.choice(train_size, batch_size) x_batch = x_train[batch_mask] t_batch = t_train[batch_mask] # gradient grad = network.gradient(x_batch, t_batch) # update for key in ('W1', 'b1', 'W2', 'b2'): network.params[key] -= learning_rate * grad[key] loss = network.loss(x_batch, t_batch) train_loss_list.append(loss) if i % iter_per_epoch == 0: train_acc = network.accuracy(x_train, t_train) test_acc = network.accuracy(x_test, t_test) train_acc_list.append(train_acc) test_acc_list.append(test_acc) print(train_acc, test_acc)

Yoshihisa Nitta

http://nw.tsuda.ac.jp/