Day 104 — アンサンブル③(Blending)— hold-outベースの軽量Stacking

2026-07-25 青 / Phase 4 コーディング→分析 blend_train / blend_holdout / 計算コスト

📚 背景知識(読んでから問題へ)

🔵
Day 103では、Level-0モデルの予測をK-FoldのOOF方式で作り、リークなくLevel-1メタモデルに渡すStackingを学びました。今日学ぶBlendingは、Stackingと同じ「予測を新しい特徴量にしてメタモデルを学習する」骨格を保ちながら、K-Foldのループを丸ごと1回のtrain/holdout分割に置き換える軽量版です。
⚖️
BlendingはStackingより実装が単純で計算コストも低い一方、「メタモデルの学習に使えるデータ量が激減する」というトレードオフを抱えています。今日はこの精度 vs 軽さの関係を、実際にコードを実行して数値で確認します。
用語直感的な意味
blend_trainLevel-0モデルの学習だけに使う分割(例: 全体の80%)
blend_holdoutLevel-0の予測を集めてLevel-1メタモデルの学習データにする分割(例: 全体の20%)。StackingのOOF予測全体に相当するが、サイズはごく一部
軽量性Level-0モデルの学習が「K-Fold × モデル数」回ではなく「モデル数」回で済む

🧩 Blendingの3つの原則

計算コストの軽さ

⚡ K-Foldループが不要

モデル数×1回のfitで済む

Level-0モデルの学習は「モデル数」回だけ。Stackingの「モデル数×Fold数」より大幅に少ない。今日の設定では9回 vs 21回、約2.3倍の差になった。

データを捨てる代償

📉 メタモデルはholdoutの一部だけ

2000行 → 400行(5分の1)

Stackingのメタモデルは学習データ全体(OOF予測)を使えるが、Blendingは切り出したblend_holdoutの行数しか使えない。これが精度差につながる。

単一分割のリスク

🎲 分割seed次第で結果がブレる

K-Foldのような「巡回」がない

Stackingは学習データ全体をK-Foldで巡回するため分割への依存が薄いが、Blendingは1回の分割に依存するため、seedを変えると真のholdout AUCが動く。

🎯 問題

Day 102・103と同じChurn(銀行の顧客離反予測)データ同じ独立holdout(X_hold, y_hold)を使います。

使用データChurn予測(Day102・103と同一データ生成関数)
import numpy as np
import pandas as pd

def make_churn_df(seed, n):
    rng_state = np.random.get_state()
    np.random.seed(seed)
    age = np.random.normal(40, 12, n).clip(18, 80)
    income = np.random.lognormal(10.5, 0.4, n)
    tenure_months = np.random.exponential(24, n).clip(0, 120)
    num_products = np.random.randint(1, 5, n)
    is_active = np.random.binomial(1, 0.6, n)

    logit = (
        -0.05 * age + 0.00003 * income - 0.03 * tenure_months - 1.2 * is_active
        + 2.2 * ((age > 55) & (num_products == 1)).astype(float)
        + 2.0 * ((tenure_months < 6) & (num_products >= 3)).astype(float)
        + np.random.normal(0, 0.5, n)
    )
    prob = 1 / (1 + np.exp(-logit))
    churn = np.random.binomial(1, prob)
    df = pd.DataFrame({'Age': age, 'Income': income, 'TenureMonths': tenure_months,
                        'NumProducts': num_products, 'IsActive': is_active, 'Churn': churn})
    np.random.set_state(rng_state)
    return df

FEATURES = ['Age', 'Income', 'TenureMonths', 'NumProducts', 'IsActive']

df_train = make_churn_df(seed=42, n=2000)     # Day102・103と同じ学習データ
df_holdout = make_churn_df(seed=999, n=1000)   # 学習に一切使わない「本番相当」の未知データ

X, y = df_train[FEATURES].values, df_train['Churn'].values
X_hold, y_hold = df_holdout[FEATURES].values, df_holdout['Churn'].values
カラム名意味
Age顧客の年齢float
Income年収(円)float
TenureMonths契約継続月数float
NumProducts契約中の商品数(1〜4)int
IsActiveアクティブ顧客フラグ(1=活発)int
Churn離反フラグ(1=離反、目的変数)int

タスク

1
【理論】BlendingとStacking(Day 103)の違いを「Level-0予測の作り方(1回のtrain/holdout分割 vs K-Fold OOF)」の観点で説明する。Blendingが「データを捨てている」と言われる理由を、Level-0とLevel-1それぞれの学習データ量の面から説明する
2
【実装】train_test_split(test_size=0.2, stratify=y, random_state=42)blend_train(80%)とblend_holdout(20%)に分割する。3モデル(LR・RF・GB)をblend_trainだけで学習し、blend_holdoutX_holdへの予測を同時に作る
3
【実装】blend_holdout側の予測を特徴量としてLogisticRegressionをメタモデルとして学習する。blend_holdout(400件)を5-Foldに分けてメタモデル自身のAUCを計算し、最終メタモデルをX_holdに適用して真のholdout AUCを計算する
4
【実装/分析】Blending・Stackingそれぞれのモデルfit回数を数え上げて比較する。さらにrandom_stateを複数回変えて実行し、真のholdout AUCのブレを確認する
5
【分析/戦略】Averaging(Day102)・Stacking(Day103)・Blending(今日)について、「真のholdout AUC」「メタモデル学習データ量」「総fit回数」を比較する表を作り、Blendingを選ぶべき場面を結論づける

🔀 BlendingとStackingの構造比較

同じ3モデル・同じ2000行のデータからスタートしても、Level-0予測の作り方によってメタモデルが手にする「材料の量」が大きく変わることを図解する。

学習データ X, y(2000行)からスタート Blending(今日) 1回のtrain_test_split(80/20) blend_train 1600行 / blend_holdout 400行 Level-0: 3モデルを1回だけfit(計3回) Level-1メタ特徴量: blend_holdoutの予測のみ = 400行だけがメタモデルの学習材料 メタモデル学習(400行のみ) ↓ 真のholdoutで検証 ↓ 真のholdout AUC = 0.7770 seed=42時/総fit回数 9回 Stacking(Day103) 5-Fold全体を巡回(K-Fold OOF) 各Fold 1600行で学習・400行で予測 ×5周 Level-0: 3モデル×5Foldをfit(計15回) Level-1メタ特徴量: 学習データ全体のOOF予測 = 2000行すべてがメタモデルの学習材料 メタモデル学習(2000行) ↓ 真のholdoutで検証 ↓ 真のholdout AUC = 0.7924 正しい版/総fit回数 21回 メタモデル学習データ: Blending 400行(5分の1) vs Stacking 2000行 総fit回数: Blending 9回 vs Stacking 21回(約2.3倍)

📊 AUC・データ量・fit回数の比較(実行結果)

実際にコードを実行して得た値(乱数シード固定・環境により多少前後する)。バーの軸は0.70始まり(切り捨て軸)。

真のholdout AUC 比較(全手法)

Simple Average(Day102)
0.7922
holdout AUC
Rank Average(Day102)
0.7937
holdout AUC・全手法中トップ
Stacking 正しい版(Day103)
0.7924
holdout AUC
Stacking リークあり版(Day103)
0.7693
CV上は0.9281と高いが実力はこれ
Blending(今日, seed=42)
0.7770
Stacking正しい版よりやや低い
Blending(seed 1〜5 平均)
0.7884
分割次第でここまで上振れも

メタモデル学習に使えたデータ量

Stacking(OOF予測全体)
2000行
学習データ全体を活用
Blending(blend_holdoutのみ)
400行
Stackingの5分の1

総モデルfit回数(計算コスト)

Stacking(Level-0 15 + Level-1 6)
21回
Blendingの約2.3倍
Blending(Level-0 3 + Level-1 6)
9回
計算コストは軽い

Blendingは分割seedでどれだけブレるか

seed=42(模範解答の基本設定)
0.7770
holdout AUC
seed=1
0.7896
holdout AUC
seed=2
0.7903
holdout AUC
seed=3
0.7873
holdout AUC
seed=4
0.7855
holdout AUC
seed=5
0.7892
holdout AUC

6つの値(seed=42, 1〜5)の標準偏差は約0.0045、最大(0.7903)と最小(0.7770)の差は0.013。Stackingの真のholdout AUC(0.7924)はこの範囲のさらに上にあり、Blendingは「たまたま良い分割」を引かない限りStackingに届きにくいことが分かる。

💡 ヒント

ヒント1方向性

Day 103のget_oof_and_test_avgは「K-Foldで学習データ全体をOOF予測に変換する」関数だった。今日はこのK-Foldのループを、たった1回のtrain_test_splitに置き換えるとどうなるかを考える。返ってくる「メタ特徴量として使える行数」が、Stackingの2000行(学習データ全体)から、Blendingではblend_holdoutのサイズ(20%=400行程度)に激減する点に注目する。

ヒント2アプローチ
  • train_test_splitblend_trainblend_holdoutを作る。各モデルはblend_trainだけでfitし、blend_holdoutX_holdの両方にpredict_probaする(Stackingのように「Foldモデルの平均」を取る必要はない。1つのモデルしか学習していないため)
  • メタモデルの評価にはblend_holdoutだけをさらに5-Foldする。400件程度と少ないため、評価スコアが不安定(ブレやすい)ことを念頭に置く
  • fit回数はLevel-0(モデル数)+メタ評価用K-Fold+最終メタfitの合計で数える
  • random_stateを変えて複数回実行し、真のholdout AUCの散らばりを確認する
ヒント3コード骨格
from sklearn.model_selection import train_test_split, KFold
from sklearn.base import clone
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score

X_bl_train, X_bl_hold, y_bl_train, y_bl_hold = train_test_split(
    X, y, test_size=0.2, stratify=___, random_state=42
)

meta_bl_hold, meta_test = {}, {}
for name, model in models.items():
    m = clone(model)
    m.fit(X_bl_train, ___)
    meta_bl_hold[name] = m.predict_proba(X_bl_hold)[:, 1]
    meta_test[name] = m.predict_proba(___)[:, 1]   # ← 真のholdoutへの予測もこのモデルで作る

X_meta_bl_hold = np.column_stack([meta_bl_hold[n] for n in model_names])
X_meta_test = np.column_stack([meta_test[n] for n in model_names])

# メタモデルをblend_holdout内の5-Foldで評価
kf_small = KFold(n_splits=5, shuffle=True, random_state=7)
meta_oof_small = np.zeros(len(X_bl_hold))
for tr_idx, val_idx in kf_small.split(X_meta_bl_hold):
    m = clone(LogisticRegression())
    m.fit(X_meta_bl_hold[tr_idx], y_bl_hold[___])
    meta_oof_small[val_idx] = m.predict_proba(X_meta_bl_hold[val_idx])[:, 1]

meta_final = LogisticRegression().fit(X_meta_bl_hold, y_bl_hold)
blend_holdout_pred = meta_final.predict_proba(___)[:, 1]  # ← X_meta_test を渡す

模範解答

import numpy as np
import pandas as pd
from sklearn.base import clone
from sklearn.model_selection import KFold, train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import roc_auc_score

# ====== データ準備(省略: 問題文のmake_churn_dfをそのまま使用) ======
df_train = make_churn_df(seed=42, n=2000)
df_holdout = make_churn_df(seed=999, n=1000)
X, y = df_train[FEATURES].values, df_train['Churn'].values
X_hold, y_hold = df_holdout[FEATURES].values, df_holdout['Churn'].values

models = {
    'LogisticRegression': make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000)),
    'RandomForest': RandomForestClassifier(n_estimators=200, max_depth=5, random_state=42),
    'GradientBoosting': GradientBoostingClassifier(n_estimators=150, max_depth=3, random_state=42),
}
model_names = list(models.keys())

# ====== タスク2: blend_train/blend_holdoutに分割し、Level-0モデルを1回だけ学習 ======
X_bl_train, X_bl_hold, y_bl_train, y_bl_hold = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)
# blend_train size: 1600, blend_holdout size: 400

meta_bl_hold, meta_test = {}, {}
for name, model in models.items():
    m = clone(model)
    m.fit(X_bl_train, y_bl_train)
    meta_bl_hold[name] = m.predict_proba(X_bl_hold)[:, 1]
    meta_test[name] = m.predict_proba(X_hold)[:, 1]
    print(f"{name:20s}: blend_holdout AUC = {roc_auc_score(y_bl_hold, meta_bl_hold[name]):.4f}"
          f"  |  true holdout AUC = {roc_auc_score(y_hold, meta_test[name]):.4f}")

X_meta_bl_hold = np.column_stack([meta_bl_hold[n] for n in model_names])
X_meta_test = np.column_stack([meta_test[n] for n in model_names])

# ====== タスク3: メタモデルをblend_holdout内の5-Foldで評価 + 真のholdout AUC ======
meta = LogisticRegression()
kf_small = KFold(n_splits=5, shuffle=True, random_state=7)
meta_oof_small = np.zeros(len(X_bl_hold))
for tr_idx, val_idx in kf_small.split(X_meta_bl_hold):
    m = clone(meta)
    m.fit(X_meta_bl_hold[tr_idx], y_bl_hold[tr_idx])
    meta_oof_small[val_idx] = m.predict_proba(X_meta_bl_hold[val_idx])[:, 1]
blend_cv_auc = roc_auc_score(y_bl_hold, meta_oof_small)

meta_final = clone(meta).fit(X_meta_bl_hold, y_bl_hold)
blend_holdout_pred = meta_final.predict_proba(X_meta_test)[:, 1]
blend_holdout_auc = roc_auc_score(y_hold, blend_holdout_pred)

print(f"\nBlending: blend_holdout内5-Fold AUC = {blend_cv_auc:.4f} | 真のholdout AUC = {blend_holdout_auc:.4f}")
print(f"メタモデル係数 (LR, RF, GB) = {np.round(meta_final.coef_[0], 3)}")

# ====== タスク4: fit回数の比較 + 分割seedを変えたときのブレ ======
print("\n--- fit回数比較 ---")
print("Blending: Level-0 fit =", len(models), " + メタ評価用5-Fold fit = 5  + 最終メタfit = 1  → 計9回")
print("Stacking(Day103): Level-0 fit = 3モデル×5Fold =", 3 * 5, " + メタ評価用5-Fold fit = 5  + 最終メタfit = 1  → 計21回")

print("\n--- 分割seedを変えた場合のブレ(Blendingの分散確認) ---")
for seed in [1, 2, 3, 4, 5]:
    X_bl_train2, X_bl_hold2, y_bl_train2, y_bl_hold2 = train_test_split(
        X, y, test_size=0.2, stratify=y, random_state=seed
    )
    mth, mte = {}, {}
    for name, model in models.items():
        m = clone(model)
        m.fit(X_bl_train2, y_bl_train2)
        mth[name] = m.predict_proba(X_bl_hold2)[:, 1]
        mte[name] = m.predict_proba(X_hold)[:, 1]
    Xm_hold = np.column_stack([mth[n] for n in model_names])
    Xm_test = np.column_stack([mte[n] for n in model_names])
    mm = LogisticRegression().fit(Xm_hold, y_bl_hold2)
    pred = mm.predict_proba(Xm_test)[:, 1]
    print(f"seed={seed}: blend 真のholdout AUC = {roc_auc_score(y_hold, pred):.4f}")

出力イメージ(実際に実行して確認した値。乱数・環境により多少前後する):

LogisticRegression  : blend_holdout AUC = 0.7243  |  true holdout AUC = 0.7714
RandomForest        : blend_holdout AUC = 0.7166  |  true holdout AUC = 0.7670
GradientBoosting    : blend_holdout AUC = 0.7066  |  true holdout AUC = 0.7481

Blending: blend_holdout内5-Fold AUC = 0.7108 | 真のholdout AUC = 0.7770
メタモデル係数 (LR, RF, GB) = [1.96  1.342 1.062]

--- fit回数比較 ---
Blending: Level-0 fit = 3  + メタ評価用5-Fold fit = 5  + 最終メタfit = 1  → 計9回
Stacking(Day103): Level-0 fit = 15  + メタ評価用5-Fold fit = 5  + 最終メタfit = 1  → 計21回

--- 分割seedを変えた場合のブレ(Blendingの分散確認) ---
seed=1: blend 真のholdout AUC = 0.7896
seed=2: blend 真のholdout AUC = 0.7903
seed=3: blend 真のholdout AUC = 0.7873
seed=4: blend 真のholdout AUC = 0.7855
seed=5: blend 真のholdout AUC = 0.7892
🧠
理論問題(タスク1): Stacking(Day 103)はK-Foldの各Foldで「学習に使っていない行」への予測を積み上げ、学習データ全体(2000行)をOOF予測に変換してからメタモデルに渡していました。Blendingはこの手順を「1回の80/20分割」に置き換えます。①Level-0モデルの学習データはblend_train(1600行)の1回分の抽選だけに依存し、Stackingのように「5通りの訓練データで学習した5モデルの合議」という分散低減効果を得られません。②さらに、メタモデルの学習データがblend_holdout(400行)だけに縮小します。Stackingの2000行の5分の1しかない、これが「データを捨てている」と言われる理由です。

🪜 Step-by-Step 解説

1train_test_splitでblend_train/blend_holdoutを作り、Level-0モデルを1回だけ学習する

X_bl_train, X_bl_hold, y_bl_train, y_bl_hold = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)
for name, model in models.items():
    m = clone(model).fit(X_bl_train, y_bl_train)
    meta_bl_hold[name] = m.predict_proba(X_bl_hold)[:, 1]
    meta_test[name] = m.predict_proba(X_hold)[:, 1]
🔑
なぜこうするか: StackingのK-Foldループがまるごと1回のtrain/holdout分割に置き換わっている点が今日の核心です。stratify=yを指定するのはChurn(陽性が少数派)のクラス比率をblend_train/blend_holdoutで揃え、小さいblend_holdout(400行)で評価が偏らないようにするためです。Level-0モデルはblend_trainだけを見て学習するため、学習に使う分割は1通りしかないという違いが生まれます。

2メタモデルはblend_holdoutだけを使い、その中でさらに5-Fold評価する

kf_small = KFold(n_splits=5, shuffle=True, random_state=7)
meta_oof_small = np.zeros(len(X_bl_hold))
for tr_idx, val_idx in kf_small.split(X_meta_bl_hold):
    m = clone(meta).fit(X_meta_bl_hold[tr_idx], y_bl_hold[tr_idx])
    meta_oof_small[val_idx] = m.predict_proba(X_meta_bl_hold[val_idx])[:, 1]
🎲
なぜこうするか: メタモデルの性能を正しく評価するにはCVが必要ですが、Blendingで使えるのはblend_holdoutの400行だけです。5-Foldにすると1Foldあたり約80行しかなく、実行結果のblend_holdout内5-Fold AUC = 0.7108は真のholdout AUC(0.7770)より0.066も低く出ています。これはリーク(Day 103)とは逆方向の誤差で、サンプル数が少なすぎることによる評価のブレです。Blendingの弱点は「見かけが甘くなる」ことではなく「見かけの数字自体が不安定になる」ことだと分かります。

3fit回数を数えて計算コストを比較する

print("Blending: 3(Level-0) + 5(メタ評価) + 1(最終メタ) = 9回")
print("Stacking: 15(Level-0=3モデル×5Fold) + 5(メタ評価) + 1(最終メタ) = 21回")
なぜこうするか: Level-0のモデル学習回数が「モデル数」で済むか「モデル数×Fold数」必要かが、BlendingとStackingの最も分かりやすい違いです。今回の設定ではBlendingが9回、Stackingが21回で、Stackingの計算コストは約2.3倍でした。モデル数やFold数が増えるほどこの差は開くため、大規模データ・多数モデルの状況ではBlendingの軽さが実務上のメリットになります。

4random_stateを変えて複数回実行し、Blendingの結果のブレを確認する

for seed in [1, 2, 3, 4, 5]:
    X_bl_train2, X_bl_hold2, y_bl_train2, y_bl_hold2 = train_test_split(X, y, test_size=0.2, stratify=y, random_state=seed)
    # ...(Level-0学習・メタ学習を繰り返す)
    print(f"seed={seed}: blend 真のholdout AUC = {auc:.4f}")
🎯
なぜこうするか: Stackingは学習データ全体をK-Foldで巡回するため分割への依存が薄い一方、Blendingは「たった1回の分割」に依存するため、その分割の運によって結果がブレます。今回はseed=42のとき真のholdout AUCが0.7770だったのに対し、seed=1〜5では0.7855〜0.7903とやや高めの値に集中しました(6つの値の標準偏差は約0.0045、最大と最小の差は0.013)。「今日実行したのがたまたま少し厳しめの分割だった」だけで実力評価が変わりうる、というのがBlending特有のリスクです。

🧮 数学・統計の補足(文系向け)

📐
直感的な理解: Stackingを「クラス全員が一度ずつ司会(ホールドアウト役)を経験する持ち回り制」に例えるなら、Blendingは「くじ引きで選ばれた1グループだけが最初から最後まで司会を担当する」方式です。持ち回り制なら全員分のデータが集まりますが、くじ引き方式では選ばれたグループの人数分のデータしか手に入りません。しかも、くじの引き方(どの乱数シードで分割するか)次第でメンバーの顔ぶれが変わり、集まるデータの「質」も多少変わってしまいます。
📝
身近な例: クラス40人のテストで「本当の実力」を知りたいとき、Stackingは「40人全員を5グループに分け、各グループが順番に模試の採点係(他の32人の答案を採点)を務める」方式で、最終的に40人全員分の採点結果が手に入ります。Blendingは「最初に8人だけをくじ引きで選び、残り32人が受けた模試をその8人だけで採点する」方式です。集まる採点結果は8人分だけで、しかも「どの8人が選ばれるか」によって採点の傾向が変わりえます。

数式を見たら

n_meta(Stacking) = n_train  n_meta(Blending) = n_train × r

rはholdoutの比率(今日は0.2)。Stackingのメタモデル学習データ量n_train(2000行)に対し、Blendingはn_train × r(400行)しか使えない。今日の設定では2000 × 0.2 = 400行、Stackingの2000行の5分の1という「データを捨てている」度合いが、この単純な掛け算で説明できる。

🏆 Kaggleでの実践的な使い方

よく使われるコンペカテゴリ: ☑ 表形式データ(Tabular) / ☐ 自然言語処理(NLP) / ☐ 画像認識(CV) / ☐ 時系列(Time Series)

  • Netflix Prizeでの起源: Blendingという用語はNetflix Prizeのコンペで広まった。当時は計算資源が限られており、K-Foldを回すStackingよりも1回のtrain/holdout分割で済むBlendingが好まれた
  • チーム戦での使い分け: チームでコンペに参加する場合、各メンバーが自分のLevel-0モデルの「holdoutへの予測列」だけを共有すれば、モデルの実装詳細やコードを公開せずにBlendingを組める。これがKaggleのチーム戦でBlendingが好まれる理由の一つ
  • 大規模データでの現実的な選択: 数百万行・多数モデルのコンペではK-Fold Stackingの計算コストが非現実的になることがある。今日の例では9回 vs 21回(約2.3倍)だったが、モデル数やFold数が増えるほどこの差は指数的に開くため、計算時間の制約が厳しい場面ではBlendingが唯一現実的な選択肢になることもある
  • 精度が最優先なら基本はStacking: 今日の結果でも真のholdout AUCはStacking正しい版(0.7924)>Blending(seed=42, 0.7770)。データ量に余裕があり計算時間が許すなら、まずStackingを検討するのが定石

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
BlendingはStackingの単なる簡易版で、精度も同等だと思う「仕組みが似ているから性能も同じ」という直感今日の実行結果ではBlending(0.7770)がStacking正しい版(0.7924)より低かった。メタモデルが使えるデータ量が2000行 vs 400行と5倍違うため、一般にBlendingはStackingよりやや不利になりやすい
Blendingのblend_holdout内5-Fold AUCをそのまま信じてしまう「CVスコアだから信頼できる」という思い込み今日はblend_holdout内5-Fold AUC=0.7108が真のholdout AUC(0.7770)より大きく低かった。400行というサンプルサイズの小ささが評価を不安定にしている(リークとは別種の問題)
train_test_splitのrandom_stateを固定していれば結果は再現できるので問題ないと考える「再現性がある=信頼できる」という混同再現できることと、その1回の分割がたまたま良い/悪い結果を生んでいないことは別問題。今日はseedを変えるだけでholdout AUCが0.7770〜0.7903まで動いた
BlendingではLevel-0モデルの学習にX_hold(真のholdout)を混ぜてよいと勘違いする「データは多いほど良い」という発想X_holdはDay 103から一貫して「一度も学習に使わない」という前提のデータ。Blendingでもblend_trainblend_holdoutはあくまでX, y(学習データ)の内部分割であり、X_holdを混ぜるとholdoutの意味が失われる
モデル数が少なければBlendingとStackingの計算コスト差は気にしなくてよいと考える「今回のfit回数差は9対21で大差ない」という近視眼的判断モデル数・Fold数が増えるほど差は開く(例: 10モデル×10Foldなら100 vs 10で10倍差)。大規模実験で初めて効いてくるトレードオフである点を見落としやすい

🚀 次のステップ

  • 発展: test_sizeを0.2から0.4や0.1に変えて、blend_holdoutのサイズが真のholdout AUCの安定性(seedによるブレの大きさ)にどう影響するかを確認してみる。理論上はholdoutを大きくするほどメタモデルの評価は安定するが、その分Level-0モデルの学習データ(blend_train)が減るというトレードオフも発生する
  • 次回予告: Day 105「外れ値の影響と対策(Huber Loss・量子回帰)」 — ここまでのアンサンブル3部作(Averaging→Stacking→Blending)を終え、次はモデルそのものが外れ値にどう対処するかというテーマに移る

📝 自己評価(解いた後に記入)

自分の回答・気づき・メモ: