5章誤差逆伝播法
いよいよ誤差逆伝播法まできた。
数式より計算グラフの方が理解しやすいらしい。
詳しくは本書参照。
まずはレイヤ概念の導入。
レイヤ(層)とは:NNにおける機能の単位を指す。レイヤ単位で実装することで、ブロックのように積み上げていける。
まず、乗算レイヤの実装と、加算レイヤの実装について。
# cat layer_naive.py # coding: utf-8 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): #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
つぎに、活性化関数レイヤ(ReLUレイヤ、Sigmoidレイヤ)の実装について。
# cat common/layers.py # coding: utf-8 import numpy as np from common.functions import * from common.util import im2col, col2im 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 class Sigmoid: def __init__(self): self.out = None def forward(self, x): out = sigmoid(x) self.out = out return out def backward(self, dout): dx = dout * (1.0 - self.out) * self.out return dx
つぎに、Affineレイヤの実装について。
Affineとは:行列の内積を行うレイヤ。幾何学で「アフィン変換」と呼ばれる。
class Affine: def __init__(self, W, b): self.W =W self.b = b self.x = None self.original_x_shape = None # 重み・バイアスパラメータの微分 self.dW = None self.db = None def forward(self, x): # テンソル対応 self.original_x_shape = x.shape x = x.reshape(x.shape[0], -1) self.x = x out = np.dot(self.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) dx = dx.reshape(*self.original_x_shape) # 入力データの形状に戻す(テンソル対応) return dx
つぎに、Softmaxレイヤの実装について。
Softmaxレイヤは学習時のみ必要で推論時には必要ない。
サンプルでは、Softmax関数と交差エントロピー誤差のレイヤSoftmax-with-Lossを定義している
導出過程が複雑なので、付録Aをあとで参照。
class SoftmaxWithLoss: def __init__(self): self.loss = None self.y = None # softmaxの出力 self.t = None # 教師データ 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] if self.t.size == self.y.size: # 教師データがone-hot-vectorの場合 dx = (self.y - self.t) / batch_size else: dx = self.y.copy() dx[np.arange(batch_size), self.t] -= 1 dx = dx / batch_size return dx
最後に、誤差逆伝播法の実装について。
以下の学習4ステップのうち、ステップ2勾配の算出で登場
学習の4ステップ
ステップ1ミニバッチ:ミニバッチをランダムに選択 ステップ2勾配算出:ミニバッチの損失関数を減らす勾配を算出 ステップ3パラメータ更新:重みパラメータを勾配方向に微小量だけ更新 ステップ4反復:ステップ1−3を反復
# cat two_layer_net.py # coding: utf-8 import sys, os sys.path.append(os.pardir) # 親ディレクトリのファイルをインポートするための設定 import numpy as np from common.layers import * from common.gradient import numerical_gradient from collections import OrderedDict class TwoLayerNet: def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01): # 重みの初期化 self.params = {} self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size) self.params['b1'] = np.zeros(hidden_size) self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) self.params['b2'] = np.zeros(output_size) # レイヤの生成 self.layers = OrderedDict() self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1']) self.layers['Relu1'] = Relu() self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2']) self.lastLayer = SoftmaxWithLoss() def predict(self, x): for layer in self.layers.values(): x = layer.forward(x) return x # x:入力データ, t:教師データ def loss(self, x, t): y = self.predict(x) return self.lastLayer.forward(y, t) def accuracy(self, x, t): y = self.predict(x) y = np.argmax(y, axis=1) if t.ndim != 1 : t = np.argmax(t, axis=1) accuracy = np.sum(y == t) / float(x.shape[0]) return accuracy # x:入力データ, t:教師データ def numerical_gradient(self, x, t): loss_W = lambda W: self.loss(x, t) grads = {} grads['W1'] = numerical_gradient(loss_W, self.params['W1']) grads['b1'] = numerical_gradient(loss_W, self.params['b1']) grads['W2'] = numerical_gradient(loss_W, self.params['W2']) grads['b2'] = numerical_gradient(loss_W, self.params['b2']) return grads def gradient(self, x, t): # forward self.loss(x, t) # backward dout = 1 dout = self.lastLayer.backward(dout) layers = list(self.layers.values()) layers.reverse() for layer in layers: dout = layer.backward(dout) # 設定 grads = {} grads['W1'], grads['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].db grads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db return grads
実装した誤差逆伝播法の勾配確認をおこなう。
勾配確認gradient checkとは:誤差逆伝播法と数値微分の結果を比較し、ほぼ近似できることで誤差逆伝播法の正しさを確認すること。
# cat deep-learning-from-scratch/ch05/gradient_check.py # coding: utf-8 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) x_batch = x_train[:3] t_batch = t_train[:3] grad_numerical = network.numerical_gradient(x_batch, t_batch) grad_backprop = network.gradient(x_batch, t_batch) for key in grad_numerical.keys(): diff = np.average( np.abs(grad_backprop[key] - grad_numerical[key]) ) print(key + ":" + str(diff))
勾配確認のサンプルコードを実行してみる:
# py gradient_check.py W2:9.77654477536e-13 W1:2.37797639143e-13 b2:1.2012613404e-10 b1:7.43019632366e-13
ほぼ誤差がないことが分かった。
そして、誤差逆伝播法を訓練データを学習させてみる。
# py train_neuralnet.py 0.102266666667 0.1016 0.90125 0.9073 0.9205 0.9221 0.936033333333 0.9349 0.945816666667 0.9461 0.9512 0.9504 0.957433333333 0.955 0.961133333333 0.959 0.965266666667 0.962 0.967566666667 0.9636 0.970366666667 0.9637 0.97065 0.9635 0.974466666667 0.9674 0.974416666667 0.9671 0.976816666667 0.968 0.97775 0.9698 0.978783333333 0.971
認識精度がかなり向上していることがわかる。
5章まとめ問題
・◯◯1を用いれば計算過程を視覚的に把握できる ・◯◯2は局所的な計算によって全体の計算を構成する ・◯◯1の◯◯3は通常の計算を行い、◯◯4によって各ノードの微分が求まる ・◯◯5とは、NNの構成要素を◯◯6で実装することで勾配計算を効率化する ・◯◯6とは、◯◯5と◯◯7の結果を比較して◯◯5の実装が誤っていないことが確認できる