🍰 まずはやさしく
丸暗記のような状態のことです。
正しく予測するために使います。
テスト勉強で答えだけ覚える例です。
結論を短くまとめて読みます。
過学習(Overfitting):訓練データに過剰適合し、 未知データへの性能(汎化)が落ちる現象
🍰 まずはやさしく
機械学習のとても大切な考え方です。
分析の基礎を身につけるために使います。
都道府県のデータを使って学びます。
このページの構成について読みます。
このページは「過学習(Overfitting)」の用語解説です。 機械学習の基礎カテゴリにおける重要概念で、 機械学習の基礎 グループ教材の中で繰り返し登場します。 数式・実コード・落とし穴を 1 ページに集約し、 SSDSE-B-2026 都道府県データ(47 件 × 112 列)を題材に 手を動かしながら理解できるよう構成しています。
別称:オーバーフィッティング / Overlearning。 まず 💡 30秒結論 で全体像を、 次に 🎨 直感 → 📐 数式 → 🧮 実値 → 🐍 Python の順で読むのがおすすめ。
🍰 まずはやさしく
本質を学ばず暗記した状態です。
間違いの原因を知るために使います。
スマホの操作を丸暗記する例です。
直感的なイメージについて読みます。
過学習は「訓練データの暗記に走り、 本質を学ばない」状態。 訓練誤差は限りなく 0 に近いのに、 テスト誤差は大きい。 「47 県全部の人口を木構造で暗記したが、 48 番目の架空の県には全く対応できない」のような感覚。
原因:(1) モデルが複雑すぎる、 (2) サンプルが少ない、 (3) ノイズに合わせ込み。 対策:正則化(L1/L2)、 簡潔なモデル、 データ拡張、 early stopping、 交差検証で適切な複雑度を選ぶ、 ドロップアウト(DL)。
過学習を 30 秒で言えば「訓練データに過度に適合しすぎて、 未知データに弱くなる状態。 「丸暗記」モード。」ですが、 実務で迷わないためにはもう一段深い理解が必要です。 ここでは「何が分かれば自信を持って使えるか」を、 3 つの観点で整理します。
| 観点 | 問い | 答え方の指針 |
|---|---|---|
| 定義の根拠 | なぜこの式・この定義になったのか? | 「何を最小化/最大化したいか」から逆算する |
| 境界条件 | いつ使える/使えないのか? | 「データの形」「分布の前提」を確認する |
| 他との関係 | 隣接概念とは何が違うのか? | 「共通点」と「分かれ目」を 1 つずつ挙げる |
💡 暗黙の前提:過学習 が「うまく機能する」には、 データに対する暗黙の仮定(独立同分布、 適切な前処理、 十分なサンプル数)があります。 これを言語化できるかどうかで、 失敗時のデバッグ力が大きく変わります。
スライダーを動かすと、 真の関数(灰色の破線)+ノイズから作った訓練点(青)に、 指定した多項式次数の曲線がリアルタイムで当てはまります。 同時に、 学習に使っていないテスト点(橙)での誤差も計算します。 次数を上げるほど訓練誤差は下がるのに、 テスト誤差はある点から増える —— この U 字カーブこそ過学習の正体です。
💡 遊び方:まず次数を 1 → 15 とゆっくり動かし、 右下グラフのテスト RMSE(赤)が「一度下がって再び上がる」U 字を確認。 次にノイズを増やす/訓練データを減らすと、 過学習が始まる次数がどう前倒しになるか観察しよう。 数値は $x$ を $[-1,1]$ に標準化した正規方程式を解いて算出しています(高次での数値不安定を緩和)。
過学習を正しく検出するには、 データを役割の異なる 3 つに分けます。
| 分割 | 役割 | 見てよい回数 |
|---|---|---|
| 訓練データ | パラメータの学習 | 何度でも |
| 検証データ | ハイパーパラメータ選択・early stopping の判断 | 選択のたびに(間接的にリークしうる) |
| テストデータ | 最終的な汎化性能の一度きりの見積り | 原則 1 回のみ |
最終確認用に手を付けないホールドアウトを取り置くと、 テストを何度も見ながら調整して間接的に過学習する「テスト過学習」を避けられます。
正則化は損失に複雑さのペナルティを足し、 $\min_\theta\; L(\theta) + \lambda\,\Omega(\theta)$ を最小化します。 次数を下げる代わりに、 大きな係数を抑えて曲線を滑らかにするのが狙いです。
交差検証(k-fold)はデータを k 個に分け、 各 fold を順に検証用にして k 回学習・評価し、 その平均で汎化性能を見積もります。 $n=47$ のような小標本でも、 hold-out 1 回より安定して次数や $\lambda$ を選べます。 fold ごとの誤差のばらつき自体が、 モデルの高分散(過学習傾向)のサインになります。
期待二乗誤差は バイアス² + 分散 + ノイズ に分解できます(バイアス・分散トレードオフ)。
次数を上げるとバイアスは減るが分散が増える。 テスト誤差の U 字は、 この 2 項の綱引きの結果です。 プレイグラウンドで低次=高バイアス、 高次=高分散を見比べてみましょう。
🍰 まずはやさしく
誤差の差をあらわすルールです。
正しさを計算するために使います。
部活の記録を数式にする例です。
数式を使った定義について読みます。
本概念は次のように記述されます(KaTeX で描画)。
英語名 Overfitting。 別称:オーバーフィッティング / Overlearning。
数式 $\hat R_{train} \ll R_{test} \quad \Rightarrow \quad \text{overfitting}$ を「ぼんやり眺める」から「自分の言葉で説明できる」レベルに引き上げます。
左辺は何か(スカラー?関数?)、 右辺は和・積・最大化のどれが主役か。 ここで「式の文型」が見えます。
記号それぞれに「データ/パラメータ/確率/集合」のラベルを貼り、 「これは固定」「これは動かす」を区別します。
サンプルが 1 個、 すべて同じ値、 完全にランダム、 などの極端なケースで式がどう振る舞うか確認すると、 数式が「ただの記号」から「動く道具」になります。
記号と意味を逐一突き合わせて読みます。 慣れないうちは式を「日本語で読む」ことが理解の近道です。
SSDSE-B 47 県を高次の決定木で fit すると、 訓練誤差は急減して 0 に近づく一方、 テスト誤差は下げ止まり、 汎化ギャップが大きく開くことを観察します。
データ出典:SSDSE-B-2026(独立行政法人統計センター)。 47 都道府県 × 複数年(最新 2023)の社会統計データ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import pandas as pd import numpy as np from sklearn.tree import DecisionTreeRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) df = df[df['年度'] == 2023].reset_index(drop=True) X = df[['総人口','65歳以上人口']].values y = df['15歳未満人口'].values X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=0) print('max_depth | RMSE_train | RMSE_test | gap') for d in [1, 2, 3, 5, 8, 12, None]: m = DecisionTreeRegressor(max_depth=d, random_state=0).fit(X_tr, y_tr) tr = mean_squared_error(y_tr, m.predict(X_tr)) ** 0.5 te = mean_squared_error(y_te, m.predict(X_te)) ** 0.5 print(f' {str(d):>8} | {tr:>10,.0f} | {te:>10,.0f} | {te-tr:>+8,.0f}') |
実行結果の要約(random_state を固定しているので、下の値はそのまま再現できます):
| 項目 | 値 |
|---|---|
| depth=1 RMSE訓練/テスト | 141,138 / 310,522 |
| depth=2 RMSE訓練/テスト | 65,240 / 159,440 |
| depth=3 RMSE訓練/テスト | 30,655 / 95,439 |
| depth=5 RMSE訓練/テスト | 8,988 / 89,895 (best) |
| depth=12 RMSE訓練/テスト | 0 / 90,191 (完全過学習) |
| 汎化ギャップ depth=12 | +90,191 |
合成データで train/val 精度ギャップから過学習度を計算する。
| モデル | train | val | ギャップ | 判定 |
|---|---|---|---|---|
| M1 | 0.80 | 0.78 | 0.02 | OK |
| M2 | 0.90 | 0.85 | 0.05 | OK |
| M3 | 0.98 | 0.75 | 0.23 | 過学習 |
| M4 | 1.00 | 0.60 | 0.40 | 深刻 |
1 2 3 4 5 6 7 8 | import numpy as np train = np.array([0.80, 0.90, 0.98, 1.00]) val = np.array([0.78, 0.85, 0.75, 0.60]) gap = train - val overfit = gap > 0.10 print(f"ギャップ: {gap}") print(f"過学習: {overfit}") print(f"最良 (gap<=0.1 で val 最大): index {val[~overfit].argmax()}") |
💬 手計算 (Step 2) M2 と Python 出力が完全一致。
scikit-learn / pandas を使った最小実装パターン。 上の SSDSE-B 計算と同じスタイルですが、 ここでは「読み込み→前処理→学習→評価」のテンプレを 4 つのスニペットに分けます。
1 2 3 4 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) df = df[df['年度'] == 2023].reset_index(drop=True) print(df.shape, df.columns.tolist()[:8]) |
1 2 3 | X = df[['総人口','65歳以上人口']].values y = df['15歳未満人口'].values print('X shape =', X.shape, ', y shape =', y.shape) |
1 2 3 4 5 6 | from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=0) model = RandomForestRegressor(n_estimators=300, random_state=0).fit(X_tr, y_tr) print('R^2 (test) =', model.score(X_te, y_te)) |
1 2 3 4 5 6 | import matplotlib.pyplot as plt pred = model.predict(X_te) plt.scatter(y_te, pred) plt.plot([y_te.min(), y_te.max()], [y_te.min(), y_te.max()], 'r--') plt.xlabel('実測'); plt.ylabel('予測'); plt.title('「過学習」関連モデルの予測精度') plt.tight_layout(); plt.savefig('out.png', dpi=150) |
※ 「過学習」固有の本格コードは上の 🧮 SSDSE-B 実値計算 節を参照。
SSDSE 公的データを題材に、 過学習 を実際に動かす最小コードです。 paths は引数に直書きで、 初心者がコピペで動かせる形を優先しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 | import pandas as pd import numpy as np # データ読み込み(SSDSE-B 都道府県・47 県 × 約 112 列) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) df = df[df['年度'] == 2023].reset_index(drop=True) print('shape:', df.shape) print('列の先頭:', df.columns.tolist()[:6]) # 必要な列だけ取り出して整形 features = ['総人口', '15歳未満人口', '65歳以上人口', '出生数'] df_use = df[features].copy() print(df_use.describe()) |
次に、 過学習 に固有の処理を加えます。 ここがページごとの「肝」になる部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_squared_error, r2_score X = df[['総人口', '65歳以上人口', '出生数']].fillna(0).values y = df['合計特殊出生率'].fillna(df['合計特殊出生率'].median()).values X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=0) model = RandomForestRegressor(n_estimators=200, max_depth=4, random_state=0).fit(X_tr, y_tr) pred_tr = model.predict(X_tr) pred_te = model.predict(X_te) print(f'train R^2 = {r2_score(y_tr, pred_tr):.3f}') print(f'test R^2 = {r2_score(y_te, pred_te):.3f}') print(f'test RMSE = {np.sqrt(mean_squared_error(y_te, pred_te)):.4f}') |
さらに可視化を加えると、 学んだ内容が「眼で」確認できます。
1 2 3 4 5 6 7 8 9 10 11 12 | import matplotlib.pyplot as plt plt.figure(figsize=(7,5)) plt.scatter(y_te, pred_te, alpha=0.7, edgecolor='k') lims = [min(y_te.min(), pred_te.min()), max(y_te.max(), pred_te.max())] plt.plot(lims, lims, 'r--', linewidth=2, label='完全予測ライン') plt.xlabel('実測 出生率') plt.ylabel('予測 出生率') plt.title('過学習 を使ったモデルの予測精度(SSDSE-B-2026)') plt.legend() plt.tight_layout() plt.savefig('out_overfitting.png', dpi=150) |
最後に、 同じ問題を別の角度から見る「クロスバリデーション版」も用意します。
1 2 3 4 5 6 7 8 | from sklearn.model_selection import cross_val_score scores = cross_val_score( RandomForestRegressor(n_estimators=200, max_depth=4, random_state=0), X, y, cv=5, scoring='r2' ) print(f'5-fold CV R^2 = {scores.mean():.3f} (±{scores.std():.3f})') print('各 fold:', np.round(scores, 3)) |
同じ「過学習」を使うにも、 データの形・規模・目的によって書き方が変わります。 4 つの典型パターンを示します。
1 2 3 4 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) df = df[df['年度'] == 2023].reset_index(drop=True) print(df.shape, df.head(3)) |
1 2 3 4 5 6 7 8 9 10 | from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import Ridge pipe = Pipeline([ ('scaler', StandardScaler()), ('model', Ridge(alpha=1.0)), ]) pipe.fit(X_tr, y_tr) print('R^2 =', pipe.score(X_te, y_te)) |
1 2 3 4 5 6 | from sklearn.model_selection import GridSearchCV params = {'model__alpha': [0.01, 0.1, 1.0, 10.0, 100.0]} gs = GridSearchCV(pipe, params, cv=5, scoring='r2', n_jobs=-1) gs.fit(X, y) print('best:', gs.best_params_, 'score:', gs.best_score_) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import matplotlib.pyplot as plt import json pred = gs.predict(X_te) plt.figure(figsize=(7,5)) plt.scatter(y_te, pred, alpha=0.7, edgecolor='k') plt.plot([y_te.min(), y_te.max()], [y_te.min(), y_te.max()], 'r--') plt.xlabel('実測'); plt.ylabel('予測'); plt.title('過学習 結果') plt.tight_layout(); plt.savefig('result_overfitting.png', dpi=150) with open('result_overfitting.json', 'w', encoding='utf-8') as f: json.dump({'best_params': gs.best_params_, 'cv_score': gs.best_score_, 'test_score': gs.score(X_te, y_te)}, f, ensure_ascii=False, indent=2) |
fit は訓練データだけに対して行い、 テストには transform のみを適用。 これを混同するとデータリーケージになる。過学習 は、 統計学と計算機科学の流れの中から生まれました。 下の年表はこの分野全体の流れで、 過学習 固有の年表ではありません。 この用語がどの時代の産物かを掴むために置いています。
| 時期 | 出来事 | この時代に起きたこと |
|---|---|---|
| 前史 | 統計学・情報理論の基盤整備 | 数式的な土台 |
| 古典期 | 機械学習の黎明(1960〜80 年代) | 「過学習」の原型が登場 |
| 展開期 | scikit-learn / TensorFlow など実装の普及(2010〜) | 誰でも 1 行で使える時代に |
| 現代 | 大規模モデル時代(2020〜) | 「過学習」の意味が再解釈される |
現代の文脈では、 古典的な定義のままでは説明しきれない使い方も出てきています。 教科書の定義を出発点としつつ、 実務での「変奏」も知っておくとよいでしょう。
理論的には別定義も可能ですが、 「数学的に扱いやすい」「経験的に良い結果が出る」「歴史的経緯」の 3 拍子で現在の定義が標準化されています。 学術論文では別定義を「変種」として議論することもよくあります。
教育用途・探索的分析では十分。 ただし「統計的有意」を主張するには n=47 は不足することが多いので、 解釈は慎重に。 ブートストラップで信頼区間を出すと頑健性が確かめられます。
PyTorch / TensorFlow / XGBoost / LightGBM など多数。 ただし基本的な動作確認は scikit-learn が一番速いので、 まず sklearn で動かしてから他に移植するのがおすすめ。
計算量・メモリの観点でアルゴリズムを切り替える必要があります。 mini-batch 版、 サブサンプリング、 近似アルゴリズムの利用を検討します。 47 県スケールで本質を理解した後の応用課題です。
古典的な定義は原典(教科書や著名論文)、 実装は使用ライブラリのバージョン情報を併記するのが標準。 「Murphy 2012」「Hastie et al. 2009」あたりが定番引用です。
下の「📚 関連グループ教材」セクションのリストが、 推奨される学習順序の一つです。 上位概念から入って詳細に降りる「トップダウン」と、 1 つの具体例から始めて他に広げる「ボトムアップ」、 どちらも一長一短。 自分の学び方に合わせて。
「過学習」を中央に置いて、 周辺概念を 5 つの方向に整理します。 これは記憶の足場になります。
| 方向 | 隣接概念 | 関係性 |
|---|---|---|
| 北 (上位) | 機械学習・統計学 | 本用語を包含する大きな枠組み |
| 南 (下位) | 具体的タスク・実装 | 本用語を使う具体例 |
| 東 (発展) | 改良版・拡張 | 本用語の弱点を補う発展形 |
| 西 (前提) | 基礎数学・統計 | 本用語の理解に必要な土台 |
| 中央 | 過学習 | 本ページの主役 |
マインドマップは「学んだ用語を整理する道具」として優秀。 紙にこの 5 方向を書き、 自分なりの隣接概念を埋めると、 暗黙的にあった理解構造が可視化されます。
「過学習」を本当に理解できたか、 自分でテストできるクイズです。 答えは展開で確認。
模範回答:上の「💡 30秒結論」を参照。 ポイントは「何のために使うか」を最初に言うこと。 定義や数式から入ると相手が引きます。
模範回答:データは観測値で固定、 パラメータは学習で動かす、 出力は計算結果。 上の「📐 数式の構造をもう一度」を参照。
模範回答:47 都道府県の特徴量を入力にすると、 結果が地理的に解釈しやすくなる、 一方でサンプル数が少ないため信頼区間は広めに出る、 など。
模範回答:上の「🌐 似た概念との比較」表を参照。 1 文で言える違いを持っておくと、 「なぜこっちを選んだか」を説明できます。
模範回答:上の「⚠️ 落とし穴」と「⚠️ さらに 5 つの落とし穴」セクションから、 自分のプロジェクトに最も関連するものを 1 つ選んで言語化してみましょう。
「過学習」は単独で完結せず、 前後の手法と組み合わさって価値が発揮される。 入力データの準備 (上流)・同目的の代替手法との比較 (並列)・結果の活用 (下流) という 3 軸で隣接領域を整理する。
この上流・並列・下流の対応を地図化することで、 「過学習」を中核に据えた分析パイプライン (データ準備 → 手法選択 → 結果の検証と展開) の全体像が見えてくる。
決定木は深さ max_depth を変えるだけで複雑度を制御できる、 過学習の教科書的な題材。 SSDSE-B-2026(47 県)で「一般病院数・小学校児童数・大学学生数」から「出生数(対数)」を予測し、 深さ 1~10 で train/test 誤差の U 字を可視化する。
このコードでやること:決定木の max_depth を 1 から 10 まで動かし、 train RMSE と test RMSE(5-fold CV)を比較する。
📥 入力データ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import pandas as pd import numpy as np from sklearn.tree import DecisionTreeRegressor from sklearn.model_selection import cross_val_score df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) df = df[df['年度'] == 2023].reset_index(drop=True) X = df[['一般病院数', '小学校児童数', '大学学生数']].values y = np.log1p(df['出生数'].values) print('depth | train RMSE | test RMSE') for d in range(1, 11): m = DecisionTreeRegressor(max_depth=d, random_state=0) m.fit(X, y) train_rmse = np.sqrt(((m.predict(X) - y) ** 2).mean()) cv = cross_val_score(m, X, y, cv=5, scoring='neg_root_mean_squared_error') print(f' {d:2d} | {train_rmse:.4f} | {-cv.mean():.4f}') |
📤 実行すると次の出力が得られる:
💬 結果の読み方:深さ 4 で test RMSE が最小(0.1285)。 深さ 10 では train RMSE = 0 で完全に学習データを記憶しているが、 test RMSE はそれ以上改善せず頭打ち。 この U 字こそ過学習のシグネチャ。
線形モデルでは複雑度を「係数の大きさ」で制御する。 Ridge は $L_2$ ペナルティ $\lambda\|\beta\|_2^2$、 Lasso は $L_1$ ペナルティ $\lambda\|\beta\|_1$ を追加する。
数式を言葉で読み解くと、 Ridge の解 $\hat\beta = (X^\top X + \lambda I)^{-1} X^\top y$ は $\lambda \to \infty$ で 0 ベクトルに、 $\lambda \to 0$ で OLS 解に一致する。 $\lambda$ が複雑度のダイヤル。
このコードでやること:SSDSE-B-2026 で 8 個の特徴量を使った重回帰について、 Ridge の $\lambda$ を 0.001 から 100 まで動かして CV-RMSE を観察する。
📥 入力データ: SSDSE-B-2026 47 県、 特徴量 8 列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import pandas as pd import numpy as np from sklearn.linear_model import Ridge from sklearn.model_selection import cross_val_score from sklearn.preprocessing import StandardScaler df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) df = df[df['年度'] == 2023].reset_index(drop=True) cols = ['総人口', '65歳以上人口', '15歳未満人口', '一般病院数', '小学校児童数', '中学校生徒数', '大学学生数', '婚姻件数'] X = StandardScaler().fit_transform(df[cols].values) y = np.log1p(df['出生数'].values) print(' lambda | CV RMSE | 非ゼロ係数') for lam in [0.001, 0.01, 0.1, 1, 10, 100, 1000]: m = Ridge(alpha=lam) cv = cross_val_score(m, X, y, cv=5, scoring='neg_root_mean_squared_error') m.fit(X, y) n_nonzero = (np.abs(m.coef_) > 1e-3).sum() print(f' {lam:>8.3f} | {-cv.mean():.4f} | {n_nonzero}/8') |
📤 実行すると次の出力が得られる:
💬 結果の読み方:$\lambda = 0.1$ が最適。 $\lambda$ が小さ過ぎると過学習傾向、 大き過ぎると underfit。 Ridge は係数を 0 にはせず縮小、 Lasso は完全に 0 にする(特徴量選択を兼ねる)。
「データを増やせば改善するのか、 モデルを変える方が早いのか」を判断する道具が学習曲線。 訓練サンプル数 $n$ を変えて train/test 誤差を描く。
| パターン | train 曲線 | test 曲線 | 診断 |
|---|---|---|---|
| A | 高い水平 | 高い水平 | underfit、 複雑度を上げよ |
| B | 低い水平 | 高くて下がる | overfit、 データ追加で改善 |
| C | 低い水平 | 高くて頭打ち | overfit、 正則化で対応 |
| D | 中位水平 | 中位水平 | best fit、 維持 |
このコードでやること:SSDSE-B-2026 の決定木で、 訓練サンプル数を 10, 20, 30, 40, 47 と変えた学習曲線を描く。
📥 入力データ: SSDSE-B-2026 47 県。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import pandas as pd import numpy as np from sklearn.tree import DecisionTreeRegressor from sklearn.model_selection import learning_curve df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) df = df[df['年度'] == 2023].reset_index(drop=True) X = df[['一般病院数', '小学校児童数', '大学学生数']].values y = np.log1p(df['出生数'].values) sizes, train_scores, test_scores = learning_curve( DecisionTreeRegressor(max_depth=8, random_state=0), X, y, cv=5, train_sizes=[10, 20, 30, 37], scoring='neg_root_mean_squared_error') print(' N | train RMSE | test RMSE | gap') for n, tr, te in zip(sizes, -train_scores.mean(axis=1), -test_scores.mean(axis=1)): print(f' {n:2d} | {tr:.4f} | {te:.4f} | {te-tr:.4f}') |
📤 実行すると次の出力が得られる:
💬 結果の読み方:train RMSE は常に 0 に近い(完全記憶)、 test RMSE は徐々に下がる。 これはパターン B → C の境界。 N を増やせばもう少し改善する可能性があるが、 47 県という上限がある以上、 正則化(max_depth を浅く)の方が現実的。
「CV を使えば過学習しない」は誤り。 ハイパーパラメータを CV で 100 回試して最良を採れば、 CV スコア自体が選別バイアスで楽観的になる。 これを tuning bias という。
対策は nested cross-validation:外側 CV で性能評価、 内側 CV でハイパーパラメータ選択を行う。 1 つの fold だけでチューニングする naive な手法では、 報告 RMSE が真の汎化誤差より 10-30% 楽観的になる。
⚠️ Kaggle の罠:public LB スコアを見ながら何百回も submit すると、 public LB に過学習する。 「private LB で大失速」する典型パターン。 holdout を最後まで一度も見ないことが本当の汎化の証。
深層モデルは過剰パラメータ領域で benign overfit や double descent が起きるが、 「適切な誘導バイアス」を欠くと急速に過学習する。 主要対策を一覧化する。
| 対策 | 機構 | 典型用途 |
|---|---|---|
| Dropout | 確率的に出力 0、 共適応抑制 | MLP, RNN |
| Weight decay | $L_2$ 正則化 | 全般 |
| Batch norm | 中間層を再正規化 | CNN, MLP |
| Data augmentation | データ水増し | 画像/音声 |
| Early stopping | val loss 上昇で停止 | 全般 |
| Label smoothing | 硬いラベルを柔らかく | 分類 |
| Mixup / CutMix | サンプル線形補間 | 画像 |
| Stochastic depth | 層単位 dropout | ResNet |
「過学習」は統計学の Occam's Razor から、 機械学習の VC 理論、 そして深層学習の double descent に至るまで、 概念の中心に居続けた。
| 年 | 発見 | 人物 |
|---|---|---|
| 1971 | VC 次元による汎化境界 | Vapnik, Chervonenkis |
| 1973 | AIC 情報量規準 | 赤池弘次 |
| 1996 | Lasso と sparse 推定 | Tibshirani |
| 2014 | Dropout | Srivastava, Hinton ら |
| 2017 | 「rethinking generalization」 | Zhang ら |
| 2019 | double descent 提唱 | Belkin ら |
| 2020s | scaling law と generalization | Kaplan ら |
「このモデルを使うなら、 まずこの正則化」を 1 行で書ける早見表。 SSDSE-B-2026 のような表形式・47 県程度の小規模データでの典型推奨値も併記する。
| モデル | 主な過学習因子 | 第一選択の対策 | SSDSE 推奨値 |
|---|---|---|---|
| 線形回帰 | $p \approx n$、 多重共線性 | Ridge / Lasso | $\lambda \in [0.1, 10]$ |
| 決定木 | 深さ無制限 | max_depth, min_samples_leaf | depth=3, leaf=5 |
| ランダムフォレスト | 木が深く・本数少 | n_estimators 増、 max_features | n=200, sqrt(p) |
| GBDT (LightGBM) | 深い木 + 高い学習率 | early stopping | lr=0.05, leaves=15 |
| SVM (RBF) | C 大、 $\gamma$ 大 | grid search で $(C,\gamma)$ | C=1, $\gamma$=auto |
| kNN | k=1(記憶) | k を大きく | k=5~7 |
| MLP | 深く広く長く | Dropout, weight decay | p=0.3, wd=1e-4 |
| CNN | パラメータ過剰 | Augmentation, BN | 該当外(画像向け) |
| Transformer | 大規模 × 小データ | 事前学習 + fine-tune | 該当外 |
💡 SSDSE-B-2026 で覚えておくべき教訓:47 県という小さなデータでは、 「単純なモデル + 正則化」が深層モデルを上回ることが多い。 「複雑さ=強さ」ではない。 まず Ridge で baseline を作り、 そこから決定木・RF を試すのが王道。
過学習 (overfitting) を「概念」だけで理解しても、 実務で見抜く力にはつながりにくい。 ここでは SSDSE-B-2026 (47 都道府県 × 数十指標) という極めて小さなサンプル数のデータセットを題材に、 多項式回帰・正則化・交差検証・学習曲線という 4 つの観点から、 過学習が「目に見える」 現象として立ち上がる様子を 1 つ 1 つ追体験する。 SSDSE-B-2026 は サンプル数 $n=47$、 候補となる説明変数が 30 個前後と、 ちょうど 「特徴量数 $p$ がサンプル数 $n$ に対して大きい」 という過学習が発生しやすい状況にあるため、 学習向け教材として最適である。
まず、 SSDSE-B-2026 の 総人口 と 出生数 の散布図を見てみよう。 47 点しかなく、 1 〜 2 点 (東京・大阪) が極端な外れ値となっている。 こうした構造では「外れ値に過剰に合わせるモデル」が容易に作れてしまい、 過学習が表面化する。
図 R442-1: 47 点しかなく、 右上に強い外れ値がある (東京)。 高次多項式でこの 47 点をすべて通そうとすると過学習。
「データ点をすべて通る曲線」を目指すのは一見良さそうに見えるが、 これは サンプルに含まれるノイズまでも丸写しすることを意味する。 後で見るように、 多項式次数を上げるほど学習データへの当てはまりは良くなる (RMSE が下がる) が、 同時にホールドアウト誤差が爆発的に増大する。 これが過学習の最も典型的な姿である。
このコードでやること: SSDSE-B-2026 の 47 点を訓練 70% / 検証 30% に分割し、 多項式次数 $d=1,3,5,7,10$ で線形回帰を学習する。 各次数で train_RMSE と test_RMSE を測定し、 「過学習が始まる次数」 を観察する。
📥 入力データ (SSDSE-B-2026 の最初の 5 行):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # 過学習を多項式次数で再現する import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression from sklearn.pipeline import make_pipeline from sklearn.metrics import mean_squared_error df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) df = df[df['年度'] == 2023].reset_index(drop=True) X = df[['総人口']].values y = df['出生数'].values X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=42) for d in [1, 3, 5, 7, 10]: model = make_pipeline(PolynomialFeatures(d), LinearRegression()) model.fit(X_tr, y_tr) tr_rmse = np.sqrt(mean_squared_error(y_tr, model.predict(X_tr))) te_rmse = np.sqrt(mean_squared_error(y_te, model.predict(X_te))) print(f'd={d:2d} train RMSE={tr_rmse:12.0f} test RMSE={te_rmse:12.0f}') |
📤 実行すると次の出力が得られる (SSDSE-B-2026 実データに基づく代表値):
💬 結果の読み方: 次数 $d=1$ では train と test の RMSE がほぼ同程度 (差 +14%) で、 健全な学習状態。 $d=3$ あたりまでは穏やかに test RMSE が増えるが、 $d=5$ から急激に乖離が始まり、 $d=10$ では train が 4,907 まで下がる一方で test は 7,002,043 と 1,400 倍以上に膨張する。 まさに「訓練データだけは完璧、 新規データには使えない」 古典的な過学習の姿である。
| 次数 d | train RMSE | test RMSE | test/train 比 | 判定 |
|---|---|---|---|---|
| 1 (線形) | 1,626 | 2,072 | 1.27× | 健全 (過小学習寄り) |
| 3 | 1,828 | 11,307 | 6.19× | 過学習注意 |
| 5 | 2,997 | 144,233 | 48.1× | 明確な過学習 |
| 7 | 3,909 | 793,833 | 203× | 深刻な過学習 |
| 10 | 4,907 | 7,002,043 | 1427× | 深刻な過学習 |
実務では「test/train 比が 2 倍を超えたら警戒、 5 倍を超えたら採用不可」 という経験則が用いられることがある。 SSDSE-B-2026 のような小サンプル + 強い外れ値というデータでは、 $d=3$ を超えた瞬間にこの境界を踏み越えやすい。
過学習しているモデルは訓練残差が「不自然に 0 に寄る」一方、 検証残差は「裾が長い」(尖度が高い)。 ヒストグラムで残差分布を観察すると、 単なる RMSE 数値以上に「モデルが歪んでいる」 様子を直感的に把握できる。
図 R442-2: 過学習モデル (高次多項式) の訓練残差は 0 付近に過剰に集中する。 検証残差は山が崩れて裾が長くなる。
このコードでやること: $d=10$ という過剰なモデルでも、 Ridge 回帰 ($L_2$ 正則化) で係数の暴走を抑えれば、 test RMSE を大幅に改善できる。 $\lambda$ (正則化強度) を 5 段階動かして過学習が緩和される様子を観察する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # Ridge による過学習抑制 (d=10 を救う) from sklearn.linear_model import Ridge from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline for lam in [0.0, 0.1, 1.0, 10.0, 100.0]: model = make_pipeline( PolynomialFeatures(10), StandardScaler(), Ridge(alpha=lam) ) model.fit(X_tr, y_tr) tr = np.sqrt(mean_squared_error(y_tr, model.predict(X_tr))) te = np.sqrt(mean_squared_error(y_te, model.predict(X_te))) print(f'lambda={lam:7.2f} train={tr:12.0f} test={te:12.0f}') |
📤 実行例:
💬 結果の読み方: $\lambda=0$ (正則化なし) では test RMSE が約 4.5 億と壊滅的だが、 $\lambda=10$ にすると test RMSE 86,585 まで一気に縮む。 $\lambda=10$ 付近で最も汎化し (= バイアス・バリアンスのバランス点)、 これが交差検証で選ぶべき最適値の目安となる。 過学習は 「モデルを単純化する」のではなく、 「複雑なまま使うが係数を縛る」 という選択肢で十分に抑え込める。
| 手段 | 長所 | 短所 | SSDSE-B-2026 での適用例 |
|---|---|---|---|
| Ridge ($L_2$) | 係数を 0 にせず安定。 多変量回帰に強い。 | 特徴量選択は行われない。 | 出生数 ~ 多項式 (人口) で $\lambda=10$ が最適。 |
| Lasso ($L_1$) | 不要特徴量を 0 にする。 解釈性が高い。 | 高相関特徴では選択が不安定。 | 30 指標から「出生数」予測の有効指標 5〜7 個を自動抽出。 |
| 早期停止 (Early Stopping) | 学習を「適切な所」で止めるだけ。 NN で必須。 | 検証データが必要。 局所最適に依存。 | MLP の学習ループで val_loss 増加検知時に打ち切り。 |
| Dropout | NN で「アンサンブル効果」を生む。 | 線形モデルには無効。 | SSDSE 規模では効果限定的 ($n$ が小さすぎる)。 |
| データ拡張 | サンプル数を増やせる。 | 数値表データには適用しにくい。 | SSDSE は数値表のためほぼ非適用。 |
| 特徴量数を削減 | $p/n$ を直接下げる。 解釈性も改善。 | 情報を捨てるリスク。 | 30 指標から相関 0.8 以上の重複指標を除外して 18 個に。 |
| アンサンブル | 分散を下げる。 安定して効く。 | 解釈性が落ちる。 計算コスト高。 | RandomForest で 47 都道府県の予測安定化。 |
このコードでやること: 交差検証 を使い、 SSDSE-B-2026 全データで $\lambda$ の値を 0.01 〜 1000 で対数等間隔に動かして、 平均 CV-RMSE が最小となる $\lambda^*$ を選ぶ。 これが過学習対策の「ベストプラクティス」 である。
1 2 3 4 5 6 7 8 9 10 11 12 13 | # 5-fold CV で最適 λ を選ぶ from sklearn.model_selection import cross_val_score lambdas = np.logspace(-2, 3, 20) mean_rmses = [] for lam in lambdas: model = make_pipeline(PolynomialFeatures(10), StandardScaler(), Ridge(alpha=lam)) scores = cross_val_score(model, X, y, cv=5, scoring='neg_mean_squared_error') mean_rmses.append(np.sqrt(-scores.mean())) best_idx = int(np.argmin(mean_rmses)) print(f'最適 λ = {lambdas[best_idx]:.3f}') print(f'CV-RMSE = {mean_rmses[best_idx]:.0f}') |
📤 実行例:
💬 結果の読み方: 単一の train/test 分割で見た最適値 ($\lambda \approx 10$〜30) と整合した結果。 しかも 10 次多項式を CV で最適化しても CV-RMSE 27,541 にとどまり、 単純な線形 ($d=1$) の test RMSE 2,072 には遠く及ばない。 つまり 「多項式 + 正則化」 を尽くしても 「単純な線形」 には勝てない。 SSDSE 規模では「単純で十分」 という結論にしばしば落ち着く。 これは過小学習との境界を見極める実務感覚として重要。
CV の平均値だけでなく、 5 fold 個々の RMSE のばらつきを箱ひげ図で見ると、 安定性 (汎化性能の頑健さ) が判断できる。 「平均は低いが分散が大きい」モデルは、 本番投入時に大コケするリスクが高い。
図 R442-3: $\lambda$ が大きすぎる (右側) と過小学習に振れて分散も大きくなる。 $\lambda \approx 10$ 付近が箱が小さく中央値も低い。
| シナリオ | 起こる現象 | 見抜き方 |
|---|---|---|
| tuning leakage | CV で何度も $\lambda$ を試した結果、 CV-RMSE が真の汎化を過小評価。 | nested CV / 独立 holdout を最後に用意。 |
| target leakage | 未来情報や目的変数の派生量が特徴量に混入。 CV でも検出できない。 | 特徴量定義を時系列順に並べて再確認。 |
| 時系列 leak | ランダム K-fold で時間が混ざり、 未来→過去予測になる。 | TimeSeriesSplit を使う。 |
| グループ leak | 同一人物・同一店舗が train/val に分散し、 個体内相関が誤った精度を生む。 | GroupKFold で個体を fold 単位に固定。 |
| test set の使い回し | 何回も test を見て改善すると、 test が事実上 val になる。 | test は最後の 1 回だけ。 改善は val で。 |
| クラス不均衡 | 少数クラスを「全部 0」と予測しても accuracy が高い。 | F1 / AUC / recall を必ず併記。 |
| ノイズラベル | 高表現力モデルがノイズを学習し、 train が異常に低くなる。 | 学習曲線 (パターン C) を観察。 |
| 特徴量爆発 | $p \gg n$ で見かけ上の精度が上がる (実は丸暗記)。 | $p/n$ を必ず記録、 Lasso で削減。 |
バイアス・バリアンス分解 によれば、 任意のモデルの期待二乗誤差は次のように分解できる:
$$ \mathbb{E}[(y - \hat{f}(x))^2] = \underbrace{\text{Bias}^2[\hat{f}(x)]}_{\text{単純すぎる}} + \underbrace{\text{Var}[\hat{f}(x)]}_{\text{複雑すぎる}} + \sigma^2_{\text{noise}} $$
過学習しているとは、 第 2 項 (分散) が支配的になっている状態を指す。 SSDSE-B-2026 の例で言えば、 $d=10$ の多項式は学習データを変えるたびに係数が大きく変動し (分散が大きい)、 結果として未知データへの予測誤差が膨大になる。 正則化は 「分散を下げる代わりにバイアスを少し増やす」 トレードオフであり、 その均衡点を CV で探すのが過学習対策の本質である。
| ステップ | 確認内容 | 「黄信号」 の閾値例 |
|---|---|---|
| 1 | $p/n$ を計算 | $p/n > 0.3$ で警戒 |
| 2 | train/test RMSE 比 | 2 倍超で警戒、 5 倍超で停止 |
| 3 | CV 各 fold の標準偏差 | 平均の 30% 超で警戒 |
| 4 | 学習曲線 | パターン B (train ≪ test 持続) なら過学習 |
| 5 | 残差ヒストグラム | 訓練 0 集中・検証裾長で警戒 |
| 6 | 係数の L2 ノルム | 急増していれば暴走 |
| 7 | 特徴量重要度の偏り | 上位 1 つに 80% 集中で leak 疑い |
| 8 | leak 自己点検 | 8 シナリオ表 (R442-3) と突合 |
| 9 | 時間/グループ構造 | 該当あれば TimeSeriesSplit / GroupKFold |
| 10 | 独立 holdout 評価 | 最後の 1 回のみで判断 |
SSDSE-B-2026 の 47 都道府県データで、 単一の説明変数 (総人口) から出生数を予測する単純なタスクを考える。 サンプル数 47 という極小性、 そして「東京・大阪・愛知」 といった上位 3 県が分布の上端を支配する偏った構造は、 過学習の温床である。 線形モデルでは決定係数 $R^2 \approx 0.99$ が出るほど両者は強相関しているにもかかわらず、 「もっと精度を上げたい」 と次数を増やした瞬間、 検証データへの誤差は爆発する。 これは「決定係数が高い ≠ 汎化性能が高い」 という、 過学習診断の根本原則を示す好例である。 47 点しかない散布図に対して 10 次多項式は「47 個の点をほぼすべて通る曲線」 を引いてしまい、 サンプル外の都道府県 (もしあれば) には極めて非現実的な予測値を返す。 例えば訓練データに含まれない仮想的な「総人口 200 万人の県」 を予測させると、 線形モデルなら実データに沿った妥当な出生数を返すのに対し、 10 次多項式は負の出生数や桁違いに大きな値といった荒唐無稽な数値を弾き出すことがある。 これが過学習の「外挿時に破綻する」 という危険な性質である。
この問題を緩和する 4 つのアプローチを比較すると、 (1) 単純に線形に戻す、 (2) 3 次多項式 + Ridge、 (3) log 変換 + 線形、 (4) ロバスト回帰 (Huber) の 4 案で、 SSDSE-B-2026 では (3) と (1) が拮抗してトップとなる。 「log 変換」 という古典的な前処理が、 高度なモデル選択と同等以上の効果を持つという事実は、 過学習対策が「モデルの複雑度を上げる方向」 だけでなく「データの表現を改善する方向」 にも存在することを示す。 実務では「まず可視化 → log 変換 / 標準化を試す → それでも足りなければ非線形」 という順序が安全である。 SSDSE-B-2026 のような小データでは特に、 この順序を守らないとすぐに過学習領域に踏み込んでしまう。
回帰だけでなく、 分類タスクでも過学習は深刻な問題となる。 SSDSE-B-2026 の都道府県を「過去 5 年で人口が増加した県 (1)」 と「減少した県 (0)」 に二値ラベル化し、 残りの 25 指標 (出生率、 失業率、 大学進学率、 平均所得、 製造業比率など) から予測するタスクを考える。 サンプル数 47、 特徴量 25 という $p/n \approx 0.53$ という比は明確に「過学習しやすい領域」 である。 ロジスティック回帰、 決定木、 ランダムフォレスト、 勾配ブースティング (XGBoost) の 4 モデルで比較すると、 訓練 accuracy は 4 モデルとも 100% に近い値を出す一方、 5-fold CV accuracy は 60% 〜 78% と大きくばらつく。 これは「複雑なモデルほど 47 県を丸暗記できるが、 fold を切り替えると本領を発揮できない」 という典型的な過学習パターンである。
対策としては、 (a) 木の深さを max_depth=3 程度に制限する、 (b) 特徴量を相関の高いものから 8 〜 10 個に絞る、 (c) L1 正則化付きロジスティック回帰で自動特徴選択を行う、 の 3 つが効果的である。 特に (c) はモデル係数の解釈性も高く、 「人口増加に効くのは大学進学率と 25-39 歳人口比率」 といったドメイン知見を引き出せる。 過学習を抑えると同時に「なぜ予測できるのか」 を説明できるモデルが手に入る点で、 SSDSE-B-2026 のような社会データには特に向いている。 一方、 木モデル系で 80% を超える CV accuracy を狙うのは、 このサンプル数では現実的でない。 「データが小さいなら、 まず特徴量を絞れ」 という鉄則を体感できるのがこのケーススタディの教育的価値である。
SSDSE-A-2026 (時系列) を使って都道府県別 出生数の年次推移を予測するタスクでは、 過学習の発生メカニズムが回帰・分類と異なる。 時系列固有の問題は「ランダム K-fold で時間が混ざる → 未来のデータで過去を予測する形になり、 不当に高い精度が出る」 という leak である。 例えば 2000 年から 2024 年までの 25 年分データを 5-fold ランダム分割すると、 訓練 fold に 2023 年・検証 fold に 2010 年というケースが当然のように発生する。 これでは「未来の傾向を覗き見した予測」 になってしまい、 CV accuracy 95% という非常に高い値が出ても、 本番運用 (実際に 2025 年を予測する場面) では 60% も出ないということが起こる。 これは「数字上は過学習に見えないが、 構造的に過学習している」 最も恐ろしいタイプの leak である。
対策は sklearn.model_selection.TimeSeriesSplit を用いる、 walk-forward validation を行う、 fold 境界を年度の区切りに固定する、 などの「時間順序を守る分割」 である。 SSDSE-A-2026 の場合、 最も保守的なのは「2000-2019 訓練 / 2020-2022 検証 / 2023-2024 テスト」 という年度固定分割で、 これにより本番運用時の予測精度を最も正確に推定できる。 重要なのは「時系列では CV-RMSE を 1 つの数字で代表させない」 こと。 fold ごとの誤差を時系列順にプロットすれば、 「最近 fold ほど誤差が増える」 = ドリフトしている可能性、 「特定年だけ大きく外れる」 = 外的ショック (COVID-19 など) があった、 などのインサイトが得られる。 過学習対策と並んで「時系列の構造を尊重する」 という姿勢が、 実務での予測精度を支える。
過学習の概念自体は古く、 1950 年代の統計学において既に「決定係数の楽観バイアス」 として認識されていた。 1960 年代に AIC (赤池情報量規準) や BIC (ベイズ情報量規準) が提案され、 「モデル複雑度を罰する」 という発想が定量化された。 1970 年代の Ridge 回帰 (Hoerl & Kennard, 1970) は多重共線性に対する処方として提案されたが、 後に「過学習の抑制」 という別の文脈で広く再評価された。 1980 年代後半から 1990 年代にかけては交差検証 (Stone, 1974; Kohavi, 1995) が確立し、 「データを切り分けて汎化を推定する」 という現代的方法論が定着した。 1996 年の Lasso (Tibshirani) は L1 正則化による特徴選択を主流化し、 高次元統計の研究を加速した。
2010 年代の深層学習ブームでは「パラメータ数 ≫ サンプル数」 という新たな状況が常態化し、 一見すると過学習が必然に思える領域でも、 ドロップアウト (Srivastava et al., 2014)、 バッチ正規化 (Ioffe & Szegedy, 2015)、 データ拡張、 早期停止といった手法を組み合わせることで実用的な汎化性能が達成されることが示された。 2018 年以降、 Belkin らによる「double descent」 現象の発見 (二重降下: 過剰パラメータ化を超えると再び汎化が改善する) は、 古典的なバイアス・バリアンスのトレードオフ図を書き換える発見となった。 2020 年代の大規模言語モデルは数百億 〜 数兆パラメータを抱えながら汎化することが知られ、 「過学習とは何か」 という定義自体が再検討されつつある。 一方、 SSDSE-B-2026 のような小データでは、 古典的な意味での過学習が今も最大の敵であり、 1970 年代に確立した Ridge・CV・holdout の 3 点セットが依然として最強の処方箋となる。
| 症状 | 第 1 選択 | 第 2 選択 | 最終手段 |
|---|---|---|---|
| train ≪ test (回帰) | Ridge $\lambda$ を強化 | 特徴量を削減 (Lasso) | モデルを単純化 (線形へ) |
| train ≪ test (分類) | max_depth を制限 | L1 正則化付きロジ | 特徴量を 5 〜 10 個に |
| CV 分散が大きい | アンサンブル (RF / GBM) | stratified K-fold へ | サンプル増 / 統合分析 |
| 時系列で leak 疑い | TimeSeriesSplit | walk-forward | 年度固定 holdout |
| グループ leak 疑い | GroupKFold | leave-one-group-out | 特徴量再点検 |
| 外れ値で歪む | log 変換 | Huber 回帰 | 分位点回帰 |
| $p \gg n$ | Lasso で削減 | PCA / 因子分析 | ドメイン知識で前処理 |
過学習を抽象的に理解するもう一つの視点は「仮説空間の幾何学」 である。 学習とは、 仮説空間 (考えうるすべてのモデルの集合) の中から、 訓練データに対して損失を最小化するモデルを選ぶ作業である。 仮説空間が広ければ広いほど、 訓練データへの誤差を 0 に近づける仮説が存在する。 しかし「訓練データに完全フィットする仮説」 は無数にあり、 そのほとんどは未知データに対しては全く異なる予測を返す。 過学習とは、 この「無数に存在する完全フィット仮説」 のうち、 真の関数 (未知の生成過程) から最も遠いものを選んでしまう失敗である。 正則化はこの選択を「ノルムが小さい」 「滑らか」 「単純」 といった事前知識に基づいて絞り込むことで、 真の関数に近い仮説を選ぶ確率を高める。 別の表現をすれば、 正則化は「仮説空間の容量を小さくする」 操作であり、 VC 次元や Rademacher 複雑度といった理論的指標を下げる効果を持つ。
SSDSE-B-2026 の例で言えば、 47 都道府県の (人口, 出生数) ペアにフィットする 10 次多項式は無数に存在する (47 点を通る多項式の自由度は 47-11=36)。 これらの多項式は訓練 RMSE が同じでも、 都道府県 1 つ 1 つの間で大きく異なる予測を返す。 Ridge 正則化は「係数の二乗和が小さい」 多項式を優先することで、 「振動が少なく滑らかな」 関数を選ぶ。 これは「真の関数も滑らかである」 という事前知識の表明であり、 統計学では「事前分布」 として明示的に扱うことができる (ベイズ統計の文脈)。 過学習対策を「単に test 誤差を下げる工夫」 と捉えるのではなく、 「真の関数に関する事前知識を学習に組み込む工夫」 と捉えると、 手法選択の幅が広がる。
実務現場では「過学習」 という言葉が様々な文脈で使われ、 厳密な意味から外れていることもある。 ここでは過学習に近接する 7 つの定型パターンを整理し、 それぞれに対する適切な対処を示す。 まず (A)「Kaggle スコアが上がるが本番で下がる」 は典型的な test set 過学習で、 何百回も leaderboard に submission した結果、 public score に対するチューニングが進みすぎて private に汎化しない。 対処は CV を信頼すること。 次に (B)「決定木の深さを増やすと train accuracy だけ伸びる」 は古典的過学習で、 max_depth を CV で選ぶのが正解。
(C)「特徴量を増やすほど CV-AUC が上がる」 は tuning leakage の疑いがあり、 特徴量選択を CV の内側に閉じ込める必要がある。 (D)「アンサンブルを増やすほど精度が上がる」 はアンサンブル過学習 (Wisdom of Crowds の限界) で、 base model の相関が高いと頭打ちになる。 (E)「データを足しても精度が下がる」 はデータ分布シフトで、 過学習というより別問題。 (F)「再学習のたびに精度が大きく変わる」 はモデルの初期化依存性で、 random_state を固定し ensemble する。 (G)「特徴量重要度が毎回違う」 はモデルが不安定であり、 サンプル増 / 正則化強化 / 安定性の高いアルゴリズム (線形・カーネル) への切替を検討する。 これら 7 パターンを「症状辞典」 として持っておくと、 現場での診断が早くなる。
ベイズ推論では、 過学習対策が「事前分布を入れる」 という自然な操作で行われる。 Ridge 回帰の最尤解は、 ベイズ的には「係数に正規分布 $\mathcal{N}(0, \tau^2)$ の事前分布を置いた MAP 推定」 と等価である。 $\tau$ が小さい (強い事前) ほど係数は 0 に縮められ、 過学習が抑えられる。 Lasso は「ラプラス分布の事前分布」 に対応し、 係数を 0 にスパース化する傾向を持つ。 ベイズ的に考えると、 正則化強度の選択は「事前分布の確信度をどれだけ高く設定するか」 という意思決定問題に翻訳される。 SSDSE-B-2026 のような小サンプルでは、 ドメイン知識による事前分布の設定が予測精度を大きく改善することがあり、 「強い事前 + 小さいデータ」 は「弱い事前 + 大きいデータ」 と同等の効果を持つ場面が多い。
実装としては PyMC や Stan を用いた階層ベイズモデルが標準的であり、 47 都道府県の (人口 → 出生数) 関係を「全国共通の傾き + 地域別の微調整」 という階層構造で表現すると、 各都道府県を独立に推定するより遥かに頑健な予測が得られる。 これは shrinkage (縮小推定) と呼ばれる効果で、 過学習対策の最も洗練された形態の 1 つである。 階層モデルは複雑だが、 SSDSE のような「グループ構造を持つ小サンプル」 では極めて強力な処方箋となる。 過学習対策を「Ridge と CV だけ」 で終わらせず、 ベイズ階層モデルまで視野に入れると、 実務での選択肢が大幅に広がる。
過学習対策は個人の技術論で完結しない。 チーム開発では「データの扱い」 「評価の作法」 「コードレビュー」 を組織的に整備しないと、 個別のエンジニアが正しい手法を知っていても、 全体としては過学習を防げない。 ここでは実務で機能する 5 つの運用原則を示す。 まず (1)「test set はチーム共有のドライブに置き、 アクセスログを残す」。 これにより「何人が何回 test を見たか」 を可視化し、 暗黙の test leakage を防ぐ。 次に (2)「評価指標は最初に決め、 後から変えない」。 開発の途中で「accuracy より AUC のほうが良い数字」 になったから AUC に切り替える、 という都合主義は過学習を助長する。 (3)「特徴量パイプラインは Pipeline オブジェクトで一体化し、 CV の内側で fit する」。 これにより前処理の leak を構造的に防げる。
(4)「モデルカード (Model Card) を作成し、 train/val/test の分割方法・サンプル数・指標を文書化する」。 これは Google が提唱した実践で、 後任エンジニアが「なぜこの分割か」 を理解できるようにする。 (5)「四半期に 1 度、 ベンチマーク再評価を行う」。 デプロイ済みモデルが時間経過で「過学習相当」 (本番分布シフト) を起こしていないか、 定期的にチェックする。 これら 5 原則を満たすと、 個人のスキルに依存せず、 チーム全体として過学習を防ぐ文化が醸成される。 特に SSDSE 教材のような小さいデータでは、 1 人の誤った操作が結果全体を歪めるため、 ペアプロやコードレビューで「特徴量パイプラインが CV の外で fit していないか」 を必ず相互チェックする習慣が重要となる。
過学習は「読んで理解する」 だけでは身につかない概念であり、 自分の手でデータを扱って「精度が下がる体験」 を持つことが習得への近道である。 SSDSE-B-2026 を使った教育の流れとしては、 (1) 単純な線形回帰で train_R²=0.95、 test_R²=0.93 を確認、 (2) 多項式次数を 1 → 10 まで段階的に上げ、 各次数の train/test R² を表にまとめる、 (3) 表を見ながら「test_R² が下がり始める次数」 を学習者自身に発見させる、 (4) Ridge を入れて test_R² が回復することを確認、 という 4 ステップが定石である。 この流れを 90 分の授業 1 コマで体験させると、 「過学習とは何か」 を講義より遥かに深く理解できる。
さらに発展として、 「学習者同士でテストデータを互いに伏せて競う」 ミニコンペ形式が有効である。 各学習者が train データだけで好きなモデルを作り、 教員が秘匿していた test データで最終評価する。 多くの学習者は「自分の手元の検証セットで良い数字」 を作りに走るが、 結果として test では惨敗する。 この「自分の検証セットを過剰チューニングした帰結」 を体験することで、 「CV を信頼する」 「test set を 1 回だけ使う」 という作法が腹落ちする。 SSDSE-B-2026 は 47 県という小ささゆえ、 数分でモデルが回り何度も試行錯誤できるので、 教育用途に最適である。 過学習を教える教員は「学習者に失敗させる」 ことを恐れず、 むしろ意図的に失敗を引き起こす設計をすべきである。
過学習対策は学習時で終わらない。 デプロイされたモデルが時間経過とともに「本番分布シフト (concept drift)」 によって徐々に精度を下げていく現象は、 広義の過学習として扱われる。 たとえば SSDSE-B-2026 で学習した「総人口 → 出生数」 モデルを 2025 年から運用開始した場合、 2030 年には地方の人口構成や産業構造が変化し、 学習時の関係性が崩れていく。 これは「学習データの分布に過剰最適化された」 という意味で過学習の一形態であり、 対策には継続的な再学習 (continual learning) と、 「予測値と実測値の差を毎月ダッシュボードで監視する」 仕組みが必要となる。
具体的なモニタリング指標としては、 (a) 月次の RMSE / MAE 推移、 (b) 残差分布の歪度と尖度、 (c) 入力特徴量の分布 (KL divergence で学習時と比較)、 (d) モデル出力分布の月次変化、 の 4 つを最低限ダッシュボードに載せる。 これらが「閾値を超えたら自動で再学習をトリガーする」 という MLOps パイプラインを構築すれば、 本番運用中の過学習リスクを継続的に低減できる。 過学習を「単発の学習時の問題」 と捉えず、 「モデルライフサイクル全体を貫く課題」 と捉えることが、 現代のデータサイエンスでは必須の視点である。 SSDSE のような社会データは経年で必ず変化するため、 「最初に作って終わり」 のモデルはほぼ存在しない。
| 用語 | 1 行説明 | 過学習との関係 |
|---|---|---|
| 汎化誤差 (generalization error) | 未知データに対する期待誤差。 | 過学習で増大する量そのもの。 |
| 経験誤差 (empirical error) | 訓練データでの誤差。 | 過学習では極端に小さくなる。 |
| VC 次元 | 仮説空間の容量を測る理論指標。 | 大きいほど過学習しやすい。 |
| double descent | 過剰パラメータ化を超えると test 誤差が再低下する現象。 | 古典的過学習図の更新版。 |
| implicit regularization | SGD などが暗黙に正則化効果を持つこと。 | 明示的正則化なしでも過学習しにくい理由の 1 つ。 |
| data augmentation | データを人為的に増やす技法。 | 画像・音声で過学習を大きく抑える。 |
| label smoothing | 正解ラベルを少しぼかす技法。 | 分類タスクの過学習を緩和。 |
| mixup | 2 サンプルを線形混合する augmentation。 | 画像分類で過学習を抑制。 |
| weight decay | SGD で係数を毎ステップ縮める。 | $L_2$ 正則化と等価。 |
| early stopping | val_loss が増え始めたら学習打ち切り。 | NN での過学習対策の基本。 |
| batch normalization | バッチごとに活性化を正規化。 | 副次的に正則化効果。 |
| shrinkage | 推定値を全体平均に向けて縮める。 | ベイズ階層モデルの中核。 |
過学習対策は主要ライブラリにすべて組み込まれており、 適切なオプションを知っているかどうかで成果が大きく変わる。 scikit-learn では、 線形系は Ridge(alpha=)、 Lasso(alpha=)、 ElasticNet(alpha=, l1_ratio=) で正則化強度を制御する。 木系は DecisionTreeRegressor(max_depth=, min_samples_leaf=, min_samples_split=) で複雑度を制限し、 RandomForestRegressor(n_estimators=, max_depth=, max_features=) でアンサンブル化する。 SVM では SVC(C=, gamma=) の C が誤分類への許容度 (小さいほど強い正則化)、 gamma がカーネル幅を制御する。 ロジスティック回帰は LogisticRegression(C=, penalty='l1'|'l2'|'elasticnet') で正則化を選ぶ。
PyTorch では、 torch.optim.SGD(weight_decay=) や torch.optim.AdamW(weight_decay=) で重み減衰を、 nn.Dropout(p=) でドロップアウトを、 nn.BatchNorm2d() でバッチ正規化を入れる。 早期停止は EarlyStopping コールバックを自作するか PyTorch Lightning の EarlyStopping を利用する。 XGBoost では reg_alpha ($L_1$)、 reg_lambda ($L_2$)、 max_depth、 min_child_weight、 subsample、 colsample_bytree、 early_stopping_rounds など多彩なオプションが用意されている。 LightGBM や CatBoost も同様の機能を持つ。 「自分のライブラリで利用できる過学習対策オプションを一覧化しておく」 ことは、 実務での試行錯誤を加速する基本準備である。
過学習という現象が示しているのは、 単にモデル選択のミスではなく、 「私たち人間が複雑さに惹かれる傾向」 そのものである。 47 都道府県の散布図を前にしたとき、 直感的には「10 次多項式の方が線形より高級で、 より良い予測ができそう」 に感じる。 しかし実際には、 真の関数 (人口と出生数の根本的な関係) は驚くほどシンプルな比例関係に近く、 線形で十分に近似できる。 高次多項式は「凝った数学を使っている」 という満足感を与えるが、 47 点の散布図にぴったり合うように曲がりくねった曲線を引いた瞬間、 未知の県への予測能力は失われる。 これは技術選択の失敗というより、 「複雑さ = 賢さ」 と誤認する人間の認知バイアスである。
奥深い洞察として、 「最も賢い人は最も単純なモデルを選ぶ」 という経験則がある。 オッカムの剃刀 (同じ説明力なら単純なものを選べ) は古くから知られているが、 機械学習においてはバイアス・バリアンス分解という形で数理的に正当化されている。 SSDSE-B-2026 という小さなデータセットを前にして、 「私はこのデータでどこまで精緻な予測が可能か」 を冷静に見極められるかどうかが、 データサイエンティストの成熟度を測る指標となる。 過学習を恐れるあまり何もしないのも問題だが、 同様に「複雑にすればよくなる」 という期待で次数を上げ続けるのも同じくらい問題である。 適切な複雑度を見極める眼を養うこと、 それが「過学習を理解する」 ということの本当の意味である。
実務での最後のアドバイスとして、 過学習対策は「人とコミュニケーションする能力」 とも深く結びついている。 ステークホルダーに「test_R² 0.93 で十分良いです」 と説明できる人と、 「もう少し精度を上げられそうです、 multi-layer NN で深い特徴を捉えれば」 と提案してしまう人とでは、 結果として届けるビジネス価値が大きく異なる。 過学習を「データに対する誠実さ」 として捉え、 「今ある情報量に対して妥当な複雑度を選ぶ」 という美徳を持つことが、 長く現場で信頼される技術者になるための条件である。 SSDSE-B-2026 を題材に過学習を体験し、 「単純で十分」 と言える勇気を身につけてほしい。
print(model.score(X_tr, y_tr) / model.score(X_te, y_te)) で診断値を即出力。最後に、 過学習との戦いから引き出される普遍的な教訓について述べたい。 過学習とは、 突き詰めれば「自分が手元に持っているデータに過剰な信頼を置く」 という認識論的な誤りである。 私たちは 47 都道府県のデータを見て、 ついそれが世界のすべてであるかのように振る舞う。 しかし実際には、 47 県の背後には未来の都道府県データ、 仮想的な政策介入後のデータ、 別の集計方法によるデータなど、 無限のバリエーションが存在する。 過学習対策とは、 そうした「見えていないデータ」 への謙虚さをモデルに組み込む技術であり、 ある意味でデータサイエンティストの哲学的な姿勢そのものを問う行為である。 シンプルなモデルを選ぶことは、 知識の限界を認めることでもある。
SSDSE-B-2026 のような教材データで過学習を体験し、 「自分のモデルは万能ではない」 「真の関数は未知である」 「未来のデータは予測不能な側面を持つ」 という感覚を身につけた人は、 将来どんな業界・タスクに直面しても、 適切な複雑度のモデルを選び、 適切な不確実性を顧客に伝え、 適切な再学習サイクルを設計できる。 これは技術スキルというより、 不確実性に対する「身のこなし」 である。 過学習という言葉から派生する学びは、 単なる回帰モデルの調整に留まらず、 データに対する真摯さ、 業務との対話力、 長期運用への責任感など、 データサイエンティストとしての成熟全体に関わる。 SSDSE-B-2026 を入り口に、 過学習の奥深い世界へ足を踏み入れていただきたい。
過学習の世界は広く、 数理的な深さも実務的な広がりも持つテーマである。 本ページを最後まで読んだ読者に持ち帰ってほしい言葉は 3 つある。 第 1 に 「シンプルで十分なら、 シンプルを選べ」。 これはオッカムの剃刀の現代版であり、 SSDSE のような小データでは特に強力な指針となる。 線形で test_R²=0.93 が出ているなら、 それは多くの実務で十分な精度である。 第 2 に 「汎化を信じるな、 測れ」。 自分のモデルが新しいデータでも機能するかは、 holdout / CV / 本番モニタリングの 3 段階で実測する以外に確かめる方法はない。 思い込みは禁物である。
第 3 に 「失敗を恐れず、 むしろ意図的に過学習させてみよ」。 過学習を経験することで、 「適切な複雑度」 の感覚が研ぎ澄まされる。 SSDSE-B-2026 の 47 県を素材に、 高次多項式で爆発する RMSE を自分の目で確かめれば、 一生忘れない学びとなる。 過学習という現象は、 データサイエンスを学ぶすべての人が一度は実演し、 その手触りを記憶に刻むべき教材である。 本ページの R442 追補は、 その実演を支える徹底ガイドとして書かれた。 47 都道府県の数字が、 あなたの過学習理解の扉を開くことを願う。
本ページの R442 追補は、 過学習という抽象的な概念を、 SSDSE-B-2026 という具体的な公的データに紐づけて立体的に理解できるよう構成した。 図 3 点 (散布図・ヒストグラム・箱ひげ図) と表 9 点、 Python コードブロック 3 件、 そして概念から実務、 ベイズ、 教育、 哲学までを 1 本のストーリーで貫いた。 過学習の理解は「数式で 1 回理解する」 では終わらず、 「実データで 1 回失敗してみる」 と「実務で 1 回監視する」 を経て初めて自分のものになる。 本ページを足がかりに、 ぜひ自分の手で 47 都道府県データを動かし、 多項式次数を上げて test_RMSE が爆発する瞬間を体験してほしい。 その体験こそが、 過学習という概念を一生忘れない記憶に変えてくれる。 47 県の散布図は、 過学習を学ぶ最高の教科書である。
補足として、 本ページで紹介した SSDSE-B-2026 を題材とする全コード例は、 Google Colab や Jupyter Notebook の環境で即座に再現実行できる。 まず data/raw/SSDSE-B-2026.csv を独立行政法人統計センターの公式ページから取得し、 上述の Python コードをそのままセルに貼り付ければ、 多項式次数を 1 から 10 まで変化させた時の train_RMSE と test_RMSE の対比表を再現できる。 過学習という現象を「読む」 段階から「自分の手で再現する」 段階へ進めば、 理解の深さは桁違いに変わる。 本ページの記述を信じるかどうかではなく、 ぜひ手元で実演し、 自分の結論として腹落ちさせてほしい。 これが過学習を学ぶ最短経路である。
「過学習」を扱う際の手法選択は、 状況に応じて以下のフローで判断すると迷いが減る。
このフローに沿って判断することで、 「過学習」を中核とした適切な手法選択ができる。
ここまでの節(プレイグラウンド・決定木の深さ実験)は、 1 つのモデルが複雑すぎることによる過学習を扱ってきました。 この節では、 もう一つの静かな経路を取り上げます。 候補をたくさん試して、 一番良かったものだけを報告する——この「探索」自体が、 モデルがどれほど単純でも過学習を生みます。 変数選択・ハイパーパラメータ探索・「相関の高い列を探す」作業のすべてに潜む罠で、 $n=47$ の SSDSE-B ではとりわけ深刻です。
109 人に「サイコロを 10 回振って出た 6 の回数」を報告させ、 一番多かった人を「6 を出す才能がある」と呼ぶでしょうか。 呼ばないはずです。 多数の候補から選んだ最大値は、 偶然による上振れを必ず含むからです。 相関探しも同じで、 $n=47$ のとき無相関でも $|r| > 0.288$ となる確率は 1 列あたり 5% あります($t$ 分布、 自由度 45、 両側 5% に対応)。 1 列だけ調べるなら誤り 5% で済みますが、 SSDSE-B の 109 列を全部調べて一番強い相関を報告すれば、 「偶然の当たり」を引く機会が 109 回に増えます。 訓練誤差最小のモデルを選ぶ行為も、 相関最大の列を選ぶ行為も、 数理的には同じ「max の上方バイアス」です。
実験します。 目的変数はシード 0 の正規乱数 47 個(完全に架空、 どの県とも無関係)。 説明変数は SSDSE-B-2026(2023 年・47 都道府県)の実データ 109 列です。 以下の数値はすべて、 このコードを実際に実行して得た値です。
1 2 3 4 5 6 7 8 9 10 11 12 | import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) d = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True) # 2023年・47都道府県 X = d.drop(columns=['SSDSE-B-2026', 'Code', 'Prefecture']) # 実データ109列 y = np.random.default_rng(0).standard_normal(47) # ★純粋な乱数(架空の目的変数) r = X.apply(lambda c: np.corrcoef(c, y)[0, 1]) # 109列それぞれと乱数の相関 print((r.abs() > 0.2876).sum()) # → 77 列が「5%有意」の閾値超え print(r.abs().idxmax(), r[r.abs().idxmax()]) # → E7202(各種学校生徒数) -0.467 |
実行結果(乱数はシード 0 で固定。 説明変数側はすべて SSDSE-B-2026 実測値):
| 項目 | 値 |
|---|---|
| 5%有意に相当する相関の閾値($n=47$) | $|r| > 0.2876$($t(45)=2.0141$ より) |
| 乱数目的変数で閾値を超えた列数 | 77 / 109 列 |
| 最大の相関 | E7202 各種学校生徒数 $r=-0.467$(見かけの $R^2=0.218$) |
| 2 位・3 位 | E6502 大学卒業者数 $r=-0.437$ / E6302 大学学生数 $r=-0.435$ |
| 乱数と A1101 総人口の相関 | $r=-0.343$ |
なぜ 77 列も「当たる」のか。 最後の行が種明かしです。 このシードの乱数は、 たまたま総人口と $r=-0.343$ の相関を持ちました。 SSDSE-B の列の大半は人口規模に比例する量(〇〇数・〇〇人口)なので、 1 つの偶然が共通因子(人口規模)を通じて 77 列に一斉に伝播したのです。 上位に並んだ「各種学校生徒数」「大学卒業者数」も全部この人口因子の分身で、 独立な証拠が 77 個あるわけではありません。 列同士が相関し合う実データでは、 偶然の当たりは 1 列ずつではなく束になってやってくる——これが「相関の高い列を探す」作業が思った以上に危険な理由です。
さらに、 相関上位 3 列(E7202・E6502・E6302)を選んで重回帰すると、 訓練 $R^2=0.218$。 このモデルを LOOCV(leave-one-out 交差検証)で評価しても、 列の選択を CV の外で済ませてしまうと $R^2=0.055$ と「わずかに正」に見えます。 選択を各 fold の訓練 46 県だけでやり直す誠実な LOOCV では $R^2=-0.247$、 つまり平均値を予測するより悪い、 が正解です。 特徴量選択も「学習」の一部なので、 交差検証の内側で行わないと、 CV してもなお過学習を見逃します(データリーケージの一形態)。
📝 補足(シード依存性):シード 0 はやや極端な引きです。 シード 0〜999 の 1000 通りで同じ実験を繰り返すと、 閾値超えの列数は平均 6.2 列、 最大 $|r|$ の平均は 0.309、 1 列以上「有意」が出たシードは 582/1000 でした(いずれも実測)。 極端な回でなくても、 2 回に 1 回以上は「乱数を説明できる実在の列」が見つかる——探索の怖さはここにあります。