「経験が浅いうちほど、ルールに頼った方がいい」を過学習の実験で確かめてみた
約13分で読めます

「経験が浅いうちほど、ルールに頼った方がいい」を過学習の実験で確かめてみた


新卒で入った現場や、新しいチームに配属された直後によくある話だと思う。まだ2〜3件しか案件を経験していないのに、その2〜3件から「このやり方が正しい」というルールを勝手に抽出して、次の案件にもそのまま当てはめてしまう。当然、外れることが多い。サンプル数が少なすぎて、それは「一般的な法則」ではなく、ただの「たまたま」だったりするからだ。

一方で、経験を十分に積んだ人ほど、逆に細かいルールやマニュアルへの依存が減っていく感覚もある。目の前の状況そのものから判断できるようになるからだと思う。

これって機械学習の**過学習(overfitting)正則化(regularization)**の関係にかなり近いのではないか、と思った。データ(経験)が少ないうちは、モデルの自由度を強く制約してあげないと、ノイズにまで無理やり適合してしまう。データが増えるほど、その制約は緩めていい。この直感が本当に数値として現れるのか、実際にPythonで確かめてみた。

過学習とは何か

適当な15個の点があったとき、それをどんな曲線で結ぶかは自由だ。直線で大雑把に近似することもできるし、点をすべて正確に通る複雑な曲線を無理やり作ることもできる。

後者は、手元の15点に対する誤差はゼロに近づけられる。しかし、その複雑な曲線は「たまたまその15点がそこにあった」というノイズの形まで再現してしまっているので、新しい16個目の点に対しては、とんでもなく外れた予測をすることがある。これが過学習だ。

これを抑えるための代表的な手法が正則化で、今回はその中でも典型的な**Ridge回帰(L2正則化)**を使う。損失関数に、モデルの係数の大きさそのものへのペナルティを足す。

minw  i(yifw(xi))2+αjwj2\min_{w} \; \sum_{i} (y_i - f_w(x_i))^2 + \alpha \sum_{j} w_j^2

α\alphaが正則化の強さで、大きくするほど「係数を無理に大きくしてまでデータに合わせにいく」ことにペナルティがかかり、モデルは滑らかで控えめな予測に寄っていく。α=0\alpha = 0なら通常の(制約なしの)回帰と同じになる。

実験の設定

真の関数を y=sin(2πx)y = \sin(2\pi x) とし、そこに標準偏差0.3のノイズを乗せて訓練データを作る。

TRUE_FN = lambda x: np.sin(2 * np.pi * x)
NOISE_STD = 0.3

def make_data(n, rng):
    x = rng.uniform(0, 1, size=n)
    y = TRUE_FN(x) + rng.normal(0, NOISE_STD, size=n)
    return x, y

訓練データはあえて15点だけにする。経験の少なさを表現するためだ。この15点に対して、次数1〜14の多項式を最小二乗法でフィットさせ、訓練誤差(手元の15点に対する誤差)とテスト誤差(別途300点用意した未知データに対する誤差)を比較する。

次数を上げると何が起きるか

同じ15点の訓練データに対して、次数1・4・14でフィットした結果を並べてみる。

同じ15点の訓練データに対する、次数1(過小適合)・4(ちょうど良い)・14(過学習)のフィットを示す3枚の折れ線グラフ。次数14では0.6〜0.8付近で予測曲線がy軸の範囲外まで激しく振動している

次数1では単純な直線で、真の関数(sin カーブ)を大まかにしか捉えられていない。次数4はほぼ真の関数に重なっていて、これが「ちょうど良い」フィットだ。そして次数14になると、15個の訓練データの点は正確に通っているが、データの隙間(特に0.6〜0.8のあたり)で予測曲線が異常な振動を起こし、グラフの表示範囲(-2〜2)を振り切っている。

数値で見ると、この振動がどれだけ危険かがよく分かる。

degree= 1  train_mse=0.1681  test_mse=0.3473
degree= 4  train_mse=0.0334  test_mse=0.1422
degree=14  train_mse=0.0050  test_mse=10171205.3168

次数14の訓練誤差は0.0050と、次数4(0.0334)よりずっと小さい。手元のデータには、より正確に適合できている。しかしテスト誤差は1,017万という、意味をなさないレベルにまで爆発している。

1回の試行だと運の要素もあるので、200回分の独立試行を平均して、次数ごとの誤差の推移を見てみる。

次数を上げるほど訓練誤差(青)は下がり続けるが、テスト誤差(橙)は次数3を底にしたU字を描いたあと、次数5あたりから指数関数的に爆発していく折れ線グラフ(縦軸は対数スケール)

訓練誤差は次数を上げるほど素直に下がり続ける(データにより正確に合わせられるようになるので当然)。一方でテスト誤差は次数3を底にしたU字を描いたあと、次数5を超えたあたりから桁が飛ぶように爆発していく。200回平均で見ても、次数14のテスト誤差はおよそ40億に達していた。訓練誤差だけを見て「モデルの性能が上がっている」と判断するのがいかに危険か、この乖離がはっきり物語っている。

次数を1から14まで動かしながら、フィット曲線とその時点までの誤差が動く様子をアニメーションにした。

次数を1から14まで動かしながら、左のパネルでフィット曲線が徐々に激しく振動していく様子と、右のパネルでtrain MSE(青)は横ばいのままtest MSE(橙)だけが跳ね上がっていく様子を同時に示すアニメーション

正則化で抑える

次に、次数14に固定したまま、Ridge回帰の正則化の強さα\alphaを変えてテスト誤差がどう変わるかを見る。

degree=14に固定してalphaを対数スケールで振ったときの訓練誤差(青)とテスト誤差(橙)の折れ線グラフ。テスト誤差はalphaがほぼ0のとき500近くまで跳ね上がり、alpha=8.377で最小の0.287を取ったあと再び緩やかに上昇する。単調なU字ではなく、alpha=1e-5付近に浅い谷、alpha=1e-3付近に小さな山がある

正則化なし(α0\alpha \approx 0)ではテスト誤差が約500。そこからα\alphaを上げていくと誤差は下がっていき、α=8.377\alpha = 8.377のときに最小値0.287を取る。正則化なしの500と比べると、1700分の1以下まで改善している計算だ。次数14という「過学習しかしないはずのモデル」でも、正則化さえ適切に効かせれば、次数4のちょうど良いフィット(テスト誤差0.142)にかなり近いところまで戻せる。

ただし、このグラフはきれいな単調なU字にはなっていない。α\alphaがとても小さい(10510^{-5}付近)ところに一度浅い谷があり、そこからα=103\alpha=10^{-3}付近まで一度盛り返してから、また下がって本当の最小値にたどり着く、という小さな波打ちが見える。「正則化を強めるほど単調に改善する」と決めつけてα\alphaを粗く探索していたら、この浅い谷を最良点と誤認していたかもしれない。ここは実際にグラフを描いてみて初めて気づいた点だった。

そしてα\alphaを上げすぎる(1000まで)と、今度は誤差が再び悪化していく。制約が強すぎて、モデルがsinカーブの形そのものを表現しきれなくなる、過小適合側の悪化だ。「正則化は強ければ強いほど良い」わけでもない、という点も、過学習を防ぐ話と同じくらい重要だと分かる。

次数と正則化、2次元で見ると

次数とα\alphaを両方振って、テスト誤差がどう変わるかをヒートマップにしてみた。

次数(縦軸)とlog10(alpha)(横軸)に対するテスト誤差のヒートマップ。左上(高次数×弱い正則化)は紫色でテスト誤差が非常に大きく、右下に向かうにつれて黄色(誤差が小さい)領域が広がる。ただし最も濃い黄色は次数3〜4×弱いalphaの領域に集中していて、最良点(赤い星)もそこにある

左上(次数が高いのに正則化がほぼ効いていない領域)は誤差が跳ね上がって紫色に染まっている一方、右下(正則化が強く効いている領域)にかけて広く黄色〜緑色の「安全地帯」が広がっている。次数を上げても、α\alphaを適切に強めれば被害を抑えられることが視覚的にも分かる。

ただし面白いのは、ヒートマップ全体で最も誤差が小さい点(赤い星)は、次数14側ではなく次数3、α\alphaがごく小さい(0.00237)あたりに位置していたことだ。正則化はあくまで過学習の被害を軽減する保険であって、そもそも身の丈に合った複雑さ(この場合は次数3〜4)のモデルを選んだ方が、最終的な性能は良くなる。「無理に背伸びしてから制約で抑え込む」より、「最初から身の丈に合わせる」方が強い、というのは、実験結果としてもわりと素直に納得できる話だった。

経験(データ数)が増えると、必要な正則化は減っていく

ここまでは次数14を固定してα\alphaだけを動かしてきたが、最後にもう一つ実験した。訓練データの数nnを10, 15, 25, 50, 100, 300と増やしていったとき、次数14のモデルにとって「最良のα\alpha」はどう変化するだろうか。

n_train=  10  best_alpha=1000.00000  best_test_mse=36.2445
n_train=  15  best_alpha=1000.00000  best_test_mse=20.4232
n_train=  25  best_alpha=0.07017    best_test_mse=0.1987
n_train=  50  best_alpha=0.00100    best_test_mse=0.1060
n_train= 100  best_alpha=0.00492    best_test_mse=0.0952
n_train= 300  best_alpha=0.00838    best_test_mse=0.0924

訓練データ数n(横軸、対数スケール)に対する最良の正則化強さalpha(縦軸、対数スケール)の折れ線グラフ。n=10,15では探索範囲の上限である1000に張り付いていて、n=25で急落し、n=50でさらに下がったあとはほぼ横ばいになる

n=10n=10n=15n=15では、最良のα\alphaが探索範囲の上限である1000に張り付いてしまっている。これは「1000でもまだ足りず、本当はもっと強く制約したい」ことを意味する。データが極端に少ない段階では、モデル自身の判断をほとんど信用せず、外からの強い制約に頼るしかない、ということだ。

そこからn=25n=25になると必要なα\alphaは0.07まで急落し、n=50n=50ではさらに0.001まで下がる。n=100n=100n=300n=300ではそこからほぼ横ばいで、多少上下はするもののオーダーとしては10310210^{-3}\sim10^{-2}の水準で安定している(この範囲でのテスト誤差自体も0.106→0.095→0.092とほぼ変わらないので、この横ばい部分の細かい上下はα\alphaの探索粒度によるノイズの範囲だろう)。

つまり、データがごく少ない段階では強い制約が必須で、データが増えるにつれてその制約は急速に必要なくなり、ある程度たまった後はそれ以上増やしても制約の必要度はほとんど変わらない、という形が実際に数値として出てきた。

制約は、経験の少なさを埋め合わせるための仮の足場

ここまでの実験を通して、最初に感じていた直感はおおむね裏付けられたと思う。

データ(経験)が少ないうちにモデルの自由度を野放しにすると、手元のわずかな事例のノイズにまで無理やり適合してしまい、少し違う状況に出会っただけで予測が破綻する。次数14のモデルがテスト誤差40億という、およそ意味をなさない数字を叩き出したのが、その極端な例だ。

一方で、正則化という「外からの強い制約」を与えてやれば、経験が少なくても被害はかなり抑えられる。ただしそれは万能ではなく、次数3〜4という身の丈に合った複雑さを最初から選んだ場合には敵わないし、制約が強すぎれば今度はモデルが本来捉えられるはずの構造(sinカーブの形)まで見失ってしまう。

そして何より、正則化の必要量はデータが増えるほど下がっていく。経験の乏しい段階では「型」や「ルール」や「マニュアル」といった外部からの強い制約に頼った方がいい。まだ自分の中に十分なデータが蓄積されておらず、自分のパターン認識を信用すると、ただのノイズを法則だと勘違いするリスクの方が高いからだ。だが経験が積み上がってくると、その制約は徐々に外していける。制約はゴールではなく、データが足りない期間を埋め合わせるための、あくまで仮の足場だったということになる。

新人のうちに型やルールを軽視せず、かといって経験を積んだ後もいつまでも同じ型に縛られ続ける必要はない、というのは、よく聞く話ではある。ただ「なぜそう言えるのか」を実際に手を動かして数値で見せられると、単なる処世訓以上の納得感があった。

まとめ

  • 訓練データが15点しかない状態で多項式の次数を14まで上げると、訓練誤差は0.0050まで下がる一方、テスト誤差は1,017万(200回平均でも約40億)まで爆発する、典型的な過学習が発生した
  • Ridge正則化(α\alpha)を効かせると、次数14のままでもテスト誤差を500から0.287まで改善できる。ただしα\alphaと誤差の関係はきれいな単調なU字ではなく、小さな谷と山が混ざった非単調な曲線だった
  • 正則化は強すぎても弱すぎてもダメで、最終的には「次数3〜4+弱い正則化」という、そもそも身の丈に合った複雑さを選んだ場合が一番良い結果になった
  • 訓練データ数を10から300まで増やすと、最良の正則化強さは1000(探索上限)から0.001付近まで急落し、その後はほぼ横ばいで安定した。データが少ないほど強い制約が必要で、増えるほど制約を緩めていい、という関係が数値として確認できた