【勾配消失と勾配爆発はなぜ起こるのか?】その原因と対策をExcelで調べてみた

2024年5月18日

◆仕事や勉強の息抜きに。。。

Excelだとわかる勾配消失と勾配爆発の原因

Excelでニューラルネットワークの実装を行うと、Pythonにあるようなライブラリーがないためとても手間がかかるのですが、利点もあります。

一つは、アルゴリズムをきちんと理解できること。

これは、自分できちんと理解していないと実装できないからです。

 

もう一つは、パラメータが収束しない時、その原因究明がやり易いことです。

Excelシートにすべての計算ステップが残るので行数はとても多くなりますが、途中経過が計算式とともにすべて見れるので、どこが悪いのか発見しやすいのです。

原因の多くは計算式の入力ミスなのですが、計算式が正しくても勾配消失勾配爆発が原因の時もあります。

そんな時にも何が原因なのか、はっきりわかります。

 

勾配消失とは、勾配降下法により計算されるすべてのパラメータの勾配がゼロになってしまうことです。

一旦すべての勾配がゼロになってしまうと、パラメータは更新されなくなってしまい、それ以上の学習が進みません。

 

逆に勾配爆発とは、勾配が大きくなりすぎてパラメータが大きくなり過ぎてしまい発散してしまうことです。

 

これら勾配消失と勾配爆発は、隠れ層が多いディープラーニングにおいてよく問題になることですが、隠れ層が1層しかなくても条件が揃えば起こります。

そして、その条件はディープラーニングで起こる問題の真因であったりします。(たぶん)

そこで、Excelで見たらどのように原因が見れるのかを以下ご紹介します。

 

勾配消失の原因をExcelで調べる

勾配消失を再現してみる

まずは勾配消失を発生させてみましょう。

入力データが5画素の輝度データを合計した値の場合、その値は最大で1,250くらいになります。

それをそのまま入力とし、パラメータの初期値を0~1の乱数とした場合の様子を以下に示します。

クリックすると拡大します

 

クリックすると拡大します

 

見事にすべての勾配が始めから消失しました。

なぜ消失したのかを追ってみましょう。

 

シグモイド関数の導関数がゼロになることが原因

パラメータの勾配はいくつかありますが、その中の一つがどのように計算されたかを見てみます。

下図の①の勾配はこのように計算されています。

クリックすると拡大します

 

2つの式を合せると、①は②×④×⑤で計算されますが、このうち⑤がゼロになっています。

これは隠れ層活性化関数の導関数です。

クリックすると拡大します

 

ここでは活性化関数はシグモイド関数を使っています。

なぜシグモイド関数の導関数がゼロになるかを調べるために、シグモイド関数の性質から見てみましょう。

 

シグモイド関数の導関数がゼロになるのは入力が大きすぎるため

シグモイド関数は次式で表されます。

f(x) = 1/(1+e-x)

 

グラフでは次のようになります。

 

xがゼロの時のf(x)は0.5、xがゼロからマイナス方向に離れるつれてf(x)はゼロ、xがゼロからプラス方向に離れるにつれてf(x)は1に近づくという性質を持っています。

つまりxが両極端の値を取る時には0や1になるのですが、そうでない時には0から1の間の値を取ります。

しかし、グラフを見ると分かるように、f(x)が0から1の間の数を取るxの区間は、非常に狭いです。

大体、0±4の区間しかありません。

xがこの区間を外れるとf(x)は0か1になります。

 

次に、シグモイド関数の導関数を見てみます。

シグモイド関数とその導関数には、次式の関係があります。

f’(x) = (1-f(x))/f(x)

 

この関係式を使ってシグモイド関数の導関数のグラフを描くと、次のようになります。

 

この導関数はxがゼロでは0.25、そこから離れるにつれてゼロになる関数です。

xが0±5くらいでは0から0.25の間の値になりますが、この範囲を離れるとゼロになります。

ゼロになるということは勾配がゼロになるということですので、パラメータは更新されません。

つまり、xは0±5という非常に限られた範囲でないとパラメータは更新されません。

 

勾配降下法の計算においてxとは前の層からの出力に重みを掛けた線形和です。

ですので、前の層の出力の値が大きいと線形和が大きくなり、簡単に5を超えてしまうことがあります。

重みが1以下でも、前の層からの出力が何百という数だと簡単に5を超えてしまい、シグモイド関数の導関数はゼロになってしまいます。

すると勾配を求める式にはシグモイド関数の導関数が掛け算で入っているため、勾配がゼロになってしまうのです。

勾配消失です。

これを回避するには、Batch normalizationのように入力を正規化するのか有効だと思います。

Batch normalizationの逆伝播の算出式を計算グラフを辿って求める

 

隠れ層が多いと勾配消失し易い

一方、ディープラーニングでは事情が異なります。

たとえ入力が大きくならないように調整したとしても、勾配消失する可能性が高くなります。

誤差逆伝播法では出力と教師データとの誤差が、出力層から逆伝播していって先頭の隠れ層の重みの勾配が求められます。

これは先頭の隠れ層から出力層まで、複数の線形関数や活性化関数が連鎖しているからです。

例えば、隠れ層が5層ある場合には、各隠れ層にある活性化関数と出力層の活性化関数を合せて6つの活性化関数が、関数の連鎖の中に含まれています。

ということは、先頭の隠れ層の重みの勾配の計算式には、活性化関数の導関数が6つ掛け算で含まれます。

 

活性化関数がシグモイド関数なら、どういうことになるでしょう?

シグモイド関数の導関数のグラフをもう一度見てみましょう。

 

xがゼロの時に0.25の最大値を取ります。

最大でも0.25なのです。

グラフを見ると分かるように、xがゼロから2ずれるだけでも0.1に急減します。

仮に6つのシグモイド導関数の値がすべて0.1だとすると、勾配は0.000001倍されてしまうのです。

あっという間に勾配はゼロ近辺になってしまいます。

勾配消失です。

 

隠れ層が多い場合の対策はReLU関数

このように活性化関数にシグモイド関数を使うと、導関数の値が最大0.25にしかならないため、勾配消失が起こりやすくなります。

そのために、隠れ層が深い場合にはReLU関数(Rectified Linear Unitがよく用いられます。

ReLU関数は次式で定義されます。

f(x) = x (x0)

f(x) = 0 (x<0)

xが正の時にはxの値をそのまま、負の時にはゼロを出力する関数で、グラフに描くとこうなります。

 

ReLU関数の良い点は、xが正であれば導関数が常に1になることです。

xをxで微分すると1ですので。

従って、隠れ層が100になっても1,000になっても、活性化関数の導関数のせいで勾配消失が起こることはありません。

 

一方、欠点はxが負になると導関数はゼロになってしまうことです。

前の層からの出力の線形和は常に正である必要があります。

しかしこれはニューラルネットワークが決める重みによりますので、担保できません。

従って、その点を改良したLeaky ReLU関数が使われます。

グラフにするとこうなります。

 

xが負の時に常にゼロではなくて、微妙に下がってますね。

Leakyとは「漏れている」という意味ですが、確かに左へ行くに従い漏れているような形になっています。

これを式で表すと次のようになります。

f(x) = x (x0)

f(x) = 0.1x (x<0)

 

なぜこのように漏らすのかというと、xが負の時に微分してもゼロにならないからです。

この場合だと微分すると0.1になりますね。

ちなみに、この係数は0.1で固定ではありません。

通常は0.01で用いる場合が多いようです。

 

これで、隠れ層が多い場合に活性化関数が原因で勾配消失することはなくなりました。

しかし、これで安心はできません。

今度は勾配爆発する危険があるからです。

 

勾配爆発の原因をExcelで追跡する

勾配爆発を再現してみる

まずは先ほど勾配消失させた例で、勾配爆発させてみましょう。

ただし、今度は活性化関数をLeaky ReLU関数とします。

クリックすると拡大します

 

クリックすると拡大します

 

勾配爆発の原因は発散

なぜこうなってしまったのかを詳しく追ってみましょう。

下図の①の勾配はこのように計算されています。

クリックすると拡大します

 

2つの式を合せると、①は②×④×⑤で計算されます。

このうち⑤が隠れ層活性化関数の導関数で、これがゼロになると勾配消失しました。

クリックすると拡大します

 

今度はLeakly ReLU関数を使っていて、x(この場合はu)が負の数なので導関数は0.01になっています。

そのため勾配消失はしませんが、逆に-10の18乗 ⇒ +10の57乗 ⇒ -10の170乗というように勾配が大きく振動しながら発散しています。

クリックすると拡大します

勾配消失を避けるためにLeakly ReLU関数を使ったら、今度は勾配爆発を起こしてしまったわけです。

 

なぜこうも勾配が大きく振動してしまうのでしょうか?

重みパラメータは勾配により

次バッチの重み=現バッチの重み-学習率×勾配

の式で更新されます。

この時あまりにも勾配が大きすぎると、上式から明らかなように次バッチの重み大きな負の値になってしまうことがわかります。

ところで、逆伝播の勾配の計算式では重みは次式④の中に入っています。

クリックすると拡大します

 

この重み大きな負の値になると④はどうなるでしょうか?

活性化関数であるLeaky ReLU関数の負領域での勾配は0.01ですので、大きな負の値をとる勾配にこれが掛けられると少しはマシになるものの、④も大きな負の値になります。

従って、次に

次バッチの重み=現バッチの重み-学習率×勾配

で計算される重みは、大きな正の値になります。

すると逆伝播の計算式より、次に計算される勾配大きな正の値になります。

すると、

次バッチの重み=現バッチの重み-学習率×勾配

で計算される次の重み大きな負の値になります。

 

このように、重みも勾配も大きな負の値と大きな正の値を行ったり来たりして、振幅もどんどん大きくなります。

つまり発散してしまうわけです。

このように一旦勾配がある範囲から外れて、大きくなり過ぎたり小さくなり過ぎたりすると、発散してしまいます。

 

勾配爆発の対策には入力の正規化と初期値の設定の見直し

これを防ぐ有効な手立てはないのでしょうか?

先ほどの逆伝播の計算式を見ると②と④、つまり入力重みが極端な値にならないように注意すれば良いことがわかります。

まず入力については、絶対値が大きくならないように正規化するBatch normalizationが有効だと思います。

重みについては、初期値の設定が重要になってくると思います。

Xavierの初期値Heの初期値として理論化されていますので、そちらも参照してみて下さい。

【初期値の設定】

【勾配消失しない重みの初期値】Excelでモンテカルロシミュレーションしてみた

またディープな層になってくると初期値の効果も薄れてくるため、ここでもBatch normalizationが有効になってくると思います。

【Batch normalization】

Batch normalizationの逆伝播の算出式を計算グラフを辿って求める