- AI ⚙️
過学習とは|原因と対策、交差検証・正則化・リークをPythonで実測
結論
過学習(オーバーフィッティング)とは、機械学習のモデルが訓練データの細かな揺れ(ノイズ)まで覚えてしまい、訓練データでは成績が良いのに、新しいデータでは成績が落ちる状態です。見分け方は「訓練のスコアとテストのスコアの差」で、対策はモデルを単純にする・正則化・データを増やす・早期終了の4つが柱です。交差検証で測り、リーク(答えの漏れ)を防ぐことが前提になります。
この記事でわかること
- 多項式の次数・決定木の深さ・SVM の gamma で、訓練とテストの差が開く様子の実測
- k分割交差検証・Ridge・Lasso を numpy で自作し、scikit-learn と一致させた結果
- リークで「良すぎる」スコア(0.917・0.995・1.000)が出る5つの実例と防ぎ方
この記事で実際に動かしたもの:自作の架空データ(sin の曲線にノイズを足したもの・純粋なノイズ・人ごとにまとまったデータ・ランダムウォーク)と、scikit-learn 同梱の load_breast_cancer・load_diabetes・load_digits・load_iris・make_moons を使い、多項式回帰・決定木・SVM・ロジスティック回帰・Ridge・Lasso・ランダムフォレスト・numpy で書いた小さなニューラルネットワークを学習して、訓練とテストのスコアを測りました。
過学習は、AI や機械学習の入門で必ず出てくる言葉です。言葉の説明だけなら1行で済みますが、実際に困るのは「自分のモデルが過学習しているのか、どう確かめ、どこまで直せばよいのか」です。この記事では、図と1行の答えから入り、後半で式・自作のコード・実測の表・判断表まで進みます。
flowchart TD
A[訓練とテストを<br>測る] --> B{訓練も低い?}
B -->|はい| U[未学習]
B -->|いいえ| C{差が大きい?}
C -->|はい| O[過学習]
C -->|いいえ| D{良すぎる?}
D -->|はい| L[リークを疑う]
D -->|いいえ| G[汎化できた]
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| 過学習とは | 訓練データに合わせすぎて、新しいデータで成績が落ちる状態。英語は overfitting | 過学習とは |
| 過学習の原因 | モデルがデータ数に比べて複雑すぎる・データが少ない・ノイズが多い・特徴量が多い | 過学習の原因 |
| 過学習の見分け方 | 訓練とテスト(検証)のスコアの差を見る。学習曲線・検証曲線で傾向を見る | 学習曲線と検証曲線 |
| 交差検証とは | データを k 個に分け、1つずつテストに回して k 回測る方法。1回の分割の運に左右されにくい | 交差検証とは |
| 正則化とは | 係数が大きくなることに罰を与えて、モデルを滑らかにする方法。L2(Ridge)と L1(Lasso) | 正則化とは |
| L1 と L2 の違い | L2 は係数を全体に小さくする。L1 は不要な係数をちょうど0にする(特徴量を選ぶ) | L1正則化(Lasso) |
| 過学習の対策 | 単純にする・正則化・データを増やす・早期終了・ドロップアウト・アンサンブル | 過学習の対策の判断表 |
| スコアが良すぎる | 過学習ではなくリーク(テストの情報が学習に混ざる)を疑う | データリークとは |
| 未学習との違い | 未学習は訓練のスコアも低い。過学習は訓練だけ高い | 過学習とは |
確認環境:Ubuntu 24.04.5 LTS(CPU 2コア・メモリ8GB、ほかの作業と同時に動いている共有のマシン)の上で、Python 3.13.18、scikit-learn 1.9.1、numpy 2.5.3、scipy 1.18.1 を使いました。BLAS のスレッド数は1に固定し(OPENBLAS_NUM_THREADS=1)、1回の計算は60秒以内に収めています(いちばん重いリークの5つの実験で合計48.3秒)。データは scikit-learn 同梱のデータセットと、numpy で作った架空のデータだけで、ネットからのデータ取得はしていません。深層学習のライブラリは使わず、ドロップアウトの実験は numpy で書いた2層のネットワークです。交差検証・学習曲線・データリーク・Ridge と Lasso の式は scikit-learn 1.9.1 の公式ドキュメント(Cross-validation・Validation curves・Common pitfalls・Linear Models)、過学習の定義は Google の Machine Learning Crash Course、ドロップアウトと二重降下は原論文で確かめました。図は mermaid 11.16.0 で描画を確かめています(確認日 2026年11月5日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「過学習とは|原因と対策、交差検証・正則化・リークをPythonで実測」
過学習とは(意味・未学習との違い)
この節では、過学習の正確な意味と、ふだんの言葉の「覚えすぎ」との違い、未学習(アンダーフィッティング)との違いが分かります。
過学習とは、モデルが訓練データにぴったり合いすぎて、まだ見ていないデータに対して正しく予測できなくなることです。 Google の Machine Learning Crash Course は「トレーニング セットと非常によく一致する(記憶する)モデルを作成した結果、新しいデータに対して正しい予測ができなくなること」と説明し、その反対を「一般化(汎化)」と呼んでいます。scikit-learn のドキュメントも、学習に使ったデータでそのままテストするのは方法の誤りで、見たラベルを繰り返すだけのモデルでも満点になってしまう、と書いています。
ふだんの「覚えすぎ」と機械学習の過学習
| 場面 | 意味 | 具体例 |
|---|---|---|
| ふだんの言葉(勉強) | 問題の中身ではなく、答えの並びを丸暗記した状態 | 過去問は満点なのに、数字を変えた問題が解けない |
| 機械学習(過学習) | 訓練データの規則だけでなく、偶然の揺れ(ノイズ)まで覚えた状態 | 次数15の多項式で、訓練の誤差0.012・テストの誤差494(下の実測) |
| 機械学習(未学習) | モデルが単純すぎて、訓練データの規則も捉えられない状態 | 曲線のデータに直線を当てて、訓練・テストともに誤差0.31〜0.33 |
| 生成AI のファインチューニング | 少ない例で合わせ直しすぎて、学習した文例ばかり出す・ほかの力が落ちる状態 | 同じ言い回しを繰り返す(考え方は同じで、測り方は検証データの損失) |
過学習(overfitting)
訓練のスコアは高い、テストは低い
差(ギャップ)が大きい
モデルを単純に・正則化・データを増やす
未学習(underfitting)
訓練のスコアもテストも低い
差は小さい
モデルを複雑に・特徴を足す・正則化を弱める
大事なのは、過学習は「訓練のスコアが高いこと」そのものではなく、「訓練とテストの差が大きいこと」だという点です。訓練の正解率が100%でも、テストも同じくらい高ければ問題ありません。反対に、テストのスコアが訓練より高い・あり得ないほど高いときは、過学習ではなくリーク(後半の節)を疑います。
機械学習の全体像(教師あり・教師なし・強化学習の違い)は、別記事「機械学習とは|種類・ディープラーニングとの違いを実行結果で解説」で扱います。この記事は、その中の「汎化」の部分を深く掘る位置づけです。
最初の5分:過学習を自分の目で見る(多項式回帰)
この節では、20行ほどのコードで過学習を再現し、「訓練の誤差は下がり続けるのに、テストの誤差がある所から跳ね上がる」様子を数値で確かめられます。
sin の曲線にノイズ(標準偏差0.3)を足した点を20個だけ作り、次数を変えた多項式を当てはめます。テスト用には同じ作り方で1,000点を作ります。ノイズの分散が0.09なので、どんなに良いモデルでもテストの平均二乗誤差(MSE)は0.09前後より下がりません。これが「理想の値」の目安です。
import numpy as np
rng = np.random.default_rng(0)
def make(n):
x = rng.uniform(0, 1, n)
return x, np.sin(2 * np.pi * x) + rng.normal(0, 0.3, n)
xtr, ytr = make(20) # 訓練は20点だけ
xte, yte = make(1000) # テストは1000点
for d in [1, 2, 3, 4, 6, 9, 12, 15, 19]:
c = np.polyfit(xtr, ytr, d) # 次数 d の多項式を最小二乗で当てはめる
tr = np.mean((np.polyval(c, xtr) - ytr) ** 2)
te = np.mean((np.polyval(c, xte) - yte) ** 2)
print(d, round(tr, 4), round(te, 4), f"{np.abs(c).max():.3g}")
| 次数 | 訓練の MSE | テストの MSE | 係数の最大の大きさ | 状態 |
|---|---|---|---|---|
| 1 | 0.3321 | 0.3143 | 1.69 | 未学習 |
| 2 | 0.3081 | 0.3252 | 2.02 | 未学習 |
| 3 | 0.0356 | 0.1319 | 44 | ちょうどよい |
| 4 | 0.0353 | 0.1327 | 39.2 | ちょうどよい |
| 6 | 0.0236 | 0.1276 | 515 | ちょうどよい |
| 9 | 0.0198 | 0.1509 | 39,400 | 過学習の始まり |
| 12 | 0.0163 | 0.3314 | 2,060万 | 過学習 |
| 15 | 0.0121 | 493.8 | 161億 | 過学習(大きく外す) |
| 19 | 0.0057 | 3,528,480 | 1.9兆 | 過学習(数値も不安定) |
読み取れることは3つです。1つ目、訓練の誤差は次数を上げるほど必ず下がります(自由度が増えるので)。訓練の誤差だけ見ていると、いちばん複雑なモデルを選んでしまいます。2つ目、訓練の誤差が理想の0.09を下回った時点で、ノイズを覚え始めています。3つ目、過学習したモデルは係数が極端に大きくなります。点と点の間で大きく振れて、無理やり全部の点を通ろうとするためです。この「係数が大きい」ことに罰を与えるのが、後半の正則化です。
次数19では RankWarning: Polyfit may be poorly conditioned という警告も出ました。係数が1兆を超え、計算そのものが不安定になっているという意味です。次数9でも、numpy の polyfit と scikit-learn の PolynomialFeatures+LinearRegression で、テストの予測が最大0.97ずれました(次数3では差が 3.6e-14 で一致)。過学習したモデルは、計算の細かな違いでも結果が変わるほど不安定だということです。
numpy の配列の作り方・演算の基本は、関連記事「NumPyの使い方 基本一覧|配列の作成・演算・形状変更」で扱っています。
過学習の原因(複雑さ・データ数・ノイズ・特徴量)
この節では、過学習が起きる原因を4つに分け、それぞれがどの実験の数値に表れているかが分かります。
過学習の原因は、一言でいえば「モデルの自由度が、データが持っている情報の量より大きい」ことです。 上位の解説記事で挙げられる原因(データ不足・偏り・複雑すぎるモデル・学習のしすぎ・特徴量の多さ)は、すべてこの1つに言い換えられます。
1モデルが複雑すぎる
多項式の次数・決定木の深さ・SVM の gamma・ニューラルネットワークの大きさ。次数15は20点に対して自由度が大きすぎた。
2データが少ない
同じ次数9でも、訓練12点ではテストの MSE が354、1,000点では0.096。データが増えると同じモデルが過学習しなくなる。
3ノイズ・誤ったラベルが多い
ラベルの20%を乱したデータでは、ネットワークが間違ったラベルまで覚えて訓練の正解率100%・テストは0.908→0.832に下がった。
4特徴量が多すぎる
本物の10列にノイズの40列を足すと、線形回帰の訓練 R² 0.65・テスト 0.30。ノイズの列にも係数が付いた。
「学習回数(エポック)が多すぎる」はニューラルネットワークや勾配ブースティングで出てくる原因で、学習を続けるほど実質的な自由度が増えていく、と考えると上の1と同じです。「データの偏り」は、訓練データが本番のデータを代表していないことで、テストのデータまで同じように偏っていると数値では見つかりません。後者は過学習というより「データの作り方」の問題で、分割の方法(後半の GroupKFold・TimeSeriesSplit)で一部を見つけられます。
データを増やす効果(次数9のまま)
モデルを変えずに、訓練データの数だけを変えました。乱数を変えて20回ずつ測った中央値です。
| 訓練データの数 | 訓練の MSE | テストの MSE | 差 |
|---|---|---|---|
| 12 | 0.0090 | 353.6 | 極端に大きい |
| 20 | 0.0411 | 1.3414 | 大きい |
| 50 | 0.0733 | 0.1206 | 0.047 |
| 100 | 0.0790 | 0.1038 | 0.025 |
| 1,000 | 0.0899 | 0.0960 | 0.006 |
| 10,000 | 0.0893 | 0.0952 | 0.006 |
データが増えるほど、訓練の誤差は上がり、テストの誤差は下がって、両方が理想の0.09に近づきます。 訓練の誤差が上がるのは悪いことではなく、「ノイズを覚える余地が減った」という意味です。1,000点を超えると、それ以上増やしても変わらなくなりました。ここから先は、モデルの形(次数9で sin をどこまで表せるか)が上限を決めています。
決定木の深さで見る過学習と未学習
この節では、分類のモデル(決定木)でも、深さを増やすと訓練とテストの正解率の差が開くことを実測で確かめられます。
2つの半月が重なった make_moons(noise=0.35)を400点作り、半分を訓練、半分をテストにしました。決定木は深さの上限(max_depth)を付けないと、訓練データを全部正しく分けるまで枝を伸ばします。
| 深さの上限 | 訓練の正解率 | テストの正解率 | 葉の数 |
|---|---|---|---|
| 1 | 0.8250 | 0.7750 | 2 |
| 2 | 0.8750 | 0.8550 | 4 |
| 3 | 0.8800 | 0.8750 | 8 |
| 4 | 0.8950 | 0.8550 | 13 |
| 6 | 0.9200 | 0.8600 | 21 |
| 8 | 0.9650 | 0.8700 | 28 |
| 12 | 1.0000 | 0.8400 | 37 |
| 上限なし | 1.0000 | 0.8400 | 37 |
テストの正解率は深さ3(0.875)が最高で、上限なしでは訓練1.000・テスト0.840まで差が開きました。葉が37枚あるのは、訓練200点のうち、ノイズで反対側にはみ出した点にも専用の葉を作ったためです。同じことを load_breast_cancer(訓練398件)で行うと、深さ5でテスト0.9123、深さ7以上で訓練1.0000・テスト0.9064でした。こちらは差が小さめで、データのノイズが少ないほど、過学習の害も小さく出ます。
決定木で深さ・葉の最小サンプル数・ccp_alpha を使って枝を切る方法(剪定)は、別記事「決定木」(公開準備中)で詳しく扱います。この記事では「深さ=モデルの複雑さのつまみ」として扱います。
学習曲線と検証曲線で過学習を見分ける
この節では、学習曲線(データ数を変える)と検証曲線(ハイパーパラメータを変える)の違いと、表のどこを見れば過学習・未学習と判断できるかが分かります。
2つの曲線の違い
どちらも、訓練のスコアと検証のスコアの「差」を見る
検証曲線:SVM の gamma を変える
手書き数字の load_digits(1,797件・8×8画素)に、RBF カーネルの SVM(C=10)を当て、gamma だけを変えて5分割の交差検証で測りました(17.8秒)。gamma は「1つの点の影響がどれだけ近くに限られるか」で、大きいほど複雑な境界を作れます。
| gamma | 訓練 | 検証 | 差 | 判断 |
|---|---|---|---|---|
| 0.0001 | 0.7122 | 0.7073 | +0.005 | 未学習(両方低い) |
| 0.001 | 0.9602 | 0.9560 | +0.004 | まだ伸びる |
| 0.01 | 0.9917 | 0.9828 | +0.009 | 良い |
| 0.1 | 1.0000 | 0.9883 | +0.012 | 良い |
| 0.3 | 1.0000 | 0.9889 | +0.011 | 検証が最高 |
| 1 | 1.0000 | 0.9811 | +0.019 | 過学習の始まり |
| 3 | 1.0000 | 0.8002 | +0.200 | 過学習 |
gamma 0.1〜3 はどれも訓練1.0000です。訓練のスコアだけでは、0.3 と 3 の違い(検証で0.989と0.800)がまったく見えません。 検証のスコアが山の形になり、山の右側で差が開くのが過学習、左側で両方低いのが未学習です。
学習曲線:データ数を変える
同じ load_digits で、決定木(深さ上限なし)とロジスティック回帰を、訓練データの数を変えて測りました(2.9秒)。
| 訓練の件数 | 決定木 訓練/検証 | 決定木の差 | ロジスティック回帰 訓練/検証 | ロジスティック回帰の差 |
|---|---|---|---|---|
| 71 | 1.000/0.469 | +0.531 | 0.994/0.773 | +0.221 |
| 143 | 1.000/0.579 | +0.421 | 0.993/0.808 | +0.186 |
| 359 | 1.000/0.700 | +0.301 | 0.994/0.914 | +0.081 |
| 718 | 1.000/0.787 | +0.213 | 0.993/0.943 | +0.050 |
| 1,077 | 1.000/0.832 | +0.168 | 0.991/0.954 | +0.036 |
| 1,437 | 1.000/0.859 | +0.141 | 0.986/0.969 | +0.016 |
決定木は訓練がずっと1.000で、検証が上がり続けています。差が大きく、検証がまだ上がっている=データを増やすと良くなる過学習です。ロジスティック回帰は差が0.016まで縮まり、2本の線がほぼ合流しています。こうなるとデータを増やしても伸びが小さく、伸ばすならモデルや特徴量を変える番です。
from sklearn.datasets import load_digits
from sklearn.model_selection import learning_curve, validation_curve, StratifiedKFold
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
X, y = load_digits(return_X_y=True); X = X / 16.0
cv = StratifiedKFold(5, shuffle=True, random_state=0)
gammas = [1e-4, 1e-3, 1e-2, 0.03, 0.1, 0.3, 1, 3]
tr, va = validation_curve(SVC(C=10), X, y, param_name="gamma", param_range=gammas, cv=cv)
print(tr.mean(axis=1), va.mean(axis=1)) # 行が設定、列が分割
sizes, tr, va = learning_curve(DecisionTreeClassifier(random_state=0), X, y,
train_sizes=[0.05, 0.1, 0.25, 0.5, 0.75, 1.0], cv=cv)
print(sizes, tr.mean(axis=1), va.mean(axis=1))
グラフにするときは、横軸に gamma(対数目盛り)やデータ数、縦軸にスコアを取り、訓練と検証の2本の線を描きます。scikit-learn 1.9.1 には、そのまま描ける LearningCurveDisplay・ValidationCurveDisplay もあります。matplotlib での描き方と日本語の文字化けの直し方は、関連記事「matplotlibの使い方と日本語文字化け対策|OS別・Colab・3.12対応」で扱っています。
交差検証とは(k分割を自作してcross_val_scoreと一致させる)
この節では、k分割交差検証の仕組みを numpy で自作し、scikit-learn の cross_val_score と同じ値になることと、1回だけの分割(ホールドアウト)がどれだけぶれるかが分かります。
k分割交差検証(k-fold cross validation)は、データを k 個に分け、そのうち1つをテスト、残りを訓練にして学習・評価する作業を、テストに回す部分を替えながら k 回繰り返す方法です。 k 回のスコアの平均を性能の見積もりにし、ばらつき(標準偏差)も一緒に見ます。
データを k 個に分ける
n 件を k 個の塊(フォールド)に分ける。割り切れないときは、前の塊から1件ずつ多くする(scikit-learn の KFold と同じ)。
1つをテスト、残りで学習する
1番目の塊をテストにし、残り k−1 個でモデルを学習して、テストのスコアを記録する。
テストに回す塊を替えて繰り返す
2番目、3番目…と替えて k 回。すべてのデータが1回ずつテストに使われる。
平均とばらつきを見る
k 個のスコアの平均を見積もりにする。標準偏差が大きいときは、データが少ないか、塊ごとに性質が違う。
import numpy as np
from sklearn.datasets import load_diabetes
from sklearn.linear_model import Ridge
from sklearn.model_selection import cross_val_score
def my_cv(make_model, X, y, k=5):
n = len(y); idx = np.arange(n)
sizes = np.full(k, n // k); sizes[: n % k] += 1 # 前の塊から1件多く
scores, start = [], 0
for s in sizes:
te = idx[start:start + s]
tr = np.concatenate([idx[:start], idx[start + s:]])
start += s
m = make_model().fit(X[tr], y[tr]) # 毎回新しく学習
scores.append(m.score(X[te], y[te])) # 回帰なので R²
return np.array(scores)
X, y = load_diabetes(return_X_y=True)
a = my_cv(lambda: Ridge(alpha=1.0), X, y)
b = cross_val_score(Ridge(alpha=1.0), X, y, cv=5)
print(a.round(6)); print(b.round(6)); print(np.abs(a - b).max())
[0.321665 0.440485 0.422104 0.424661 0.441961]
[0.321665 0.440485 0.422104 0.424661 0.441961]
0.0
差は0.0で、完全に一致しました。回帰のモデルに cv=5 と整数を渡すと、scikit-learn はデータを並べ替えずに前から5つに分けます(KFold)。 公式ドキュメントには、分類器(ClassifierMixin を継承したもの)なら StratifiedKFold、それ以外は KFold を使う、と書かれています。StratifiedKFold は、各塊のクラスの割合をそろえる分け方です。
1回の分割(ホールドアウト)はどれだけぶれるか
同じ Ridge を、テスト20%の1回だけの分割で、乱数を0〜19に変えて20回測りました。
分け方の運だけで、R² が0.33にも0.50にもなりました。 2つのモデルを比べるとき、1回の分割の差が0.05なら、それはモデルの差ではなく運の差かもしれません。交差検証は、この運を平均して小さくするための方法です。データが数百件しかないときほど効きます。
並び順の罠:iris で正解率0になる
load_iris は、先頭50件がクラス0、次の50件がクラス1、最後の50件がクラス2の順に並んでいます。これを並べ替えない KFold で3つに分けると、次のようになりました。
KFold(3, shuffle=False): [0. 0. 0. ]
cv=3(分類器なので StratifiedKFold): [0.98 0.96 0.96]
KFold(3, shuffle=True, random_state=0): [0.94 0.96 0.96]
テストの塊が「クラス2だけ」のとき、訓練にはクラス2が1件も無いので、全部外れます。自分で KFold を作って渡すときは shuffle=True を付けるか、分類なら StratifiedKFold を使います。 反対に、時系列のデータでは並べ替えてはいけません(データリークの節)。
正則化とは(L2正則化・Ridgeを自作して一致させる)
この節では、正則化の考え方と L2 正則化(Ridge 回帰)の式、numpy で解いた係数が scikit-learn と一致すること、次数15の過学習が正則化でどこまで直るかが分かります。
正則化とは、「誤差を小さくする」ことに加えて「係数を大きくしない」ことも目標に入れて、モデルが無理な形に曲がるのを抑える方法です。 最初の実験では、過学習したモデルの係数が161億まで膨らんでいました。その膨らみに罰金を課すのが正則化です。
Ridge 回帰(L2 正則化)が最小にする量は、次のとおりです(scikit-learn の Ridge の定義)。
誤差の二乗の合計 ||y − Xw||² + alpha × 係数の二乗の合計 ||w||²
alpha が0なら普通の最小二乗法、alpha を大きくするほど係数は0に近づきます。この式は答えを1回の計算で出せます(切片は平均を引いて外に出します)。
w = (XᵀX + alpha·I)⁻¹ Xᵀy
import numpy as np
from sklearn.linear_model import Ridge
def my_ridge(X, y, alpha):
xm, ym = X.mean(axis=0), y.mean()
Xc = X - xm # 平均を引く(切片には罰を与えない)
w = np.linalg.solve(Xc.T @ Xc + alpha * np.eye(X.shape[1]), Xc.T @ (y - ym))
return w, ym - xm @ w # 係数と切片
w, b = my_ridge(A, ytr, 10.0)
m = Ridge(alpha=10.0).fit(A, ytr)
print(np.abs(w - m.coef_).max(), abs(b - m.intercept_))
2.6645352591003757e-14 0.0
係数の差は 2.7e-14 で、計算の丸め誤差の範囲で一致しました(A は次の節で使う、標準化した50列のデータです)。
次数15の過学習を Ridge で直す
最初の実験と同じ20点に、x¹〜x¹⁵ の15列を作り、alpha を変えて当てはめました。sklearn の Ridge(solver="cholesky")と自作の結果は、テストの予測で最大でも 2.7e-9 の差でした。
| alpha | 訓練の MSE | テストの MSE | 係数の最大の大きさ |
|---|---|---|---|
| 0(正則化なし) | 0.0121 | 493.8 | 161億 |
| 0.00000001 | 0.0197 | 0.1392 | 299 |
| 0.000001 | 0.0209 | 0.2582 | 71.6 |
| 0.0001 | 0.0276 | 0.1219 | 17.4 |
| 0.001 | 0.0365 | 0.1270 | 13.9 |
| 0.01 | 0.0951 | 0.1300 | 6.18 |
| 0.1 | 0.1934 | 0.1990 | 1.78 |
| 1 | 0.2890 | 0.2699 | 0.643 |
alpha を 0.00000001 にしただけで、テストの MSE が494から0.14に下がりました。 係数の最大は161億から299になっています。一方で alpha=1 まで強めると、訓練もテストも0.27〜0.29で、今度は未学習です。正則化は「強ければ良い」ものではなく、検証曲線や交差検証で alpha を選びます。scikit-learn では RidgeCV・LassoCV・GridSearchCV がこの選び方を自動で行います。
L1正則化(Lasso)で係数がちょうど0になる様子
この節では、L1 正則化(Lasso)と L2 正則化(Ridge)の違いを、ノイズの列を混ぜたデータで、残った係数の数と精度の変化から確かめられます。
糖尿病の進行度を予測する load_diabetes(442件・10列)に、意味のないノイズの列を40本足して50列にしました。訓練221件・テスト221件に分け、訓練データだけで標準化しています。正則化なしの線形回帰は、訓練 R² 0.6515・テスト R² 0.3033 で、ノイズの40列の係数の絶対値の合計が134.9 もありました。ノイズまで「説明に使って」しまっています。
| Ridge の alpha | 訓練 R² | テスト R² | 係数の大きさ(L2 ノルム) | ノイズ列の係数の合計 |
|---|---|---|---|---|
| 0.01 | 0.6515 | 0.3033 | 58.1 | 134.8 |
| 10 | 0.6500 | 0.3203 | 46.5 | 127.4 |
| 100 | 0.6199 | 0.3850 | 35.2 | 94.6 |
| 300 | 0.5423 | 0.3781 | 25.1 | 64.7 |
| 1000 | 0.3747 | 0.2816 | 13.9 | 32.0 |
| Lasso の alpha | 訓練 R² | テスト R² | 残った本物の列(10中) | 残ったノイズの列(40中) |
|---|---|---|---|---|
| 0.01 | 0.6515 | 0.3043 | 10 | 40 |
| 0.5 | 0.6483 | 0.3484 | 8 | 37 |
| 1 | 0.6419 | 0.3784 | 7 | 32 |
| 2 | 0.6255 | 0.4146 | 6 | 25 |
| 4 | 0.5885 | 0.4404 | 5 | 13 |
| 8 | 0.5260 | 0.4311 | 4 | 1 |
| 20 | 0.4301 | 0.3719 | 3 | 0 |
L2(Ridge)
罰は係数の二乗の合計
全部の係数を少しずつ小さくする。0にはならない
手元の最高はテスト R² 0.385(alpha=100)
L1(Lasso)
罰は係数の絶対値の合計
不要な係数をちょうど0にする=列を選ぶ
手元の最高はテスト R² 0.440(alpha=4)
ノイズの列が多いこのデータでは、列を捨てられる Lasso の方がテストの成績が良くなりました。 alpha=4 で残った本物の列は sex・bmi・bp・s3・s5 の5本で、BMI と血圧が残るのは直感にも合います。ただし、互いに似た列(相関が強い列)があると、Lasso はそのうち1本だけを残して他を0にしがちで、どれが残るかはデータの揺れで変わります。両方の罰を混ぜた Elastic Net(ElasticNet)は、その中間です。
Lasso を座標降下法で自作する
Lasso の式(scikit-learn の定義は (1/(2n))·||y − Xw||² + alpha·||w||₁)は1回の計算では解けないので、係数を1つずつ順に更新する「座標降下法」で解きます。1つの係数について解くと、「小さい値を0に切り捨てる」形(ソフトしきい値)になるのが、係数がちょうど0になる理由です。
def my_lasso(X, y, alpha, iters=2000):
n, p = X.shape
xm, ym = X.mean(axis=0), y.mean()
X = X - xm; y = y - ym
w = np.zeros(p); r = y.copy() # r は残差
z = (X ** 2).sum(axis=0) / n
for _ in range(iters):
biggest = 0.0
for j in range(p):
r += X[:, j] * w[j] # j 番目を外した残差
rho = X[:, j] @ r / n
new = np.sign(rho) * max(abs(rho) - alpha, 0.0) / z[j] # ソフトしきい値
biggest = max(biggest, abs(new - w[j]))
w[j] = new
r -= X[:, j] * w[j]
if biggest < 1e-10:
break
return w, ym - xm @ w
alpha=4 で、自作と Lasso(alpha=4.0, tol=1e-12) の係数の差は最大 6.3e-11、0でない係数の数はどちらも18本(本物5+ノイズ13)で一致しました。
データを増やす・早期終了・ドロップアウトの効果
この節では、ニューラルネットワークでよく使われる早期終了(early stopping)とドロップアウトが、ラベルに誤りの混じったデータでどう効くかを、numpy で自作したネットワークで確かめられます。
load_digits から訓練240件・検証60件・テスト600件を取り出し、訓練のラベルの20%(49件)をわざと乱しました。ノイズの多いデータで過学習を起こしやすくするためです。ネットワークは入力64→隠れ層256(ReLU)→出力10(ソフトマックス)の2層で、全件をまとめた勾配降下(学習率0.1)を2,000回行いました(1本あたり5.6〜8.3秒)。
| 学習の回数 | ドロップアウトなし 訓練/テスト | ドロップアウト0.5 訓練/テスト |
|---|---|---|
| 50 | 0.775/0.875 | 0.767/0.880 |
| 100 | 0.783/0.908 | 0.783/0.905 |
| 400 | 0.850/0.893 | 0.850/0.907 |
| 700 | 0.892/0.872 | 0.871/0.908 |
| 1,000 | 0.925/0.845 | 0.904/0.893 |
| 1,500 | 0.992/0.835 | 0.946/0.887 |
| 2,000 | 1.000/0.832 | 0.992/0.880 |
訓練の正解率は「乱したラベル」に対するものです。乱したラベルの約2割は本当の答えと違うので、訓練で100%を取ったということは、間違ったラベルまで丸暗記したということです。そのぶんテストは0.908から0.832まで下がりました。ドロップアウト(学習のたびに隠れ層の半分をランダムに0にする)を入れると、暗記の進みが遅くなり、2,000回でもテストは0.880に留まりました。
1早期終了
検証の正解率が最高だった時点で止めると、ドロップアウトなしで450回目(テスト0.888)、ありで575回目(テスト0.908)。
2ドロップアウト
最後まで学習しても、テストの下がり方が小さい(0.832に対し0.880)。一部のニューロンに頼った暗記がしにくくなる。
3検証データの小ささ
検証60件では1件が0.017に当たる。早期終了の止める時点は、検証の件数が少ないとぶれやすい。
ドロップアウトの部分は、学習のときだけ次の2行を足しています。学習時に残したニューロンの値を 1/(1−p) 倍しておくと、予測のときは何もせずに全部のニューロンを使えます(逆ドロップアウト)。
M = (rng.random(H.shape) > p_drop) / (1 - p_drop) # 残す所は 1/(1-p)、消す所は 0
H_drop = H * M # 逆伝播でも同じ M を掛ける
scikit-learn の早期終了は、止まるのが早すぎることもある
scikit-learn の MLPClassifier(隠れ層128)にも early_stopping=True があります。訓練の10%を検証に取り分け、検証のスコアが10回続けて良くならなければ止めます。ラベルを乱さない load_digits(訓練898件)では、次のようになりました。
MLP early_stopping False n_iter_ 282 train 1.0 test 0.9755
MLP early_stopping True n_iter_ 47 train 0.9465 test 0.9244 best_validation_score_ 0.9556
ノイズの少ないデータでは、早期終了をしない方がテストは高く(0.9755)、早期終了は47回で止まってテスト0.9244でした。 訓練の一部を検証に回すぶん学習に使えるデータが減り、検証90件の揺れで早く止まったためです。早期終了は「過学習の兆しがある」ときの対策で、使うなら n_iter_no_change を大きめにし、学習曲線と合わせて判断します。
ニューラルネットワークの順伝播・逆伝播そのものの仕組みは、関連記事「ニューラルネットワークとは|仕組み・学習・AttentionをNumPyで実演」で扱っています。
データリークとは(「良すぎる」スコアの実例5つ)
この節では、評価のときにテストの情報が学習に漏れて、スコアが本当より良く見える「データリーク」の典型5つを、実際に出た数値と直した後の数値で確かめられます。
データリーク(leakage)とは、予測のときには手に入らないはずの情報が、学習や前処理に混ざってしまうことです。 scikit-learn の Common pitfalls のページは、交差検証などで性能が楽観的に見積もられ、新しいデータで性能が下がる、と説明し、基本の決まりとして「テストデータで fit を呼ばない」ことを挙げています。過学習は「テストで悪くなる」ので見つけられますが、リークは「テストでも良く見える」ので、見つけにくいのが厄介です。
| リークの種類 | データ | リークあり | 正しく分けた後 | 本当の実力の目安 |
|---|---|---|---|---|
| 1. 全データで特徴量を選ぶ | 純粋なノイズ 60件×5,000列 | 正解率 0.917 | 0.483 | 0.5(当てずっぽう) |
| 2. 答えから作られた列(ターゲットリーク) | breast_cancer+診断後の列 | 正解率 1.000 | 0.965(その列を除く) | 0.965 |
| 3. 同じ人が訓練とテストにまたがる | 40人×10件、ラベルは人ごとにランダム | 正解率 0.995 | 0.402(GroupKFold) | 0.5 |
| 4. 時系列を並べ替えて分割 | ランダムウォーク600点を時刻から予測 | R² 0.993 | −1.127(TimeSeriesSplit) | 予測できない |
| 5. 143通りの設定から最良を選ぶ | ノイズ100件×20列の SVM | 正解率 0.61 | 0.55(入れ子の交差検証) | 0.5 |
1. 全データで特徴量を選んでから交差検証する
答えと何の関係もない乱数の列を5,000本作り、全60件を使って「答えと相関が強い20本」を選んでから交差検証しました。5回とも0.917です。5,000本もあれば、偶然答えとそろった列が必ず混ざり、その選び方にテストの塊の答えが使われているためです。選ぶ処理を Pipeline に入れて、各回の訓練データだけで選ばせると0.483になりました。 scikit-learn の公式ページにも同じ形の例があり、そこでは0.76と0.5でした。
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.pipeline import make_pipeline
# NG: 全データで選んでから交差検証(テストの答えが選択に使われる)
Xs = SelectKBest(f_classif, k=20).fit_transform(X, y)
cross_val_score(LogisticRegression(max_iter=1000), Xs, y, cv=cv) # 0.917
# OK: 選択も Pipeline に入れ、各回の訓練データだけで fit させる
pipe = make_pipeline(SelectKBest(f_classif, k=20), LogisticRegression(max_iter=1000))
cross_val_score(pipe, X, y, cv=cv) # 0.483
同じことは、標準化(平均と標準偏差)や欠損値の穴埋めを全データで fit したときにも起きます。標準化で起きるリークと Pipeline の書き方は、別記事「標準化と正規化(特徴量スケーリング)」(公開準備中)で詳しく扱います。
2. 答えから作られた列が混ざる(ターゲットリーク)
乳がんの診断データに、「診断が出た後に記録される値」を想定して、答えに小さなノイズを足しただけの列を1本加えました。正解率は0.965から1.000になり、ランダムフォレストの重要度でその列が1位になりました。本番の予測のときにはその値はまだ無いので、このモデルは使えません。 「その列は、予測する時点で手に入るか」を列ごとに確かめるのが唯一の防ぎ方です。重要度で1本だけ飛び抜けた列は、まずリークを疑います。
3. 同じ人・同じ物のデータが訓練とテストにまたがる
40人から10件ずつデータを取り、ラベルは人ごとにランダムに決めました(特徴と答えには関係がない)。普通の KFold では0.995、人ごとに分ける GroupKFold では0.402 です。KFold では、同じ人の別のデータが訓練に入っているので、「この人の癖ならこのラベル」と覚えるだけで当たってしまいます。患者・利用者・機械・店舗など、同じ発生源から複数件あるデータは GroupKFold(または StratifiedGroupKFold)で分けます。
4. 時系列を並べ替えて分ける
ランダムウォーク(次の値が前の値に乱数を足したもの)を、時刻だけから予測しました。並べ替える KFold では R² 0.993 と、ほぼ完全に当たっています。テストの点の前後の時刻が訓練に入っていて、その間を埋めるだけだからです。過去で学習して未来をテストする TimeSeriesSplit では、5回とも R² がマイナス(平均 −1.127) で、未来はまったく予測できていません。売上・アクセス数・株価などは、必ず時間の順に分けます。
5. 検証データに合わせすぎる(設定の選びすぎ)
答えがランダムな100件に、SVM の C と gamma の組み合わせ143通りを試し、交差検証のスコアが最高のものを選びました。best_score_ は0.61です。143回も試せば、偶然0.6を超える設定が出てきます。選ぶ処理ごと外側の交差検証で包む「入れ子の交差検証(nested CV)」では0.55 で、0.5に近づきました。設定を選ぶのに使ったスコアを、そのまま性能として報告しないことが大事です。
やってはいけない → 代わりに
公式の決まり:テストデータで fit を呼ばない
- 1分割は前処理より先に標準化・穴埋め・特徴量選択・次元削減は、全部 Pipeline の中に入れる
- 2列ごとに「予測の時点で手に入るか」診断後・解約後・支払い後に記録される値は使えない
- 3同じ発生源は同じ側へ同じ人・同じ機械のデータは GroupKFold でまとめて分ける
- 4時系列は時間の順にTimeSeriesSplit。テストより未来のデータで学習しない
- 5最終テストは1回だけ設定選びは検証データか入れ子の交差検証で。テストを見て設定を直さない
二重降下(パラメータを増やすと再び良くなる現象)
この節では、「パラメータが多いほど過学習する」という教科書の説明が、パラメータをさらに増やすと当てはまらなくなる「二重降下(double descent)」を、小さな実験で確かめられます。
大規模なニューラルネットワークは、訓練データの件数よりはるかに多いパラメータを持ち、訓練データを完全に覚えられるのに、テストでも良い成績を出します。Belkin らの論文(PNAS、2019年)は、モデルの大きさを増やすと、テストの誤差が「下がる→データ数と同じあたりで山になる→また下がる」と2回下がる現象を報告し、二重降下と名付けました。
手元では、訓練40件の回帰に、ランダムな重みで作った ReLU の特徴を p 個作り、最小二乗(解が複数あるときは係数が最も小さい解)で当てはめました。乱数を変えた10回の中央値です(全体で0.9秒)。
| 特徴の数 p | 訓練の MSE | テストの MSE | 状態 |
|---|---|---|---|
| 5 | 0.0678 | 0.0852 | 未学習ぎみ |
| 20 | 0.0087 | 0.0385 | 1回目の谷 |
| 36 | 0.0014 | 0.1821 | 過学習 |
| 40(=訓練の件数) | 0.0000 | 3.4163 | 山の頂上 |
| 50 | 0.0000 | 0.1579 | 再び下がる |
| 160 | 0.0000 | 0.0462 | 再び下がる |
| 1,600 | 0.0000 | 0.0345 | 1回目の谷より良い |
p=40 で訓練をちょうど通る解が1つだけになり、ノイズに合わせるために係数が極端に大きくなって、テストの誤差が3.4まで跳ね上がりました。p を増やすと、訓練を通る解がたくさんある中から「係数の最も小さい解」が選ばれ、これが正則化と同じ働きをして、テストの誤差が1回目の谷(0.0385)より下の0.0345まで下がりました。
ここから言えるのは「大きなモデルは過学習しない」ではなく、「訓練の誤差が0でも、どの解を選ぶか(暗黙の正則化)次第で汎化する」ということです。 表形式の小さなデータでモデルを作る多くの業務では、この記事の前半の考え方(差を見る・正則化・交差検証)がそのまま当てはまります。
よくあるエラー・つまずき(原文つき)
この節では、交差検証・正則化・学習曲線のコードで実際に出たエラーと警告の原文と、その直し方が分かります。
| 出たメッセージ(原文) | 原因 | 直し方 |
|---|---|---|
ValueError: Setting a random_state has no effect since shuffle is False. | KFold(5, random_state=0) と、並べ替えなしで乱数だけ指定した | KFold(5, shuffle=True, random_state=0) |
ValueError: Cannot have number of splits n_splits=5 greater than the number of samples: n_samples=3. | データが分割数より少ない | データを増やすか、cv を小さくする |
UserWarning: The least populated class in y has only 1 members, which is less than n_splits=5. | 1件しかないクラスがあり、各塊に配れない(エラーにはならず実行は続いた) | まれなクラスをまとめる・データを増やす・cv を減らす |
ValueError: The 'groups' parameter should not be None. | GroupKFold に groups を渡していない | cross_val_score(m, X, y, cv=GroupKFold(5), groups=g) |
スコアが [-3305.7 -3549.8 ...] とマイナスになる | scoring="neg_mean_squared_error" は「大きいほど良い」にそろえるため符号を反転している | MSE は -scores で読む |
InvalidParameterError: The 'scoring' parameter of cross_val_score must be a str among {...} | scoring="mse" のように、無い名前を書いた | エラーに並ぶ名前から選ぶ("neg_mean_squared_error" など) |
FitFailedWarning: 5 fits failed out of a total of 10.(中身は This solver needs samples of at least 2 classes in the data, but the data contains only one class) | learning_curve の小さな訓練サイズに1クラスしか入らない(iris を並べ替えずに分けた)。スコアは nan になる | cv に StratifiedKFold(shuffle=True) を渡す。原因を見るには error_score="raise" |
ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations, check the scale of the features or consider increasing regularisation. | Lasso の反復回数が足りない(max_iter=50、alpha が小さい) | max_iter を増やす・特徴を標準化する・alpha を大きくする |
UserWarning: With alpha=0, this algorithm does not converge well. You are advised to use the LinearRegression estimator | Lasso(alpha=0) は正則化なしで、座標降下法に向かない | LinearRegression を使う |
RankWarning: Polyfit may be poorly conditioned | 多項式の次数がデータに比べて高すぎる(次数19・20点) | 次数を下げる・正則化する・x を標準化する |
エラーではないつまずきも3つ挙げます。1つ目は、model.score(X_train, y_train) だけを見て「精度99%」と報告することです。決定木や SVM は訓練で1.000になりやすく、それ自体には意味がありません。2つ目は、テストのスコアを見て設定を何度も直すことで、これはテストに対する過学習(上の5番目のリーク)です。3つ目は、交差検証のスコアを平均だけで比べることです。標準偏差0.045のときに平均0.01の差は、運の範囲です。
Python の環境を分けて scikit-learn の版をそろえる方法は、関連記事「Python venvの使い方|作成・有効化コマンド早見表と有効化できない時の直し方」で扱っています。
過学習の対策の判断表(症状からどれを使うか)
この節では、学習曲線・検証曲線で見えた症状から、どの対策を選べばよいかと、対策を試す順番が分かります。
| 見えた症状 | 考えられる状態 | まず試す対策 | この記事の実測 |
|---|---|---|---|
| 訓練が高く、検証との差が大きい。データを増やすと検証が上がる | データ不足の過学習 | データを増やす・データ拡張 | 次数9:12点で354、1,000点で0.096 |
| 差が大きく、設定のつまみで検証が山になる | モデルが複雑すぎる | 次数・深さ・gamma を下げる | gamma 3 で0.800、0.3 で0.989 |
| 係数が極端に大きい・列が多い | 線形モデルの過学習 | Ridge(L2)。不要な列が多ければ Lasso(L1) | R² 0.303→0.385(Ridge)・0.440(Lasso) |
| 学習を続けると検証が下がり始める | 学習のしすぎ(ニューラルネットワーク・ブースティング) | 早期終了・ドロップアウト・重み減衰 | テスト0.832→0.888(早期終了)・0.880(ドロップアウト) |
| 訓練も検証も低く、差が小さい | 未学習 | モデルを複雑に・特徴量を足す・正則化を弱める | 次数1:訓練0.33・テスト0.31 |
| 検証やテストが良すぎる・1列の重要度が飛び抜ける | リーク | 分割を先に・Pipeline・GroupKFold・TimeSeriesSplit | 0.917→0.483、0.995→0.402 |
| 分割の乱数でスコアが大きく変わる | 評価のぶれ | k分割交差検証・繰り返しの交差検証 | R² 0.331〜0.504 のぶれ |
対策を試す順番は、次のとおりにすると、原因の切り分けがしやすくなります。
- 1評価の方法を正す先に分割・Pipeline・交差検証。リークがあるまま対策しても意味がない
- 2訓練と検証の差を測る学習曲線と検証曲線で、過学習か未学習かを決める
- 3モデルを単純にする次数・深さ・層の大きさを下げる。いちばん効きが分かりやすい
- 4正則化を入れて強さを選ぶalpha・C・weight decay を交差検証で選ぶ(RidgeCV・GridSearchCV)
- 5データを増やす・質を上げる学習曲線で検証が上がり続けているとき。誤ったラベルを直すのも効く
- 6早期終了・ドロップアウト・アンサンブルニューラルネットワーク・ブースティング・決定木の組み合わせで
線形回帰・ロジスティック回帰
Ridge・Lasso・LogisticRegression の C(小さいほど正則化が強い)。
C と alpha は向きが逆
決定木・ランダムフォレスト
max_depth・min_samples_leaf・ccp_alpha。森は木を平均して過学習を和らげる。
剪定は決定木の記事で
SVM
C と gamma。どちらも大きいほど複雑な境界になる。
検証曲線で選ぶ
ニューラルネットワーク
MLPClassifier の alpha(L2)・early_stopping。自作ならドロップアウト。
止める時点は検証で
モデルの良し悪しを測る指標(正解率・適合率・再現率・F1)の選び方は、別記事「Precision(適合率)・再現率・正解率・F1の違い|陽性1%で実測」で扱います。生成AI の出力を評価するときの考え方は、関連記事「LLMの評価方法と評価指標一覧|テストケース表のテンプレート付き」で扱っています。評価用のデータを学習やプロンプトの調整に使わない、という点は同じです。
よくある質問
この節では、過学習・交差検証・正則化について、よく聞かれる質問への短い答えが分かります。
Q. 過学習とは、簡単に言うと何ですか。
A. 訓練データの答えを丸暗記して、新しいデータで当たらなくなることです。訓練のスコアとテストのスコアの差が大きいことで見分けます。
Q. 訓練の正解率が100%なら、必ず過学習ですか。
A. いいえ。テストのスコアも同じくらい高ければ問題ありません。SVM の gamma 0.1 は訓練1.000・検証0.988で、良いモデルでした。差を見ます。
Q. 交差検証の k はいくつにすればよいですか。
A. 5か10がよく使われ、scikit-learn の既定は5です。データが少ないほど k を大きくすると訓練に使える件数が増えますが、学習の回数(時間)も k 倍になります。
Q. 交差検証をすれば過学習は防げますか。
A. 交差検証は過学習を「見つけて、正しく測る」方法で、それだけでは直りません。見つけた後に、単純化・正則化・データを増やすなどの対策を選びます。
Q. ホールドアウト法と交差検証はどちらを使えばよいですか。
A. データが数百〜数千件なら交差検証を勧めます。手元では1回の分割で R² が0.33〜0.50とぶれました。データが非常に多く、1回の学習が重いときはホールドアウトでも安定します。
Q. L1 正則化と L2 正則化はどちらを使えばよいですか。
A. 不要な列が多く、列を減らしたいなら L1(Lasso)、列がどれも少しずつ効いているなら L2(Ridge)が向きます。迷ったら両方を混ぜた Elastic Net を交差検証で調整します。
Q. 正則化の強さ(alpha)はどう決めますか。
A. 候補を対数の間隔(0.01・0.1・1・10…)で並べ、交差検証のスコアが最も良い値を選びます。RidgeCV・LassoCV・GridSearchCV がこれを自動で行います。
Q. 検証データとテストデータは何が違うのですか。
A. 検証データは設定を選ぶために何度も使うデータ、テストデータは最後に1回だけ使うデータです。テストを見て設定を直すと、テストにも過学習します。
Q. スコアが良すぎるときは何を疑えばよいですか。
A. リークです。全データでの前処理、予測時点で手に入らない列、同じ人のデータの混在、時系列の並べ替えの4つを順に確かめます。
Q. ディープラーニングでも過学習は起きますか。
A. 起きます。データが少ない・ラベルに誤りが多いときは特にそうで、手元の小さなネットワークでもテストが0.908から0.832に下がりました。早期終了・ドロップアウト・データ拡張・重み減衰が使われます。
Q. 過学習と未学習はどちらが問題ですか。
A. どちらも汎化が悪い状態です。未学習は訓練の時点で分かりますが、過学習は訓練のスコアだけ見ていると気づかないため、見落とされやすい方です。
まとめ
この節では、この記事の要点を5つにまとめます。
- 過学習とは、訓練データのノイズまで覚えて、新しいデータで成績が落ちること。見分け方は訓練とテスト(検証)の差で、次数15の多項式は訓練0.012・テスト494でした。
- 原因は、データの情報量に比べてモデルの自由度が大きすぎること。同じ次数9でも、データを12点から1,000点に増やすとテストの誤差は354から0.096になりました。
- 交差検証は、分割の運を平均して正しく測る方法。自作と cross_val_score は差0.0で一致し、1回の分割では R² が0.33〜0.50とぶれました。
- 正則化は係数の大きさに罰を与える。L2(Ridge)は全体を小さく、L1(Lasso)は不要な列を0にします。ノイズ40列のデータでテスト R² は0.303から0.440になりました。
- 良すぎるスコアはリークを疑う。分割を先に、前処理は Pipeline に、同じ発生源は GroupKFold、時系列は TimeSeriesSplit、最終テストは1回だけ。
参考・出典
この節では、この記事で確かめた公式ドキュメントと論文の一覧が分かります(確認日 2026年11月5日)。
- scikit-learn 1.9.1 documentation「Cross-validation: evaluating estimator performance」
https://scikit-learn.org/stable/modules/cross_validation.html - scikit-learn 1.9.1 documentation「Common pitfalls and recommended practices」(Data leakage)
https://scikit-learn.org/stable/common_pitfalls.html - scikit-learn 1.9.1 documentation「Validation curves: plotting scores to evaluate models」
https://scikit-learn.org/stable/modules/learning_curve.html - scikit-learn 1.9.1 documentation「Linear Models」(Ridge・Lasso・Elastic-Net の目的関数)
https://scikit-learn.org/stable/modules/linear_model.html - Google for Developers「過学習」Machine Learning Crash Course
https://developers.google.com/machine-learning/crash-course/overfitting/overfitting?hl=ja - Srivastava et al.「Dropout: A Simple Way to Prevent Neural Networks from Overfitting」JMLR 15(2014)
https://jmlr.org/papers/v15/srivastava14a.html - Belkin, Hsu, Ma, Mandal「Reconciling modern machine learning practice and the bias-variance trade-off」PNAS(2019)
https://arxiv.org/abs/1812.11118
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「過学習とは|原因と対策、交差検証・正則化・リークをPythonで実測」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。