深層学習Day2
Section1 勾配消失問題
<確認テスト>
連鎖律の原理を用い、$\frac{dz}{dx}$を求めよ。
z = t^2 \\
t = x + y \\
\frac{dz}{dx} = \frac{dz}{dt} \frac{dt}{dx} = 2t = 2(x+y)
勾配消失問題とは、多層ニューラルネットワークにおいて、誤差逆伝播を進めていくにつれて、勾配がどんどん緩やかになっていき、勾配降下法におけるパラメータの更新量が小さくなりすぎてしまい、重みが収束しなくなる現象。
特に活性化関数でよく使用されるSigmoid関数などは、大きい/小さい値に対して勾配が0に近づくので、勾配消失問題を引き起こしやすいという問題点があった。
<確認テスト>
シグモイド関数を微分した時、入力値が0の時に最大値を取る。その値として正しいものを選択肢から選べ。
⇒実際にシグモイド関数を微分して、0を代入してみる。
\sigma(x) = \frac{1}{1+\exp(-x)} \\
\frac{d \sigma}{dx} = (1- \sigma(x))\sigma(x) \\
\therefore (1-0.5)*0.5 = 0.25
よって、答えは(2)0.25
勾配消失問題に対する一般的な解決方法は、
- 活性化関数の選択
- 重みの初期値設定
- バッチ正規化
の3つ。
活性化関数の選択
層が深いときに勾配消失問題を起こしやすいSigmoid関数に代わってよく用いられるのが、ReLU関数。
数式で書くと以下。
f(x)=
\begin{cases}
x \ \ (x>0) \\
0 \ \ (x\le 0)
\end{cases}
サンプルコードは以下
import numpy as np
def relu(x):
return np.maximum(0,x)
このReLU関数を用いることによって、勾配消失問題に対処でき、微分計算の簡略化もできる。
重みの初期値設定
重みの初期値を、活性化関数に合わせて変える。
使用されるのは主に2種類。
Xavierの初期値
- 重みの要素を、前の層のノード数の平方根で除算した値にする。
- 使用する活性化関数は以下。
- ReLU関数
- Sigmoid関数
- 双曲線正接関数
Heの初期値
- 重みの要素を、前の層のノード数の平方根で除算した値に対し$\sqrt{2}$を掛け合わせた値にする。
- 使用する活性化関数は以下。
- ReLU関数
<確認テスト>
重みの初期値を0に設定すると、どのような問題が発生するか?
簡潔にまとめよ。
⇒入力値に対して0をかけて伝播していくことになるので、全く学習が進まない。
バッチ正規化
ミニバッチ単位で入力値のデータの偏りを抑制する方法。
活性化関数に値を渡す前後に、バッチ正規化の処理を含んだ層を加える。
確認テスト
一般的に考えられるバッチ正規化の効果を2点挙げよ。
⇒以下の2点
- 安定して学習できるようになる
- 計算の効率化
実装演習
MNISTデータセットの分類のためのニューラルネットワークを学習させてみる。
import numpy as np
from commonDSpackage import layers
from collections import OrderedDict
from commonDSpackage import functions
from commonDSpackage.mnist import load_mnist
import matplotlib.pyplot as plt
# mnistをロード
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)
train_size = len(x_train)
print("データ読み込み完了")
# 重み初期値補正係数
wieght_init = 0.01
# 入力層サイズ
input_layer_size = 784
# 中間層サイズ
hidden_layer_1_size = 40
hidden_layer_2_size = 20
# 出力層サイズ
output_layer_size = 10
# 繰り返し数
iters_num = 2000
# ミニバッチサイズ
batch_size = 100
# 学習率
learning_rate = 0.1
# 描写頻度
plot_interval=10
# 初期設定
def init_network():
network = {}
network['W1'] = wieght_init * np.random.randn(input_layer_size, hidden_layer_1_size)
network['W2'] = wieght_init * np.random.randn(hidden_layer_1_size, hidden_layer_2_size)
network['W3'] = wieght_init * np.random.randn(hidden_layer_2_size, output_layer_size)
#xavierの初期値
network['W1'] = np.random.randn(input_layer_size, hidden_layer_1_size) / (np.sqrt(input_layer_size))
network['W2'] = np.random.randn(hidden_layer_1_size, hidden_layer_2_size) / (np.sqrt(hidden_layer_1_size))
network['W3'] = np.random.randn(hidden_layer_2_size, output_layer_size) / (np.sqrt(hidden_layer_2_size))
network['b1'] = np.zeros(hidden_layer_1_size)
network['b2'] = np.zeros(hidden_layer_2_size)
network['b3'] = np.zeros(output_layer_size)
return network
# 順伝播
def forward(network, x):
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']
#活性化関数の変更
#hidden_f = functions.sigmoid
hidden_f = functions.relu
u1 = np.dot(x, W1) + b1
z1 = hidden_f(u1)
u2 = np.dot(z1, W2) + b2
z2 = hidden_f(u2)
u3 = np.dot(z2, W3) + b3
y = functions.softmax(u3)
return z1, z2, y
# 誤差逆伝播
def backward(x, d, z1, z2, y):
grad = {}
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']
#活性化関数の変更
hidden_d_f = functions.d_relu
last_d_f = functions.d_softmax_with_loss
# 出力層でのデルタ
delta3 = last_d_f(d, y)
# b3の勾配
grad['b3'] = np.sum(delta3, axis=0)
# W3の勾配
grad['W3'] = np.dot(z2.T, delta3)
# 2層でのデルタ
delta2 = np.dot(delta3, W3.T) * hidden_d_f(z2)
# b2の勾配
grad['b2'] = np.sum(delta2, axis=0)
# W2の勾配
grad['W2'] = np.dot(z1.T, delta2)
# 1層でのデルタ
delta1 = np.dot(delta2, W2.T) * hidden_d_f(z1)
# b1の勾配
grad['b1'] = np.sum(delta1, axis=0)
# W1の勾配
grad['W1'] = np.dot(x.T, delta1)
return grad
# パラメータの初期化
network = init_network()
accuracies_train = []
accuracies_test = []
# 正答率
def accuracy(x, d):
z1, z2, y = forward(network, x)
y = np.argmax(y, axis=1)
if d.ndim != 1 : d = np.argmax(d, axis=1)
accuracy = np.sum(y == d) / float(x.shape[0])
return accuracy
for i in range(iters_num):
# ランダムにバッチを取得
batch_mask = np.random.choice(train_size, batch_size)
# ミニバッチに対応する教師訓練画像データを取得
x_batch = x_train[batch_mask]
# ミニバッチに対応する訓練正解ラベルデータを取得する
d_batch = d_train[batch_mask]
z1, z2, y = forward(network, x_batch)
grad = backward(x_batch, d_batch, z1, z2, y)
if (i+1)%plot_interval==0:
accr_test = accuracy(x_test, d_test)
accuracies_test.append(accr_test)
accr_train = accuracy(x_batch, d_batch)
accuracies_train.append(accr_train)
#print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
#print(' : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))
# パラメータに勾配適用
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
network[key] -= learning_rate * grad[key]
lists = range(0, iters_num, plot_interval)
plt.plot(lists, accuracies_train, label="training set")
plt.plot(lists, accuracies_test, label="test set")
plt.legend(loc="lower right")
plt.title("accuracy")
plt.xlabel("count")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
# グラフの表示
plt.show()
コードの途中で、重みの初期値・活性化関数を変更して実験を行った。
この実験結果からも、活性化関数の変更と重みの初期値の変更は、共に効果があることが分かった。