機械学習

過学習と未学習

訓練誤差とテスト誤差の動き方からモデル容量の過不足を判定し、容量不足側と容量過多側で違う対処を選ぶための整理。

  • A|中核
  • 機械学習

数式の記号で止まったら 記号の読み方 (∂・⊙・転置・上付き添字を、読み方から)

ひとことで言うと

過学習(overfitting)はモデルの容量(表現できる関数の幅)がタスクに対して大きすぎ、訓練データの雑音まで覚えてしまう状態です。未学習(underfitting)は逆に容量が足りず、訓練データすら十分に説明できない状態です。どちらも「モデルの容量とタスクの複雑さの不一致」という同じ軸の両端であり、見分け方も対処も対称ではありません。

試験勉強に例えると、未学習は過去問の解き方すら身についていない状態、過学習は過去問の解答を丸暗記して、出題パターンが変わると解けない状態です。前者は基礎知識を増やす(容量を上げる)ことが効き、後者は暗記に頼らず考え方を絞る(容量を下げる、または問題数=データを増やす)ことが効きます。どちらにも「もっと勉強しろ」で片付けると、過学習側は悪化します。

なぜ必要か

訓練誤差だけを見て「学習がうまくいっているか」を判断すると、過学習を見逃します。訓練誤差は容量を上げるほど単調に下がりますが、知りたいのは未知データへの性能(汎化誤差)です。容量と汎化誤差の関係はU字カーブを描き、容量が低すぎる側(未学習領域)と高すぎる側(過学習領域)の両方で汎化誤差が悪化します。中間のどこかに最適な容量があり、そこを訓練誤差だけでは見つけられません。

見ている指標わかることわからないこと
訓練誤差だけ容量を上げれば上げるほど下がり続ける未知データでの性能。過学習を見逃す
訓練誤差とテスト誤差の両方差(汎化ギャップ)の開き方で未学習・適正・過学習を判定できる—

だから訓練誤差とテスト誤差(または検証誤差)を両方見て、その差=汎化ギャップの動き方で今どちら側にいるかを判定する必要があります。

仕組み

判定:学習曲線の形で見分ける

容量を横軸に取ったとき、訓練誤差は常に単調減少、テスト誤差はU字を描きます。この2本の相対位置で状態が決まります。

状態訓練誤差テスト誤差差(汎化ギャップ)
未学習高い高い小さい(両方とも下手)
ちょうど良い低い低い小さい
過学習低い(〜0に近い)訓練より明確に高い大きい

容量を横軸にした図では、未学習は左端(両誤差とも高い領域)、過学習は右端(訓練誤差は下がり続けるが訓練とテストの差が開いていく領域)に対応します。「テスト誤差が高い」だけでは判定できず、訓練誤差と比較して初めて未学習か過学習かが決まる点が要です。

対処:容量不足側と容量過多側で打つ手が違う

対処何を制約するか効くのはどちら側か
データを増やす経験分布を真の分布に近づける。容量そのものは変えない主に過学習側(データが増えるほど過学習の余地が減る)
容量を下げる(層・ユニット数を減らす、次数を下げる)表現できる関数の幅そのものを狭める過学習側。未学習側にはこれ以上下げると逆効果
容量を上げる表現できる関数の幅を広げる未学習側
L2正則化(重み減衰)目的関数に α2∥w∥22\frac{\alpha}{2}\|w\|_2^2 を足し、重みを原点方向に縮めることで実効的な容量を下げる過学習側
L1正則化目的関数に α∥w∥1\alpha\|w\|_1 を足し、多くの重みを厳密に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は重み全体を原点方向に一様に縮めます。

ハイパーパラメータのチューニングにテストセットを繰り返し使うと何が起きるか

テストセット自体に過学習し、報告した性能が未知データで再現しなくなります。チューニングは検証セットで行い、テストセットは最終評価にのみ使います。

出典