勾配消失を24層のネットワークで実測したら、sigmoidだけテスト精度0.49(当てずっぽう)まで崩壊し、勾配が15桁消えていた
約16分で読めます

勾配消失を24層のネットワークで実測したら、sigmoidだけテスト精度0.49(当てずっぽう)まで崩壊し、勾配が15桁消えていた


前回の重み初期化の記事を書いたあと、noteを巡回していたら「ReLUはsigmoidを区分線形に簡略化しただけのもの」という趣旨の投稿を見かけた。たしかに両者は「小さい入力では0付近、大きい入力では増加する」という大まかな形が似ていなくもない。でも、この説明はどうも引っかかった。sigmoidは出力が[0,1][0,1]に収まる「飽和する」関数で、ReLUは正の領域で青天井に伸び続ける「飽和しない」関数だ。この違いは些細な近似誤差では済まない気がする。今回は、この俗説を(1)関数としての当てはめ、(2)実際のニューラルネットの学習という2つの角度から検証する。

実験に使ったコードの全文はGitHubに置いています

検証1: sigmoidに一番フィットするReLUを探す

まず素直に、sigmoid(x)に一番近くなるように scale・ReLU(x + shift) + offset の3つのパラメータを最小二乗フィットさせてみた。

def scaled_relu(x, scale, shift, offset):
    return scale * relu(x + shift) + offset

popt, _ = curve_fit(scaled_relu, xs, sigmoid(xs), p0=[0.25, 0, 0])

フィット窓を x[8,8]x \in [-8, 8] に取ると、最良フィットは scale=0.1009, shift=4.5522, offset=0.0029 で、R²=0.922。数字だけ見ると「悪くない近似」に見えなくもない。

左パネルはフィット窓x∈[-8,8]の範囲で、sigmoid(x)の滑らかなS字曲線(赤)と、最良フィットのscaled ReLU(青破線)を重ねて描いた図。青の直線はx=-4.55付近で折れ曲がり、そこから右肩上がりの直線として伸びており、赤いS字曲線とその範囲内ではそれなりに近い軌道を辿っている(R²=0.922)。右パネルは同じ2本の曲線をx∈[-30,30]まで外挿した図で、左パネルのグレーの帯がフィット窓を示す。赤のsigmoidはx=10以降ずっと1.0で水平に飽和し続けるのに対し、青のscaled ReLUは飽和せず右肩上がりの直線のまま無限に伸び続け、x=30時点で最大誤差2.49まで大きく乖離している

ところが、このR²=0.922という数字は「フィット窓の中でだけ」の話だ。同じ係数のまま、フィット窓の外(x[30,30]x \in [-30, 30])まで評価範囲を広げると話が変わってくる。

x=30時点:  sigmoid(30) = 1.0000 (飽和したまま)
           最良フィットのscaled ReLU(30) = 3.49 (飽和せず伸び続ける)
窓外(x∈[-30,30])での最大誤差: 2.49

sigmoidはxxがどれだけ大きくなっても1.0で頭打ちになるのに対し、ReLUは同じ係数のまま伸び続けるので、x=30x=30の時点で早くも3.49という、確率としてはあり得ない値まで乖離してしまう。「窓の中でだけそれらしく見える近似」と「その関数の本質的な振る舞い」は別物だ、というのがこの最初の検証で分かったことだった。

検証2: 深いネットワークで実際に学習させる

関数としての形の違いは分かったが、もっと実務的に効いてくるのは「深いネットワークで学習させたときにどう違うか」だ。ReLU・GELU・tanh・sigmoidの4つの活性化関数について、隠れ層の深さを1から24まで振った幅32のMLP(手書きnumpy実装、Adam)をtwo-moonsで学習させ、(a)初期化直後の1層目の勾配の大きさ、(b)実際に学習させた後のテスト精度、を比較した。

そもそも何をやらせるのか: two-moonsと「テスト精度」の中身

本題に入る前に、土台になるデータとタスクを先に見せておく。make_moons(two-moons)は、2つの三日月型の点群が互い違いに絡み合った、2クラス分類用の定番の人工データだ。今回は1000点をnoise=0.25で生成して座標を標準化し、先頭700点を訓練データ、残り300点をテストデータに分けた。

左パネルは訓練データ700点、右パネルはテストデータ300点の散布図。どちらも標準化済みの2次元平面上に、青いclass 0(上の三日月)と赤いclass 1(下の三日月)の点群が互い違いに絡み合う三日月型に分布している。noise=0.25のため三日月の輪郭はにじんでおり、2つのクラスの境目付近では点が入り混じっている。右パネルの凡例にはテスト300点の内訳がclass 0が153点・class 1が147点と示されている

タスクは「平面上のある点が、上の三日月(class 0)と下の三日月(class 1)のどちらから生まれたかを当てる」こと。この後ずっと出てくるテスト精度とは、学習中に一度も見せていない右側の300点をモデルに分類させたときの正解率のことだ。2クラスがほぼ半々に混ざっているので、何も学習できていないモデル(全部同じクラスに投票するだけ)の精度はおよそ0.5——これが「チャンスレベル」で、後半でsigmoidが叩き落とされる場所でもある。noise=0.25で境目の点が多少入り混じっているぶん、完璧な分類器でも精度1.0にはならず、今回の設定ではおおむね0.93〜0.95あたりが「ちゃんと学習できたモデル」の水準になる。

初期化はそれぞれのベストコンディションで

ACTIVATIONS = {
    "ReLU":    (relu,    d_relu,    "he"),      # He初期化
    "GELU":    (gelu,    d_gelu,    "he"),
    "tanh":    (tanh,    d_tanh,    "xavier"),   # Xavier初期化
    "sigmoid": (sigmoid, d_sigmoid, "xavier"),
}

各活性化関数には、それぞれに合った原理的な初期化(ReLU/GELUはHe、tanh/sigmoidはXavier)をきちんと使っている。つまりこれは「sigmoidをわざと不利な条件で戦わせた」実験ではなく、各活性化関数にとってのベストコンディションで戦わせたうえでの比較だ。

勾配は、sigmoidだけが指数的に消えていった

学習を始める前、初期化した直後の状態で1回だけ順伝播・逆伝播をして、ネットワークの入力に一番近い1層目の勾配の大きさ(重み勾配の絶対値平均)を深さごとに記録した。

横軸に隠れ層の深さ(1から24)、縦軸に初期化直後の1層目の勾配の大きさ(logスケール)を取った折れ線グラフ。ReLU(青)・GELU(緑)・tanh(紫)の3本はいずれも10^-2から10^-3程度の狭い範囲でほぼ横ばいに推移しているのに対し、sigmoid(オレンジ)だけが深さ1の1.1×10^-2から深さ24の2.1×10^-17まで、右肩下がりの直線としてほぼ一直線に、指数的に落ち続けている

              depth=1     depth=2     depth=4     depth=8     depth=12    depth=16    depth=24
ReLU:         2.87e-02    2.08e-02    3.00e-02    7.32e-03    5.89e-03    5.89e-03    4.60e-03
GELU:         2.93e-02    1.98e-02    3.13e-02    5.28e-03    2.86e-03    3.28e-03    2.16e-03
tanh:         4.64e-02    4.64e-02    5.72e-02    3.42e-02    4.31e-02    1.92e-02    1.59e-02
sigmoid:      1.14e-02    3.07e-03    2.14e-04    5.54e-07    2.40e-09    2.34e-12    2.05e-17

ReLU・GELU・tanhの3つは、深さが24まで増えても勾配の大きさはだいたい10210^{-2}10310^{-3}の範囲に収まっている。ところがsigmoidだけは、深さ1の1.14×1021.14 \times 10^{-2}から深さ24では2.05×10172.05 \times 10^{-17}まで、15桁消えてしまった。前回の記事で見た「初期化のスケールを間違えると勾配が桁違いに崩れる」という現象と数字の規模感としては近いが、あちらは初期化のスケールという「設定ミス」が原因だったのに対し、今回は各活性化関数にとって正しい初期化を使ってもなお、sigmoidという関数の性質そのものが原因で崩壊しているという違いがある。

テスト精度も、sigmoidだけが「チャンスレベル」まで落ちた

勾配が消えることが、実際の学習結果にどう響くかも確認した。

横軸に隠れ層の深さ、縦軸にtwo-moonsのテスト精度を取った折れ線グラフ。灰色の破線は2値分類のチャンスレベル(0.5)を示す。ReLU(青)とGELU(緑)は深さ1から24まで一貫して精度0.89〜0.95の範囲を保っている。tanh(紫)は深さ12を過ぎたあたりから緩やかに低下し始め深さ24では0.80まで下がる。sigmoid(オレンジ)は深さ8までは0.93前後を保っているが、深さ12で0.803に下がり、深さ16で一気に0.49まで急落し、深さ24でも0.49のままチャンスレベルに張り付いている

              depth=1  depth=2  depth=4  depth=8  depth=12  depth=16  depth=24
ReLU:         0.943    0.953    0.937    0.927    0.947     0.947     0.943
GELU:         0.943    0.927    0.940    0.930    0.910     0.890     0.950
tanh:         0.947    0.920    0.923    0.927    0.940     0.837     0.800
sigmoid:      0.953    0.943    0.933    0.933    0.803     0.490     0.490

ReLUとGELUは深さ24まで一貫して0.89〜0.95の範囲を保った。tanhは深さ16あたりから緩やかに崩れ始め、深さ24では0.80まで落ちる。そしてsigmoidは、深さ12ではまだ0.803と持ちこたえていたのに、深さ16で一気に0.490まで急落し、そのまま深さ24でも0.490。この0.490という半端な数字にも実は意味がある。先ほど見せたとおりテスト300点の内訳はclass 0が153点・class 1が147点なので、全部をclass 1に投票すると精度はちょうど147/300 = 0.490になる。つまりこのモデルは文字通り「全部同じクラスに投票しているだけ」の状態まで落ちている。勾配がほぼゼロになった時点で、そこから先の重みは事実上ずっと初期値のまま動いていない、ということになる。

決定境界の「形」も別物だった

最後に、まだ全員がそれなりに学習できている深さ6の時点で、4つの活性化関数それぞれの決定境界(色=予測確率)を並べてみた。

深さ6層での4活性化関数(ReLU・GELU・tanh・sigmoid)それぞれの決定境界を横に並べた図。色は予測確率で赤に近いほどクラス1、青に近いほどクラス0への確信度が高いことを示す。ReLU・GELU・tanhの3枚は、境界線(黒線)のすぐ外側でほぼ真っ赤または真っ青の濃い色に切り替わっており、中間的な薄い色の帯がほとんど見えない。一方sigmoidの1枚だけは、境界線の周りに薄い青〜白〜薄い赤へと連続的に変化する幅広いグラデーション帯がはっきり見えており、確信度が急激に切り替わらず緩やかに変化している

4枚ともテスト精度自体は0.927〜0.943とほぼ横並びだが、予測確率の「切り替わり方」がsigmoidだけ明らかに違う。ReLU・GELU・tanhは、境界線からわずかに離れただけでほぼ確信度100%(真っ赤/真っ青)に切り替わっているのに対し、sigmoidだけは境界線の周りに薄い色のグラデーション帯が広く残っていて、確信度が緩やかにしか上がらない。同じ精度でも、モデルが「自信を持って言い切る」範囲がこれだけ違う、というのは決定境界を実際に描いてみるまで気づかなかった点だった。

正直に書いておくべきこと

まず、検証1のカーブフィットは「1個の折れ線でsigmoid全体を近似する」という、そもそも無理のある設定だ。区分線形近似というアイデア自体は、十分な数の折れ線(区分)を使えば任意の関数に近づけられる(区分線形近似の一般論としては正しい)。今回否定したのはあくまで「ReLU1本がsigmoidの簡略版」という俗説の方で、「複数のReLUを組み合わせれば滑らかな関数を近似できる」という、ニューラルネットそのものの前提を否定しているわけではない。

次に、検証2はtwo-moonsという比較的単純な2次元タスク、幅32・Adam・学習率0.01という1組の設定でしか確認していない。タスクや最適化手法、学習率を変えれば、sigmoidが崩壊し始める深さの閾値(今回は深さ12〜16の間)は変わるはずだ。ただ、ReLU/GELU系とsigmoid/tanh系とで崩壊の仕方が定性的に異なるという傾向自体は、勾配消失の一般的な理論(sigmoidの微分の最大値が0.25しかなく、深さLL層で0.25L0.25^Lのオーダーで縮む)からも支持される内容で、この設定に固有の偶然ではないと考えている。

手を動かして意外だったこと

一番意外だったのは、崩壊が緩やかな坂道ではなくだったことだ。sigmoidのテスト精度は深さ12の0.803から深さ16の0.490まで、1段階でほぼ全部失われた。「深くするほど少しずつ性能が落ちる」というイメージを持っていたが、実際には「ある深さまでは持ちこたえて、そこを超えると一気に壊れる」という崖状の崩壊だった。勾配が指数的に減っていくグラフ自体は滑らかな直線(logスケールで)なのに、その結果として起きる学習の成否は、なぜかなだらかには壊れず、閾値を境にオン/オフのように切り替わる。

まとめ

  • 「ReLUはsigmoidの簡略化」という俗説を、(1)カーブフィット、(2)実際の深層MLP学習、の2方向で検証した
  • カーブフィットでは、フィット窓x[8,8]x\in[-8,8]内ではR²=0.922とそれらしく見えた最良フィットのscaled ReLUも、窓の外(x[30,30]x\in[-30,30])に広げると最大誤差2.49まで乖離した。sigmoidが飽和する一方、ReLUは飽和せず無限に伸び続けるという本質的な違いが原因
  • 幅32・深さ1〜24のMLP(各活性化関数に適した初期化)で、初期化直後の1層目の勾配の大きさを比較したところ、ReLU/GELU/tanhは深さ24でも10210^{-2}10310^{-3}台を維持したのに対し、sigmoidだけが深さ1の1.14×1021.14\times10^{-2}から深さ24で2.05×10172.05\times10^{-17}まで15桁消えた
  • 実際にtwo-moonsで学習させたテスト精度も、ReLU/GELUは深さ24まで0.89〜0.95を維持したが、sigmoidは深さ12の0.803から深さ16で0.490(チャンスレベル)まで崖状に崩壊した
  • 深さ6(全員がまだ学習できている時点)での決定境界を比較すると、精度はほぼ同じでもsigmoidだけ境界周りに幅広い滑らかな確率勾配帯を残しており、他の3つは境界のすぐ外でほぼ確信度100%に切り替わっていた

最後に少し私事を。この実験をしながら思い出したのは、昔上司から言われた「似ているように見える2つの選択肢ほど、実は根本が違うことがあるから注意しろ」という言葉だった。sigmoidとReLUは、グラフを遠目に見れば「小さい入力でほぼ0、大きい入力で増加する」という同じような輪郭に見える。でも近づいて、極端な状況(今回で言えば「深いネットワーク」や「窓の外の入力値」)まで条件を広げてみると、片方は飽和して止まり、もう片方は際限なく走り続ける、という全く違う性質が顔を出す。仕事でも、2つの案が「だいたい同じような結果になりそう」に見えるとき、実はその判断は自分が試した範囲(フィット窓)の中でしか成り立っていないことがある。条件を思い切って極端にずらしてみて、それでも同じように振る舞うかを確かめるまでは、「似ている」と結論づけるのは早いのだと思う。