前回までで誤差逆伝播法については理解できたと思うのでこれを実装していきましょう。
前回までのまとめ
- 誤差逆伝播法とは微分を効率的に計算できる手法である
- 計算グラフによって可視化するとわかりやすい
- 途中の計算結果を共有することで効率化がなされる
誤差逆伝播法の実装
今回までのをベースにしつつ少し変更箇所があるので一から書いていきましょう。
まずデータセットの準備。
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 1次元に変換
x_train_fla=np.array([x.flatten() for x in x_train])
x_test_fla=np.array([x.flatten() for x in x_test])
# 0~1へ変換
x_train_fla_norm=x_train_fla/255.0
x_test_fla_norm=x_test_fla/255.0
# One-Hot
num_classes = 10
y_train_one_hot = np.eye(num_classes)[y_train]
y_test_one_hot = np.eye(num_classes)[y_test]
今回からレイヤという概念を導入します。
これがあることで途中の計算結果を保持できるようにします。
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
Sigmoidレイヤ
活性化関数のSigmoid関数をレイヤにしたものです。
def sigmoid(x):
return 1 / (1 + np.exp(-x))
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
Softmaxと損失関数のレイヤ
出力層のSoftmaxと損失関数を合わせたレイヤです。
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
if t.size == y.size:
t = t.argmax(axis=1)
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
def softmax(x):
x = x - np.max(x, axis=-1, keepdims=True)
return np.exp(x) / np.sum(np.exp(x), axis=-1, keepdims=True)
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
レイヤに対応したニューラルネットワーク
from collections import OrderedDict
def init_layer_network(input_size, hidden_size, output_size, weight_init_std=0.01):
# 重みの初期化
network={}
network['params']={}
network['params']['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
network['params']['b1'] = np.zeros(hidden_size)
network['params']['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
network['params']['b2'] = np.zeros(output_size)
# レイヤの生成
network['layers'] = OrderedDict()
network['layers']['Affine1'] = Affine(network['params']['W1'], network['params']['b1'])
network['layers']['Sigmoid1'] = Sigmoid()
network['layers']['Affine2'] = Affine(network['params']['W2'], network['params']['b2'])
network['lastLayer'] = SoftmaxWithLoss()
return network
def predict(layer_network, x):
for layer in layer_network['layers'].values():
x = layer.forward(x)
return x
def loss(layer_network, x, t):
y = predict(layer_network, x)
return layer_network['lastLayer'].forward(y, t)
def gradient(layer_network, x, t):
# forward
loss(layer_network, x, t)
# backward
dout = 1
dout = layer_network['lastLayer'].backward(dout)
layers = list(layer_network['layers'].values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
# 設定
grads = {}
grads['W1'], grads['b1'] = layer_network['layers']['Affine1'].dW, layer_network['layers']['Affine1'].db
grads['W2'], grads['b2'] = layer_network['layers']['Affine2'].dW, layer_network['layers']['Affine2'].db
return grads
def accuracy(layer_network, x, t):
y = predict(layer_network,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
実際に学習させていきましょう。
layer_network=init_layer_network(784, 50, 10, weight_init_std=0.01)
iters_num = 100 # 繰り返しの回数を適宜設定する
train_size = x_train.shape[0]
batch_size = 10
learning_rate = 0.5
train_loss_list = []
train_acc_list = []
test_acc_list = []
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train_fla_norm[batch_mask]
y_batch = y_train_one_hot[batch_mask]
# 勾配
grad = gradient(layer_network, x_batch, y_batch)
# 更新
for key in ('W1', 'b1', 'W2', 'b2'):
layer_network['params'][key] -= learning_rate * grad[key]
loss_ = loss(layer_network, x_batch, y_batch)
train_loss_list.append(loss_)
train_acc = accuracy(layer_network, x_train_fla_norm, y_train_one_hot)
test_acc = accuracy(layer_network,x_test_fla_norm, y_test_one_hot)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
print(train_acc,test_acc)
めっちゃ早くなってる!
1分くらいしかかからなかった。
でも学習の精度は落ちていますね。
学習率も少し上げないとうまく学習されませんでした。(なぜ?)
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()
Reluにしてみると精度も変わらなかった。
Sigmoidだと勾配消失問題が起きているのかな?(誤差逆伝搬法だとそうなるのか?)
まあでもこれで30分の一以上に早くできたのですごいですね。
コメント
コメントを投稿