"BOKU"のITな日常

BOKUが勉強したり、考えたことを頭の整理を兼ねてまとめてます。

重み(Weight)の初期値の種類と使い分け及び「Optimizer」の種類/ニューラルネットワーク

f:id:arakan_no_boku:20190326212937j:plain

目次

はじめに

この記事は2017年9月に書いた記事のリライトです。

一部、説明にNeural Network Consoleの画面を使っていますが、すべて、Version1.00時点のものなので、最新版とはデザインも機能も違っていると思います。

ご容赦ください。

ディープラーニングの重み(Weight)

ディープラーニングの学習精度向上に影響がある、重み(W)の初期値の与え方と、Optimizerの変更についてです。

ディープラーニングの学習を、細かいところに目をつぶって、めちゃくちゃ簡略化すると以下の図のような感じになります。

f:id:arakan_no_boku:20170825221728j:plain

マニュアルでは、「o = Wi+b(iは入力、oは出力、Wは重み、bはバイアス項を示す)」みたいに計算式で書かれている部分が上記の計算です。

見ていただければわかると思いますが、入力データに重みを掛け合わせて計算結果をだし、その結果を次の入力として、次の重みを使って計算する・・と繰り返して、その結果で答え合わせをして、ずれてたら、さかのぼって重みを調整してやりなおす・・なんてことを何万回も繰り返して、だんだんと、正解に近い答えを求められる確率をあげていくという行為が学習だといえます。

その調整は、学習前の重みで計算した結果と正解を比較して、どのくらい差異があるか(損失率といいます)を求めて、その差異が小さくなる方向に重みの値を更新していくことで行います。 

これは、ちょっとずつずらしながら正解に近づけていくという地道な作業なので、その出発点になる「重みの初期値」の値によって、調整に非常に時間がかかったりする可能性があるのは、なんとなくイメージできると思います。 

重みの初期値の与え方

例えば、ニューラルネットワークコンソールだと「重みの初期値」にどういう数値を与えるかを決めるのは、ConvolutionAffineの各レイヤーにある、「W.Initializer」という設定項目です。 

初期値の与え方の種類については、ニューラルネットワークコンソールのレイヤーリファレンスに一応記載はあります。

まず、convolutionの方です。

重みWの初期化方法を指定します
Uniform:-1.0~1.0の一様乱数で初期化します

UniformConvolutionGlorot:一様乱数にXavier Glorot提案の 係数をかけて初期化します

Normal:平均0.0、分散1.0であるガウス乱数で初期化します

NormalConvolutionHeForward:ガウス乱数にKaiming He提案の係数をかけて初期化します(Forward Case)

NormalConvolutionHeBackward:ガウス乱数にKaiming He提案の係数をかけて初期化します(Backward Case)

NormalConvolutionGlorot:ガウス乱数にXavier Glorot提案の 係数をかけて初期化します(デフォルト)

Constant:全ての要素を一定値(1.0)で初期化します

続いて、Affineの方です。

重みWの初期化方法を指定します
Uniform:-1.0~1.0の一様乱数で初期化します

UniformAffineGlorot:一様乱数にXavier Glorot提案の 係数をかけて初期化します

Normal:平均0.0、分散1.0であるガウス乱数で初期化します

NormalAffineHeForward:ガウス乱数にKaiming He提案の係数をかけて初期化します(Forward Case)

NormalAffineHeBackward:ガウス乱数にKaiming He提案の係数をかけて初期化します(Backward Case)

NormalAffineGlorot:ガウス乱数にXavier Glorot提案の 係数をかけて初期化します(デフォルト)

Constant:全ての要素を一定値(1.0)で初期化します

難しいですね。

平均0.0、分散1.0であるガウス乱数とか、数学が苦手な文系人間には、頭がクラクラきそうな言葉がでてきます。

この中で、どのネットワーク構成の時に、どの初期値を選ぶのが適切か・・ということを、まともに考えるととても難しいのですが、ありがたいことに、すでに頭の良い人が考えた「効率的な初期値の規則」があります。 

我々凡人は、ありがたく使わせていただきましょう。  

Xavierの初期値 

まず「Xavierの初期値」と呼ばれるものがあります。

上記のうち、説明に「Xavier Glorot提案」と書かれている種類が「Xavierの初期値」と呼ばれるものです。 

この「Xavierの初期値」は「Sigmoid」か「Tanh」に適している初期値として知られています。

ちなみに、Neural Network Libraries の重みのデフォルトは、Convolutionの場合は「NormalConvolutionGlorot」で、Affineの場合は「NormalAffineGlorot」・・つまり、どちらも「Xavierの初期値」になってます。

なので、後ろに「Sigmoid」か「Tanh」が来るときは、デフォルトでよいと考えることができます。  

Heの初期値 

もうひとつ。

Heの初期値と呼ばれるものがあります。

レイヤーリファレンスでは「Kaiming He提案」と書かれているのが「Heの初期値」になります。

上の説明で「Xavierの初期値」は「Sigmoid」と「Tanh」に適していると書きましたが、「Heの初期値」は、「ReLU」に適していると知られてます。 

だから、後ろに「ReLu」が来るときは「Heの初期値」に変更したほうが、よりよくなる可能性があるわけですが、Neural Network Libraries でHeの初期値を選ぼうとすると「Forward」と「Backward」の二種類があって迷います。

どちらが良いのか・・について明確に書いたものがないので、自分で試してみた感じでは「・・Forward」の方が結果が良かったので、一旦、おススメは以下にします。

ReLU を使う時:「NormalConvolutionHeForward」または「NormalAffineHeForward」に変更する。

もちろん、ケースによって「もう一方の方が良かったじゃないか!」という事もあるかもしれませんが、そのへんはご容赦くださいね。  

補足;初期値の名前のUniformとNormalについて 

ちなみに・・ですが、補足です。

初期値の名前に「Uniform・・」と「Normal・・」と書かれているものがあります。

これは元になる分布の種類を表しています。

Uniformは「一様分布」のことで、簡単に言うと「サイコロの目のでる確率みたいに、すべての事象の発生確率が等しい分布」になります。

Normalは「ガウス分布」つまり「正規分布」です。

こちらはすべての事象の発生確率は同じではなくて、よく見る以下のグラフになるような感じで分布することになります。

f:id:arakan_no_boku:20190326223032j:plain 

Neural Network Consoleで試してみる 

さて、やってみます。

ニューラルネットワークコンソールを起動します。 

例によって、シンプルなCNN(Convolutional Neural Network)を組みます。

f:id:arakan_no_boku:20170826132310j:plain

 

デフォルトのまま学習・推論した結果は以下の通りです。

f:id:arakan_no_boku:20170827144252j:plain

 

ここに初期値の変更を加えてみます。

Convolutionレイヤーを選択し、左側の設定リストから「W.Initializer」を選んで、表示される選択肢から「NormalConvolutionHeForward」を選びます。

 

f:id:arakan_no_boku:20170827143117j:plain

 

Convolutionレイヤーに続くのが、「ReLU」だからですね。 

同様に、その下のConvolutionレイヤーでも同じように、「W.Initializer」を変更します。 

その下のAffineについては、後続が「Sigmoid」なので、デフォルトのままでいいです。 

変更はこれだけです。 

上書き保存して学習をやってみます。 

学習結果のグラフはこんな感じです。

f:id:arakan_no_boku:20170827143223j:plain

 

評価結果は、99.2%になりました。

f:id:arakan_no_boku:20170827143300j:plain

 

初期値を変更しただけで、0.02%とはいえ、改善の方向に数字が改善しました。 

他にも、色々、初期値を変えてやってみると、面白いと思います。 

Optimizerについて 

次は「Optimizer」です。 

図を再掲します。

f:id:arakan_no_boku:20170825221728j:plain

前の方で、学習とは「重み」のパラメータを「正解との差が小さくなる方向に、重みの数値を少しだけ増やすか減らす」ことで調整しますと書きました。 

この「増やすか減らす幅」は学習係数で指定するのですけど、その方向や実際の幅を調節するのが「Optimizer」です。

この方向や幅の調整によって、学習結果が大きく変わる場合もあったりします。

Optimizerのざっくりした種類 

ひとくちに「Optimizer」と言っても、色々種類があります。 

その種類によって、正解にたどり着くまでの試行錯誤の効率が違う・・と、考えてもれればいいんじゃないかと思います。 

一番単純なのが、SGD確率的勾配降下法)です。 

これは単純に一定の値を増やしたり、減らしたりするだけです。 

シンプルですけど、効率はよいとは言えない場合があります。 

上へ下へジグザグが大きくなる時があるわけですね。 

それを改善して、ジグザグが小さくなる方向に幅を調整する「Momentum」。 

学習の進むにつれて、学習係数を減衰させていく考え方で収束しやすくする考え方の「AdaGrad」。 

その両方を融合させた「Adam」。 

などが、工夫してあみだされてきました。 

他にも色々ありますが、上記の「SGD」「Momentum」「AdaGrad」「Adam」の4つが代表的な「Optimizer」ですから、まあ、この4つを知ってれば、お勉強レベルなら大丈夫なはずです。 

たいていの場合、一番すぐれているのは「Adam」です。 

だから、ニューラルネットワークコンソールも、デフォルトは「Adam」です。 

なので、チューニングのためにあえて変更する必要はないのですけど、遊び感覚で、Optimizerを変更して、学習の結果グラフがどう変わって、正解率がどう変わるか? 

これを見るのは面白いと思って、とりあげてます。  

Optimizerの変更で影響があるかを試す 

やってみましょう。 

変更は、COFIGタブで行います。

f:id:arakan_no_boku:20170827152527j:plain

 

左側のOptimizerを選択して、プルダウンで変更し、上書き保存するだけです。 

今回、試しに「AdaGrad」を選んでやってみました。

f:id:arakan_no_boku:20170827152656j:plain

 

Adamの時に比べて、若干前半部分の凸凹が大きいのがわかりますか。 

そんな大きな差ではありませんけどね。 

ただ、正解率は98.4%と、0.6%下がりました。

f:id:arakan_no_boku:20170827152811j:plain

意外と微妙なものだということがわかりますね。 

ではでは。

2017/12/01追記

ニューラルネットワークコンソールのVersion1.10でアイコンデザインが変更になったのはここです。

f:id:arakan_no_boku:20171130202351j:plain