Day 176 — 2026-10-09 | Playground Series 実戦投入⑪ — 木の大きさには山があった。中雑音では葉255 が最良(−36.62、5/5)で葉511 は +7.69 戻り、min_child_samples=50 はどの世界でも悪化(+9.8〜+24.7)。決定15 の候補一覧に葉255 を足すと中雑音の損が 5.20→0.74、511 と mcs50 を足しても損は動かない(×10⁻⁴、9ワールドの途中集計)

2026-10-09 金 / Phase 6(月替わりテーマ2: Playground Series 実戦投入) 分析→戦略 低雑音3+小得1+中雑音5ワールド(40予定→9で打ち切り) · 乱数なし6本 · 270 fit

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

Day175 のおさらい。 乱数なしの葉 31(lgb_det)・63・127 を3種類の世界で比べた。

世界(雑音 元/合成)leaf63 の真の差leaf127 の真の差
低雑音(0.05/0.03)−3.67−3.50(引き分け)
小得(0.12/0.08)−12.72−22.96
中雑音(0.15/0.10)−16.47−31.74

(×10⁻⁴、lgb_det との差。マイナスが良い)

小得・中雑音では葉 127 がまだ良くなり続けていた。表の端(一番大きい葉)が最良なら、その先にもっと良いものがあるかもしれない。 これが今日の問いの出発点。

決定15(Day175 改訂・印 ○): 乱数なしの候補(葉 31・63・127)を補正CVで比べ、最良を基準にする。余裕は付けない

木の大きさを決める2つのつまみ(LightGBM):

つまみ意味大きくすると
num_leaves1本の木の葉の最大数(木の細かさ)細かい組み合わせを拾える。ただし雑音も拾いやすく、fold ごとの予測もばらつく
min_child_samples1枚の葉に最低いる行数(既定 20)葉が「少人数の偶然」を覚えにくくなる(正則化)。ただし細かい規則も拾えなくなる

LightGBM の公式ドキュメント(Parameters Tuning、参照 2026-10-09・記憶による要約で未照合)は、num_leaves を上げると精度が上がりうるが過学習の危険があり、min_data_in_leaf(= min_child_samples)は過学習を防ぐ重要なつまみ、と説明している。どちらに転ぶかはデータ次第なので、今日は測る。

用語整理(Day175 と同じものは省略):

用語意味
DET8乱数なしの8本: lgb_det(葉31)・leaf63・leaf127・leaf255・leaf511・leaf127_m50・leaf255_m50・leaf511_m50(_m50 は min_child_samples=50)
候補一覧 L3/L4/L5/L8決定15 で比べる一覧。L3={31,63,127}(Day175)、L4=L3+255、L5=L4+511、L8=DET8 全部
一覧内 oracleその一覧の中で真に最良のものを選べたときの損。一覧が狭いことによる損(取りこぼし)
A比test 行の fold 間分散 A_test の、lgb_det に対する倍率。生の CV の偏りの大きさの目安(Day171)

数値はすべて縮小シミュレータの上の実測。実データ(S5E5)で同じになるとは限らない。

データ・保存物の構造(スキーマ)

名前形・型意味・作り方使う場所
Sex … Body_Temp, Dur_x_HR, Keytel_kcalint / float(9列)Day163〜175 と同じ合成(ワールドの乱数 3000+番号)。Calories の雑音だけ 0.05/0.03(低)・0.12/0.08(小得)・0.15/0.10(中)全候補
raw176_w*.npz(low/mid/sm)oof (6,30000)・evk (6,5,80000) float32・fit_s (6,)今日作った乱数なし6本(葉127/255/511 × mcs 20/50)。9 ワールド × 6本 × 5 fold = 270 fit表0〜5
raw175(葉31/63/127)oof (3,30000)・evk (3,5,80000)Day175 の保存物。今日の leaf127 と最大差 0.0 で一致を確認再現テスト・一覧 L3
raw172 / raw173 / raw174oof (11〜13, seed, 30000)・evk (…, 5, 80000)乱数ありの候補(seed 組 s0・s1)表5(決定14)
C / Ca / T5 / Afloat生の CV(1モデルの OOF)/補正CV √(C²−A)/真(6万行 truth で5 fold 平均を採点)/test 行の fold 間分散表1〜5

評価指標(RMSLE)と、生の CV の偏り

RMSLE は log1p 空間の RMSE(小さいほど良い)。生の CV は1モデルの OOF で採点するが、提出は5モデルの平均なので、1本ごとの予測がばらつく候補(葉が多い木)ほど不利に見える。下の図は、中雑音で葉127/255/511 を生の CV(赤)がどれだけ悪く見せたかと、補正CV(緑)で残った偏り。

中雑音(5ワールド): CV が大きい木をどれだけ悪く見せるか(CV差 − 真の差、×10⁻⁴)赤=生の CV(1モデルの OOF)/緑=補正CV(決定13)。右端の数字は A_test の葉31 比010203040葉127A比 2.25+15.27-1.43葉255A比 3.11+28.92+0.94葉511A比 3.98+42.29+3.23

❓ 問題

タスクA(予想)— 測る前に書く

  1. 中雑音で、葉 255・511 は葉 127 よりさらに良いか。山(最良点)はどこにあるか
  2. min_child_samples を 20→50 にすると、葉の多い木は良くなるか(「葉を増やした分の過学習を抑える」と期待できるか)
  3. 生の CV の偏り(大きい木を悪く見せる量)は葉 511 でどこまで育つか。補正CV(決定13)はそれでも消せるか

タスクB(コーディング)

  1. day175.py の生成部(ワールドの乱数 3000+番号・雑音だけ引数)をそのまま使い、DET を6本に差し替えた day176.py を書く。leaf127(mcs20)が raw175 の leaf127 と OOF・fold 別 test 予測の両方で完全一致することを確かめる(決定9)。候補ごとの学習秒も保存する
  2. 低雑音・小得・中雑音を回す(今日は途中で打ち切った。打ち切った事実と世界ごとの件数を結果に書く)
  3. 集計 analyze176.py で、(a) 真の差と隣どうしの差、(b) 生と補正の CV の偏り、(c) 候補一覧 L3〜L8 ごとの基準選びの損、(d) 乱数ありの候補まで含めた方針の損、を出す

タスクC(分析と戦略)

  1. 山はどこにあり、世界でどう動くか。min_child_samples=50 の結果をどう読むか
  2. 候補一覧はどこまで広げるべきか。費用(学習秒)と損の両方で答える
  3. 決定13・15 の印をどう付け替えるか。打ち切った9ワールドで言えること・言えないことを分ける

💡 ヒント

ヒント1(方向性)

「一覧の端が選ばれたら、その先を足す」。一覧の真ん中が選ばれているなら、もう広げる必要は無い。

ヒント2(アプローチ)

一覧を広げた損の変化は、一覧内 oracle(取りこぼし)と選び間違い(補正CVの揺れ)に分けて見る。一覧を広げると取りこぼしは減るが、候補が増えると選び間違いと選択の上振れが増えうる。

ヒント3(コード骨格)

LISTS = {"L3": ["lgb_det", "leaf63", "leaf127"], "L4": [..., "leaf255"], "L5": [..., "leaf511"], "L8": DET8}
for (kind, w), h in S0[S0.cand.isin(DET8)].groupby(["kind", "world"]):
    h = h.set_index("cand"); best = h.T5.min()
    for ln, L in LISTS.items():
        p = h.Ca[L].idxmin()          # 補正CVで選ぶ(決定15)
        o = h.T5[L].idxmin()          # 一覧内 oracle
        rows.append(dict(L=ln, oracle=(h.T5[o]-best)*1e4, regret=(h.T5[p]-best)*1e4))

✅ 模範解答

タスクA(予想の模範と、実測との照合)

予想模範の予想(書いた時点)実測(9ワールド)当たり外れ
1. 山の位置中雑音は 255 か 511 まで良くなる中雑音: 255 が最良 −36.62±0.63(5/5)、511 は 255 より +7.69±0.75 悪い。小得も 255 が最良(1ワールドのみ)。低雑音は 63/127 が山で 255 は lgb_det より +2.48 悪い(0/3)半分当たり。山は 255 で、511 は過ぎていた。山の位置は雑音で動く(低雑音 63〜127、小得・中雑音 255)
2. mcs50葉の多い木では良くなる6組すべてで悪化: 葉127 +9.80〜+12.88、葉255 +11.05〜+19.25、葉511 +8.68〜+24.72外れ。この合成データでは細かい規則を拾う方が効き、少人数の葉を禁じると損をする
3. 生の偏り葉511 で +25 くらい中雑音 leaf511 +42.29±3.01(A比 3.98)。補正後 +3.23±3.06(約1SE)大きさは外れ(もっと大きい)。補正CVは中雑音ではほぼ消した。ただし低雑音 leaf511 は補正後 −4.78±1.76(3ワールド、約2.7SE)で、逆向きに少し効きすぎの兆し

タスクB(完全なコード)

day176.py(生成部は day175.py と1文字も変えていない。差し替えたのは DET の一覧・保存名・学習秒の記録だけ):

# --- day175.py からの差分だけ(生成器 keytel_rate / make_original / synth_from / add_feats は同一)---
LG = dict(objective="regression", learning_rate=0.1, n_estimators=300, num_leaves=31, min_child_samples=20,
          n_jobs=1, verbose=-1)
# Day176: 木の大きさの上限を測る。leaf127 は raw175 との一致確認を兼ねる
DET = [("leaf127", dict(num_leaves=127)), ("leaf255", dict(num_leaves=255)), ("leaf511", dict(num_leaves=511)),
       ("leaf127_m50", dict(num_leaves=127, min_child_samples=50)), ("leaf255_m50", dict(num_leaves=255, min_child_samples=50)),
       ("leaf511_m50", dict(num_leaves=511, min_child_samples=50))]
names = [c[0] for c in DET]
rng = np.random.default_rng(3000 + W)
orig = make_original(N_ORIG, rng)
pool = synth_from(orig, N_TRAIN + N_TEST + N_TRUTH, rng)
orig, pool = add_feats(orig), add_feats(pool)
train = pool.iloc[:N_TRAIN].reset_index(drop=True)
evalset = pool.iloc[N_TRAIN:].reset_index(drop=True)
XE = evalset[BASE].values
oof = np.zeros((len(names), N_TRAIN))
evk = np.zeros((len(names), 5, N_TEST + N_TRUTH), np.float32)
fit_s = np.zeros(len(names))  # 候補ごとの学習+予測の秒数(費用の表に使う)
for k, (tr, va) in enumerate(KFold(5, shuffle=True, random_state=0).split(train)):
    tr_df = pd.concat([train.iloc[tr], orig], ignore_index=True)
    X, y, XV = tr_df[BASE].values, np.log1p(tr_df.Calories.values), train.iloc[va][BASE].values
    for j, (nm, over) in enumerate(DET):
        t1 = time.time()
        m = lgb.LGBMRegressor(**{**LG, **over}, random_state=1000 * 0 + k).fit(X, y)
        oof[j, va] = m.predict(XV); evk[j, k] = m.predict(XE)
        fit_s[j] += time.time() - t1
    print(f"world {W} fold {k} {time.time()-T0:.0f}s", flush=True)
assert names == ["leaf127", "leaf255", "leaf511", "leaf127_m50", "leaf255_m50", "leaf511_m50"]  # 決定9
np.savez_compressed(f"raw176_w{W}.npz", oof=oof, evk=evk, ytr=train.Calories.values,
                    yev=evalset.Calories.values, names=np.array(names), fit_s=fit_s)
print(f"world {W} wall {time.time()-T0:.0f}s")

analyze176.py(要点。保存物 raw172/173/174 の乱数あり10本+raw175 の葉31/63/127+今日の6本を同じワールドで突き合わせる):

def stats(y, ytru, O, E):  # O: OOF (30000,)  E: fold 別 test 予測 (5, 80000)
    c = sle(y, O).mean(); a = E[:, :N].var(axis=0).mean(); t5 = sle(ytru, E.mean(0)[N:]).mean()
    return dict(C=np.sqrt(c), Ca=np.sqrt(c - a), T5=np.sqrt(t5), A=a)   # 生CV・補正CV(決定13)・真・A_test

# 決定9: ラベルの一致と leaf127 の完全一致を assert/記録
assert np.array_equal(a6["ytr"], ytr) and np.array_equal(a6["yev"], yev)
rep.append(dict(l127_oof=np.abs(a6["oof"][0] - a5["oof"][2]).max(),
                l127_ev=np.abs(a6["evk"][0].astype(np.float64) - a5["evk"][2]).max()))

LISTS = {"L3 {31,63,127}": ["lgb_det", "leaf63", "leaf127"], "L4 +255": [..., "leaf255"],
         "L5 +511": [..., "leaf511"], "L8 +mcs50": DET8}
for (kind, w), h in S0[S0.cand.isin(DET8)].groupby(["kind", "world"]):
    h = h.set_index("cand"); best = h.T5.min()
    for ln, L in LISTS.items():
        p = h.Ca[L].idxmin(); o = h.T5[L].idxmin(); pr = h.C[L].idxmin()
        rows.append(dict(kind=kind, world=w, L=ln, oracle=(h.T5[o] - best) * 1e4,
                         regret=(h.T5[p] - best) * 1e4, raw=(h.T5[pr] - best) * 1e4, pick=p))

# 表5: 決定15(L3 または L8 で基準)→ 決定14(乱数ありの補正CV最良が m 以上良ければ乗り換え)を全19本の最良と比べる
base = h.Ca[LISTS[ln]].idxmin(); b = h.Ca[RAND].idxmin()
p = b if (h.Ca[base] - h.Ca[b]) * 1e4 > m else base

実行:

# 1ワールド分を作る(run.sh は Day175 と同じく DYLD_LIBRARY_PATH に scikit-learn 同梱の libomp を入れて day176.py を呼ぶ)
./run.sh 0 0.12 0.08 $(pwd)/sm
# 残りを 5 並行で回す(今日は 9 ワールドで打ち切り)
(for w in 0 1 2 3 4 5; do echo "$w 0.05 0.03 low"; echo "$w 0.15 0.10 mid"; done; for w in 1 2 3 4 5; do echo "$w 0.12 0.08 sm"; done) | xargs -P 5 -L 1 sh -c './run.sh $0 $1 $2 '"$(pwd)"'/$3'
# 保存物(d172・d173/mid・d174/sm・d175)と今日の raw176 を合わせて集計する
python analyze176.py <d172> <d173/mid> <d174/sm> <d175>

実行結果(実測・途中打ち切り)

# ============ 実行環境: macOS (M1, メモリ8GB)・Day172 と同じ uv venv(Python 3.13.12・lightgbm 4.7.0)
#   OMP_NUM_THREADS=1・n_jobs=1。他のセッション・VM と同居し load average 20〜70
#   完了: 低雑音 3(w0-2)・小得 1(w0)・中雑音 5(w0-4)= 9 ワールド × 6 本 × 5 fold = 270 fit
#   1ワールドの壁時計 339〜997 秒。予定 40 ワールドのうち 31 は未計測(打ち切り)
=== 表0 再現(決定9): 今日の leaf127 と raw175 の leaf127 の最大絶対差 ===
中雑音 0.0 0.0 / 低雑音 0.0 0.0 / 小得 0.0 0.0

=== 表0b 費用: 1ワールド5fold の学習+予測秒(中央値、同居環境の実測)===
leaf127 30 / leaf255 60 / leaf511 139 / leaf127_m50 43 / leaf255_m50 85 / leaf511_m50 175

=== 表1 真の差 T5−T5(lgb_det)(×1e-4、ワールド平均±SE)と lgb_det より良い割合 ===
[低雑音] leaf63 -3.18±0.77(1.00) leaf127 -2.27±0.71(1.00) leaf255 +2.48±0.67(0.00) leaf511 +10.30±0.80(0.00)
         leaf127_m50 +7.53±1.14(0.00) leaf255_m50 +13.54±0.73(0.00) leaf511_m50 +18.98±1.09(0.00)
[小得]   leaf63 -12.41 leaf127 -23.77 leaf255 -26.54 leaf511 -18.88 leaf127_m50 -10.97 leaf255_m50 -9.04 leaf511_m50 +1.58(1ワールド、SE なし)
[中雑音] leaf63 -15.56±0.71(1.00) leaf127 -31.42±0.35(1.00) leaf255 -36.62±0.63(1.00) leaf511 -28.93±1.36(1.00)
         leaf127_m50 -18.54±0.78(1.00) leaf255_m50 -17.37±0.96(1.00) leaf511_m50 -4.21±1.64(0.80)

=== 表1b 隣どうしの真の差(×1e-4)===
[低雑音] 255−127 +4.75±0.72  511−255 +7.82±0.14  127_m50−127 +9.80±1.62  255_m50−255 +11.05±1.40  511_m50−511 +8.68±1.72
[小得]   255−127 -2.78        511−255 +7.67        127_m50−127 +12.80       255_m50−255 +17.50       511_m50−511 +20.46
[中雑音] 255−127 -5.20±0.45  511−255 +7.69±0.75  127_m50−127 +12.88±0.73  255_m50−255 +19.25±0.42  511_m50−511 +24.72±0.68

=== 表2 CV の偏り(CV差−真の差、対 lgb_det、×1e-4)と A比 ===
[低雑音] leaf127 生 +9.34±2.28 補正 -2.95±2.17 A比 1.97 | leaf255 生 +17.28±1.02 補正 -2.73±1.06 A比 2.60 | leaf511 生 +22.04±1.70 補正 -4.78±1.76 A比 3.16
[小得]   leaf127 生 +7.32 補正 -7.18 A比 2.14 | leaf255 生 +12.02 補正 -12.55 A比 2.94 | leaf511 生 +23.84 補正 -10.68 A比 3.75(1ワールド)
[中雑音] leaf127 生 +15.27±3.37 補正 -1.43±3.41 A比 2.25 | leaf255 生 +28.92±3.94 補正 +0.94±4.06 A比 3.11 | leaf511 生 +42.29±3.01 補正 +3.23±3.06 A比 3.98

=== 表3 候補一覧を広げたときの基準選び(対 DET8 の真の最良、×1e-4)===
[低雑音] L3: oracle 0.00 補正CV選択 1.31±0.80 生CV選択 3.01 | L4/L5/L8: 同じ(255 以上は1回も選ばれない)
[小得]   L3: oracle 2.78 選択 2.78 | L4/L5/L8: oracle 0.00 選択 0.00(leaf255 を選択)
[中雑音] L3: oracle 5.20 選択 5.20±0.45 生CV 5.20 | L4/L5/L8: oracle 0.00 選択 0.74±0.74(leaf255 4・leaf127 1)生CV 5.20
DET8 の真の最良: 低雑音 {leaf63: 2, leaf127: 1}  小得 {leaf255: 1}  中雑音 {leaf255: 5}
ワールド対 L8 − L3: 低雑音 +0.00  小得 -2.78  中雑音 -4.46±1.14(良くなった 4/5、悪くなった 0)

=== 表5 全19本(乱数あり10+乱数なし8)の真の最良との損(s0/s1 平均)===
                                    低雑音   小得  中雑音  最悪の超過  乗り換え回数
決定15 L8 → 決定14 m=0/2             1.31   0.00   0.74    0.74        0
常にleaf255                          6.05   0.00   0.00    4.75        0
決定15 L3 → 決定14 m=0/2             1.31   2.78   5.20    5.20        0
常にleaf127                          1.31   2.78   5.20    5.20        0
常にleaf255_m50                     17.11  17.50  19.25   19.25        0

タスクC(分析の模範)

  1. 木の大きさには山があり、山は雑音で動く。 中雑音では 127→255 で −5.20±0.45 良くなり、255→511 で +7.69±0.75 戻る(5/5 で同じ向き)。低雑音では山がもっと手前(63〜127)で、255 はもう lgb_det より悪い(+2.48、0/3)。min_child_samples=50 は6組すべてで悪化(最小 +8.68)。この合成データの目的変数は Keytel 式の掛け算と少しの雑音で、細かい組み合わせを拾うほど得をする。葉を増やすことと「少人数の葉を禁じる」ことは同じ「木の大きさ」ではなく、ここでは前者が効き後者が害になった
  2. 一覧は L4(31/63/127/255)まで広げれば今日の世界では足りる。 L3→L4 で中雑音の損 5.20→0.74(4/5 で改善、悪化 0)、小得 2.78→0。511 と mcs50 を足した L5・L8 は損を1つも変えなかった(どの世界でも選ばれない)。費用(1ワールド5fold、同居環境の実測)は葉255 が 60 秒(葉127 の約2倍)、葉511 が 139 秒、mcs50 の3本で計 303 秒。L3→L4 で足すのは 60 秒、L4→L8 でさらに 442 秒を足して得が 0。止め方は「一覧の端(一番大きい葉)が補正CVで最良になったら、次の倍(例: 511)を1本だけ足す。真ん中が最良なら止める」とする。今日の9ワールドでは端の255 が選ばれた中雑音・小得で 511 を足しても選ばれなかったので、このルールは「1本足して止まる」
  3. 印の更新。 決定15 は ○ のまま一覧を L4 に改訂し、上の止め方を追記。決定13(補正CV)は中雑音で葉511 の生の偏り +42.29 を +3.23±3.06 まで消したので効いているが、低雑音の葉511 で −4.78±1.76(3ワールド)、小得で −4〜−12(1ワールド)と、大きい木で補正が効きすぎる兆しがある。ワールド数が足りず SE が出ない世界もあるので、◎ は「葉 127 まで」に限り、葉 255 以上は ○ に下げる(後で測り直す)

タスクD(決定)

#決定採った案採らなかった案と理由前提が変わったら
15(改訂、印 ○ 据え置き)基準の候補一覧乱数なしの 葉 31/63/127/255(L4)を補正CVで比べる。一覧の端が最良になったら次の倍を1本だけ足し、真ん中が最良なら止める(a) L8(511・mcs50 も入れる): 損は L4 と同じで、1ワールドあたり 442 秒余分にかかる。(b) 常に leaf255: 中雑音・小得では 0 だが低雑音で 6.05(最悪の超過 4.75)。(c) L3 のまま: 中雑音で 5.20 の取りこぼし学習率・木の本数を変えると山の位置も動く(今日は lr 0.1・300本で固定)。実データで葉255 が端なら 511 を足す
15b(新設、印 △)min_child_samples は既定の 20 から上げないmcs50 は6組すべてで +8.7〜+24.7 悪化(a) mcs50 を一覧に入れ続ける(得 0・費用 303 秒)。(b) mcs を下げる(5・10)方向は未計測雑音が大きく行数が少ないデータ(実データで行数が1桁少ない場合)では逆になりうる。そこでは mcs 10/20/50 を測り直す
13(補正CV、印を分割)葉127 まで ◎、葉255 以上 ○中雑音の葉511 で生 +42.29→補正 +3.23(約1SE)◎ のまま(低雑音 −4.78±1.76 の兆しを無視することになる)残り31ワールドを回して、葉255 以上の補正の残りが 2SE を超え続けるなら、A_test の推定誤差(fold 5個で分散を取る)を疑う
14(休眠のまま)乗り換え m=2決定15(L4/L8)のあとも乗り換え 0 回(m=0/2 で損が同じ)—乱数ありの候補を葉 255 の上で作り直したら測り直す

📊 結果の可視化

3種類の世界の最悪の超過損(全19本の真の最良との損、×10⁻⁴、短いほど良い)9ワールドの途中集計。L4 と L8 は損が同じ(511・mcs50 は1回も選ばれない)。決定14 の乗り換えは0回決定15 一覧 L4/L8(31〜255 以上)0.74常に leaf2554.75決定15 一覧 L3(31/63/127、Day175)5.20常に leaf255_m5019.25
葉の数ごとの真の差(T5 − T5(lgb_det=葉31)、×10⁻⁴、左へ長いほど良い)括弧はワールド数。中雑音・小得は葉255 が山、低雑音は葉63〜127 が山。葉511 はどの世界でも山を越えている葉63葉127葉255葉5110 = 葉31低雑音(3)-3.18-2.27+2.48+10.30小得(1)-12.41-23.77-26.54-18.88中雑音(5)-15.56-31.42-36.62-28.93
min_child_samples 20→50 の真の差(×10⁻⁴、右へ長いほど悪化)低雑音(3)小得(1)中雑音(5)葉127+9.80+12.80+12.88葉255+11.05+17.50+19.25葉511+8.68+20.46+24.72

1枚目: 3世界での最悪の超過損。一覧に葉255 を足した L4 は 0.74 で、Day175 の L3(5.20)から中雑音の取りこぼしが消えた。L8(511・mcs50 も入れる)は L4 と同じ。2枚目: 葉の数ごとの真の差。中雑音・小得は葉255 が山で、葉511 は戻る。3枚目: min_child_samples=50 は6組すべてで悪化。いずれも9ワールドの途中集計(残り31ワールドは未計測)。

🪜 Step-by-Step 解説

Step 1: 作るものを最小にする

乱数ありの候補(Day172〜174)と葉31/63/127(Day175)は保存してある。今日作るのは新しい6本だけで、そのうち leaf127 は再現確認のための重複。

Step 2: 先に再現を確かめる

今日の leaf127 と Day175 の leaf127 を OOF・fold 別 test 予測で比べ、3世界とも最大差 0.0。ラベルも np.array_equal で assert。ここが崩れていたら、保存物と今日の予測を混ぜた表は全部意味を失う(決定9)。

Step 3: 予定を変えたことを記録する

1ワールドが同居環境で 6〜17 分かかり、40 ワールドは数時間になると分かった。9ワールドで打ち切り、どの世界が何ワールドかを結果の先頭に書いた(低3・小得1・中5)。小得は1ワールドなので SE が出ず、結論の根拠には使わない(方向の確認だけ)。

Step 4: 真の差を「隣どうし」で見る

lgb_det との差だけを見ると「葉511 も −28.93 で良い」と読めてしまう。隣どうし(255→511)の差 +7.69±0.75 を見て初めて、山を越えたことが分かる。

Step 5: 一覧を広げた損を「取りこぼし」と「選び間違い」に分ける

L3→L4 で中雑音の一覧内 oracle は 5.20→0(取りこぼしが消えた)、選び間違いは 0.74(1ワールドで leaf127 を選んだ)。L5・L8 は oracle も選択も変わらない。広げても悪くならなかったのは、足した候補(511・mcs50)が真に大きく悪く、補正CVでも選ばれなかったため。

Step 6: 費用を並べて止め方を決める

得の出ない候補に時間を払わない。「端が最良なら1本足す」は、今日の世界では L4 で止まり、511 は確認の1回だけで済む。

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

「山がある」とは。 写真の解像度に例えると、葉の数は画素数。画素が少なすぎると輪郭がぼやけ(拾えない規則がある)、多すぎると撮影時のノイズまで細かく写る。ちょうど良い画素数はノイズの量で決まる。低雑音ではノイズが少なく、もとから少ない画素で足りる…と思いきや、今日の結果では低雑音の山が手前(63〜127)、中雑音の山が奥(255)だった。これは「雑音が大きいほど、正解の形を細かく描かないと平均で損をする」ことを示し、直感と逆向き。理由として考えられるのは、雑音を足したことで目的変数のばらつきが増え、5 fold 平均(提出)が1本ごとの細かすぎる部分を打ち消してくれるから。この説明は仮説で、今日は分離していない。

生の偏りが葉とともに増える理由。 Day171 の恒等式: 生の CV² − 補正CV² = A_test(5本の予測のずれの大きさ)。葉が多い木ほど fold ごとの予測がずれる(A比 葉127 約2倍・葉511 約4倍)ので、1本だけで採点する生の CV は大きい木を不利に見せる。中雑音の葉511 で +42.29 は、真の差(−28.93)より大きい。生の CV だけを見ると「葉511 は葉31 より悪い」と真逆の判断になる。

🏆 Kaggleでの実践的な使い方

  • num_leaves は倍々(31→63→127→255)で並べ、一覧の端が勝ったら次の倍を1本足す。 真ん中が勝ったら止める。全部の格子を回すより速く、今日は得を1つも落とさなかった
  • num_leaves と min_child_samples を同じ「複雑さ」として扱わない。 葉を増やして mcs で抑える、という組み合わせは今日のデータでは両方の悪いところを取った(葉511_m50 は葉511 より +8.7〜+24.7 悪い)
  • 大きい木を比べるときこそ生の CV を使わない。 提出が fold 平均なら、生の CV は葉511 を最大 +42 悪く見せる。test 予測の fold 間分散を候補ごとに引く(決定13)
  • 時間の上限を先に決め、打ち切ったら件数を書く。 今日は 40→9 ワールド。どの数字が何ワールドからかを表の上に書いておけば、後から足しても混ざらない

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
葉511 は葉31 より良いので大きいほど良い基準との差だけを見る隣どうしで見ると 255→511 は +7.69 悪化。山を越えている
min_child_samples を上げれば大きい木の過学習が直る「正則化は大きいモデルの味方」という一般論今日のデータでは6組すべて悪化。細かい規則を拾えなくなる害の方が大きい
候補を増やすほど選択の上振れで損をする候補数 K の害だけを考える今日は足した候補が真に大きく悪く、補正CVでも選ばれなかったので損は増えなかった(L4→L8 で差 0)。害が出るのは、真に近い候補を多数足したとき
9ワールドの結果を40ワールドと同じ重さで扱う打ち切りを書かない小得は1ワールド(SE なし)。結論は中雑音5・低雑音3の方向のそろい方(5/5、3/3)だけに頼る
補正CV はどんな大きさの木でも偏りを消す中雑音で効いたことを一般化低雑音の葉511 で −4.78±1.76(効きすぎの兆し)。葉255 以上は印を ○ に下げて測り直す

🧭 扱わなかった範囲と理由

  • 残り31ワールド(未計測): 低雑音 w3〜15(13)、小得 w1〜11(11)、中雑音 w5〜11(7)。他の処理と同居した 8GB 機で1ワールド 6〜17 分かかり、全部回すと数時間かかるため打ち切った。とくに小得は1ワールドしかなく、表の小得の数字は SE が無い
  • 学習率・木の本数との組み合わせ: lr 0.1・300本で固定。山の位置は学習率を下げて本数を増やすと動くはず(未計測)
  • min_child_samples を下げる方向(5・10): 上げると悪いことは分かったが、下げると良いかは未計測
  • 補正CVの効きすぎの原因: A_test を fold 5個の分散で推定する誤差が、大きい木で増えているのではないかという仮説。未分離
  • 乱数ありの候補を葉255 で作り直すこと(決定14 の再検証): 持ち越し
  • 実データ(S5E5): 縮小シミュレータの上の数字のみ。S5E5 のルール確認・公開割合・敵対的AUC は Day171 で利用者の作業として切り出したまま
  • 持ち越し: 候補数 K と選択の上振れ、系統別の m(et)、公開LB と2本目の枠、avg3 での損、OOF 側と test 側の seed の揺れの分離、noKeytel・raw7 の補正後のずれ、補正CV と入れ子CV の比較、Day170 のハイブリッド・狭い10本の出し直し、SE 個数差と分割の新しさ(Day165)、Day162 の選択バイアス、真の効果ちょうど0の偽採用率、Nadeau & Bengio・Krogh & Vedelsby 原典、LightGBM 公式ドキュメントの原文照合

🚀 次のステップ

  • 発展: 残り31ワールドを、他の処理が少ない時間(または Mac mini 48GB)で回し、表1〜5 を40ワールドで出し直す。小得の SE と、補正CV の葉255 以上での効きすぎ(低雑音 −4.78)が残るかを確かめる
  • 発展: 学習率 0.05・600本で葉 63/127/255 を作り、山の位置が動くかを見る
  • 次回予告: Day 177 — Playground Series 実戦投入⑫(補正CV の効きすぎの原因: 大きい木で A_test の推定誤差が増えるかを、fold 数 5/10 と test 行数で分けて測る。あわせて未計測ワールドの追加分で表を更新)。月替わりテーマ3「画像(CV)系コンペへの応用」は 10/25 から

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

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