ラビットチャレンジレポート 深層学習DAY2その1
Section1 勾配消失問題
勾配消失問題とは
誤差逆伝搬法が下位層に進んでいくに連れて、勾配がどんどん緩やかになっていく問題。
そのため、勾配降下法による更新では、階層のパラメータはほとんど変わらず、訓練は最適値に収束しなくなる。
シグモイド関数を使用した場合、値の絶対値が大きいと勾配がほぼ0になるため、勾配が消失する。
また、微分の連鎖律に起因する問題もある。
例として、出力から3層下位の重みwの連鎖律の式を下記に示す。
この式を見ると、活性化関数zの入力uに対する微分が2回出てくる。活性化関数としてシグモイド関数を使用した場合、シグモイド関数の微分値は最大で0.25程度である。これを二回乗算すると、0.0625となってしまい、勾配がほぼ消失してしまうように見える。
これが勾配消失問題の例である。
- 確認テスト
シグモイド関数を微分した時、入力値が0の時に最大値をとる。その値として正しいものを選択肢から選べ。
(1)0.15
(2)0.25
(3)0.35
(4)0.45
[解答]
(2)0.25
勾配消失問題の解決法として知られているものは次の3つである。
- 活性化関数の選択
- 重みの初期値設定
- バッチ正規化
活性化関数
勾配消失問題回避に効果のある活性化関数として広く用いられているものに、ReLu関数がある。
ReLu関数は、x>0では微分すると1のため、何度乗算しても減衰しないという特徴がある。
また、x≦0では0のため、活性ノードが疎になる(スパース化)ことで、計算量の軽減や精度向上につながるという利点がある。
重みの初期設定
ニューラルネットワークにおける重みの初期値は、学習の精度や速度に大きく影響する非常に重要な問題である。
通常の標準正規分布から生成した初期値では、0や1に偏ってしまったり、標準偏差を非常に狭くしたものでは、0.5付近に偏ってしまったりと、偏りや勾配消失が起こりやすくなってしまう。
そこで、初期値生成の手法として、「Xavierの初期値」と「Heの初期値」が提案された。
バッチ正規化
勾配降下法の一つで、データを小さな塊に分けて学習をするミニバッチ学習がある。このミニバッチ学習をそのままのデータで行うと、バッチごとにデータの偏りが出て、勾配消失問題が起こる等学習が上手く出来ない場合がある。それを抑制するために各バッチのデータの分布を正規化することをバッチ正規化という。
- 確認問題
一般に考えられるバッチ正規化の効果を2点あげよ。
[解答]
-学習が安定化する
-パラメータのスケールや初期値の影響が小さくなるので、高い学習率を設定することが可能になり、学習スピードが速くなる。
- バッチ正規化の手順
1 データの平均値をとる
2 データの分散をとる
3 平均ゼロにし、標準偏差にθを加えて分布をコントロール
4 変倍とバイアスをつけて変換
実装演習
シグモイド関数の活性化関数による勾配消失と、ReLu関数や、重み初期値の設定方法による勾配消失問題の回避効果をニューラルネットワークモデルを用いて検証する。
まず、ニューラルネットワークを定義する。
import numpy as np from common import layers from collections import OrderedDict from common import functions from data.mnist import load_mnist import matplotlib.pyplot as plt class MultiLayerNet: ''' input_size: 入力層のノード数 hidden_size_list: 隠れ層のノード数のリスト output_size: 出力層のノード数 activation: 活性化関数 weight_init_std: 重みの初期化方法 ''' def __init__(self, input_size, hidden_size_list, output_size, activation='relu', weight_init_std='relu'): self.input_size = input_size self.output_size = output_size self.hidden_size_list = hidden_size_list self.hidden_layer_num = len(hidden_size_list) self.params = {} # 重みの初期化 self.__init_weight(weight_init_std) # レイヤの生成, sigmoidとreluのみ扱う activation_layer = {'sigmoid': layers.Sigmoid, 'relu': layers.Relu} self.layers = OrderedDict() # 追加した順番に格納 for idx in range(1, self.hidden_layer_num+1): self.layers['Affine' + str(idx)] = layers.Affine(self.params['W' + str(idx)], self.params['b' + str(idx)]) self.layers['Activation_function' + str(idx)] = activation_layer[activation]() idx = self.hidden_layer_num + 1 self.layers['Affine' + str(idx)] = layers.Affine(self.params['W' + str(idx)], self.params['b' + str(idx)]) self.last_layer = layers.SoftmaxWithLoss() def __init_weight(self, weight_init_std): all_size_list = [self.input_size] + self.hidden_size_list + [self.output_size] for idx in range(1, len(all_size_list)): scale = weight_init_std if str(weight_init_std).lower() in ('relu', 'he'): scale = np.sqrt(2.0 / all_size_list[idx - 1]) elif str(weight_init_std).lower() in ('sigmoid', 'xavier'): scale = np.sqrt(1.0 / all_size_list[idx - 1]) self.params['W' + str(idx)] = scale * np.random.randn(all_size_list[idx-1], all_size_list[idx]) self.params['b' + str(idx)] = np.zeros(all_size_list[idx]) def predict(self, x): for layer in self.layers.values(): x = layer.forward(x) return x def loss(self, x, d): y = self.predict(x) weight_decay = 0 for idx in range(1, self.hidden_layer_num + 2): W = self.params['W' + str(idx)] return self.last_layer.forward(y, d) + weight_decay def accuracy(self, x, d): y = self.predict(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 def gradient(self, x, d): # forward self.loss(x, d) # backward dout = 1 dout = self.last_layer.backward(dout) layers = list(self.layers.values()) layers.reverse() for layer in layers: dout = layer.backward(dout) # 設定 grad = {} for idx in range(1, self.hidden_layer_num+2): grad['W' + str(idx)] = self.layers['Affine' + str(idx)].dW grad['b' + str(idx)] = self.layers['Affine' + str(idx)].db return grad
このモデルを用いて、まず活性化関数にシグモイドを用い、重みの初期値を、標準偏差0.01、平均0の正規分布で発生させたものを用いて学習効果を確認する。
# データの読み込み (x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True) print("データ読み込み完了") network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='sigmoid', weight_init_std=0.01) iters_num = 2000 train_size = x_train.shape[0] batch_size = 100 learning_rate = 0.1 train_loss_list = [] accuracies_train = [] accuracies_test = [] plot_interval=10 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] # 勾配 grad = network.gradient(x_batch, d_batch) for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'): network.params[key] -= learning_rate * grad[key] loss = network.loss(x_batch, d_batch) train_loss_list.append(loss) if (i + 1) % plot_interval == 0: accr_test = network.accuracy(x_test, d_test) accuracies_test.append(accr_test) accr_train = network.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)) 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()
結果のグラフ表示
Accuracy(予測精度)が学習の回数を重ねても向上せず、学習が進んでいないことがわかる。
次に、活性化関数にReLu関数を使用し、重みの初期値は前回と同じく標準偏差が0.01、平均0の正規分布に従って設定する。
# データの読み込み (x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True) print("データ読み込み完了") network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='relu', weight_init_std=0.01) iters_num = 2000 train_size = x_train.shape[0] batch_size = 100 learning_rate = 0.1 train_loss_list = [] accuracies_train = [] accuracies_test = [] plot_interval=10 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] # 勾配 grad = network.gradient(x_batch, d_batch) for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'): network.params[key] -= learning_rate * grad[key] loss = network.loss(x_batch, d_batch) train_loss_list.append(loss) if (i + 1) % plot_interval == 0: accr_test = network.accuracy(x_test, d_test) accuracies_test.append(accr_test) accr_train = network.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)) 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()
結果の図示
500回前後から精度が向上し、1000回程度まで急速に学習が進み、その後緩やかに向上している。
勾配消失問題が緩和していることがわかる。
次に、活性化関数をシグモイド関数に戻し、重みの初期値をXavierを用いて設定する。
# データの読み込み (x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True) print("データ読み込み完了") network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='sigmoid', weight_init_std='Xavier') iters_num = 2000 train_size = x_train.shape[0] batch_size = 100 learning_rate = 0.1 train_loss_list = [] accuracies_train = [] accuracies_test = [] plot_interval=10 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] # 勾配 grad = network.gradient(x_batch, d_batch) for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'): network.params[key] -= learning_rate * grad[key] loss = network.loss(x_batch, d_batch) train_loss_list.append(loss) if (i + 1) % plot_interval == 0: accr_test = network.accuracy(x_test, d_test) accuracies_test.append(accr_test) accr_train = network.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)) 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()
シグモイド関数を使っているにも関わらず、学習回数に応じて精度は向上しており、勾配消失が緩和していることがわかる。
最後に、活性化関数をReLu関数にして、重みの初期値をHeで設定する。
# データの読み込み (x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True) print("データ読み込み完了") network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='relu', weight_init_std='He') iters_num = 2000 train_size = x_train.shape[0] batch_size = 100 learning_rate = 0.1 train_loss_list = [] accuracies_train = [] accuracies_test = [] plot_interval=10 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] # 勾配 grad = network.gradient(x_batch, d_batch) for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'): network.params[key] -= learning_rate * grad[key] loss = network.loss(x_batch, d_batch) train_loss_list.append(loss) if (i + 1) % plot_interval == 0: accr_test = network.accuracy(x_test, d_test) accuracies_test.append(accr_test) accr_train = network.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)) 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()
学習の初期からaccuracyが向上しており、Heを使わない場合に対して、学習のスピードが著しく向上していることがわかる。
- 例題チャレンジ
以下は特徴データdata_x、ラベルデータdata_tに対してミニバッチ学習を行うプログラムである。
下記の(き)に当てはまるコードを答えよ。
[解答]
data_x[i:i_end],data_t[i:i_end]