1.はじめに
名著、「ゼロから作るDeep Learning」を読んでいます。今回は第5章のメモ。
コードの実行はGithubからコード全体をダウンロードし、ch05の中で jupyter notebook にて行っています。
2.誤差逆伝播の実装(レイヤー生成なし)
第5章の最後に、レイヤーを生成して誤差逆伝播を行うコード( twoLayerNet.py & train_neuralnet.py )がありますが、その前にレイヤーを生成せず誤差伝播を行うコードが既に第4章にある(同じく twoLayerNet.py & train_neuralnet.py )ので、そちらからやってみます。
第4章でやった様に、見通しを良くするために、1つのコードにまとめてみると、前回とほとんど同じで、--- 勾配計算 --- のところが異なるだけです。とりあえず、動かしてみます。
import sys, os
sys.path.append(os.pardir) # 親ディレクトリのファイルをインポートするための設定
from common.functions import * # common フォルダーの function.py にある関数を全て使えるように設定
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
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)
# 順伝播
def predict(self, x):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
return y
# ロス計算
def loss(self, x, t):
y = self.predict(x)
return cross_entropy_error(y, t)
# 精度計算
def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(y, axis=1)
t = np.argmax(t, axis=1)
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
# ----------------- 勾配計算 -------------------
def gradient(self, x, t):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
grads = {}
batch_num = x.shape[0]
# forward
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
# backward
dy = (y - t) / batch_num
grads['W2'] = np.dot(z1.T, dy)
grads['b2'] = np.sum(dy, axis=0)
dz1 = np.dot(dy, W2.T)
da1 = sigmoid_grad(a1) * dz1
grads['W1'] = np.dot(x.T, da1)
grads['b1'] = np.sum(da1, axis=0)
return grads
# ------------------------------------------------
# データの読み込み
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
# TwoLayerNet をインスタンス化
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]
# 勾配の計算
grad = network.gradient(x_batch, t_batch)
# パラメータの更新
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 | " + str(train_acc) + ", " + str(test_acc))
# グラフの描画
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
素晴らしい!数値微分と比べると、実行速度が天と地ほども違います。激早です!(というか、数値微分が激遅か)
コードの内容は、--- 勾配計算 --- 以外は、第4章メモで説明済みですので、それをご覧になりたい方は第4章メモをご参照下さい。この章では、勾配計算の部分のみ説明します。
3.勾配計算
勾配計算のコードの一部を計算グラフで説明すると、こんな感じです。
grads['b2'] = np.sum(dy, axis=0)
とsumを取っているのは、バッチ処理への対応です。それにしても、一見複雑に思える誤差逆伝播が行列演算に置き換えられることに感動を覚えますね。
4.誤差逆伝播式の導出
まず、$\frac{\partial L}{\partial W2}=z1^{T}*dy$ を導出します。連鎖率によって、
次に、$\frac{\partial y}{\partial Z1}=\frac{\partial y}{\partial a2}W2^T$ を導出します。
数値微分の世界から行列演算による誤差逆伝播の世界への転換は正に革新的なことですよね。
5.レイヤーの生成
先程の様に、順伝播・誤差逆伝播とも行列演算式をダイレクトにコードに書く方法でも実用的な実行速度が得られますが、コードを書くのがちょっと面倒ですよね。そこで、レイヤーを生成して、もっと簡略にコードを記載する方法があります。
レイヤーを生成するためには、まず from collections import OrderedDict
で、 OrderedDictをインポートします。
class TwoLayerNet:
def __init__(self, input_size, hidden_size, 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()
クラスTwoLayerNetの初期設定時に、self.layers = OrderedDict()
で OrderedDictをインスタンス化します。
OrderedDict は順番を含めて覚えるので、辞書 self.layers
に、Affine1
, Relu1
,Affine2
とレイヤー名と処理を順次登録すると、その順番も含めて記憶します。
最終層だけは、誤差逆伝播式が異なるので、self.lastLayer
として別に扱います。
# 順伝播
def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
# 順伝播・ロス計算
def loss(self, x, t):
y = self.predict(x)
return self.lastLayer.forward(y, t)
そうすると、順伝播 predict
は、辞書 self.layers
からレイヤー名を順番に1つづつ読み出して、forward
処理を実行することを繰り返すだけなので、レイヤーの数がいくつであろうと、このコードだけで済みます。
順伝播・ロス計算 loss
も、順伝播して self.lastLayer.forward(y, t)
で最終層の forward
処理をするだけなので、最終層の処理内容が変わっても、このコードのままです。
# 勾配計算
def gradient(self, x, t):
# 順伝播・ロス計算
self.loss(x, t)
# 誤差逆伝播
dout = 1
dout = self.lastLayer.backward(dout) # 最終層のbackwardの結果をdoutへ代入
layers = list(self.layers.values()) # 辞書self.layersからレイヤー名を読む込む
layers.reverse() # レイヤー名の順番を逆転
for layer in layers: # 逆転したレイヤー名を読み込む
dout = layer.backward(dout) # レイヤー名のbackwardを実行
# ..................
return grads
そして、勾配計算も同様に、レイヤーの組み合わせが変わっても同じで済みます。これは便利ですよね。
それでは、レイヤー生成ありのコードを実行してみましょう。
6.誤差逆伝播の実装(レイヤー生成あり)
先程と同様、コードの見通しを良くするために、twoLayerNet.py
と train_neuralnet.py
を合体した形にします。また、グラフの描画部分を追加しています。
import sys, os
sys.path.append(os.pardir) # 親ディレクトリのファイルをインポートするための設定
from common.layers import * # common フォルダーのlayers.py にある関数を全て使えるように設定
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from collections import OrderedDict # 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
# 順伝播・ロス計算
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
# 勾配計算
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
# データの読み込み
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
# TwoLayerNetをインスタンス化
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]
# 勾配の計算
grad = network.gradient(x_batch, t_batch)
# パラメータの更新
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)
# グラフの描画
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epoch")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
先程のレイヤー生成無しの場合と比べ精度が+3ポイントほど改善しています。この理由は、もちろんレイヤー生成したからではありません。活性化関数が sigmoid
から ReLU
に変更されたためです。