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

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

 

ドイツに移住してきて数年経ちますが、ドイツでの決まりごとを新しく知る機会に触れると、おっ、と感じますね。ドイツでは、12月29日から31日の3日間は、花火、爆竹の販売が許可されているらしんです。もともと、花火、爆竹それ自体は、戦時中の爆弾を彷彿とさせる、という理由で禁止されているらしんですが、この時期ばかりはドイツ政府も許可を出している、という事です。そして、12月31日、1月1日の2日間については、花火、爆竹の使用が許可されている、という事です。

花火と言えば、僕は線香花火を思い出します。8月のお盆の時期に、迎え火、そして送り火をする際に、従兄弟たちと線香線香をしていた事がとても良い記憶として残っています。はじめは静かに、そして少しずつ勢いを増し、盛大に周りを明るく照らした後は、少しずつその存在感を弱めていく。とても儚く、それでいて素敵な花火ですよね。

 

f:id:KinnikuMegane:20181230004406j:plain

 

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

前回の記事では、過学習を抑制する手法の一つとしてWeight decayを紹介しました。本日は、もう一つ本書で紹介されているdropoutについて書き、そしてハイパーパラメータの最適化について書いていきます。

Dropoutとは、ニューラルネットワークが「学習」するフェーズにおいて、複数あるニューロンのうち、幾つかのニューロンをランダムに不活性させる事で、つまり動かなくする事で信号の伝達を抑制させるものです。そして、順伝播時に、信号が伝わらなかったニューロンについては、逆伝播時にも、そこで信号はストップします。

 

ここで、改めて順伝播と逆伝播の意味をおさらいしておきます。

先ずは、ニューラルネットワークの「学習」ステップをおさらいしましょう。

1. 訓練データをニューラルネットワークに投げ込み、層から層へと信号を伝達していき、最後の出力層から出てくるデータを、教師データと比較します。

*その時の誤差を小さくしていく事こそが、ニューラルネットワークの「学習」の目的です。すべき事は、重みパラメータやバイアスを変化させた時の、誤差の変化量を小さくする事です。つまり、誤差の重みによる微分を小さくしていくんですね。そして、このステップ1こそが順伝播です。

2. 続いて、誤差逆伝播法を用いて、各重みパラメータに関する損失関数の勾配を求め、その勾配方向に重みパラメータを更新していくわけですね。これが、逆伝播ですね。

 

そもそもあるニューロンを不活性させると何が嬉しいか、というと、ニューラルネットワークが簡単になるわけですね。つまり複雑さが減る、という事です。過学習が起きる一つの原因はニューラルネットワークの複雑性にありました。モデルが複雑になればなるほど、訓練データ群の外れ値にまでも適合してしまうモデルができあがってしまいます。そこで、モデルを簡素化させるわけです。

 

そもそもDropoutの何が嬉しいか、というと、「学習」の1サイクル(入力データをニューラルネットワークに入れ、データを出力し、それを教師データと比較し、そして誤差逆伝播法により重みパラメータを更新する)毎に、ランダムに異なるニューロンを不活性化させる、という事です。つまり、サイクル毎に、見た目上異なるニューラルネットワークを用いて「学習」させている場合と同じような効果を得られるわけですね。機械学習の分野では、アンサンブル学習と呼ぶものがあり、それは、似通った異なる複数のニューラルネットワークに対して同様の訓練データを使い、「学習」させ、テストの時には、その出力の平均をとる、というものです。Dropoutでは、アンサンブル学習を擬似的に実現しているわけですね。

 

続いて、ハイパーパラメータの最適化です。ハイパーパラメータとは重みパラメータ以外の、ニューラルネットワークにおける変数ですね。例えばニューロンの数や、以下の式における学習率ηですね。

w ←  w - η * (∂L/∂W)

 

本書では学習係数とweight decay係数の最適な値を求める実験を行っていますね。

以下、テスト結果とコードです。

訓練データを破線で、テストデータを実線で表しています。そして、100回ランダムに学数係数とweight decay係数を決め、それらの100セットを使って、そしてMNISTのデータセットを使って、ニューラルネットワークに学習させた結果、テストデータの認識精度が高くなった順からBest1...と並べた結果です。

f:id:KinnikuMegane:20181230024655p:plain

Best1から3までのケースの学習係数lrとweight decay係数は以下の通りです。

結果、学習がうまく進むのは、学習係数が0.0045から0.0079であり、そしてweight decay係数が10^-5から10^-8である事がわかります。例えばこういう手法で最適なハイパーパラメータを探索していくんですね。

 

f:id:KinnikuMegane:20181230030007p:plain

 

こちらがコードです。

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.multi_layer_net import MultiLayerNet
from common.util import shuffle_dataset
from common.trainer import Trainer

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

# 高速化のため訓練データの削減
x_train = x_train[:500]
t_train = t_train[:500]

# 検証データの分離
validation_rate = 0.20
validation_num = int(x_train.shape[0] * validation_rate)
x_train, t_train = shuffle_dataset(x_train, t_train)
x_val = x_train[:validation_num]
t_val = t_train[:validation_num]
x_train = x_train[validation_num:]
t_train = t_train[validation_num:]


def __train(lr, weight_decay, epocs=50):
    network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100],
                            output_size=10, weight_decay_lambda=weight_decay)
    trainer = Trainer(network, x_train, t_train, x_val, t_val,
                      epochs=epocs, mini_batch_size=100,
                      optimizer='sgd', optimizer_param={'lr': lr}, verbose=False)
    trainer.train()

    return trainer.test_acc_list, trainer.train_acc_list


# ハイパーパラメータのランダム探索======================================
optimization_trial = 100
results_val = {}
results_train = {}
for _ in range(optimization_trial):
    # 探索したハイパーパラメータの範囲を指定===============
    weight_decay = 10 ** np.random.uniform(-8, -4)
    lr = 10 ** np.random.uniform(-6, -2)
    # ================================================

    val_acc_list, train_acc_list = __train(lr, weight_decay)
    print("val acc:" + str(val_acc_list[-1]) + " | lr:" + str(lr) + ", weight decay:" + str(weight_decay))
    key = "lr:" + str(lr) + ", weight decay:" + str(weight_decay)
    results_val[key] = val_acc_list
    results_train[key] = train_acc_list

# グラフの描画========================================================
print("=========== Hyper-Parameter Optimization Result ===========")
graph_draw_num = 20
col_num = 5
row_num = int(np.ceil(graph_draw_num / col_num))
i = 0

for key, val_acc_list in sorted(results_val.items(), key=lambda x:x[1][-1], reverse=True):
    print("Best-" + str(i+1) + "(val acc:" + str(val_acc_list[-1]) + ") | " + key)

    plt.subplot(row_num, col_num, i+1)
    plt.title("Best-" + str(i+1))
    plt.ylim(0.0, 1.0)
    if i % 5: plt.yticks([])
    plt.xticks([])
    x = np.arange(len(val_acc_list))
    plt.plot(x, val_acc_list)
    plt.plot(x, results_train[key], "--")
    i += 1

    if i >= graph_draw_num:
        break

plt.show()

 

これで6章は以上でございます。次は7章ですね。

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

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



Pythonランキング