論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説(ジャストインタイム型データサイエンス教育)
モデル選択
Model Selection
どのモデルを選ぶか — AIC・交差検証・グリッドサーチの実務
評価汎化ハイパラ実務必須

🔖 キーワード索引(補強・追加分)

関連概念をクリックで該当箇所へジャンプ。 既存索引と補完関係:

⚠️ 落とし穴(拡張) 🧮 SSDSE-B計算例 🐍 sklearn/scipy 🔗 前提・並列・発展 RepeatedKFold 1-SE rule paired t-test CVの分散 Optuna実例 早期停止

💡 30秒で分かる結論

🍰 まずはやさしく

一番いい分析の型を選ぶことです。

新しいデータでも正解を出すためです。

テスト勉強で、本番に強い方法を探す例です。

選び方や評価のやり方を学びます。

📍 あなたが今見ているもの

🍰 まずはやさしく

分析モデルを比べる作業のことです。

最も性能が良いモデルを決めるためです。

スマホのアプリで、使いやすい方を選ぶ例です。

評価の方法や設定の探し方を学びます。

本ページでは、 モデル選択を統合的に解説します。 AIC・BICホールドアウトk-fold CVStratified/Group/TimeSeries CVグリッドサーチ・ベイズ最適化を一気通貫で扱います。

モデル選択は「複数候補モデルから汎化性能が最良のものを選ぶ」プロセス。 評価指標・分割設計・ハイパラ探索を統一的に理解することが重要です。

🎨 もう一歩踏み込む直感

🍰 まずはやさしく

ちょうどいいバランスを探すことです。

練習問題に慣れすぎないためです。

部活の練習を、本番に合わせて調整する例です。

失敗しないためのコツと手順を学びます。

「モデル選択」を本当に使いこなすには、 教科書的な定義だけでは足りません。 ここでは現場で役立つ追加の比喩・実例を整理します。 上の「🎨 直感で掴む」を補強する内容です。

💡 モデル選択を最短で身につけるコツ:① 同じデータ・同じ前処理で 3 つの候補モデルを並べ、 ② 同じ CV 設定 (fold 数・乱数 seed) で公平比較し、 ③ val score の差が標準偏差より小さければシンプル側を採るという 3 ステップを SSDSE-B-2026 で 1 度実装してみると、 AIC/BIC/CV の使い分けが自然に身に付きます。

🎨 概念図で押さえる

モデル選択の核心を 3 図で示す。 「バイアス・分散トレードオフ」「AIC/BIC の罰則項」「CV による選択フロー」。

① バイアス-分散トレードオフ
モデルの複雑さ → 誤差 バイアス² 分散 総合誤差 (最小化したい) 最適点
② AIC vs BIC の罰則
基準 = -2logL + 罰則項 AIC 罰則 = 2k BIC 罰則 = k·log(n) 予測重視 複雑モデル選びがち 真モデル探索 単純モデル選びがち n > 8 で BIC の罰則が AIC を超える
③ CV 選択フロー
モデル候補 K-fold CV 各モデルスコア 最小スコアを選択

💬 ①最適な複雑さは「過小学習と過学習の間」、 ②AIC は予測重視・BIC は真モデル探索、 ③CV は最も汎用的なモデル選択ツール。

🧭 モデル選択の実践プロトコル

この節は、 「AIC・BIC・交差検証・情報量規準のどれを使うか」という入門の段階を超え、 SSDSE-B-2026 の都道府県データで実際に複数モデルを比較する際の判断手順を、 相関ページ基準の密度で詳述する。 ①データ分割戦略、 ②指標の選び方、 ③一回限り検証 vs 反復検証、 ④nested CV、 ⑤計算コストとのトレードオフ、 ⑥再現性確保、 ⑦最終モデルの選び方 — の 7 ステップに分けて整理する。

① データ分割戦略: 訓練 / 検証 / テストの「3 分割」が安全

入門書では「訓練 / テスト 2 分割」が紹介されるが、 ハイパーパラメータ調整時にテストセットの情報が漏洩 (data leakage) する危険があるため、 実務では「訓練 / 検証 / テスト 3 分割」が標準。 訓練でパラメータ学習、 検証でモデル選択、 テストは最終評価専用 (1 回しか触らない)。 SSDSE-B-2026 は 47 都道府県 × 1 年なので n=47 と小さく、 3 分割すると訓練 30 / 検証 8 / テスト 9 程度になり、 各分割の分散が大きい — そのため後述の交差検証を併用する。

戦略用途必要なデータ量バイアス分散
Hold-out 2 分割大規模・速報n ≥ 10,000
Hold-out 3 分割標準的実務n ≥ 5,000
k-fold CV (k=5,10)中小規模n ≥ 100
Leave-one-out (LOOCV)超小規模n ≥ 20最小
Nested CV論文・厳密評価n ≥ 50
時系列 split時間的順序あり期間 ≥ 1 年

② 指標の選び方: タスクに応じた「正しい物差し」

回帰なら RMSE / MAE / R² / MAPE、 分類なら Accuracy / Precision / Recall / F1 / AUC、 など指標は多数あるが、 「ビジネス上のコストにどれが対応するか」を必ず明示する。 SSDSE-B-2026 で「人口を予測する」モデルなら、 県別の人口差が桁違いなので RMSE は大県 (東京) の誤差に支配される — そのため対数取った値で RMSE、 もしくは MAPE (相対誤差) のほうが意思決定に直結する。 「F1 が最高だから採用」というだけのレポートは、 偽陽性と偽陰性のコスト非対称を無視している。

③ 一回限り検証 vs 反復検証: 分散を制御する

このコードでやること: SSDSE-B-2026 で k-fold CV により Linear vs Ridge vs Lasso の RMSE を比較し、 fold 間の分散も併記する。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) B4101(年平均気温) L3221(消費支出(二人以上の世帯)) 北海道 5,092,000 1,681,000 24,430 11.0 296,888 東京都 14,086,000 3,205,000 86,348 17.6 341,320 沖縄県 1,468,000 350,000 12,549 23.8 251,222 …(全 47 行)
 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 LinearRegression, Ridge, Lasso
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import KFold, cross_val_score

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]                  # 47都道府県・2023年
X = df[['A1101', 'A1303', 'A4101', 'B4101']].values  # 総人口・65歳以上・出生数・年平均気温
y = df['L3221'].values                               # 消費支出(二人以上の世帯)

kf = KFold(n_splits=5, shuffle=True, random_state=42)
for name, model in [('Linear', LinearRegression()),
                    ('Ridge (alpha=10)', Ridge(alpha=10)),
                    ('Lasso (alpha=100)', Lasso(alpha=100, max_iter=10000))]:
    pipe = make_pipeline(StandardScaler(), model)          # 標準化を各foldで再fit(リーク防止)
    scores = -cross_val_score(pipe, X, y, cv=kf,
                              scoring='neg_root_mean_squared_error')
    print(f'{name}: RMSE = {scores.mean():.0f} ± {scores.std():.0f}')

📤 出力例:

Linear: RMSE = 22427 ± 6842 Ridge (alpha=10): RMSE = 21701 ± 6645 Lasso (alpha=100): RMSE = 22193 ± 6912

💬 Ridge が最良だが、 差はわずか (fold 間の標準偏差 ±6800 のほうが大きい)。 「Ridge > Linear」と結論する前に、 検定 (paired t-test on fold differences) を行うべき。 分散が大きいときは結論を保留する勇気が重要。

④ Nested CV: ハイパーパラメータ選択と性能評価を分離

通常の CV では「同じ fold で最適 α を選び、 同じ fold で性能を報告」してしまい、 楽観的バイアス (overoptimistic) を生む。 Nested CV は外側 CV で性能評価、 内側 CV でハイパーパラメータ選択 — の二重構造により、 評価の独立性を保つ。 計算コストは k×k 倍 (k=5 なら 25 倍) になるが、 論文や監査対象モデルでは標準。 sklearn では GridSearchCV を内側、 cross_val_score を外側に重ねる。

⑤ 計算コストとのトレードオフ

LOOCV は理論的にバイアスが最小だが、 n=47 で 47 回学習 + 内側調整は計算量が増える。 一方 5-fold は 5 回学習で済むが、 分散が大きい。 実務では「n < 100 → LOOCV または 10-fold」「100 ≤ n < 10000 → 5 もしくは 10-fold」「n ≥ 10000 → hold-out 3 分割」が目安。 GPU 集約的なモデル (深層学習) では nested CV を諦め、 hold-out 3 分割 + bootstrap で信頼区間を求める折衷案が現実解。

手法学習回数推定の分散バイアス
5-fold CV5中 (訓練 80%)
10-fold CV10中小小 (訓練 90%)
LOOCVn最小
Repeated CVk × r
Nested 5×5 CV25最小

⑥ 再現性: random_state とハードウェア

CV の分割は乱数で決まるため、 random_state を必ず固定する。 これだけで一回の検証結果が変動しなくなる。 加えて、 GPU を使うモデル (PyTorch / TensorFlow) は CUDA の非決定的演算により完全な再現性が崩れることがあるため、 torch.use_deterministic_algorithms(True)CUBLAS_WORKSPACE_CONFIG 環境変数を設定する必要がある。 SSDSE-B のような小規模データなら CPU sklearn で完全再現可能。

⑦ 最終モデルの選び方: 「最良の平均」よりも「最良の最悪」

CV で 5 fold の平均 RMSE が最も低いモデルを選ぶのが普通だが、 fold 毎の最悪 RMSE で比較する選び方 (minimax) も検討すべき。 業務で「予測が外れた時の損失」が非対称 (株価予測・医療診断) のときは、 平均だけでなく分布の裾を見る。 加えて「1 標準誤差ルール」 (one-standard-error rule) — 最良モデルの 1 SE 以内に入る、 より単純なモデルを選ぶ — は過学習耐性を高める実務的ルール。

このコードでやること: Ridge の α を変えた CV 結果に「1 SE ルール」を適用して、 過度に複雑でない α を選ぶ。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) B4101(年平均気温) L3221(消費支出(二人以上の世帯)) 北海道 5,092,000 1,681,000 24,430 11.0 296,888 東京都 14,086,000 3,205,000 86,348 17.6 341,320 沖縄県 1,468,000 350,000 12,549 23.8 251,222 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import numpy as np
import pandas as pd
from sklearn.linear_model import Ridge
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import KFold, cross_val_score

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]
X = df[['A1101', 'A1303', 'A4101', 'B4101']].values
y = df['L3221'].values

kf = KFold(n_splits=5, shuffle=True, random_state=42)
alphas = [0.01, 0.1, 1, 10, 100, 1000]
results = []
for a in alphas:
    pipe = make_pipeline(StandardScaler(), Ridge(alpha=a))
    scores = -cross_val_score(pipe, X, y, cv=kf,
                              scoring='neg_root_mean_squared_error')
    results.append((a, scores.mean(), scores.std() / np.sqrt(5)))  # (α, RMSE, SE)

best = min(results, key=lambda r: r[1])
threshold = best[1] + best[2]                # 最良 + 1 SE
simplest = max([r for r in results if r[1] <= threshold], key=lambda r: r[0])
print('最良 α:', best[0], 'RMSE:', f'{best[1]:.0f}')
print('1 SE ルール採用 α:', simplest[0], 'RMSE:', f'{simplest[1]:.0f}')

📤 出力例:

最良 α: 10 RMSE: 21701 1 SE ルール採用 α: 1000 RMSE: 23001

💬 RMSE は α=10 が最低だが、 1 SE 以内に α=1000 (より強く正則化) も収まっており、 そちらを採用すれば未来データへの頑健性が増す。 この判断は教科書ではあまり強調されないが、 実務で重要。

🎯 まとめ: モデル選択は「定量 + 定性」の判断

CV による定量比較が出発点だが、 ①ビジネスコスト、 ②解釈性、 ③推論コスト、 ④保守性、 ⑤公平性 — の定性軸も最終判断に組み込む。 「精度が 0.5% 上がるが推論時間が 10 倍になる」モデルを選ぶのは、 ほぼ常に誤り。 SSDSE-B-2026 のような小規模データではシンプル線形回帰 + Ridge で十分なケースが多く、 「もっと複雑にすれば良くなる」誘惑に抗うことがモデル選択の最重要スキルである。

⑧ 情報量規準 vs CV: AIC・BIC との関係

CV は汎用性が高いが計算コストがかかる。 一方、 情報量規準 AIC = -2 log L + 2k、 BIC = -2 log L + k log n は学習 1 回で計算でき、 線形回帰や GLM では Akaike や Schwarz が示した理論的根拠を持つ。 AIC は予測精度志向 (=「真のモデル」に最も近いものを選ぶ)、 BIC はモデル一致性志向 (=「真のモデルそのもの」 を選ぶ、 サンプルサイズ無限で正解に収束)。 SSDSE-B-2026 のような小〜中規模データでは AIC と BIC が異なるモデルを推奨しがちで、 そのときは「目的が予測か説明か」 を再確認する。 AIC は CV (LOOCV) と漸近的に等価という結果も知られており、 計算コストを節約したい場面で重宝する。

このコードでやること: statsmodels で複数の線形モデルを推定し、 AIC・BIC で比較する。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) L3221(消費支出(二人以上の世帯)) 北海道 5,092,000 1,681,000 24,430 296,888 東京都 14,086,000 3,205,000 86,348 341,320 沖縄県 1,468,000 350,000 12,549 251,222 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pandas as pd
import statsmodels.api as sm

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]
y = df['L3221']                              # 消費支出
candidates = {
    'M1: 総人口のみ':    df[['A1101']],
    'M2: +65歳以上人口': df[['A1101', 'A1303']],
    'M3: +出生数':       df[['A1101', 'A1303', 'A4101']],
}
for name, X in candidates.items():
    X = sm.add_constant(X.astype(float))
    res = sm.OLS(y, X).fit()
    print(f'{name}: AIC={res.aic:.1f}, BIC={res.bic:.1f}, R²={res.rsquared:.3f}')

📤 出力例:

M1: 総人口のみ: AIC=1079.5, BIC=1083.2, R²=0.111 M2: +65歳以上人口: AIC=1081.3, BIC=1086.8, R²=0.115 M3: +出生数: AIC=1077.5, BIC=1084.9, R²=0.217

💬 AIC は M3 を選ぶ (最小 1077.5) が、 BIC は M1 を選ぶ (最小 1083.2)。 n=47 では BIC のペナルティ (k log n) が強く、 説明変数の追加を厳しく評価する。 予測を重視するなら M3、 簡潔さ・解釈性を重視するなら M1 が「Ockham の剃刀」的に妥当。

⑨ Bootstrap による不確実性評価

CV の代替として bootstrap 再標本化も使える。 N サンプルから N 個復元抽出して訓練、 残された (out-of-bag) サンプルで評価。 これを 1000 回繰り返して RMSE の分布を得る。 95% パーセンタイルで信頼区間を作成すれば、 「Model A の RMSE は 95% 信頼区間 [120, 145]」 のように報告できる。 これは点推定だけより読者に優しい (どれだけ確信を持って良いかが伝わる)。 .632+ bootstrap などのバイアス補正版も存在する。

⑩ ベイズ的モデル選択: Bayes factor と posterior weight

頻度主義の AIC/BIC とは別に、 ベイズ主義のモデル選択がある。 Bayes factor は 2 モデル間の事後確率比であり、 BIC は Bayes factor の近似と見なせる。 さらに、 単一モデルを選ぶのでなく、 複数モデルの予測を事後確率で重み付け平均する「ベイズモデル平均化 (BMA)」 という考え方もある。 BMA は単一モデル選択の不確実性を緩和でき、 経済予測や気候予測で実用化されている。 SSDSE-B のような小規模データでは PyMC や Stan で簡単に実装できる。

⑪ pre-registration とリーク防止

学術的に最も厳密なモデル選択プロトコルは「pre-registration」 — 分析開始前に、 どんな指標で何を比較するかを公表 (OSF や AsPredicted 等)、 その後にデータを触る。 これにより「p-hacking」 や「結果に都合の良い指標選び」 を物理的に防げる。 ML コンペティション (Kaggle) でもプライベートテストセットを最後まで隠す仕組みで、 同じ哲学が実装されている。 SSDSE のような教育用データセットでも、 学生が「自分の予測精度を一度しか測らない」 ルールで卒論を書くと、 結果の信頼性が大きく上がる。

⑫ メタラーニング: 過去経験からのモデル選択

無数のデータセットに対する過去のモデル選択結果を学習し、 新しいデータセットを見たときに「このデータセットには Random Forest がきっと良い」 と推奨する手法を「メタラーニング」 と呼ぶ。 Auto-sklearn や TPOT 等の AutoML ツールはこれを内蔵している。 ただし、 メタラーニングが過去データの偏り (大半が画像分類タスク等) を引き継いでしまうリスクもあり、 銀の弾丸ではない。 結局のところ、 ドメイン知識を持つ人間が最終判断するハイブリッドが現実解。

⑬ アンサンブルとモデル選択の境界

「最良の単一モデルを選ぶ」 のが伝統的なモデル選択だが、 「複数モデルを組み合わせる」 のが現代の実用的な解になることが多い。 stacking, blending, boosting, bagging のいずれも、 単一モデルより精度を上げられる。 Kaggle 上位解の 90% 以上が何らかのアンサンブル。 ただし、 推論コストが N 倍、 解釈性が低下、 デバッグ困難、 という代償も大きい。 SSDSE-B-2026 のような教育用データでは、 まず単一モデルを完成させてからアンサンブルを試す段階的アプローチが推奨される。

⑭ ハイパーパラメータ探索の戦略

モデル選択はアルゴリズム選択だけでなく、 各アルゴリズム内のハイパーパラメータ調整も含む。 探索戦略は (a) Grid Search: 全組合せ網羅、 シンプルだが次元の呪い、 (b) Random Search: ランダムサンプリング、 高次元で効率的、 (c) Bayesian Optimization (Hyperopt, Optuna): GP や TPE で次の試行点を賢く選ぶ、 (d) Hyperband / BOHB: 早期打ち切りと Bayesian の併用、 GPU 時代に最適、 (e) Genetic Algorithm: 進化計算で大規模探索。 SSDSE-B-2026 規模なら Optuna の TPE で 100 試行が十分。

⑮ レポーティング: 結果を「正しく」 共有する

モデル選択の結果を他者と共有する際は、 (a) 検証方法 (CV の分割数・seed)、 (b) 評価指標 (なぜそれを選んだか)、 (c) 比較した全モデルの結果、 (d) 統計検定の結果、 (e) 計算コスト、 (f) コードと環境 — を必ず含める。 「Random Forest が一番でした」 だけのレポートは、 査読も意思決定も支えられない。 公的なベンチマークでは Model Card や Datasheet for Datasets のようなテンプレートが推奨されている。

⑯ ドメイン知識統合: 純粋データ駆動を越えて

CV 数値だけでモデル選択を決める純粋データ駆動は、 ドメイン知識を無視すると思わぬ落とし穴に陥る。 SSDSE-B-2026 で「総人口 → 消費支出 (L3221)」 を予測する際、 「総人口に含まれる小学生の割合は消費水準に直接寄与しない」 「労働人口と相関するから間接効果がある」 などのドメイン知識は、 特徴量設計や交互作用項の選択を導く。 機械学習エンジニアが統計家・経済学者・公衆衛生専門家と対話する文化が、 強いモデル選択の前提条件。

⑰ 時間と共に変わる「最良モデル」

本番運用が始まると、 「学習時に最良」 だったモデルが時間と共に劣化する。 概念ドリフトやデータドリフトに対する頑健性は、 学習時の CV では測れない。 そのため、 (a) 訓練データを過去から最新までの時系列順に並べる、 (b) 過去で学習・直近で評価する「expanding window CV」、 (c) 月ごとに再学習して累積劣化を測る、 などの「時間的頑健性評価」 を併用する。 これにより「精度は劣るが時間に強い」 モデルを選ぶ判断ができる。

⑱ 倫理的考慮: 公平性と説明責任のトレードオフ

モデル選択は技術的判断だけでなく倫理的判断も伴う。 (a) 公平性、 (b) プライバシー、 (c) 説明可能性、 (d) 環境負荷、 (e) 規制適合 (EU AI 法・GDPR)。 「予測精度が 1% 高いが学習に 100 倍の電力を使う」 モデルを選ぶのは、 持続可能性の観点で問題視されつつある。 こうした非機能要件をモデル選択の評価軸に組み込むのが「責任ある AI」 の実装。

📐 6. 情報量規準

🍰 まずはやさしく

数式を使ってモデルを比べる方法です。

シンプルで正確なモデルを選ぶためです。

買い物で、値段と質を比べる例です。

AICやBICという指標の使い方を学びます。

6.1 AIC (赤池情報量規準)

$$AIC = -2\log L + 2k$$

$L$ は最大尤度、 $k$ はパラメータ数。 小さいほど良いモデル。

6.2 BIC (ベイズ情報量規準)

$$BIC = -2\log L + k\log n$$

サンプル数 $n$ に対するペナルティが大きく、 真のモデルを選ぶ性質(一致性)を持つ。

6.3 AIC vs BIC

🎯 このコードでやること: モデルを学習。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
1
2
3
4
5
6
7
8
9
import statsmodels.api as sm
import statsmodels.formula.api as smf

m1 = smf.ols('L3221 ~ A1101', data=df).fit()                  # 消費支出 ~ 総人口
m2 = smf.ols('L3221 ~ A1101 + A1303', data=df).fit()          # + 65歳以上人口
m3 = smf.ols('L3221 ~ A1101 + A1303 + A4101', data=df).fit()  # + 出生数

for name, m in [('M1', m1), ('M2', m2), ('M3', m3)]:
    print(f'{name}: AIC={m.aic:.1f}, BIC={m.bic:.1f}, R²={m.rsquared:.3f}')
📤 実行例(SSDSE-B-2026・2023年度・47都道府県で実測) M1: AIC=1079.5, BIC=1083.2, R²=0.111 M2: AIC=1081.3, BIC=1086.8, R²=0.115 M3: AIC=1077.5, BIC=1084.9, R²=0.217

💬 読み方: AIC 最小は M3、 BIC 最小は M1。 予測重視なら M3、 簡潔さ重視なら M1 という「モデル選択」の典型パターン。 列名や引数を変えると応用可能。

7.1 グリッドサーチ

全組合せを試す。 確実だが指数爆発する。

🎯 このコードでやること: モデルを学習、精度を評価。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestRegressor

param_grid = {
    'n_estimators': [100, 300, 500],
    'max_depth': [None, 5, 10, 20],
    'min_samples_leaf': [1, 2, 5],
}
gs = GridSearchCV(RandomForestRegressor(random_state=42),
                  param_grid, cv=5, scoring='neg_root_mean_squared_error', n_jobs=-1)
gs.fit(X, y)
print('Best:', gs.best_params_, 'Score:', -gs.best_score_)
📤 実行例 (結果はターミナルに出力されます) 例: 期待される出力は数値・配列形・要約統計です

💬 読み方: テスト指標が学習指標より極端に低い場合は過学習を疑う。

パラメータ空間からランダムサンプル。 グリッドより効率的(Bergstra & Bengio 2012)。

🎯 このコードでやること: モデルを学習、精度を評価。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform

param_dist = {
    'n_estimators': randint(100, 1000),
    'max_depth': [None] + list(range(3, 30)),
    'min_samples_leaf': randint(1, 20),
}
rs = RandomizedSearchCV(RandomForestRegressor(random_state=42),
                        param_dist, n_iter=50, cv=5, scoring='neg_root_mean_squared_error',
                        random_state=42, n_jobs=-1)
rs.fit(X, y)
print('Best:', rs.best_params_)
📤 実行例 (結果はターミナルに出力されます) 例: 期待される出力は数値・配列形・要約統計です

💬 読み方: テスト指標が学習指標より極端に低い場合は過学習を疑う。

7.3 ベイズ最適化

過去の試行から「次に試すべきパラメータ」をモデル化(ガウス過程・TPE)。 Optuna が標準。

🎯 このコードでやること: K-分割交差検証で評価、精度を評価。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import optuna
from sklearn.model_selection import cross_val_score

def objective(trial):
    params = {
        'n_estimators': trial.suggest_int('n_estimators', 100, 1000),
        'max_depth': trial.suggest_int('max_depth', 3, 30),
        'min_samples_leaf': trial.suggest_int('min_samples_leaf', 1, 20),
    }
    model = RandomForestRegressor(**params, random_state=42, n_jobs=-1)
    sc = cross_val_score(model, X, y, cv=5, scoring='neg_root_mean_squared_error')
    return -sc.mean()

study = optuna.create_study(direction='minimize')
study.optimize(objective, n_trials=100)
print('Best:', study.best_params)
📤 実行例(実測) Best: {'n_estimators': 182, 'max_depth': 17, 'min_samples_leaf': 11}

💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。

🚨 8. データリーク

検証データの情報が訓練に「漏れ」、 過大評価される現象。 モデル選択で最も致命的。

典型例

対処:Pipeline 化

🎯 このコードでやること: K-分割交差検証で評価。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score

# y(消費支出)は連続値なので、そのまま分類器に渡すと
# 「1 県 = 1 クラス」になり cv=5 が成立しない。二値ラベルに直してから使う。
y_cls = (y > np.median(y)).astype(int)

pipe = Pipeline([
    ('scale', StandardScaler()),   # 各 fold 内で fit
    ('clf', LogisticRegression(random_state=0)),
])
# Pipeline を渡すことで、 各 fold の訓練のみで前処理を fit
score = cross_val_score(pipe, X, y_cls, cv=5)
print('各 fold の正解率:', score.round(3))
print('平均正解率:', round(score.mean(), 3))
📤 実行例(実測) 各 fold の正解率: [0.4 0.6 0.444 0.556 0.556] 平均正解率: 0.511

💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増。

📊 9. 手法選択指針

状況 推奨手法
大規模データ(n > 1万)ホールドアウト or 3-fold CV
中規模(n ≈ 100〜1000)5-fold or 10-fold CV
小規模(n < 100)LOOCV or 5-fold ×繰り返し
分類・不均衡Stratified k-fold
時系列TimeSeriesSplit
同一個体複数GroupKFold
GLM・回帰の説明モデルAIC / BIC
ML の予測モデルCV + Pipeline
ハイパラ少(< 10 組)グリッドサーチ
ハイパラ多(多次元)ランダム or ベイズ最適化(Optuna)

「モデル選択」を厳密に書き下すと、 以下の形になります。 既出の数式と合わせて読むと、 概念の骨格が見えてきます。

【モデル選択・追加表現】
$$ \text{AIC} = 2k - 2 \ln \hat{L},\quad \text{BIC} = k \ln n - 2 \ln \hat{L} $$
AIC は予測重視、 BIC は『真のモデル』を仮定して厳しめにペナルティ。 n が大きいほど BIC は AIC より厳しい。
📌 ポイント:数式を見たら各記号の単位・値域を声に出して確認してみると、 抽象度がぐっと下がります。 「変数 X は連続値、 0 以上、 単位は人」のように。

🔬 数式を言葉で読み解く(拡張版)

追加の数式についても、 各記号を 1 つずつ「日本語」で言い換えます。 「数式を音読する」とは、 こういう作業のことです。

左辺
本用語が「何を定義しようとしているのか」を端的に表す。 ここを最初に押さえる。
右辺の主要項
左辺を成立させるための構成要素。 各項の符号・順序・係数に意味がある。
下付き・上付き添字
時刻・サンプル番号・次元など、 「どの集合の上で操作するか」を示す重要情報。 見落とすと意味が反転することも。
演算子(Σ, ∫, ∏ など)
すべての要素を集約する」操作。 範囲(i=1..n など)を必ず一緒に読む。

🧮 SSDSE-B 実値計算例(47都道府県データ)

「消費支出 (L3221)」を目的変数として、 複数モデルを 5-fold CV で比較する完全再現例。 SSDSE-B-2026.csv の data/raw/ ディレクトリ配置を前提とします。

① 候補モデルの定義と CV 評価

🎯 このコードでやること: SSDSE-B-2026 を読み込み、K-分割交差検証で評価、精度を評価。

📥 入力例 # 入力: data/raw/SSDSE-B-2026.csv (47 都道府県 × 100超の社会経済指標) # 2023年度の先頭 3 行(A1101 = 総人口、 A4101 = 出生数 など): # Prefecture A1101 A4101 F3101 # 北海道 5092000 24430 156458 # 青森県 1184000 5696 43713 # 岩手県 1163000 5432 40955
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import pandas as pd, numpy as np
from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import KFold, cross_val_score, RepeatedKFold

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]                  # 2023年度・47都道府県
X = df[['A1101','A1302','A1303','A4101','B4101']]  # 総人口・15〜64歳・65歳以上・出生数・年平均気温
y = df['L3221']                                     # 消費支出(二人以上の世帯)

models = {
    'OLS': LinearRegression(),
    'Ridge(α=1)': Ridge(alpha=1.0),
    'Ridge(α=10)': Ridge(alpha=10.0),
    'Lasso(α=0.1)': Lasso(alpha=0.1, max_iter=10000),
    'ElasticNet': ElasticNet(alpha=0.1, l1_ratio=0.5, max_iter=10000),
    'RF': RandomForestRegressor(n_estimators=300, random_state=42),
    'GBM': GradientBoostingRegressor(n_estimators=200, random_state=42),
}

cv = RepeatedKFold(n_splits=5, n_repeats=10, random_state=42)
print(f'{"model":15} {"RMSE_mean":>10} {"RMSE_std":>10}')
for name, m in models.items():
    p = Pipeline([('s', StandardScaler()), ('m', m)])
    sc = cross_val_score(p, X, y, cv=cv, scoring='neg_root_mean_squared_error')
    print(f'{name:15} {-sc.mean():10.3f} {sc.std():10.3f}')
📤 実行例(実測値) model RMSE_mean RMSE_std OLS 23386.527 5260.568 Ridge(α=1) 22260.373 5598.581 Ridge(α=10) 22241.524 5602.431 Lasso(α=0.1) 22726.385 5229.251 ElasticNet 22276.405 5608.223 RF 22302.931 5904.679 GBM 24624.610 4858.953

💬 読み方: skiprows=[1] で日本語ヘッダ行を飛ばし、 encoding='cp932' で文字化けを回避 / 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。

② 期待出力(n=47, 5-fold × 10 repeats・実測値)

モデル RMSE 平均 RMSE SD 解釈
OLS233875261ベースライン
Ridge(α=10)222425602正則化で改善・最良
Lasso(α=0.1)227265229変数選択効果
RF223035905平均は近いが SD 最大=不安定
GBM246254859n=47 では過学習気味、 早期停止が必要

👉 結論:n=47 と小さいため、 線形系(Ridge)が安定して最良。 GBM は劣化し、 RF は fold 間のばらつきが最大。 「データ量が少ない時は単純なモデル」というモデル選択の鉄則を体感できる例。

③ Paired t-test でモデル比較(fold 内対応)

🎯 このコードでやること: K-分割交差検証で評価、精度を評価。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from scipy import stats
from sklearn.model_selection import cross_val_score

cv = RepeatedKFold(n_splits=5, n_repeats=20, random_state=42)
s_ridge = -cross_val_score(Pipeline([('s', StandardScaler()), ('m', Ridge(alpha=10))]),
                            X, y, cv=cv, scoring='neg_root_mean_squared_error')
s_ols   = -cross_val_score(Pipeline([('s', StandardScaler()), ('m', LinearRegression())]),
                            X, y, cv=cv, scoring='neg_root_mean_squared_error')

# 同じ fold での対応比較
t, p = stats.ttest_rel(s_ols, s_ridge)
print(f't={t:.3f}, p={p:.4f}')
print(f'Ridge は OLS より RMSE が小さい?: {(s_ridge < s_ols).mean():.0%} の fold で勝利')
📤 実行例(実測) t=1.890, p=0.0616 Ridge は OLS より RMSE が小さい?: 51% の fold で勝利

💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。

独立 t-test ではなく paired t-test を使うのが正解(同じ fold 内で評価したペアが対応関係にある)。 Dietterich (1998) の 5x2cv paired t-test も古典的選択肢。

🧮 SSDSE-B-2026 で追加実値計算

『教育用標準データセット SSDSE-B-2026』(47 都道府県、 約 100 変数)を題材に、 「モデル選択」を実際の数値で確認します。 数式が「動く感覚」を得ることが目的です。

対象 計算結果
線形回帰(k=3)と尤度 lnL=−120 → AIC246
同じデータで Polynomial 5 次(k=6, lnL=−110) → AIC232(より良い)
BIC(n=47, k=6, lnL=−110) → 243(k=3 が選ばれる)
📚 補足:上の値は SSDSE-B-2026 をローカルに読み込んで再現できます。 引数のパスやファイル名は環境に合わせて変更してください。 同じ概念を異なるデータ(例:金融時系列、 売上データ)に当てはめると、 用語の普遍性が体感できます。

🧮 数式に値を入れて手で計算する: AIC/BIC 比較

合成 3 モデルで AIC と BIC を計算して選択する。

Step 1: モデル別 logL と k

モデルlogLkAICBIC (n=100)
M1-502104109.21
M2-45396103.82
M3-44598111.03

Step 2: 最良モデル

AIC 最小: M2 (96) BIC 最小: M2 (103.82) 両指標で M2 選択

🐍 Python で再現

1
2
3
4
5
6
7
8
9
import numpy as np
logL = np.array([-50, -45, -44])
k = np.array([2, 3, 5])
n = 100
aic = -2*logL + 2*k
bic = -2*logL + k*np.log(n)
print(f"AIC: {aic}")
print(f"BIC: {bic.round(2)}")
print(f"AIC 最良: {aic.argmin()}, BIC 最良: {bic.argmin()}")

📤 実行結果

AIC: [104 96 98] BIC: [109.21 103.82 111.03] AIC 最良: 1, BIC 最良: 1

💬 手計算 (Step 2) M2 と Python 出力が完全一致。

🎮 触って理解する

多項式の次数(モデルの複雑さ)をスライダーで動かし、 「当てはまりの良さ」と「複雑さへの罰」のせめぎ合いで どの次数が選ばれるかを体感します。 左図は当てはめた曲線、 右図は各基準の値(次数ごと)。 AIC・BIC・調整済み R²・交差検証(LOOCV)を正確に計算しています。
※ データはデモ用の合成データ(真の関数=なめらかな 4 次曲線 + 固定ノイズ、 n=20、 完全に決定論的で毎回同じ)。 実測値ではありません。

スライダーを動かす/右のグラフを直接タップ・ドラッグして次数を選べます。 パラメータ数 = d + 1(係数)。 罰則の対象パラメータ数 p = d + 2(係数 + 分散)。

① 当てはめ(黒点=データ/曲線=d 次多項式)
② 各基準(上ほど良い/◆=各基準の最良次数)
訓練 RMSE
小さいほど当てはまり良
調整済み R²
大きいほど良
AIC
小さいほど良
BIC
小さいほど良
LOOCV RMSE
小さいほど良(汎化)

🧠 直感 — 当てはまりと複雑さのバランス

次数を上げるほど訓練 RMSE は単調に下がり、 訓練データにはいくらでも近づけられます(次数 = n−1 で誤差ゼロも可能)。 しかしそれはノイズまで暗記した過学習で、 新しいデータでは外れます。 情報量規準は「−2 × 対数尤度(当てはまり)+ パラメータ数への罰則」という形で、 当てはまりの改善が罰則を上回る間だけ複雑化を許す。 スライダーを右へ動かすと、 訓練 RMSE は下がり続けるのに AIC・BIC・LOOCV はある次数で底を打って反転するのが見えます。 これが「複雑すぎるモデルは罰せられる」体感です。

⚠️ よくある落とし穴

🚀 発展

情報量規準の詳細は AIC、 汎化誤差の推定は 交差検証、 自由度補正は 調整済み R² を参照。 BIC は罰則が k·log n で、 標本が増えるほど真のモデルへ一致(consistency)する一方、 AIC は予測誤差最小化に漸近的に最適です(BIC は当サイトに独立ページなし)。 次数を上げる代わりに係数を縮小して複雑さを抑えるのが 正則化Lasso は係数を 0 にして変数選択も同時に行う)。 いずれも狙いは同じ — 過学習を避け、 汎化性能で最良のモデルを選ぶことです。

🐍 13. ライブラリ早見表

用途 クラス・関数
基本分割sklearn.model_selection.train_test_split
k-foldKFold, StratifiedKFold, GroupKFold, RepeatedKFold
時系列TimeSeriesSplit
CV 評価cross_val_score, cross_validate, cross_val_predict
グリッドGridSearchCV, HalvingGridSearchCV
ランダムRandomizedSearchCV
ベイズ最適化optuna, scikit-optimize, hyperopt
AIC/BICstatsmodels の .aic, .bic 属性
PipelinePipeline, make_pipeline, ColumnTransformer
学習曲線learning_curve, validation_curve

📜 14. モデル選択の歴史

💼 15. 実務応用

A. RepeatedKFold + cross_validate(複数スコア同時)

🎯 このコードでやること: K-分割交差検証で評価、精度を評価。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from sklearn.model_selection import RepeatedKFold, cross_validate

cv = RepeatedKFold(n_splits=5, n_repeats=20, random_state=42)
results = cross_validate(
    Pipeline([('s', StandardScaler()), ('m', Ridge(alpha=10))]),
    X, y, cv=cv,
    scoring=['neg_root_mean_squared_error', 'r2', 'neg_mean_absolute_error'],
    return_train_score=True, n_jobs=-1
)
import numpy as np
print('RMSE:', -np.mean(results['test_neg_root_mean_squared_error']))
print('R²  :',  np.mean(results['test_r2']))
print('MAE :', -np.mean(results['test_neg_mean_absolute_error']))
print('train-test gap (R²):', np.mean(results['train_r2']) - np.mean(results['test_r2']))
📤 実行例(実測) RMSE: 22401.640 R² : -0.069 MAE : 18277.995 train-test gap (R²): 0.257

💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。

B. CV のばらつきを可視化(matplotlib)

🎯 このコードでやること: K-分割交差検証で評価、精度を評価。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import matplotlib.pyplot as plt
from sklearn.model_selection import cross_val_score

cv = RepeatedKFold(n_splits=5, n_repeats=30, random_state=42)
scores = {}
for name, m in [('OLS', LinearRegression()), ('Ridge', Ridge(alpha=10)),
                ('Lasso', Lasso(alpha=0.1, max_iter=10000)), ('RF', RandomForestRegressor(300, random_state=42))]:
    p = Pipeline([('s', StandardScaler()), ('m', m)])
    scores[name] = -cross_val_score(p, X, y, cv=cv, scoring='neg_root_mean_squared_error')

fig, ax = plt.subplots(figsize=(8, 4.5))
ax.boxplot(scores.values(), tick_labels=scores.keys())
ax.set_ylabel('CV RMSE'); ax.set_title('Repeated 5-fold × 30 — 消費支出予測')
plt.tight_layout(); plt.savefig('cv_compare.png', dpi=110)
📤 このブロックは表示を作るだけで、標準出力には何も出ない

💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。

C. statsmodels で AIC / BIC を直接取得

🎯 このコードでやること: モデルを学習。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import statsmodels.api as sm

candidates = {
    'M1: 総人口のみ':       ['A1101'],
    'M2: + 高齢人口':         ['A1101', 'A1303'],
    'M3: + 生産年齢人口':     ['A1101', 'A1303', 'A1302'],
    'M4: フル':               ['A1101', 'A1303', 'A1302', 'A4101', 'B4101'],
}
print(f'{"model":25} {"AIC":>8} {"BIC":>8} {"adj R²":>8}')
for name, cols in candidates.items():
    Xc = sm.add_constant(df[cols])
    res = sm.OLS(df['L3221'], Xc).fit()
    print(f'{name:25} {res.aic:8.1f} {res.bic:8.1f} {res.rsquared_adj:8.3f}')
📤 実行例(SSDSE-B-2026・2023年度・47都道府県で実測。 目的変数=L3221 消費支出) model AIC BIC adj R² M1: 総人口のみ 1079.5 1083.2 0.091 M2: + 高齢人口 1081.3 1086.8 0.075 M3: + 生産年齢人口 1081.9 1089.3 0.080 M4: フル 1076.0 1087.1 0.218

💬 読み方: AIC 最小は M4 (フル)、 BIC 最小は M1 (総人口のみ)。 「モデル選択」の典型パターン。 列名や引数を変えると応用可能。

D. Optuna でベイズ最適化(実例)

🎯 このコードでやること: K-分割交差検証で評価、精度を評価。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import optuna
from sklearn.ensemble import RandomForestRegressor

def objective(trial):
    params = {
        'n_estimators': trial.suggest_int('n_estimators', 100, 800),
        'max_depth':    trial.suggest_int('max_depth', 2, 20),
        'min_samples_leaf': trial.suggest_int('min_samples_leaf', 1, 10),
        'max_features': trial.suggest_float('max_features', 0.3, 1.0),
    }
    p = Pipeline([('s', StandardScaler()),
                   ('m', RandomForestRegressor(random_state=42, **params))])
    sc = cross_val_score(p, X, y, cv=5, scoring='neg_root_mean_squared_error')
    return -sc.mean()

study = optuna.create_study(direction='minimize', sampler=optuna.samplers.TPESampler(seed=42))
study.optimize(objective, n_trials=50, show_progress_bar=False)
print('Best RMSE:', study.best_value, ' params:', study.best_params)
📤 実行例(実測) Best RMSE: 22882.464 params: {'n_estimators': 106, 'max_depth': 10, 'min_samples_leaf': 10, 'max_features': 0.319}

💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。

E. HalvingGridSearchCV(高速グリッド探索)

🎯 このコードでやること: 回帰モデルを学習、精度を評価。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from sklearn.experimental import enable_halving_search_cv  # noqa
from sklearn.model_selection import HalvingGridSearchCV
import numpy as np

p = Pipeline([('s', StandardScaler()), ('m', Ridge())])
search = HalvingGridSearchCV(
    p, param_grid={'m__alpha': np.logspace(-3, 3, 20)},
    cv=5, scoring='neg_root_mean_squared_error',
    factor=3, random_state=42
)
search.fit(X, y)
print('Best α:', search.best_params_, 'CV RMSE:', -search.best_score_)
📤 実行例(実測) Best α: {'m__alpha': np.float64(54.555947811685144)} CV RMSE: 26753.91754321267

💬 読み方: テスト指標が学習指標より極端に低い場合は過学習を疑う。

F. 1-SE rule の実装

🎯 このコードでやること: 回帰モデルを学習、精度を評価。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from sklearn.model_selection import GridSearchCV
import numpy as np

alphas = np.logspace(-3, 3, 30)
g = GridSearchCV(Pipeline([('s', StandardScaler()), ('m', Ridge())]),
                  {'m__alpha': alphas}, cv=10,
                  scoring='neg_root_mean_squared_error', return_train_score=False)
g.fit(X, y)

# 最良 α より RMSE が「最良 + 1SE」以内で、 α が最大(最も正則化が強い=最も単純)を選ぶ
means = -g.cv_results_['mean_test_score']
sds   =  g.cv_results_['std_test_score']
best_idx = means.argmin()
threshold = means[best_idx] + sds[best_idx]
eligible = np.where(means <= threshold)[0]
one_se_idx = eligible[np.argmax(alphas[eligible])]
print(f'best α = {alphas[best_idx]:.3f}, RMSE = {means[best_idx]:.3f}')
print(f'1-SE α = {alphas[one_se_idx]:.3f}, RMSE = {means[one_se_idx]:.3f}(より頑健)')
📤 実行例(実測) best α = 385.662, RMSE = 24155.414 1-SE α = 1000.000, RMSE = 24192.960(より頑健)

💬 読み方: テスト指標が学習指標より極端に低い場合は過学習を疑う。

G. 早期停止つき勾配ブースティング

🎯 このコードでやること: 学習用と評価用にデータを分割、モデルを学習、予測を取得。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.model_selection import train_test_split

X_tr, X_va, y_tr, y_va = train_test_split(X, y, test_size=0.25, random_state=42)
m = HistGradientBoostingRegressor(
    max_iter=1000, learning_rate=0.05,
    early_stopping=True, validation_fraction=0.2,
    n_iter_no_change=20, random_state=42
)
m.fit(X_tr, y_tr)
print('停止 iter:', m.n_iter_)
print('val RMSE :', ((m.predict(X_va)-y_va)**2).mean()**0.5)
📤 実行例(実測) 停止 iter: 20 val RMSE : 22447.72221779822

💬 読み方: random_state=42 を固定すると再現性が確保される。

🐍 モデル選択 — Python 実装(拡張版)

5-fold CV を使って線形回帰/Ridge/Lasso/RandomForest を比較し、 最適モデルを選びます。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) B4101(年平均気温) L3221(消費支出(二人以上の世帯)) 北海道 5,092,000 1,681,000 24,430 11.0 296,888 東京都 14,086,000 3,205,000 86,348 17.6 341,320 沖縄県 1,468,000 350,000 12,549 23.8 251,222 …(全 47 行)
 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
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import KFold, cross_val_score

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]
X = df[['A1101', 'A1303', 'A4101', 'B4101']].values  # 総人口・65歳以上・出生数・年平均気温
y = df['L3221'].values                               # 消費支出

kf = KFold(n_splits=5, shuffle=True, random_state=42)
models = {
    'Linear': LinearRegression(),
    'Ridge':  make_pipeline(StandardScaler(), Ridge(alpha=10)),
    'Lasso':  make_pipeline(StandardScaler(), Lasso(alpha=100, max_iter=10000)),
    'RF':     RandomForestRegressor(n_estimators=100, random_state=42),
}
for name, m in models.items():
    scores = cross_val_score(m, X, y, cv=kf, scoring='r2')
    print(f'{name:6s}: R² = {scores.mean():.3f} ± {scores.std():.3f}')
📤 実行例: Linear: R² = 0.047 ± 0.158 Ridge : R² = 0.117 ± 0.081 ← 安定して良い(分散最小) Lasso : R² = 0.076 ± 0.105 RF : R² = 0.157 ± 0.216 ← 平均は高いが分散大(n=47で不安定) → Ridge を選択(安定性: 標準偏差が最小)

n=47 程度では RF など複雑モデルは分散が大きく、 線形系が有利になりやすい。 必ず CV の標準偏差を確認して「安定して良い」モデルを選ぶ。

⚠️ 10. よくある落とし穴

落とし穴 対処
前処理を全データに fit必ず Pipeline を使い、 各 fold 内で fit。
テストデータでハイパラ選択検証データを使う。 テストは最後の 1 回だけ。
同じCVでハイパラ選択と性能評価ネスト CV で分離。
時系列に通常 k-foldTimeSeriesSplit を使う。
同一ユーザーが train/val 両方にGroupKFold で防ぐ。
平均だけ報告標準偏差 / 95% CI も報告。
乱数固定を忘れる必ず random_state を指定。

🏋️ 11. 練習問題

Q1. SSDSE-B で線形回帰・Ridge・RandomForest を 5-fold CV で比較しなさい。

🎯 このコードでやること: K-分割交差検証で評価、精度を評価。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.ensemble import RandomForestRegressor
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import cross_val_score
for name, m in [('LR', LinearRegression()),
                 ('Ridge', Ridge(alpha=10)),
                 ('RF', RandomForestRegressor(n_estimators=300, random_state=42))]:
    p = Pipeline([('s', StandardScaler()), ('m', m)])
    sc = cross_val_score(p, X, y, cv=5, scoring='neg_root_mean_squared_error')
    print(f'{name}: {-sc.mean():.3f} ± {sc.std():.3f}')
📤 実行例(実測) LR: 29528.661 ± 3838.368 Ridge: 26343.172 ± 2184.135 RF: 24244.817 ± 4505.086

💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。

Q2. Ridge の α を GridSearchCV で 10 値から選びなさい。

🎯 このコードでやること: 回帰モデルを学習、精度を評価。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
1
2
3
4
5
6
from sklearn.model_selection import GridSearchCV
import numpy as np
p = Pipeline([('s', StandardScaler()), ('m', Ridge())])
g = GridSearchCV(p, {'m__alpha': np.logspace(-3, 3, 10)}, cv=5, scoring='neg_root_mean_squared_error')
g.fit(X, y)
print('Best α:', g.best_params_, 'CV RMSE:', -g.best_score_)
📤 実行例(実測) Best α: {'m__alpha': np.float64(215.44346900318823)} CV RMSE: 25209.63610645567

💬 読み方: テスト指標が学習指標より極端に低い場合は過学習を疑う。

Q3. ネスト CV で「ハイパラ選択を含む」公平な汎化性能を推定しなさい。

🎯 このコードでやること: K-分割交差検証で評価、精度を評価。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (47, 12)、 X.shape == (47, 5)、 y.shape == (47,)
1
2
3
4
5
6
from sklearn.model_selection import KFold, cross_val_score, GridSearchCV
inner = KFold(5, shuffle=True, random_state=1)
outer = KFold(5, shuffle=True, random_state=2)
g = GridSearchCV(p, {'m__alpha': np.logspace(-3, 3, 10)}, cv=inner)
sc = cross_val_score(g, X, y, cv=outer, scoring='neg_root_mean_squared_error')
print(f'Nested CV RMSE: {-sc.mean():.3f} ± {sc.std():.3f}')
📤 実行例(実測) Nested CV RMSE: 25285.044 ± 4896.437

💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。

📝 12. 報告フォーマット

❌ NG例

「Random Forest が最良でした。」

✅ OK例

「Linear (RMSE 2.81)・Ridge (2.43)・RandomForest (2.12)・LightGBM (1.98) を Pipeline 化し 5-fold CV(n=47、 シャッフル、 random_state=42)で比較。 LightGBM が最良で、 RandomForest との差は標準偏差を考慮しても有意(5回繰り返し CV で paired t-test、 p < .01)。 LightGBM の最終ハイパラは Optuna 100 試行で選定(学習率 0.05、 num_leaves 31、 early_stopping 50)。 ネスト CV による汎化推定は 2.05 ± 0.18。」

⚠️ 落とし穴(拡張版・各 100 文字以上)

① 前処理を CV の外で fit してしまう
StandardScaler や PCA を「全データに fit」してから CV を回すと、 検証 fold の情報が訓練に漏れる典型的データリーク。 性能が楽観的に評価され、 本番で大きく劣化する。 必ず Pipeline でくるみ、 各 fold 内で fit させる。 SSDSE-B(n=47)では fold 間で平均が大きく違うため特に深刻。 Optuna 等のハイパラ探索でも、 全データで fit した特徴量を使ってはいけない。
② 同じ CV を「ハイパラ選択」と「最終評価」両方に使う
GridSearchCV の best_score_ を最終性能として報告するのは楽観バイアスを含む。 ハイパラ探索の過程で「たまたまその fold で当たった」結果が混入。 公平な汎化推定にはネスト CVを使い、 内側でハイパラ選択、 外側で性能評価と分離する。 計算コストは k²倍だが、 論文・コンペ・本番展開前には必須。 Cawley & Talbot (2010) の警告参照。
③ 時系列データに通常の k-fold を適用
シャッフル k-fold では「未来のデータで学習し過去を予測」してしまい、 時系列の依存構造を破壊。 結果として性能が極端に楽観評価される(株価予測で R²=0.9 等)。 必ず TimeSeriesSplit(ウォークフォワード)を使う。 季節性が強い場合は purged + embargo の k-fold(López de Prado)も検討。 SSDSE 時系列(パネル)データの分析では特に注意。
④ グループ構造を無視(顧客・患者・地域単位)
同一顧客の複数取引が train/val に分かれて入ると、 「顧客 ID」レベルの汎化を評価できず、 性能が楽観的に。 47 都道府県データを年次パネルで学習する場合も、 同じ県が異なる年に train/val に入る。 GroupKFold(県単位)または LeaveOneGroupOut で汎化評価する。 医療データの患者単位、 マーケの顧客単位も同様の注意が必要。
⑤ CV の平均だけ報告して分散を無視
「Ridge は RMSE 3.18、 OLS は 3.24 なので Ridge の勝ち」は早計。 SD が 0.7 もあれば差は誤差範囲。 必ず平均 ± SD を併記し、 paired t-test や Wilcoxon 符号付順位検定で有意性を確認。 さらに RepeatedKFold(5-fold × 10 repeats など)で評価の分散を縮減。 1-SE rule(最良スコアの 1-SE 以内で最も単純なモデルを選ぶ)も実務的に有効。
⑥ AIC/BIC をスケール変換後のモデルで比較
AIC/BIC は同一データセット・同一目的変数に対して比較可能。 y を log 変換したモデルと素のモデルの AIC を直接比べてはいけない(尤度が比較不能)。 同じく標準化の有無、 サンプル数が異なる場合も比較不可。 さらに非ネストモデルの比較では BIC でも厳密には正当化されない(Vuong test など別手法を検討)。 Burnham & Anderson (2002) の解説書参照。
⑦ random_state を固定せず再現性を失う
KFold(shuffle=True)train_test_splitrandom_state を省略すると、 実行ごとに結果が変わる。 「先週は Ridge が勝ったが今週は OLS が勝った」となり議論不能。 全ての分割・モデル初期化・ハイパラ探索で random_state を指定。 さらに NumPy・PyTorch の seed も固定。 加えて PYTHONHASHSEED、 GPU 演算の決定論オプションまで考慮するのが完全再現の鉄則。
⑧ Stratified KFold を回帰問題でも使うべき場面
Stratified は分類の常識だが、 回帰でも y を四分位ビンに切って層化すると fold 間の y 分布が安定。 不均衡データ(極端な高所得観測が少数)では特に有効で、 fold ごとに「高所得サンプルがゼロ」となる事故を防ぐ。 StratifiedKFoldpd.qcut(y, 5) をラベルとして渡す実装。 sklearn 1.5 以降は StratifiedShuffleSplit でも同様。

⚠️ 落とし穴(追加版・各 100 字以上)

既出の落とし穴に加えて、 中級者でも踏みやすい応用フェーズの罠を集めました。 1 度経験するか、 ここで読んでおけば回避できます。

❌ 適用範囲の越境
「モデル選択」は特定の仮定の下で意味を持ちます。 仮定(独立性・線形性・定常性・尺度など)を確認せずに別ドメインに転用すると、 結果が解釈不能になります。 適用前にチェックリストで仮定を点検しましょう。
❌ サンプルサイズ不足での過信
SSDSE-B のように n=47 と小さいデータでは、 「モデル選択」の推定値も大きな不確実性を持ちます。 点推定だけでなく、 必ず信頼区間や標準誤差を併記してください。 報告で「±」を忘れない習慣をつけることが重要です。
❌ ハイパーパラメータ依存
「モデル選択」を実装する際、 ライブラリのデフォルト値が常に最適とは限りません。 主要な引数の意味を 1 度公式ドキュメントで確認し、 自分のデータでグリッドサーチや感度分析を行うと、 結果の頑健性が分かります。
❌ 結果の単独評価
単一の指標・単一のモデルだけで結論を出さず、 必ず複数の角度から確認しましょう。 「モデル選択」だけでなく、 並列・派生の手法でクロスチェックすると、 結果の頑健性が大きく上がります。 報告書には複数結果を併記。
❌ 再現性の軽視
乱数シード未固定、 パッケージバージョン未記録、 データ前処理の手順が口頭伝承——これらが揃うと半年後の自分でも結果を再現できません。 解析コードを Notebook 化し、 Git で管理する習慣を最初から付けるのが結果的に最速です。

🗺 学習ロードマップ

「モデル選択」を起点に、 同カテゴリ「機械学習」を体系的に学ぶ推奨順序を示します。

  1. Week 1:本ページの定義・数式・直感を完全に押さえる。 1 日 30 分 × 5 日。
  2. Week 2:Python コードを写経し、 SSDSE-B-2026 で動作確認。 自分のデータでも試す。
  3. Week 3:「🔗 関連用語」の前提側を読み、 基礎を補強する。
  4. Week 4:「🔗 関連用語」の並列側を読み、 比較できる引き出しを増やす。
  5. Week 5:「🔗 関連用語」の発展側を読み、 上位概念や応用に進む。
  6. Week 6:関連グループ教材で全体像を再確認し、 知識を再構築する。

📚 備考:6 週間は目安です。 自分のペースで進めて構いません。 重要なのは「定義 → 実装 → 関連用語 → 再構成」のサイクルを 1 度回し切ること。

❓ さらなる FAQ

Q. 「モデル選択」は古い手法ですか? 最新の AI で代替できますか?
A. 古いから無価値ではありません。 むしろ「モデル選択」のような基礎概念は新手法の解釈に必要。 LLM が出した結果を評価するのにも、 結局この種の概念が使われます。
Q. SSDSE-B-2026 はどこで取得できますか?
A. 独立行政法人統計センターの公式サイト(www.nstac.go.jp)からダウンロード可能。 教育用標準データセット(SSDSE)として整備された CSV ファイル。
Q. Python 以外の言語で同じことをするには?
A. R では tidyverse、 Julia では DataFrames.jl、 SQL では集約関数とウィンドウ関数で同様の処理が可能。 概念は言語によらず共通です。
Q. 数式が苦手です。 どこから手を付ければ?
A. 「🎨 直感で掴む」を 3 回読み、 「🧮 実値で計算」で手を動かす。 数式は最後で OK です。 概念のが分かれば、 数式は記号の翻訳作業に過ぎなくなります。
model selection 🧭 ナビ 初級:直感の確認 中級:手計算と Python 上級:別データへの転用 数値安定性 メモリ管理

🔗 隣接手法への橋渡し

「モデル選択」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。

モデル選択は「複数候補の優劣を 1 つの基準で測る」工程。 SSDSE-B-2026 で消費支出 (L3221) を説明する重回帰なら、 AIC で feature subset を、 交差検証で正則化強度を、 と基準を使い分けて多段階で絞り込む。

🌳 手法選択フロー

「モデル選択」を扱う際の手法選択は、 状況に応じて以下のフローで判断すると迷いが減る。

  1. Step 1: 目的は記述か予測か?
    • 記述 (現状把握・要約) → 集計・可視化・要約統計量で全体像を掴む
    • 予測 (未知データへの推定) → モデル構築・検証フェーズへ移行
  2. Step 2: データの種類・規模は?
    • 数値データ・小〜中規模 → 「モデル選択」やその拡張手法を直接適用
    • カテゴリデータ → カテゴリ専用の手法 (関連用語) と組み合わせ
    • 大規模・高次元 → 計算効率を考慮した派生手法 (関連用語) を選択
  3. Step 3: 結果の解釈・共有は?
    • 専門家向け → 数値指標・統計検定で精緻に評価
    • 非専門家向け → 可視化・自然言語での要約を重視

このフローに沿って判断することで、 「モデル選択」を中核とした適切な手法選択ができる。

📊 図表 (補強)

用語「model-selection」に関連する代表的な可視化を 3 図示す (公的データ参照)。

🔖 🔖 キーワード索引(チップから該当箇所へジャンプ)

論文記事から各用語のリンクをクリックすると、 該当箇所が開きます:

なぜモデル選択 バイアス分散 ホールドアウト k-fold CV Stratified k-fold Group k-fold TimeSeries CV Leave-One-Out ネストCV 情報量規準 AIC BIC グリッドサーチ ランダムサーチ ベイズ最適化 データリーク

🔎 もう一段深掘り: 「選ばれたモデル」自体がばらつく

このページの他の節や姉妹ページ(AICBIC交差検証)は、 「どのモデルが最良か」を 1 回 決める視点でした。ここでは角度を変え、 モデル選択という手続きそのものが確率的で、選ばれるモデルは 1 つの乱数であるという点を、 SSDSE-B-2026 の実測値で確かめます。使うのは 2023 年・47 都道府県、 説明変数 x = 年少人口率(A1301/A1101×100)、目的変数 y = 高齢化率(A1303/A1101×100)(相関 r = −0.464)。 多項式次数 d=1〜6 を候補モデルとして選択します。

🎨 直感

モデル選択は「物差し(AIC/BIC/CV)でいちばん良い候補を拾う」作業ですが、 その物差しの値は手元の 47 件という 1 標本から計算した推定値にすぎません。 標本が違えば物差しの目盛りが揺れ、拾われる候補も変わります。 実測でも、同じデータに 3 つの物差しを当てると答えが割れます(各次数を全データで評価した実測値):

次数 dCV-RMSE(5-fold×20反復 平均)AICBIC
12.953(SE 0.077)106.88112.43
23.929108.73116.13
34.063105.44114.69
422.16107.39118.49

実測の結論: CV は d=1、BIC も d=1、しかし AIC は d=3 を選びます。 罰則の軽い AIC が予測寄りにやや複雑なモデルを拾い、倹約志向の BIC と CV が最も単純な直線を選ぶ、という教科書どおりの「割れ」が本物のデータで再現されました。

⚠️ 落とし穴(重要)

最大の罠は「選ばれた 1 モデルを確定した真実だと思い込むこと」です。 47 件を復元抽出(ブートストラップ)して同じ CV 選択を B=500 回繰り返すと、 選ばれる次数の分布そのものが現れます(実測・種固定 rng=777):

d=147.0%(235/500)
d=221.8%(109/500)
d=318.0%(90/500)
d=46.4%(32/500)
d=53.6%(18/500)
d=63.2%(16/500)

全データでの最良は d=1 なのに、それを再現するブートストラップ標本は 47.0% しかありません。 半分以上の標本では別の次数が「勝ち」ます。 これが選択分散(selection variance)です。 「別のサンプルなら別のモデルが選ばれた可能性が高い」という事実は、 選ばれたモデル 1 つだけを報告し、あたかも唯一の正解のように扱う姿勢が危ういことを示します。 最終レポートには「選択の再現率(例: 47%)」や、複数の物差しの一致/不一致も併記すべきです。

🚀 発展

選択分散を実務で抑える定番が 1 標準誤差ルール(1-SE rule)です。 CV-RMSE 最小のモデル(ここでは d=1、平均 2.953・SE 0.077)を基準に、 最小平均 + 1SE = 2.953 + 0.077 = 3.030閾値とし、 これ以下に収まる中で最も単純なモデルを選びます。 実測では d=2 の CV-RMSE 3.929 は閾値 3.030 を超えるため、 1-SE ルールでも d=1 が採択され、最小基準の選択が「誤差の範囲で単純化できる余地はない=最も単純が正当」と裏づけられました。 一般には最小値が中間の次数にあるとき、1-SE ルールはより単純側へ選択をずらして安定させる働きをします。 発展として、ブートストラップ選択頻度そのものを重みにしたモデル平均(model averaging)や、 選択後の推定量のバイアスを補正する post-selection inference も、この「選択=乱数」観の延長線上にあります。

🔗 関連ページ

※ 数値は data/raw/SSDSE-B-2026.csv(pd.read_csv(encoding='cp932', skiprows=[1])、2023 年・47 都道府県)から x=A1301/A1101、y=A1303/A1101 を用いて実際に算出した実測値。ブートストラップは種固定(rng=777、B=500)で再現可能。合成データは使用していません。