関連概念をクリックで該当箇所へジャンプ。 既存索引と補完関係:
🍰 まずはやさしく
一番いい分析の型を選ぶことです。
新しいデータでも正解を出すためです。
テスト勉強で、本番に強い方法を探す例です。
選び方や評価のやり方を学びます。
🍰 まずはやさしく
分析モデルを比べる作業のことです。
最も性能が良いモデルを決めるためです。
スマホのアプリで、使いやすい方を選ぶ例です。
評価の方法や設定の探し方を学びます。
本ページでは、 モデル選択を統合的に解説します。 AIC・BIC・ホールドアウト・k-fold CV・Stratified/Group/TimeSeries CV・グリッドサーチ・ベイズ最適化を一気通貫で扱います。
モデル選択は「複数候補モデルから汎化性能が最良のものを選ぶ」プロセス。 評価指標・分割設計・ハイパラ探索を統一的に理解することが重要です。
🍰 まずはやさしく
ちょうどいいバランスを探すことです。
練習問題に慣れすぎないためです。
部活の練習を、本番に合わせて調整する例です。
失敗しないためのコツと手順を学びます。
「モデル選択」を本当に使いこなすには、 教科書的な定義だけでは足りません。 ここでは現場で役立つ追加の比喩・実例を整理します。 上の「🎨 直感で掴む」を補強する内容です。
モデル選択の核心を 3 図で示す。 「バイアス・分散トレードオフ」「AIC/BIC の罰則項」「CV による選択フロー」。
💬 ①最適な複雑さは「過小学習と過学習の間」、 ②AIC は予測重視・BIC は真モデル探索、 ③CV は最も汎用的なモデル選択ツール。
この節は、 「AIC・BIC・交差検証・情報量規準のどれを使うか」という入門の段階を超え、 SSDSE-B-2026 の都道府県データで実際に複数モデルを比較する際の判断手順を、 相関ページ基準の密度で詳述する。 ①データ分割戦略、 ②指標の選び方、 ③一回限り検証 vs 反復検証、 ④nested CV、 ⑤計算コストとのトレードオフ、 ⑥再現性確保、 ⑦最終モデルの選び方 — の 7 ステップに分けて整理する。
入門書では「訓練 / テスト 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 が最高だから採用」というだけのレポートは、 偽陽性と偽陰性のコスト非対称を無視している。
このコードでやること: SSDSE-B-2026 で k-fold CV により Linear vs Ridge vs Lasso の RMSE を比較し、 fold 間の分散も併記する。
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}') |
📤 出力例:
💬 Ridge が最良だが、 差はわずか (fold 間の標準偏差 ±6800 のほうが大きい)。 「Ridge > Linear」と結論する前に、 検定 (paired t-test on fold differences) を行うべき。 分散が大きいときは結論を保留する勇気が重要。
通常の 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 CV | 5 | 中 | 中 (訓練 80%) |
| 10-fold CV | 10 | 中小 | 小 (訓練 90%) |
| LOOCV | n | 大 | 最小 |
| Repeated CV | k × r | 小 | 中 |
| Nested 5×5 CV | 25 | 中 | 最小 |
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 ルール」を適用して、 過度に複雑でない α を選ぶ。
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}') |
📤 出力例:
💬 RMSE は α=10 が最低だが、 1 SE 以内に α=1000 (より強く正則化) も収まっており、 そちらを採用すれば未来データへの頑健性が増す。 この判断は教科書ではあまり強調されないが、 実務で重要。
CV による定量比較が出発点だが、 ①ビジネスコスト、 ②解釈性、 ③推論コスト、 ④保守性、 ⑤公平性 — の定性軸も最終判断に組み込む。 「精度が 0.5% 上がるが推論時間が 10 倍になる」モデルを選ぶのは、 ほぼ常に誤り。 SSDSE-B-2026 のような小規模データではシンプル線形回帰 + Ridge で十分なケースが多く、 「もっと複雑にすれば良くなる」誘惑に抗うことがモデル選択の最重要スキルである。
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 で比較する。
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}') |
📤 出力例:
💬 AIC は M3 を選ぶ (最小 1077.5) が、 BIC は M1 を選ぶ (最小 1083.2)。 n=47 では BIC のペナルティ (k log n) が強く、 説明変数の追加を厳しく評価する。 予測を重視するなら M3、 簡潔さ・解釈性を重視するなら M1 が「Ockham の剃刀」的に妥当。
CV の代替として bootstrap 再標本化も使える。 N サンプルから N 個復元抽出して訓練、 残された (out-of-bag) サンプルで評価。 これを 1000 回繰り返して RMSE の分布を得る。 95% パーセンタイルで信頼区間を作成すれば、 「Model A の RMSE は 95% 信頼区間 [120, 145]」 のように報告できる。 これは点推定だけより読者に優しい (どれだけ確信を持って良いかが伝わる)。 .632+ bootstrap などのバイアス補正版も存在する。
頻度主義の AIC/BIC とは別に、 ベイズ主義のモデル選択がある。 Bayes factor は 2 モデル間の事後確率比であり、 BIC は Bayes factor の近似と見なせる。 さらに、 単一モデルを選ぶのでなく、 複数モデルの予測を事後確率で重み付け平均する「ベイズモデル平均化 (BMA)」 という考え方もある。 BMA は単一モデル選択の不確実性を緩和でき、 経済予測や気候予測で実用化されている。 SSDSE-B のような小規模データでは PyMC や Stan で簡単に実装できる。
学術的に最も厳密なモデル選択プロトコルは「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」 の実装。
🍰 まずはやさしく
数式を使ってモデルを比べる方法です。
シンプルで正確なモデルを選ぶためです。
買い物で、値段と質を比べる例です。
AICやBICという指標の使い方を学びます。
$$AIC = -2\log L + 2k$$
$L$ は最大尤度、 $k$ はパラメータ数。 小さいほど良いモデル。
$$BIC = -2\log L + k\log n$$
サンプル数 $n$ に対するペナルティが大きく、 真のモデルを選ぶ性質(一致性)を持つ。
🎯 このコードでやること: モデルを学習。
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}') |
💬 読み方: AIC 最小は M3、 BIC 最小は M1。 予測重視なら M3、 簡潔さ重視なら M1 という「モデル選択」の典型パターン。 列名や引数を変えると応用可能。
全組合せを試す。 確実だが指数爆発する。
🎯 このコードでやること: モデルを学習、精度を評価。
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)。
🎯 このコードでやること: モデルを学習、精度を評価。
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_) |
💬 読み方: テスト指標が学習指標より極端に低い場合は過学習を疑う。
過去の試行から「次に試すべきパラメータ」をモデル化(ガウス過程・TPE)。 Optuna が標準。
🎯 このコードでやること: K-分割交差検証で評価、精度を評価。
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) |
💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。
検証データの情報が訓練に「漏れ」、 過大評価される現象。 モデル選択で最も致命的。
🎯 このコードでやること: K-分割交差検証で評価。
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)) |
💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増。
| 状況 | 推奨手法 |
|---|---|
| 大規模データ(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) |
「モデル選択」を厳密に書き下すと、 以下の形になります。 既出の数式と合わせて読むと、 概念の骨格が見えてきます。
追加の数式についても、 各記号を 1 つずつ「日本語」で言い換えます。 「数式を音読する」とは、 こういう作業のことです。
「消費支出 (L3221)」を目的変数として、 複数モデルを 5-fold CV で比較する完全再現例。 SSDSE-B-2026.csv の data/raw/ ディレクトリ配置を前提とします。
🎯 このコードでやること: SSDSE-B-2026 を読み込み、K-分割交差検証で評価、精度を評価。
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}') |
💬 読み方: skiprows=[1] で日本語ヘッダ行を飛ばし、 encoding='cp932' で文字化けを回避 / 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。
| モデル | RMSE 平均 | RMSE SD | 解釈 |
|---|---|---|---|
| OLS | 23387 | 5261 | ベースライン |
| Ridge(α=10) | 22242 | 5602 | 正則化で改善・最良 |
| Lasso(α=0.1) | 22726 | 5229 | 変数選択効果 |
| RF | 22303 | 5905 | 平均は近いが SD 最大=不安定 |
| GBM | 24625 | 4859 | n=47 では過学習気味、 早期停止が必要 |
👉 結論:n=47 と小さいため、 線形系(Ridge)が安定して最良。 GBM は劣化し、 RF は fold 間のばらつきが最大。 「データ量が少ない時は単純なモデル」というモデル選択の鉄則を体感できる例。
🎯 このコードでやること: K-分割交差検証で評価、精度を評価。
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 で勝利') |
💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。
独立 t-test ではなく paired t-test を使うのが正解(同じ fold 内で評価したペアが対応関係にある)。 Dietterich (1998) の 5x2cv paired t-test も古典的選択肢。
『教育用標準データセット SSDSE-B-2026』(47 都道府県、 約 100 変数)を題材に、 「モデル選択」を実際の数値で確認します。 数式が「動く感覚」を得ることが目的です。
| 対象 | 計算結果 |
|---|---|
| 線形回帰(k=3)と尤度 lnL=−120 → AIC | 246 |
| 同じデータで Polynomial 5 次(k=6, lnL=−110) → AIC | 232(より良い) |
| BIC(n=47, k=6, lnL=−110) → | 243(k=3 が選ばれる) |
合成 3 モデルで AIC と BIC を計算して選択する。
| モデル | logL | k | AIC | BIC (n=100) |
|---|---|---|---|---|
| M1 | -50 | 2 | 104 | 109.21 |
| M2 | -45 | 3 | 96 | 103.82 |
| M3 | -44 | 5 | 98 | 111.03 |
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()}") |
💬 手計算 (Step 2) M2 と Python 出力が完全一致。
多項式の次数(モデルの複雑さ)をスライダーで動かし、 「当てはまりの良さ」と「複雑さへの罰」のせめぎ合いで どの次数が選ばれるかを体感します。 左図は当てはめた曲線、 右図は各基準の値(次数ごと)。 AIC・BIC・調整済み R²・交差検証(LOOCV)を正確に計算しています。
※ データはデモ用の合成データ(真の関数=なめらかな 4 次曲線 + 固定ノイズ、 n=20、 完全に決定論的で毎回同じ)。 実測値ではありません。
スライダーを動かす/右のグラフを直接タップ・ドラッグして次数を選べます。 パラメータ数 = d + 1(係数)。 罰則の対象パラメータ数 p = d + 2(係数 + 分散)。
次数を上げるほど訓練 RMSE は単調に下がり、 訓練データにはいくらでも近づけられます(次数 = n−1 で誤差ゼロも可能)。 しかしそれはノイズまで暗記した過学習で、 新しいデータでは外れます。 情報量規準は「−2 × 対数尤度(当てはまり)+ パラメータ数への罰則」という形で、 当てはまりの改善が罰則を上回る間だけ複雑化を許す。 スライダーを右へ動かすと、 訓練 RMSE は下がり続けるのに AIC・BIC・LOOCV はある次数で底を打って反転するのが見えます。 これが「複雑すぎるモデルは罰せられる」体感です。
情報量規準の詳細は AIC、 汎化誤差の推定は 交差検証、 自由度補正は 調整済み R² を参照。 BIC は罰則が k·log n で、 標本が増えるほど真のモデルへ一致(consistency)する一方、 AIC は予測誤差最小化に漸近的に最適です(BIC は当サイトに独立ページなし)。 次数を上げる代わりに係数を縮小して複雑さを抑えるのが 正則化(Lasso は係数を 0 にして変数選択も同時に行う)。 いずれも狙いは同じ — 過学習を避け、 汎化性能で最良のモデルを選ぶことです。
| 用途 | クラス・関数 |
|---|---|
| 基本分割 | sklearn.model_selection.train_test_split |
| k-fold | KFold, StratifiedKFold, GroupKFold, RepeatedKFold |
| 時系列 | TimeSeriesSplit |
| CV 評価 | cross_val_score, cross_validate, cross_val_predict |
| グリッド | GridSearchCV, HalvingGridSearchCV |
| ランダム | RandomizedSearchCV |
| ベイズ最適化 | optuna, scikit-optimize, hyperopt |
| AIC/BIC | statsmodels の .aic, .bic 属性 |
| Pipeline | Pipeline, make_pipeline, ColumnTransformer |
| 学習曲線 | learning_curve, validation_curve |
🎯 このコードでやること: K-分割交差検証で評価、精度を評価。
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'])) |
💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。
🎯 このコードでやること: K-分割交差検証で評価、精度を評価。
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 を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。
🎯 このコードでやること: モデルを学習。
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}') |
💬 読み方: AIC 最小は M4 (フル)、 BIC 最小は M1 (総人口のみ)。 「モデル選択」の典型パターン。 列名や引数を変えると応用可能。
🎯 このコードでやること: K-分割交差検証で評価、精度を評価。
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) |
💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。
🎯 このコードでやること: 回帰モデルを学習、精度を評価。
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_) |
💬 読み方: テスト指標が学習指標より極端に低い場合は過学習を疑う。
🎯 このコードでやること: 回帰モデルを学習、精度を評価。
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}(より頑健)') |
💬 読み方: テスト指標が学習指標より極端に低い場合は過学習を疑う。
🎯 このコードでやること: 学習用と評価用にデータを分割、モデルを学習、予測を取得。
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) |
💬 読み方: random_state=42 を固定すると再現性が確保される。
5-fold CV を使って線形回帰/Ridge/Lasso/RandomForest を比較し、 最適モデルを選びます。
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}') |
n=47 程度では RF など複雑モデルは分散が大きく、 線形系が有利になりやすい。 必ず CV の標準偏差を確認して「安定して良い」モデルを選ぶ。
| 落とし穴 | 対処 |
|---|---|
| 前処理を全データに fit | 必ず Pipeline を使い、 各 fold 内で fit。 |
| テストデータでハイパラ選択 | 検証データを使う。 テストは最後の 1 回だけ。 |
| 同じCVでハイパラ選択と性能評価 | ネスト CV で分離。 |
| 時系列に通常 k-fold | TimeSeriesSplit を使う。 |
| 同一ユーザーが train/val 両方に | GroupKFold で防ぐ。 |
| 平均だけ報告 | 標準偏差 / 95% CI も報告。 |
| 乱数固定を忘れる | 必ず random_state を指定。 |
🎯 このコードでやること: K-分割交差検証で評価、精度を評価。
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}') |
💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。
🎯 このコードでやること: 回帰モデルを学習、精度を評価。
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_) |
💬 読み方: テスト指標が学習指標より極端に低い場合は過学習を疑う。
🎯 このコードでやること: K-分割交差検証で評価、精度を評価。
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}') |
💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増 / テスト指標が学習指標より極端に低い場合は過学習を疑う。
「Random Forest が最良でした。」
「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。」
Pipeline でくるみ、 各 fold 内で fit させる。 SSDSE-B(n=47)では fold 間で平均が大きく違うため特に深刻。 Optuna 等のハイパラ探索でも、 全データで fit した特徴量を使ってはいけない。
best_score_ を最終性能として報告するのは楽観バイアスを含む。 ハイパラ探索の過程で「たまたまその fold で当たった」結果が混入。 公平な汎化推定にはネスト CVを使い、 内側でハイパラ選択、 外側で性能評価と分離する。 計算コストは k²倍だが、 論文・コンペ・本番展開前には必須。 Cawley & Talbot (2010) の警告参照。
TimeSeriesSplit(ウォークフォワード)を使う。 季節性が強い場合は purged + embargo の k-fold(López de Prado)も検討。 SSDSE 時系列(パネル)データの分析では特に注意。
GroupKFold(県単位)または LeaveOneGroupOut で汎化評価する。 医療データの患者単位、 マーケの顧客単位も同様の注意が必要。
KFold(shuffle=True) や train_test_split で random_state を省略すると、 実行ごとに結果が変わる。 「先週は Ridge が勝ったが今週は OLS が勝った」となり議論不能。 全ての分割・モデル初期化・ハイパラ探索で random_state を指定。 さらに NumPy・PyTorch の seed も固定。 加えて PYTHONHASHSEED、 GPU 演算の決定論オプションまで考慮するのが完全再現の鉄則。
StratifiedKFold に pd.qcut(y, 5) をラベルとして渡す実装。 sklearn 1.5 以降は StratifiedShuffleSplit でも同様。
既出の落とし穴に加えて、 中級者でも踏みやすい応用フェーズの罠を集めました。 1 度経験するか、 ここで読んでおけば回避できます。
「モデル選択」を起点に、 同カテゴリ「機械学習」を体系的に学ぶ推奨順序を示します。
📚 備考:6 週間は目安です。 自分のペースで進めて構いません。 重要なのは「定義 → 実装 → 関連用語 → 再構成」のサイクルを 1 度回し切ること。
tidyverse、 Julia では DataFrames.jl、 SQL では集約関数とウィンドウ関数で同様の処理が可能。 概念は言語によらず共通です。「モデル選択」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。
モデル選択は「複数候補の優劣を 1 つの基準で測る」工程。 SSDSE-B-2026 で消費支出 (L3221) を説明する重回帰なら、 AIC で feature subset を、 交差検証で正則化強度を、 と基準を使い分けて多段階で絞り込む。
「モデル選択」を扱う際の手法選択は、 状況に応じて以下のフローで判断すると迷いが減る。
このフローに沿って判断することで、 「モデル選択」を中核とした適切な手法選択ができる。
用語「model-selection」に関連する代表的な可視化を 3 図示す (公的データ参照)。



論文記事から各用語のリンクをクリックすると、 該当箇所が開きます:
このページの他の節や姉妹ページ(AIC/BIC/交差検証)は、
「どのモデルが最良か」を 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 つの物差しを当てると答えが割れます(各次数を全データで評価した実測値):
| 次数 d | CV-RMSE(5-fold×20反復 平均) | AIC | BIC |
|---|---|---|---|
| 1 | 2.953(SE 0.077) | 106.88 | 112.43 |
| 2 | 3.929 | 108.73 | 116.13 |
| 3 | 4.063 | 105.44 | 114.69 |
| 4 | 22.16 | 107.39 | 118.49 |
実測の結論: CV は d=1、BIC も d=1、しかし AIC は d=3 を選びます。 罰則の軽い AIC が予測寄りにやや複雑なモデルを拾い、倹約志向の BIC と CV が最も単純な直線を選ぶ、という教科書どおりの「割れ」が本物のデータで再現されました。
最大の罠は「選ばれた 1 モデルを確定した真実だと思い込むこと」です。 47 件を復元抽出(ブートストラップ)して同じ CV 選択を B=500 回繰り返すと、 選ばれる次数の分布そのものが現れます(実測・種固定 rng=777):
全データでの最良は 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)で再現可能。合成データは使用していません。