ラビットチャレンジレポート 深層学習DAY2その1

Section1 勾配消失問題

誤差逆伝搬法

 誤差逆伝搬法とは、計算結果から微分を逆算することで、不要な再帰的計算を避けて微分を算出する手法である。

  • 確認テスト

 連鎖律の原理を使い、下記の式のdz/dxを求めよ
 z=t^2
 t=x+y
[解答]
 \dfrac{dz}{dt}=2t
 \dfrac{dt}{dx}=1
 \dfrac{dz}{dx}=\dfrac{dz}{dt}\dfrac{dt}{dx}=2t\cdot 1=2t=2)x+y)
 

勾配消失問題とは

誤差逆伝搬法が下位層に進んでいくに連れて、勾配がどんどん緩やかになっていく問題。
そのため、勾配降下法による更新では、階層のパラメータはほとんど変わらず、訓練は最適値に収束しなくなる。
 シグモイド関数を使用した場合、値の絶対値が大きいと勾配がほぼ0になるため、勾配が消失する。
 また、微分の連鎖律に起因する問題もある。
例として、出力から3層下位の重みwの連鎖律の式を下記に示す。
 \dfrac{\partial E}{\partial w^{(3)}}=\dfrac{\partial E}{\partial y}\dfrac{\partial y}{\partial u^{(3)}}\dfrac{\partial u^{(3)}}{\partial z^{(2)}}\dfrac{\partial z^{(2)}}{\partial u^{(2)}}\dfrac{\partial u^{(2)}}{\partial z^{(2)}}\dfrac{\partial z^{(2)}}{\partial u^{(1)}}\dfrac{\partial u^{(1)}}{\partial w^{(1)}}
この式を見ると、活性化関数zの入力uに対する微分 \dfrac{\partial z}{\partial u}が2回出てくる。活性化関数としてシグモイド関数を使用した場合、シグモイド関数微分値は最大で0.25程度である。これを二回乗算すると、0.0625となってしまい、勾配がほぼ消失してしまうように見える。
これが勾配消失問題の例である。

  • 確認テスト

シグモイド関数微分した時、入力値が0の時に最大値をとる。その値として正しいものを選択肢から選べ。
(1)0.15
(2)0.25
(3)0.35
(4)0.45

[解答]
(2)0.25

勾配消失問題の解決法として知られているものは次の3つである。

  • 活性化関数の選択
  • 重みの初期値設定
  • バッチ正規化

活性化関数

勾配消失問題回避に効果のある活性化関数として広く用いられているものに、ReLu関数がある。
 f(x)= {x(x>0), 0(x \leq 0)}
ReLu関数は、x>0では微分すると1のため、何度乗算しても減衰しないという特徴がある。
また、x≦0では0のため、活性ノードが疎になる(スパース化)ことで、計算量の軽減や精度向上につながるという利点がある。

重みの初期設定

ニューラルネットワークにおける重みの初期値は、学習の精度や速度に大きく影響する非常に重要な問題である。
 通常の標準正規分布から生成した初期値では、0や1に偏ってしまったり、標準偏差を非常に狭くしたものでは、0.5付近に偏ってしまったりと、偏りや勾配消失が起こりやすくなってしまう。
 そこで、初期値生成の手法として、「Xavierの初期値」と「Heの初期値」が提案された。

Xavierの初期値

 前層のノード数がn個の時、平均0、標準偏差 \dfrac{1}{\sqrt n} である正規分布から初期値を生成する。主にシグモイド関数を活性化関数としたときに効果を発揮する初期値生成法である。

Heの初期値

 前層のノード数がn個の時、平均0、標準偏差  \dfrac{2}{\sqrt n} である正規分布から初期値を生成する手法。主にReLu関数を活性化関数としたときに効果を発揮する初期値生成法である。

  • 確認テスト

 重みの初期値を0に設定すると、どのような問題が発生するか。簡潔に説明せよ。
[解答]
 学習が出来なくなる。重みを均質にすると、学習時に同じように更新されてしまい、特徴の表現が出来なくなる。

バッチ正規化

 勾配降下法の一つで、データを小さな塊に分けて学習をするミニバッチ学習がある。このミニバッチ学習をそのままのデータで行うと、バッチごとにデータの偏りが出て、勾配消失問題が起こる等学習が上手く出来ない場合がある。それを抑制するために各バッチのデータの分布を正規化することをバッチ正規化という。

  • 確認問題

 一般に考えられるバッチ正規化の効果を2点あげよ。
[解答]
-学習が安定化する
-パラメータのスケールや初期値の影響が小さくなるので、高い学習率を設定することが可能になり、学習スピードが速くなる。

  • バッチ正規化の手順

1 データの平均値をとる
 μ_t=\dfrac{1}{N_t}\displaystyle \sum_{i=1}^{N_t}x_ni
2 データの分散をとる
 σ_t^2=\dfrac{1}{N_t}\displaystyle \sum_{i=1}^{N_t}(x_{ni}-μ_t)^2
3 平均ゼロにし、標準偏差にθを加えて分布をコントロール
 \hat{x}_{ni}=\dfrac{x_{ni}-μ_t}{\sqrt{σ_t^2+θ}}
4 変倍とバイアスをつけて変換
 y_ni=ɤx_ni+β

実装演習

シグモイド関数の活性化関数による勾配消失と、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()

結果のグラフ表示
f:id:tibet:20211119175631p:plain
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()

結果の図示
f:id:tibet:20211119194844p:plain
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()

f:id:tibet:20211119195909p:plain
シグモイド関数を使っているにも関わらず、学習回数に応じて精度は向上しており、勾配消失が緩和していることがわかる。

最後に、活性化関数を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()

f:id:tibet:20211119200237p:plain
学習の初期からaccuracyが向上しており、Heを使わない場合に対して、学習のスピードが著しく向上していることがわかる。

  • 例題チャレンジ

以下は特徴データdata_x、ラベルデータdata_tに対してミニバッチ学習を行うプログラムである。
下記の(き)に当てはまるコードを答えよ。
f:id:tibet:20211119200438p:plain
[解答]

data_x[i:i_end],data_t[i:i_end]