ゼロから作るDeep Learning(20日目)

こんにちは、筋肉めがねです。

 

12月上旬に今年のM1が開催され、「霜降り明星」が優勝しましたね。おめでたい事でございます。今年のクリスマスは沢山笑いましょう、という事で、2018年のM1の動画を最初から最後まで見させて頂きました。僕は特に「ジャルジャル」の披露したネタがとても好きでした。昨年のネタ、そして今年のネタを続けて見ましたが、素人目に見ても、笑える要素がとても増え、そして彼らのこの一年間の努力が分かるネタでございましたね。昨年は「変な校内放送」というネタで、なんとなくではありますが、彼らの展開の早いやりとりを見るにつけ、早口言葉を言えて凄い、という感覚に似た印象を抱きましたが、今年のネタは、見ている側がリズムの良いネタに引き込まれた絶妙なタイミングで、テンポを落とす、という、僕のツボにとてもはまる緩急もあり爆笑させて頂きました。面白く良く笑ったクリスマスとなりました。

そして、24日のクリスマスイブにはまつぼっくりちゃんと餃子を作り、とても美味しく頂きました。

f:id:KinnikuMegane:20181227050955j:plain

 

それでは、本日も「ゼロから作るDeep Learning」を進めていきましょう。本日は、6章の続きでございます。

前回の記事では、ニューラルネットワークの「学習」フェーズにおいて有用な勾配降下法と、他の3種類の手法の比較を行いました。実際にMNISTのデータセットを使用し、「学習」の経過を、損失関数が小さくなるスピードという観点で比較を行い、結果、勾配降下法が常に優れた手法ではない、という事を書きました。

本日は、ニューラルネットワークにおける「学習」のフェーズにおいて、「勾配消失」という問題を発生させないためには、ニューラルネットワークをどのように設計する必要があるのか、という事を書きます。

では、「勾配消失」とは何なのか。

その前に、ニューラルネットワークの「学習」フェーズのステップをおさらいしておきましょう。

1. 訓練データの中からランダムに一部のデータを選び出す。

2. 各重みパラメータに関する損失関数の勾配を求める。

3. 重みパラメータを勾配方向に微少量だけ更新する。

4. 1-3を繰り返す。

 

それで、ステップ2において、各重みパラメータに関する損失関数の勾配を求めるわけなんですが、これは、下の図において、盆地のある地点において、地形の傾きを求める事でしたね。f:id:KinnikuMegane:20181124223347p:plain

例えば、上図の場合、盆地のどの地点においても、その地点における傾きは盆地の一番低い箇所を示しますよね。でも、我々が実際にニューラルネットワークを適用して「学習」させる対象となるこの世の中の事象は、このような綺麗な分布を示す事は皆無に等しいですよね。

例えば、「学習」させる対象となる盆地が以下のような地形であった場合、そして、「SGD(確率的勾配降下法)」を使用して、ある地点(ある重みパラメータ)から「学習」をスタートすると、盆地に投げ込んだボールが、盆地の最低地点まで到達させずに、盆地の途中で止まってしまう事があります。何故なら、ボールが転がっている地点における勾配(地形の傾き)が0になってしますんですよね。

これを「勾配消失」問題と呼んでいます。

 

 

escape saddle point

出典:http://imgur.com/a/Hgolp

 

では、「勾配消失」を起こさないためには何をすれば良いのか。その答えは、重みの初期値を適切なものに設定する、という事なんです。そして、活性化関数にReLUを使う場合は「Heの初期値」を使い、Sigmoid関数やtahnなどの、S字の関数を使う場合には「Xavierの初期値」を使う、という事です。

この段階では、重みの初期値により、「学習」の進み具合が違う、という事をきちんと理解しておきましょう。

 

では、ここで実験をしてみます。

MNISTのデータセットを用いて、5層のニューラルネットワークで「学習」をさせた時、重みの初期値により、「学習」の進み方がどのように変わるのか見ていきましょう。活性化関数はReLU関数です。

先ずは、結果です。

f:id:KinnikuMegane:20181227075640p:plain

続いてコードです。

import os
import sys
sys.path.append(os.pardir) 
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.util import smooth_curve
from common.multi_layer_net import MultiLayerNet
from common.optimizer import SGD

(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)

train_size = x_train.shape[0]
batch_size = 128
max_iterations = 2000

weight_init_types = {'std=0.01': 0.01, 'Xavier': 'sigmoid', 'He': 'relu'}
optimizer = SGD(lr=0.01)

networks = {}
train_loss = {}
for key, weight_type in weight_init_types.items():
    networks[key] = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100],
                                  output_size=10, weight_init_std=weight_type)
    train_loss[key] = []

for i in range(max_iterations):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]
    
    for key in weight_init_types.keys():
        grads = networks[key].gradient(x_batch, t_batch)
        optimizer.update(networks[key].params, grads)
    
        loss = networks[key].loss(x_batch, t_batch)
        train_loss[key].append(loss)
    
    if i % 100 == 0:
        print("===========" + "iteration:" + str(i) + "===========")
        for key in weight_init_types.keys():
            loss = networks[key].loss(x_batch, t_batch)
            print(key + ":" + str(loss))

markers = {'std=0.01': 'o', 'Xavier': 's', 'He': 'D'}
x = np.arange(max_iterations)
for key in weight_init_types.keys():
    plt.plot(x, smooth_curve(train_loss[key]), marker=markers[key], markevery=100, label=key)
plt.xlabel("iterations")
plt.ylabel("loss")
plt.ylim(0, 2.5)
plt.legend()
plt.show()

 

先ほど書いた通り、「Heの初期値」の場合、他の2つの初期値に比べて、損失関数が早い段階で小さくなっている事、つまり「学習」が効率良く進んでいる事が分かりますね。

 

それでは、本日は以上でございます。


にほんブログ村 IT技術ブログへ
にほんブログ村



Pythonランキング