
二重降下を実験で再現した。補間閾値でテスト誤差が76.8倍に跳ね上がり、そこから64分の1まで下がったが、シンプルな次数5モデルには最後まで届かなかった
「モデルを複雑にしすぎると過学習してテスト誤差が悪化する」——これは機械学習の教科書に必ず出てくる話で、モデルの複雑さ(パラメータ数)を横軸、テスト誤差を縦軸に取ると、単純すぎる(過小適合)と複雑すぎる(過学習)の間に最良点があるU字カーブになる、と説明される。過学習の記事でもこのU字を実際に確認した。
ところが現代の機械学習には、この話の続きがある。パラメータ数をU字の底からさらにどんどん増やし続けると、あるところ(補間閾値、モデルが訓練データにギリギリぴったり適合できるパラメータ数)でテスト誤差が跳ね上がった後、さらに増やし続けると今度はテスト誤差が再び下がってくる、という現象がある。double descent(二重降下)と呼ばれている。grokkingの記事で見た「訓練が終わってから遥か後に何かが起きる」のと同じ系統の、直感に反する現象だ。
話だけ聞くと出来すぎに聞こえるので、今回も実際に手を動かして確かめた。
実験設定: 25点のノイズ入り多項式回帰
真の関数を ()とし、標準偏差0.3のノイズを乗せて訓練データはわずか25点用意する。これに次数を1から1000まで振った多項式をフィットさせ、パラメータ数(=次数+1)ごとにテスト誤差がどう動くかを追う。
def true_fn(x):
return np.sin(1.5 * np.pi * x) + 0.5 * x
x_tr = rng.uniform(-1, 1, size=25)
y_tr = true_fn(x_tr) + rng.normal(0, 0.3, size=25)
モデルの仕事は、学習に使っていない新しいに対してを言い当てることだ。そこで「テスト誤差」の定義も先にはっきりさせておく。訓練に使う25点とは別に、まったく同じ作り方(をから一様に引き、真の関数の値に標準偏差0.3のノイズを乗せる)でテストデータを1000点用意し、そこでの予測のMSE(平均二乗誤差)を測る。訓練25点に対してテスト1000点という数字の非対称が気になるかもしれないが、この2つは役割がまったく違う。25点という少なさは今回わざと作っている「データに対してモデルが大きすぎる状況」そのもの——実験対象だ。一方の1000点はモデルには一切見せない、採点専用の物差しで、テスト点まで少ないと誤差の値そのものが運でブレて、後で出てくる0.186と0.223のような差を安定して比べられなくなる。なお、テストデータのにもノイズが乗っているので、仮に真の関数を完璧に言い当てる理想のモデルでも、テストMSEは約0.09(=0.3²)より下には下がらない。以降の数字は、この理論下限を頭の隅に置いて読むと位置づけがわかりやすい。
補間閾値は、パラメータ数がちょうど訓練データ数と一致するパラメータ数25(次数24)にあたる。ここでモデルは訓練データ25点にぴったり適合できる自由度を初めて持つ。パラメータ数がそれ以下なら、未知数(パラメータ)より方程式(データ点)の方が多い優決定の状況なので、普通の最小二乗で解が一つに決まる。それを超えると逆に未知数の方が多い劣決定になり、訓練誤差を最小にする解が無数に存在するようになるので、そのうちノルムが最小のものを選ぶ——これが二重降下を語るときの標準的な設定だ。
最初の失敗: 正則化なしのridgeless解は10^20のオーダーで破綻する
多項式の特徴量は という生の累乗ではなく、Legendre多項式という直交多項式基底で作った(表現できる関数空間は「次数以下の多項式全体」で同じだが、生の累乗は次数が上がるほどVandermonde行列の条件数が爆発的に悪化することが知られているので、数値的にまだましなこちらを選んだ)。
それでも、劣決定領域の最小ノルム解をnumpy.linalg.lstsq(rcond=None)で厳密に(正則化ゼロで)求めたところ、補間閾値の周辺でテスト誤差が意味をなさない値まで爆発した。
degree=20 params=21 test_mse=4.17e+11
degree=21 params=22 test_mse=5.03e+13
degree=22 params=23 test_mse=3.99e+16
degree=23 params=24 test_mse=1.94e+20 <- 最大
degree=24 params=25 test_mse=1.66e+16 (補間閾値)
degree=25 params=26 test_mse=2.01e+19
最大でテスト誤差1.94×10^20。これは「テスト誤差が悪化した」というレベルではなく、浮動小数点演算がほぼ限界に達しているサイズの数字だ。理論上、補間閾値付近では最小ノルム解の分散が本当に発散しうるので現象自体は本物なのだが、この規模の数字は数値誤差にほぼ支配されていて、意味のある比較には使えない。そこで、ごく小さいridge項()を足して数値的に安定させた解を、以降の「事実上のridgeless解」として使うことにした。

二重降下の全体像: パラメータ数6→19→1001
500回、訓練データとテストデータをそれぞれ独立に引き直して平均を取った結果が以下だ(横軸・縦軸とも対数スケール)。
- U字の底(過小適合と過学習の間の最良点): パラメータ数6(次数5)でテスト誤差0.186
- ピーク: パラメータ数19(次数18)でテスト誤差14.31——最良点の76.8倍
- 補間閾値(パラメータ数25、次数24): テスト誤差12.36。訓練誤差は0.017(tiny ridgeのせいで完全な0ではないが、正則化なしの単発試行では確かに0.00000まで落ちていた)
- パラメータ数81(次数80): テスト誤差1.81。ピークからここまでで7.9分の1まで下がった(まだ途中経過)
- パラメータ数1001(次数1000): テスト誤差0.223。ピークからは最終的に64.3分の1まで下がったが、U字の底の0.186と比べるとまだ1.19倍悪い

ここで正直に書いておきたいのは、ピークの位置が理論的な補間閾値(パラメータ数25)ぴったりではなく、少し手前のパラメータ数19だったことだ。500回の平均を取ってもこの位置は安定していた。中央値で見ると話は少し変わり、パラメータ数22で8.30、26で8.28と、平均よりも補間閾値に近い位置に山ができる。つまり平均のピークが手前にずれていたのは、パラメータ数14〜20あたりで時々出る「一部の試行だけ極端にテスト誤差が跳ねる」外れ値に平均が引っ張られていたためらしい——中央値の方が理論の言う「ピークは補間閾値で」に近い形をしていた。きれいな理論通りの一点スパイクを期待していたが、実際にはもう少し幅を持った、しかも平均と中央値で位置がずれる荒っぽい山だった。
フィットの中身を並べてみる
同じ25点の訓練データに対して、パラメータ数を変えたときにフィット曲線がどう変わるかを見ると、この数字の意味がわかりやすい。

次数2はそもそも真の関数のうねりを表現しきれていない。次数5は真の関数にほぼ重なる理想的なフィット。次数18になると、25個の訓練点をほぼすべて正確に通そうとする代償として、データがまばらな端(x=1付近)で予測が暴れ、表示範囲の上限近くまで跳ね上がっている——これがテスト誤差14.31の正体だ。次数80まで来ると、全体に細かい波(高周波のノイズ成分)が乗るが、次数18で見られたような局所的な暴走は影を潜め、真の関数の形はむしろ次数18より捉えられている。
容量を上げていく様子をアニメーションで
次数を0から80まで動かしながら、左にフィット曲線、右に現在位置を示すテスト誤差カーブを並べたアニメーションを作った。

次数30あたりでx=1近くのデータ点(訓練データが疎な端)のところだけ予測が突き抜けているのが、フィット曲線を実際に眺めると一番わかりやすい。データが密な中央部分はどの次数でもそれなりに真の関数を捉えているのに、端の1〜2点だけが最小ノルム解の暴走を引き起こしている。
二重降下は「U字の最良点」を超えたのか
これが今回いちばん誠実に書きたいところだ。二重降下という現象自体は確かに起きた——ピーク(14.31)から先、パラメータ数を増やすほどテスト誤差はどんどん下がっていく。ただしパラメータ数1001(補間閾値の40倍)まで押し切っても、U字の底(パラメータ数6、0.186)を下回ることは一度もなかった。
念のため、さらにパラメータ数を1500・2000まで確認する追加実験もしたが、テスト誤差は0.21〜0.22のあたりに留まったままで、0.186を下回る気配はなかった。教科書やよく引用される図では「過剰パラメータ化を推し進めると、最終的にはU字の最良点よりさらに良くなることもある」という書かれ方をすることがあるが、少なくとも今回のこの設定(25点、ノイズ標準偏差0.3、この真の関数)では、それは起きなかった。二重降下で「下がる」ことと、「そもそも身の丈に合った次数5のシンプルなモデルを選んだ場合を上回る」ことは別の主張で、前者は再現できたが後者は再現できなかった、というのが正直な結果だ。
過学習の記事で「次数14の過学習しかしないモデルでも正則化さえ効かせれば次数4のちょうど良いフィットに近いところまで戻せる」という結果を見たが、それと似ている。無理に複雑にしてから力技で戻すより、最初から身の丈に合った複雑さを選んだ方が、少なくとも今回試した範囲では強かった。
手を動かして意外だったこと
一番怖いと思ったのは、「モデルを複雑にしていく途中でやめる」のが一番危険な選択肢になり得るということだ。パラメータ数19(補間閾値のすぐ手前)で実験を打ち切っていたら、「複雑にするほど悪化する」という結論で終わっていただろう。実際その時点のテスト誤差は、シンプルな次数5モデルの76.8倍という、実用に耐えない数字だった。そこからさらに勇気を出して(あるいは単に計算資源を惜しまずに)パラメータ数を増やし続けたからこそ、64分の1まで戻ってくるのが見えた。
ただし同時に、最後まで単純なモデルには追いつかなかったという事実も、都合よく忘れてはいけないと思う。「行けるところまで行けば必ず報われる」わけではない。中途半端に手を広げて「ギリギリ全部を覚え込んだ」状態で立ち止まるのが一番まずく、そこを突き抜ければ状況はかなり改善するが、それでも最初から絞り込んで身の丈に合ったやり方を選んでいた場合には、最後まで届かないことがある。手を広げるなら中途半端で止めない、しかし手を広げること自体が常に最善とは限らない——両方が同時に本当だった、というのが今回の実験から得た、少し据わりの悪い教訓だった。
まとめ
- 25点のノイズ入り(標準偏差0.3)多項式回帰で、Legendre基底・パラメータ数1〜1001の範囲で二重降下を再現した
- 厳密なridgeless最小ノルム解は補間閾値付近でテスト誤差が最大1.94×10^20まで数値的に破綻し、λ=10^-4のtiny ridgeで安定化させて以降の実験を行った
- U字の底はパラメータ数6(次数5)でテスト誤差0.186。ピークはパラメータ数19(次数18)で14.31(最良点の76.8倍)。500回平均でのピーク位置は理論上の補間閾値(パラメータ数25)よりやや手前にずれ、中央値ベースではより閾値に近い位置に山ができた
- ピークからパラメータ数81まで押し切ると7.9分の1、パラメータ数1001まで押し切ると64.3分の1までテスト誤差は下がった
- ただしパラメータ数1001(補間閾値の40倍)まで確認しても、U字の底の0.186を下回ることは一度もなかった(1500・2000での追加確認でも同様)。二重降下で「下がる」ことと「シンプルな最良モデルを上回る」ことは別の話だと、実際に数値で確認できた


