医療AI研究でよく起きる失敗が、過学習です。訓練データではよく当たるのに、別の患者集団では急に性能が落ちます。本稿では、過学習の見つけ方と防ぎ方を、リハビリテーション研究の具体例で整理します[1]。
本稿は、第9部「医療AI研究の落とし穴と対策」の第1記事です。過学習は、機械学習だけの問題ではありません。ロジスティック回帰や線形回帰でも起こります。特に、症例数が少なく、説明変数が多いリハ研究では注意が必要です。続く第2記事「データリーケージ」、第3記事「EPV・サンプルサイズ設計」と合わせて読むことで、予測モデルが過大評価される構造的な原因を整理できます。
// 01 · LEARN OUTCOMESこの記事でわかること
読了後、次の4つを説明できるようになります。
- 過学習が、訓練誤差と汎化誤差のズレとして起きる理由を説明できる
- 訓練曲線、検証曲線、Learning Curve から過学習を疑えるようになる
- 正則化、early stopping、変数削減、外部検証の役割を整理できる
- リハビリ予後予測で、訓練 AUC だけを見る危うさを理解できる
// 02 · CONCLUSIONまず結論
// 03 · FIGURE過学習を図で理解する
過学習は、訓練データでの成績だけを見ると気づきにくい問題です。まず、モデルを複雑にしたときに、訓練誤差と検証誤差がどう動くかを見ます。重要なのは、訓練誤差ではなく、検証誤差が下がっているかです。
次に、サンプル数を増やしたときの変化を見ます。Learning Curve は、データ不足なのか、モデルの複雑さが問題なのかを考える手がかりになります。小規模なリハ研究では、この差を確認するだけでも過学習を疑いやすくなります。
Bias は、モデルの単純化によるズレです。Variance は、データの分け方で結果が揺れる度合いです。小規模な医療データでは、Variance が大きくなりやすいです。この関係は、統計的学習の基本として説明されています[1]。
図を見るときは、線の高さだけでなく、線の差を見ます。訓練誤差だけが下がり続ける場合、注意が必要です。検証誤差が横ばい、または上昇していれば、複雑さが過剰かもしれません。
// 04 · CLINICALリハビリ研究で起きる過学習
回復期脳卒中患者 100 例で、FIM 利得を予測したとします。訓練データでは R² = 0.65 でした。しかし検証データでは R² = 0.20 でした。この差は、予測モデルが訓練データの癖を拾った可能性を示します。
自施設のデータでは、訓練 AUC = 0.95 でした。しかし、外部施設では AUC = 0.65 でした。自宅退院の基準、家族支援、地域連携の違いなどが影響した可能性があります。過学習と施設差が重なる典型例です。
歩行解析で速度、歩幅、左右差、関節角度、加速度特徴量など 100 個の特徴量を作り、症例数が 50 例だったとします。この場合、偶然にアウトカムと合う特徴量が出やすくなります。変数削減や正則化が重要になります。
回復期病棟データで学習した歩行自立モデルを、急性期データに使ったとします。患者背景、評価時期、FIM の分布が変わります。予測性能低下は過学習だけでなく、ドメイン差でも起こります。どちらの問題かを分けて考える必要があります。
これらの例に共通するのは、訓練性能だけでは判断できない点です。研究者は、手元のデータに対する当たりやすさを見ています。しかし、臨床で知りたいのは、次の患者で予測ができるかどうかです。この差を、汎化性能として考えます。
特にリハビリでは、自宅退院などのアウトカムが、その施設や家族構成、住宅環境、経済状況などの影響も受けるため、身体機能だけでは決まりません。そのため、予測モデルが患者の病状以外のデータを覚えることがあります。
歩行解析や画像解析では、特徴量がさらに急増します。特徴量が多いほど、もっともらしい説明が後から見つかりますが、臨床的に意味があるとは限らないため注意が必要です。そのため、解析前に、候補変数の範囲を決めておくことが大切です。
// 05 · THEORY過学習の最小限の理論
過学習は、訓練データに対する誤差と、未知データに対する誤差のズレです。研究では、未知データでの誤差を直接知ることはできません。そのため、検証データや交差検証で近似します。
訓練誤差(Training error)
モデルの学習に使ったデータでの誤差
検証誤差(Validation error)
モデルの学習に使わなかったデータでの誤差
過学習のパターン
訓練誤差は下がり続ける
検証誤差は最初は下がる
モデルが複雑すぎると、検証誤差が上がりはじめる
正則化は、モデルの自由度にペナルティを加える考え方です。L1 正則化は、係数の一部を 0 に近づけます。L2 正則化は、係数全体を小さくします。詳細は、03·03 Lasso / Ridge / Elastic Netで扱っています[2][5]。
通常の損失
予測誤差
L1 正則化付き損失
予測誤差 + λ × Σ |β| (係数の絶対値の和)
L2 正則化付き損失
予測誤差 + λ × Σ β² (係数の二乗の和)
λ(ラムダ)は、ペナルティの強さです。大きくするとモデルは単純になり、小さくすると訓練データにより合わせやすくなります。この値も、検証データや交差検証で決めます(test データで決めると、後述するデータリーケージになります)。
過学習を検出する3つの視点
1 つ目は、訓練性能と検証性能の差です。差が大きいほど、訓練データに依存している可能性があります。AUC、R²、RMSE など、目的に合う指標で確認します。
2 つ目は、Learning Curve です。サンプル数を増やしたとき、検証性能が伸びるかを見ます。伸びる余地が大きいなら、データ不足が関係しているかもしれません。伸びない場合は、特徴量やアウトカム定義も見直します。
3 つ目は、交差検証のばらつきです。5-fold CV の平均 AUC が 0.78 でも、fold ごとに 0.60 から 0.90 まで揺れる場合があります。この場合、平均値だけを強調すると不安定さが隠れます。小規模研究では、標準偏差も合わせて示します。
過学習を防ぐ主な方法
対策は、1 つだけで解決するものではありません。データ量を増やすことは有効ですが、常に可能とは限りません。そのため、変数の数を抑え、正則化を使い、検証設計を丁寧に組みます。
early stoppingは、学習を早めに止める方法です。勾配ブースティングやニューラルネットワークでよく使われます。検証性能が改善しなくなった時点で、学習を止めます。訓練データへの過度な適合を抑える考え方です。
アンサンブルも有効な場合があります。複数のモデルを組み合わせることで、予測の揺れを抑えます。ただし、特徴量が多すぎる小規模データでは過信できません。アンサンブルでも、検証設計が弱いと過学習します。
// 06 · IMPLEMENTATION · PYTHONscikit-learn で検出する
ここでは、退院時歩行自立の 2 値分類を想定します。ポイントは、前処理を Pipeline の中に入れることです。これにより、標準化や欠測補完が検証データへ漏れにくくなります。以下は教育用の仮想データを想定したサンプルです。
# ============================================
# 教育用サンプル
# 実臨床データを使う場合は、個人情報保護、
# 倫理審査、施設ルール、利用規約を必ず確認してください。
# ============================================
# 退院時歩行自立(0/1)の予測を題材に、過学習を検出する。
import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import (
StratifiedKFold,
cross_validate,
learning_curve,
train_test_split,
)
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
# 教育用の仮想データを想定
# df = pd.read_csv("example_rehab_dataset.csv")
feature_cols = [
"age",
"days_since_onset",
"fim_motor_admission",
"fim_cognition_admission",
"sias_lower_limb",
"nihss_total",
"diagnosis_type",
]
target_col = "walking_independent_discharge"
X = df[feature_cols].copy()
y = df[target_col].astype(int).copy()
numeric_features = [
"age",
"days_since_onset",
"fim_motor_admission",
"fim_cognition_admission",
"sias_lower_limb",
"nihss_total",
]
categorical_features = ["diagnosis_type"]
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.25,
random_state=42,
stratify=y,
)
# 前処理は Pipeline 内で fit する
numeric_pipe = Pipeline([
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler()),
])
categorical_pipe = Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore")),
])
preprocess = ColumnTransformer([
("num", numeric_pipe, numeric_features),
("cat", categorical_pipe, categorical_features),
])
model = LogisticRegression(
penalty="l2",
C=0.2, # C が小さいほど正則化が強い
solver="liblinear",
max_iter=1000,
)
pipe = Pipeline([
("preprocess", preprocess),
("model", model),
])
pipe.fit(X_train, y_train)
train_pred = pipe.predict_proba(X_train)[:, 1]
test_pred = pipe.predict_proba(X_test)[:, 1]
train_auc = roc_auc_score(y_train, train_pred)
test_auc = roc_auc_score(y_test, test_pred)
print(f"Train AUC: {train_auc:.3f}")
print(f"Test AUC: {test_auc:.3f}")
print(f"Gap : {train_auc - test_auc:.3f}")
# ============================================
# 交差検証で訓練/検証 AUC を見比べる
# ============================================
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_validate(
pipe, X, y,
cv=cv,
scoring="roc_auc",
return_train_score=True,
)
print("CV train AUC:", np.round(scores["train_score"], 3))
print("CV valid AUC:", np.round(scores["test_score"], 3))
print("Mean valid AUC:", scores["test_score"].mean().round(3))
print("SD valid AUC:", scores["test_score"].std(ddof=1).round(3))
# ============================================
# Learning Curve で症例数の効果を見る
# ============================================
train_sizes, train_scores, valid_scores = learning_curve(
pipe, X, y,
cv=cv,
scoring="roc_auc",
train_sizes=np.linspace(0.2, 1.0, 5),
)
learning_table = pd.DataFrame({
"train_size": train_sizes,
"train_auc_mean": train_scores.mean(axis=1),
"valid_auc_mean": valid_scores.mean(axis=1),
"valid_auc_sd": valid_scores.std(axis=1, ddof=1),
})
print(learning_table)
訓練 AUC と検証 AUC の差が大きい場合、過学習を疑います。ただし、差だけで結論は出せません。検証データの症例数、アウトカム数、施設差も見ます。
このコードでは、欠測補完と標準化を Pipeline 内に入れています。これは、検証データの情報を前処理に使わないためです。前処理を先に全データで行うと、軽いリーケージになります。次の記事では、データリーケージを詳しく扱います。
C は、ロジスティック回帰の正則化の強さに関係します。scikit-learn では、C が小さいほど正則化が強くなります。C をテストデータで選ぶと、テストデータへ適合してしまいます。そのため、探索用の検証手順と、最終評価を分けます。
// 07 · MYTHSよくある誤解
- 訓練データでの精度が高ければ良いモデルである
- 医療AIで見たいのは、未知の患者への性能です。訓練 AUC = 0.95 でも、外部検証 AUC = 0.65 なら慎重に解釈します。訓練データへの適合度は、モデルの学習状況を示す一部にすぎません。
- 交差検証を 1 回行えば過学習は防げる
- 交差検証は有用ですが、万能ではありません。分割の仕方で結果が揺れることがあります。特に症例数が少ない場合は、平均値だけでなく分散も確認します。
- 決定木やランダムフォレストは過学習しない
- 木系モデルも過学習します。木を深くしすぎると、少数例の偶然の分岐を覚えます。
max_depth、min_samples_leaf、特徴量数の調整が必要です。 - データを増やせば過学習は必ず解決する
- データ量は重要ですが、質も重要です。測定方法が変わる、施設基準が違う、アウトカム定義が揺れる場合があります。その場合は、ドメインシフトとして別に検討します。
// 08 · WRITING論文 Methods で書くこと
過学習への対応は、論文の Methods で明確に書きます。「機械学習を使いました」だけでは不十分です。モデル開発、検証、チューニングの流れを読者が追える形にします。
- 訓練データ、検証データ、テストデータの分け方
- 前処理を、どのデータに基づいて学習したか
- 交差検証の種類、fold 数、層化の有無
- ハイパーパラメータ探索の範囲
- 正則化、変数選択、early stopping の有無
- 外部検証データの施設、時期、対象集団
TRIPOD+AI では、予測モデル研究の報告で透明性が重視されます。モデルがどのデータで開発され、どの集団で評価されたかを明確にします。サンプルサイズ設計では、Riley らの考え方が参考になります。これは、単純な EPV だけでなく、過学習の抑制も考える枠組みです[3][4]。
- 「訓練性能しか示していない」
- 「テストデータをチューニングに使っている」
- 「説明変数が多いのに症例数が少ない」
- 「外部検証がないのに一般化して述べている」
これらは、リハビリ予後予測の論文で指摘されやすい点です。
回帰モデルでは、shrinkage(縮小推定)の考え方も重要です。係数を少し小さくすることで、訓練データへの過度な適合を抑えます。正則化つき回帰は、この考え方と近い位置にあります。線形回帰やロジスティック回帰でも、過学習への配慮が必要です[2][5]。
EPV とイベント数の書き方
自宅退院の有無を予測する場合、重要なのは全症例数だけではありません。自宅退院あり、または自宅退院なしのイベント数が問題になります。説明変数が多く、イベント数が少ないと、係数が不安定になります。
以前は、EPV ≥ 10 という目安がよく使われました。しかし現在は、研究条件に応じた設計が重視されます。アウトカムの割合、説明変数の数、期待する性能を考えます。小規模データでは、候補変数を絞る判断が重要です。
結果の書き方
Results では、訓練性能だけを主役にしません。検証 AUC、感度、特異度、calibration も合わせて示します。回帰なら、検証 R²、MAE、RMSE も候補になります。ばらつきが大きい場合は、その不確実性も書きます。
Discussion では、性能低下の理由を分けて書きます。過学習、症例数不足、施設差、アウトカム定義の違い。これらを同じ言葉で片づけると、査読者には弱く見えます。どの可能性が高いかを、データ構造に沿って説明します。
// 09 · CHECKLIST過学習チェックリスト
研究計画と論文読解のセルフチェックに使う 8 項目。
- 01訓練性能と検証性能を別々に示している
- 02前処理を訓練データ内で完結させている
- 03ハイパーパラメータ探索と最終評価を分けている
- 04交差検証の平均だけでなく、ばらつきも見ている
- 05説明変数の数に対して、症例数とイベント数が十分である
- 06外部検証や時期をずらした検証を検討している
- 07正則化、変数選択、early stopping のいずれかを検討している
- 08TRIPOD+AI に沿って Methods と Results を点検している
これらは、研究計画の段階で確認する項目です。解析後に気づくと、修正が難しい場合があります。特に、テストデータを何度も見ながらモデルを直す流れは避けます。
過学習対策は、モデル名で決まりません。ロジスティック回帰でも、SVM でも、勾配ブースティングでも起こります。重要なのは、症例数、イベント数、変数数、検証方法の組み合わせです。モデルを複雑にする前に、データ設計を確認します。
// 10 · QUIZミニクイズ
-
Q1訓練 AUC = 0.96、検証 AUC = 0.68 のとき、最も疑いやすい状況はどれか?
- モデルが単純すぎる
- 過学習している
- アウトカムが連続値である
- 評価指標が不要である
SHOW ANSWER
B. 訓練性能と検証性能の差が大きいため、過学習を疑います。差の原因は、説明変数の多さ、症例数の少なさ、施設差など複数考えられます。 -
Q2正則化の主な目的として最も適切なのはどれか?
- モデルの自由度を抑える
- 欠測値を自動で削除する
- 外部検証を不要にする
- すべての変数を大きくする
SHOW ANSWER
A. 正則化は、係数やモデルの複雑さにペナルティを加えます。L1 は係数の一部を 0 に近づけ、L2 は係数全体を小さくします。 -
Q3リハビリ予後予測で外部検証が重要な理由として最も適切なのはどれか?
- 施設差や患者背景の違いに耐えるかを見るため
- 訓練データを増やしたように見せるため
- 目的変数を消すため
- AUC を計算しないため
SHOW ANSWER
A. 外部検証は、別の施設や時期で性能が保たれるかを見る方法です。リハ研究では、施設運用やアウトカム定義の違いが性能差を生むため特に重要です。 -
Q4過学習対策として、最も適切でないものはどれか?
- 候補変数の数を抑える
- 正則化を導入する
- テストデータを見ながら C を調整する
- 交差検証で検証性能のばらつきを確認する
SHOW ANSWER
C. テストデータを見ながらハイパーパラメータを調整すると、テストデータへ適合してしまい、未知データでの性能を正しく評価できなくなります。これはデータリーケージの典型例です。
// REF参考文献
- Hastie T, Tibshirani R, Friedman J. The Elements of Statistical Learning: Data Mining, Inference, and Prediction. 2nd ed. Springer, 2009.
- Steyerberg EW. Clinical Prediction Models: A Practical Approach to Development, Validation, and Updating. 2nd ed. Springer, 2019.
- Riley RD, Ensor J, Snell KIE, Harrell FE Jr, Martin GP, Reitsma JB, et al. Calculating the sample size required for developing a clinical prediction model. BMJ 2020;368:m441.
- Collins GS, Moons KGM, Dhiman P, Riley RD, Beam AL, Van Calster B, et al. TRIPOD+AI statement: updated guidance for reporting clinical prediction models that use regression or machine learning methods. BMJ 2024;385:e078378.
- Harrell FE Jr. Regression Modeling Strategies: With Applications to Linear Models, Logistic and Ordinal Regression, and Survival Analysis. 2nd ed. Springer, 2015.