ひとことで言うと
過学習(overfitting)はモデルの容量(表現できる関数の幅)がタスクに対して大きすぎ、訓練データの雑音まで覚えてしまう状態です。未学習(underfitting)は逆に容量が足りず、訓練データすら十分に説明できない状態です。どちらも「モデルの容量とタスクの複雑さの不一致」という同じ軸の両端であり、見分け方も対処も対称ではありません。
試験勉強に例えると、未学習は過去問の解き方すら身についていない状態、過学習は過去問の解答を丸暗記して、出題パターンが変わると解けない状態です。前者は基礎知識を増やす(容量を上げる)ことが効き、後者は暗記に頼らず考え方を絞る(容量を下げる、または問題数=データを増やす)ことが効きます。どちらにも「もっと勉強しろ」で片付けると、過学習側は悪化します。
なぜ必要か
訓練誤差だけを見て「学習がうまくいっているか」を判断すると、過学習を見逃します。訓練誤差は容量を上げるほど単調に下がりますが、知りたいのは未知データへの性能(汎化誤差)です。容量と汎化誤差の関係はU字カーブを描き、容量が低すぎる側(未学習領域)と高すぎる側(過学習領域)の両方で汎化誤差が悪化します。中間のどこかに最適な容量があり、そこを訓練誤差だけでは見つけられません。
| 見ている指標 | わかること | わからないこと |
|---|---|---|
| 訓練誤差だけ | 容量を上げれば上げるほど下がり続ける | 未知データでの性能。過学習を見逃す |
| 訓練誤差とテスト誤差の両方 | 差(汎化ギャップ)の開き方で未学習・適正・過学習を判定できる | — |
だから訓練誤差とテスト誤差(または検証誤差)を両方見て、その差=汎化ギャップの動き方で今どちら側にいるかを判定する必要があります。
仕組み
判定:学習曲線の形で見分ける
容量を横軸に取ったとき、訓練誤差は常に単調減少、テスト誤差はU字を描きます。この2本の相対位置で状態が決まります。
| 状態 | 訓練誤差 | テスト誤差 | 差(汎化ギャップ) |
|---|---|---|---|
| 未学習 | 高い | 高い | 小さい(両方とも下手) |
| ちょうど良い | 低い | 低い | 小さい |
| 過学習 | 低い(〜0に近い) | 訓練より明確に高い | 大きい |
容量を横軸にした図では、未学習は左端(両誤差とも高い領域)、過学習は右端(訓練誤差は下がり続けるが訓練とテストの差が開いていく領域)に対応します。「テスト誤差が高い」だけでは判定できず、訓練誤差と比較して初めて未学習か過学習かが決まる点が要です。
対処:容量不足側と容量過多側で打つ手が違う
| 対処 | 何を制約するか | 効くのはどちら側か |
|---|---|---|
| データを増やす | 経験分布を真の分布に近づける。容量そのものは変えない | 主に過学習側(データが増えるほど過学習の余地が減る) |
| 容量を下げる(層・ユニット数を減らす、次数を下げる) | 表現できる関数の幅そのものを狭める | 過学習側。未学習側にはこれ以上下げると逆効果 |
| 容量を上げる | 表現できる関数の幅を広げる | 未学習側 |
| L2正則化(重み減衰) | 目的関数に を足し、重みを原点方向に縮めることで実効的な容量を下げる | 過学習側 |
| L1正則化 | 目的関数に を足し、多くの重みを厳密に0にする(スパース化) | 過学習側 |
| ドロップアウト | 学習時にユニットをランダムに無効化し、多数の部分ネットワークを暗黙にアンサンブルする | 過学習側 |
| 早期終了 | 訓練エポック数そのものを制約する。検証誤差が上がり始めた時点で止める | 過学習側 |
| データ拡張 | 実データを持たずに経験分布の見かけの幅を広げる | 過学習側 |
L2とL1の違いは「縮める」か「削る」かです。L2は重みを一様に小さくするだけで0にはしにくく、L1は不要な重みを厳密に0へ落とすため、スパースな解が欲しい場面(特徴選択も兼ねたい場合)はL1が向きます。早期終了は追加の目的関数を持たない点で他の正則化と性質が違い、「実質的にどれだけのエポック数を学習に使わせるか」という形で容量を制約していると捉えられます。
テスト誤差を見ながら手を打ち続けると、テストセット自体に過学習します。 ハイパーパラメータ(容量・正則化の強さ・エポック数)の調整は検証セットで行い、テストセットは最後に一度だけ評価に使うのが原則です。テスト誤差を見ては設定を変える、を繰り返すと、テストセットの特性に合わせてハイパーパラメータを選んでしまい、報告した性能が未知データでは再現しません。
試験でどう問われるか
| 問われ方 | 正解に寄る条件 | 引っかけ |
|---|---|---|
| 訓練誤差とテスト誤差のグラフから状態を判定させる | 訓練誤差が低いのにテスト誤差との差が大きい=過学習、両方とも高い=未学習 | テスト誤差の絶対値だけで判定しようとする |
| 容量とU字カーブの関係を選ばせる | 容量を横軸にすると訓練誤差は単調減少、テスト誤差はU字 | 「テスト誤差も容量とともに単調に下がる」 |
| 過学習への対処を選ばせる | データ追加・容量削減・正則化・早期終了・データ拡張のいずれか | 未学習の状態で正則化を強める、容量を下げる |
| L1とL2の使い分け | L1は重みをスパースにする、L2は一様に縮める | L1とL2の効果を逆に説明する |
| ハイパーパラメータ調整に使うデータ | 検証セットで調整し、テストセットは最終評価のみ | テストセットを見ながら繰り返しチューニングする |
実装で確かめる
多項式回帰の次数を容量として動かし、訓練誤差とテスト誤差が食い違っていく様子を見ます。
import numpy as np
rng = np.random.default_rng(0)
n = 12
x = np.sort(rng.uniform(-1, 1, n))
y = x**2 + rng.normal(scale=0.05, size=n)
x_test = np.sort(rng.uniform(-1, 1, 200))
y_test = x_test**2
for degree in [1, 2, 9]:
X = np.vander(x, degree + 1)
X_test = np.vander(x_test, degree + 1)
w, *_ = np.linalg.lstsq(X, y, rcond=None)
train_err = np.mean((X @ w - y) ** 2)
test_err = np.mean((X_test @ w - y_test) ** 2)
print(f"degree={degree}: train={train_err:.4f} test={test_err:.4f}")
実行結果は次の通りです。
degree=1: train=0.0942 test=0.1257
degree=2: train=0.0012 test=0.0007
degree=9: train=0.0003 test=0.0506
真の関数は2次です。degree=1は訓練・テストとも誤差が高く未学習、degree=2は両方とも低くちょうど良い容量、degree=9は訓練誤差が最小なのにテスト誤差だけ跳ね上がっており、これが過学習の典型的な数値の出方です。訓練誤差だけを見ていると degree=9 が一番良く見える点に注意してください。
取り違えやすいもの
| 用語 | 過学習・未学習との関係 |
|---|---|
| バイアス・バリアンス分解 | 過学習・未学習を誤差の期待値として数式で分解した見方。未学習=高バイアス、過学習=高バリアンスに対応するが、判定と対処の実務は本ノートの範囲 |
| 汎化誤差 | テスト分布上での期待誤差そのもの。過学習・未学習はこの汎化誤差が悪化する2つの原因であって、汎化誤差の定義そのものではない |
| 正則化全般 | 過学習対策の総称。早期終了やデータ拡張のように目的関数を変えない手法も含む、より広い括り |
| 交差検証 | ハイパーパラメータ選びやモデル評価の手続き。過学習の判定基準そのものではなく、限られたデータで検証誤差の推定を安定させる手段 |
| アンサンブル学習 | 複数モデルの平均で分散を下げる手法。ドロップアウトは単一ネットワーク内で暗黙にこれを行っている点で関係するが、目的の建て付けが違う |
想起チェック
訓練誤差が0に近く、テスト誤差だけが高いとき、容量を上げるべきか下げるべきか
下げるべきです。これは過学習の典型パターンで、容量がタスクに対して大きすぎます。容量を上げると症状はさらに悪化します。
容量を横軸にとったとき、訓練誤差とテスト誤差はそれぞれどんな形の曲線を描くか
訓練誤差は単調減少、テスト誤差はU字カーブです。U字の底が汎化誤差を最小化する容量にあたります。
L1正則化とL2正則化の効果の違いを一言で言うと
L1は重みの多くを厳密に0へ落としてスパースにし、L2は重み全体を原点方向に一様に縮めます。
ハイパーパラメータのチューニングにテストセットを繰り返し使うと何が起きるか
テストセット自体に過学習し、報告した性能が未知データで再現しなくなります。チューニングは検証セットで行い、テストセットは最終評価にのみ使います。