【勾配消失と勾配爆発はなぜ起こるのか?】その理由をExcelで追跡してみる

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

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

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

 

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

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

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

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

 

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

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

 

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

 

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

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

そこで、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を超えてしまい、シグモイド関数の導関数はゼロになってしまいます。

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

勾配消失です。

 

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

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

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

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

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

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

 

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

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

 

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

最大でも0.25なのです。

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

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

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

勾配消失です。

 

このように活性化関数にシグモイド関数を使うと、導関数の値が最大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で用いる場合が多いようです。

 

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

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

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

 

勾配爆発問題

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

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

 

 

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

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

 

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

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

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

そのため勾配消失はしませんが、uがとても大きな負の数(10の-15乗)になっているため0.01を掛けても焼け石に水で、勾配は負の数で発散してしまっています。

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

 

なぜuが大きくなるのかというと、前の層からの出力信号が大きすぎるからです。

シグモイド関数を活性化関数として使っていれば出力は0から1に調整されますが、Leakly ReLU関数を使うと正の数ならそのままの値が出力されるため、上限のリミットがかかりません。

今回の場合、uは負の数ですのでu×0.01が出力されますが、それでも焼け石に水です。

そのため、一旦前の層から大きな信号をもらうと、その信号はどんどん増幅されてしまい勾配爆発に至ります。

 

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

プログラマーができることとしては、Leakly ReLU関数の負の領域での勾配を調整することが考えられます。

この例ですと0.01にしましたが、これですとuが10の-15乗レベルだと焼け石に水でした。

これを0.00001にしても、なお焼け石に水ですが、uを-7乗レベルにまで抑えられれば勾配爆発には至りません。

ただ今度はuが小さな数の場合には勾配消失が起きる可能性がありますので、その辺りはプログラマーの調整加減によってくると思います。

 

このように勾配消失や勾配爆発問題に限らず、学習率の決め方隠れ層のユニット数の設定など、ニューラルネットワークの調整はかなりデリケートで、試行錯誤による調整分野がまだまだ残っていると感じます。