Day 174 — 2026-10-07 | Playground Series 実戦投入⑨ — 良い候補の得が小さい世界(2.47×10⁻⁴)でも、決定14 の余裕 m=2 の超過損は 0.14 で、3種類の世界での最悪は 0.18 のまま(据え置き)。ところが分離用に足した「葉63・抜き出しなし」が −12.72 で、m をどう決めるかより候補に何を入れるかの方が 60 倍効いていた

2026-10-07 金 / Phase 6(月替わりテーマ2: Playground Series 実戦投入) 分析→戦略 低雑音16+小得12+中雑音12ワールド · seed 2 · 1,440 fit · 1,156秒

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

Day173 のおさらい。 2 種類の世界で余裕 m を測った。

世界雑音(元/合成)本当に良い候補m を上げると
低雑音(Day172、16 ワールド)0.05 / 0.03ほぼ無し外れの損が減る(m=0: 1.12 → m≥5: 0.33)
中雑音(Day173、12 ワールド)0.15 / 0.10leaf63_b7、得 6.51見逃しの損が増える(m≤2: 0 → m=3: 0.53)

2 つを混ぜる割合 p を知らないときの「最悪の超過損」が最小の m=2 を採った(0.18)。ただし弱点が2つ残った。

  1. 得が小さい良い候補への弱さを、仮想でしか見ていない。 Day173 の追加2 は「中雑音の 24 件の余裕と得を同じ量だけ下げる」という導出で、得 3 なら m=2 の損 0.65・見逃し 6/24 と出した。補正CV のずれの分布が得の大きさと無関係だと置いている
  2. leaf63_b7 がなぜ勝ったか分かっていない。 葉 63(表現力)と抜き出し 0.7(平均で丸める)のどちらが効いたか、候補が無いので分けられなかった

今日はこの2つを実測する。雑音を中間の 0.12/0.08 に置き、leaf63_b7 の得が 2〜3 になる世界を作る。分離のため、葉 63・抜き出しなし(leaf63、乱数なし) と 葉 31・抜き出し 0.7(b7) を候補の末尾に足す。m を決める集計は Day173 とそろえるため元の 11 本だけで行い、足した2本は分離(表6)と感度(表7)にだけ使う。

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

用語意味
小得ワールド今日の 12 ワールド(雑音 0.12/0.08)。raw174_w*.npz。生成器・ワールドの乱数(3000+番号)・候補 0〜10 は Day172・173 と同じ
2×2 の分離葉 {31, 63} × 抜き出し {なし, 0.7} の 4 本: lgb_det・b7・leaf63・leaf63_b7。交互作用 = leaf63_b7 − leaf63 − b7(どれも lgb_det との差)
選択の上振れ補正CV最良(lgb_det 以外の 10 本から最小を選んだもの)の余裕 adv − 真の得 gain。10 本から一番良く見えるものを選ぶので、見かけが真より良く出やすい(勝者の呪い)

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

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

名前形・型意味・作り方使う場所
Sex … Body_Temp, Dur_x_HR, Keytel_kcalint / float(9列)Day163〜173 と同じ合成。Calories の雑音だけ 0.05/0.03(低)・0.12/0.08(小得)・0.15/0.10(中)全候補
raw172_w0..15.npzoof (11,3,30000)・evk (11,3,5,80000)低雑音 16 ワールド(Day172 の保存物)。seed 組 s0・s1 だけ使う外れの損
raw173_w0..11.npzoof (11,2,30000)・evk (11,2,5,80000)中雑音 12 ワールド(Day173 の保存物)見逃しの損
raw174_w0..11.npzoof (13,2,30000)・evk (13,2,5,80000)小得 12 ワールド(今日、1,440 fit)。候補 0〜10 は同じ、11=leaf63・12=b7見逃し・2×2 の分離
adv / gain / regretfloat(×10⁻⁴)adv=補正CV の余裕、gain=真の得、regret=選んだ1本の T5 − 候補中の最良表2〜5

評価指標(RMSLE)と、今日の損の測り方

RMSLE は log1p 空間の RMSE。「損」は選んだ1本の RMSLE が候補中の最良からどれだけ悪いか(×10⁻⁴)。下の図は3種類の世界での m ごとの損。小得(青)は中雑音(金)と同じ向きに上がるが、傾きがゆるく、m=0.5〜2 の差は 0.14 しかない。

余裕 m ごとの損(regret、×10⁻⁴、実測)灰=低雑音(外れ)/青=小得(今日、得 2.47)/金=中雑音(見逃し)。緑の縦線=m=2(据え置き)0123401234568余裕 m(×10⁻⁴)中雑音 m=8: 4.12小得 m=8: 2.36低雑音 m=0: 1.12

❓ 問題

タスクA(予想)— コードを書く前に

  1. 小得ワールドで m=2 の損は、Day173 の仮想(得 3 で 0.65、得 2 で 0.55)と比べてどうなるか
  2. 3 種類の世界(低雑音・小得・中雑音)の最悪の超過損で、最小になる m は 2 のままか
  3. leaf63(葉 63・抜き出しなし)と b7(葉 31・抜き出し 0.7)は、lgb_det より良いか悪いか

タスクB(分析)

  1. day173.py の末尾に候補2本を足した day174.py を作り、雑音 0.05/0.03 で候補 0〜10 が Day172 の予測と完全一致することを確かめる(決定9)
  2. 小得(0.12/0.08)で 12 ワールド × model seed 2組を作り、低雑音・中雑音の保存物と合わせて m=0〜8 の損を出す。低雑音・中雑音の数字が Day173 と一致することも確かめる
  3. Day173 の追加2(仮想)の予測と、今日の実測を同じ得の大きさで並べる
  4. 2×2 で葉の数と抜き出しの効果を分ける

タスクC(戦略)

  1. 決定14 の m を変えるか。3 種類の世界の表から決め、理由を書け
  2. 表6・表7 の結果から、決定14 の「基準=lgb_det」という置き方自体をどう直すか

💡 ヒント

ヒント1(方向性)

  • 得が小さいと、余裕 adv の山(得 ± ずれ)が m の線に近づく。見逃しが増えるのは当然だが、ずれの SD も同じとは限らない
  • 2×2 は「4 本の差」から主効果と交互作用を出すだけ。足し算で説明できない分が交互作用

ヒント2(アプローチ)

  • 最悪の超過損: 期待損は混ぜる割合の一次式なので、最大は必ず「どれか1種類の世界だけ」の端で起きる → 各世界の超過損の最大を取ればよい
  • 仮想と実測を比べるときは、仮想の得を実測の平均得に合わせる

ヒント3(コード骨格)

L = G.groupby(["kind", "m"]).regret.mean().unstack("kind")
X = L - L.min()                          # 各世界の最良 m からの超過
X["最悪(3世界)"] = X[["低雑音", "小得", "中雑音"]].max(axis=1)
S = R.xs("小得").groupby("world").mean()  # R = T5 − T5(lgb_det)
inter = S["leaf63_b7"] - S["leaf63"] - S["b7"]

✅ 模範解答

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

#予想の模範実測
1仮想どおり 0.5〜0.65 前後ほぼ当たり。 小得の平均得は 1.93(中央値 2.15)。m=2 の実測損 0.67(仮想 0.54)。ただし m=3 は実測 0.78・見逃し 7 に対し仮想 1.27・見逃し 14 で、仮想は大きい m の見逃しを2倍に見積もっていた(追加の表)
2m を下げる方が良くなる(1 か 1.5)外れ。 3 世界の最悪の超過損は m=2.5 が 0.14、m=2 が 0.18、m=1 が 0.35。小得だけなら m=0.5 が最良(0.53)だが、m=2 との差は 0.14±0.07(ワールド対、表5)と小さく、低雑音での外れの損(m=1 で 0.35 超過)の方が重い
3両方とも少し良い半分外れ。 leaf63 は −12.72±0.29(12/12 ワールドで良い)、b7 は +5.79±0.27(全部悪い)。抜き出し 0.7 は葉 31 でも葉 63 でも悪くし、交互作用は +4.47±0.24。leaf63_b7 の −2.47 は「葉 63 の大きな得」から「抜き出しの損」を引いた残りだった

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

day174.py は day173.py から次の差分だけを変えた(他は Day173 の模範解答と同じ):

1,4c1,4
< """Day173: Day172 と同じ生成器・同じ候補11本で、目的変数の雑音だけを大きくしたワールドを作る(今日の本番は 0.15/0.10 の「中雑音」)。
< 狙い: bagging などの乱数ありの候補が、乱数なしの基準 lgb_det より本当に良くなる世界を作る。
< 使い方: OMP_NUM_THREADS=1 python day173.py <world> <noise_orig> <noise_syn> <n_seeds>  -> raw173_w<world>.npz
<   Day172 は noise_orig=0.05, noise_syn=0.03(このとき day172.py と同じ予測になる)
---
> """Day174: Day173 と同じ生成器・同じ候補11本に、分離用の2本(leaf63=葉63・抜き出しなし、b7=葉31・抜き出し0.7)を末尾に足す。
> 今日の本番は雑音 0.12/0.08 の「小得ワールド」(leaf63_b7 の真の得が 2〜3×1e-4 になる狙い)。
> 使い方: OMP_NUM_THREADS=1 python day174.py <world> <noise_orig> <noise_syn> <n_seeds>  -> raw174_w<world>.npz
>   候補 0..10 は day173.py と同じ順・同じ乱数(雑音 0.05/0.03 なら Day172 の予測と一致するはず)
72c72,74
<          ("et", dict(extra_trees=True))]
---
>          ("et", dict(extra_trees=True)),
>          ("leaf63", dict(num_leaves=63)),                    # Day174 追加: 葉の数だけ(乱数なし)
>          ("b7", dict(**B7))]                                 # Day174 追加: 抜き出しだけ(葉31)
92a95,96
>             if nm == "leaf63" and si > 0:                    # 乱数なしなので seed 0 の予測を写す(fit を節約)
>                 oof[j, si, va] = oof[j, 0, va]; evk[j, si, k] = evk[j, 0, k]; continue
98c102
< np.savez_compressed(f"raw173_w{W}.npz", oof=oof, evk=evk, ytr=train.Calories.values,
---
> np.savez_compressed(f"raw174_w{W}.npz", oof=oof, evk=evk, ytr=train.Calories.values,

repcheck.py(決定9 の再現テスト):

"""決定9 の再現テスト: day174.py(雑音 0.05/0.03・seed 1組)の候補 0..10 が Day172 の raw172_w0.npz の seed 0 と一致するか"""
import sys, numpy as np, hashlib
a = np.load(sys.argv[1]); b = np.load(sys.argv[2])
assert list(a["names"][:11]) == list(b["names"])
print("oof 最大差", np.abs(a["oof"][:11, 0] - b["oof"][:, 0]).max(), " evk 最大差", np.abs(a["evk"][:11, 0] - b["evk"][:, 0]).max(),
      " ytr 一致", (a["ytr"] == b["ytr"]).all(), " 追加2本", list(a["names"][11:]))

実行(run.sh は DYLD_LIBRARY_PATH に scikit-learn 同梱の libomp を入れ、day174.py <w> <雑音元> <雑音合成> <seed組数> を出力先ディレクトリで呼ぶだけ):

./run.sh 0 0.05 0.03 1 $PWD/rep                     # 再現テスト用のワールド0(seed 1組、193秒)
python repcheck.py rep/raw174_w0.npz <d172>/raw172_w0.npz   # Day172 との一致を確かめる
seq 0 11 | xargs -P 4 -I{} ./run.sh {} 0.12 0.08 2 $PWD/sm  # 小得 12 ワールドを 4 並行で作る(壁時計 1,156 秒)
python analyze174.py <d172> <d173/mid> sm           # 表1〜7(低雑音・中雑音は保存物を再利用)
python extra174.py <d173>/guard173.pkl              # Day173 追加2(仮想)と実測の照合

analyze174.py:

"""Day174 集計: 決定14(乱数なしの基準 lgb_det より補正CV が m×1e-4 以上良いときだけ乗り換える)の余裕 m を、
  低雑音(Day172 raw172_w0..15、良い候補なし)・中雑音(Day173 raw173 w0..11、得 約6.5)・
  小得(今日 raw174 w0..11、雑音 0.12/0.08)の3種類の世界で測る。seed 組は s0・s1。
  選ぶ候補の集まりは3種類とも Day173 と同じ11本(今日足した leaf63・b7 は表6 の分離と表7 の感度にだけ使う)。
使い方: python analyze174.py <d172のdir> <d173 midのdir> <d174 smのdir>
"""
import sys, glob
import numpy as np, pandas as pd

N = 20000
MS = [0, 0.5, 1, 1.5, 2, 2.5, 3, 4, 5, 6, 8]
POOL11 = ["hgb_default", "lgb_det", "bag08", "bag05", "ff05", "bag05_ff05", "bag03_ff05",
          "leaf63_b7", "leaf15_b7", "lr05_it600_b7", "et"]
KINDS = ("低雑音", "小得", "中雑音")


def sle(y, lp):
    return (np.log1p(np.clip(np.expm1(lp), 1, None)) - np.log1p(y)) ** 2


def load(pattern, kind):
    rows = []
    for f in sorted(glob.glob(pattern), key=lambda f: int(f.split("_w")[1].split(".")[0])):
        w = int(f.split("_w")[1].split(".")[0])
        z = np.load(f); O, E, ytr, yev, names = z["oof"], z["evk"].astype(np.float64), z["ytr"], z["yev"], list(z["names"])
        assert names[:11] == POOL11                                            # 決定9: 名前と行の対応
        ytru = yev[N:]
        for s in (0, 1):
            for j, n in enumerate(names):
                c = sle(ytr, O[j, s]).mean(); a = E[j, s, :, :N].var(axis=0).mean()
                t5 = sle(ytru, E[j, s].mean(0)[N:]).mean()
                rows.append(dict(kind=kind, world=w, st=f"s{s}", cand=n, C=np.sqrt(c), Ca=np.sqrt(c - a), T5=np.sqrt(t5)))
    return pd.DataFrame(rows)


D = pd.concat([load(sys.argv[1] + "/raw172_w*.npz", "低雑音"), load(sys.argv[2] + "/raw173_w*.npz", "中雑音"),
               load(sys.argv[3] + "/raw174_w*.npz", "小得")])
print({k: D[D.kind == k].world.nunique() for k in KINDS})

print("\n=== 表1 真の差 T5 − T5(lgb_det)(×1e-4、seed 組 s0/s1 平均、ワールド平均±SE)と、lgb_det より真に良かった割合(ワールド×seed組) ===")
P = D.pivot_table(index=["kind", "world", "st"], columns="cand", values="T5")
R = P.sub(P["lgb_det"], axis=0) * 1e4
g = R.groupby(["kind", "world"]).mean()
t = g.groupby("kind").agg(lambda s: f"{s.mean():+.2f}±{s.std(ddof=1)/np.sqrt(s.notna().sum()):.2f}" if s.notna().any() else "-").T[list(KINDS)]
for k in KINDS: t[k + " 良"] = (R.xs(k) < 0).mean().round(2)
print(t.to_string())
Q = D.pivot_table(index=["kind", "world", "st"], columns="cand", values="Ca")
RC = Q.sub(Q["lgb_det"], axis=0) * 1e4
print("\n補正CV の lgb_det との差 − 真の差 の SD(×1e-4)")
print((RC - R).groupby("kind").std(ddof=1).round(2).T[list(KINDS)].to_string())


def guard(D, pool):
    rows = []
    for (kind, w, st), h in D[D.cand.isin(pool)].groupby(["kind", "world", "st"]):
        h = h.set_index("cand"); b = h.Ca.drop("lgb_det").idxmin()
        adv = (h.Ca["lgb_det"] - h.Ca[b]) * 1e4
        gain = (h.T5["lgb_det"] - h.T5[b]) * 1e4
        best = h.T5.min()
        for m in MS + ["常にlgb_det"]:
            sw = (m != "常にlgb_det") and adv > m
            pick = b if sw else "lgb_det"
            rows.append(dict(kind=kind, world=w, st=st, m=str(m), regret=(h.T5[pick] - best) * 1e4, switch=sw,
                             good=sw and gain > 0, bad=sw and gain <= 0,
                             harm=-gain if (sw and gain <= 0) else 0.0, miss=gain if (not sw and adv > 0 and gain > 0) else 0.0,
                             adv=adv, gain=gain, b=b, bestc=h.T5.idxmin()))
    return pd.DataFrame(rows)


G = guard(D, POOL11)
order = [str(m) for m in MS] + ["常にlgb_det"]
print("\n=== 表2 余裕 m ごとの損(regret、×1e-4、ワールド平均±SE)と乗り換えの内訳 ===")
for kind in KINDS:
    out = []
    for m in order:
        x = G[(G.kind == kind) & (G.m == m)]; wv = x.groupby("world").regret.mean()
        out.append(dict(m=m, regret=f"{wv.mean():.2f}±{wv.std(ddof=1)/np.sqrt(len(wv)):.2f}", 乗り換え=int(x.switch.sum()),
                        正しい=int(x.good.sum()), 誤り=int(x.bad.sum()), 外れの損=round(x.harm.mean(), 2),
                        見逃しの損=round(x.miss.mean(), 2), 止めた良い候補=int((x.miss > 0).sum()), n=len(x)))
    print(f"[{kind}]"); print(pd.DataFrame(out).set_index("m").to_string())

print("\n=== 表3 余裕(adv)と真の得(gain):補正CV最良(lgb_det 以外)の時点 ===")
H = G[G.m == "0"]
for kind in KINDS:
    x = H[H.kind == kind]
    print(f"[{kind}] 選ばれた {x.b.value_counts().to_dict()}  真の最良 {x.bestc.value_counts().to_dict()}  adv 中央値 {x.adv.median():.2f}  gain 中央値 {x.gain.median():.2f}")
    for lo, hi in [(-99, 0), (0, 1), (1, 2), (2, 3), (3, 5), (5, 99)]:
        y = x[(x.adv > lo) & (x.adv <= hi)]
        if len(y): print(f"   adv ({lo},{hi}]: n={len(y):2d}  真に良い {int((y.gain>0).sum()):2d}  gain 平均 {y.gain.mean():+.2f}")

print("\n=== 表4 各世界での超過損(その世界の最良 m との差)と、3世界の最悪の超過損(混ぜ方を知らないとき)===")
L = G.groupby(["kind", "m"]).regret.mean().unstack("kind")[list(KINDS)].loc[order]
X = L - L.min()
X["最悪(低・中の2世界)"] = X[["低雑音", "中雑音"]].max(axis=1)
X["最悪(3世界)"] = X[list(KINDS)].max(axis=1)
print(L.round(2).add_prefix("損 ").join(X.round(2).add_prefix("超過 ")).to_string())

print("\n=== 表5 ワールド対の差(×1e-4、平均±SE)===")
for kind in KINDS:
    W = G[G.kind == kind].groupby(["world", "m"]).regret.mean().unstack()
    for a, b in [("2", "0"), ("2", "1"), ("3", "2"), ("常にlgb_det", "2")]:
        d = W[a] - W[b]; print(f"[{kind}] m={a} − m={b}: {d.mean():+.2f}±{d.std(ddof=1)/np.sqrt(len(d)):.2f}")

print("\n=== 表6 葉の数と抜き出しの分離(小得ワールドのみ、真の差 T5 − T5(lgb_det)、×1e-4、ワールド平均±SE)===")
S = R.xs("小得").groupby("world").mean()
for c in ["leaf63", "b7", "leaf63_b7"]:
    print(f"  {c:10s} {S[c].mean():+.2f}±{S[c].std(ddof=1)/np.sqrt(len(S)):.2f}   良い割合 {(R.xs('小得')[c] < 0).mean():.2f}")
inter = S["leaf63_b7"] - S["leaf63"] - S["b7"]
print(f"  交互作用 leaf63_b7 − leaf63 − b7 = {inter.mean():+.2f}±{inter.std(ddof=1)/np.sqrt(len(S)):.2f}")
ds = (RC - R).xs("小得")
print(f"  補正CV ずれ SD: leaf63 {ds['leaf63'].std(ddof=1):.2f}  b7 {ds['b7'].std(ddof=1):.2f}")

print("\n=== 表7 感度: 小得ワールドで候補を13本(leaf63・b7 も選べる)にしたときの m ごとの損 ===")
G13 = guard(D[D.kind == "小得"], POOL11 + ["leaf63", "b7"])
x = G13.groupby("m").regret.mean().loc[order]
print(x.round(2).to_string())
print("選ばれた", G13[G13.m == "0"].b.value_counts().to_dict(), " 真の最良", G13[G13.m == "0"].bestc.value_counts().to_dict())
G.to_pickle("guard174.pkl"); D.to_pickle("dec174.pkl")

extra174.py:

"""Day174 追加: Day173 の追加2(中雑音の得を縮めた仮想)が、今日の小得ワールドの実測を当てたか。
仮想の得は今日の実測の平均得(選ばれた候補の gain 平均)に合わせる。使い方: python extra174.py <guard173.pkl>"""
import sys, numpy as np, pandas as pd
G = pd.read_pickle("guard174.pkl"); G3 = pd.read_pickle(sys.argv[1])
S = G[(G.kind == "小得") & (G.m == "0")]
g = S.gain.mean(); print(f"小得ワールドの選ばれた候補の真の得 平均 {g:.2f}(中央値 {S.gain.median():.2f})、n={len(S)}")
H = G3[(G3.kind == "中雑音") & (G3.m == "0")]
d = H.gain.mean() - g; adv, gain = H.adv - d, H.gain - d
rows = []
for m in [0, 1, 2, 3, 4]:
    sw = adv > m; loss = np.where(sw, np.where(gain > 0, 0, -gain), np.where(gain > 0, gain, 0))
    x = G[(G.kind == "小得") & (G.m == str(m))]
    rows.append(dict(m=m, 仮想_損=round(loss.mean(), 2), 仮想_見逃し=int((~sw & (gain > 0)).sum()), 仮想_誤り=int((sw & (gain <= 0)).sum()),
                     実測_損=round(x.regret.mean(), 2), 実測_見逃し=int(((~x.switch) & (x.gain > 0)).sum()), 実測_誤り=int(x.bad.sum()), n=len(x)))
print(pd.DataFrame(rows).set_index("m").to_string())

実行結果(実測)

# ============ 実行環境: macOS (M1, メモリ8GB)・Python 3.13.12・scikit-learn 1.9.1・numpy 2.5.3・pandas 3.0.6・lightgbm 4.7.0(Day172 と同じ uv venv)
#   小得: 12ワールド × 5 fold × (HistGB 1 + LightGBM 乱数あり 11本 × seed 2 + leaf63 1) = 1,440 fit。OMP_NUM_THREADS=1・n_jobs=1
#   壁時計: 4 並行で 1,156 秒(1ワールド 325〜467 秒、平均 374 秒)。再現テストを1本並行で回した(193 秒)
# 再現テスト(雑音 0.05/0.03・seed 1組・ワールド0):
oof 最大差 0.0  evk 最大差 0.0  ytr 一致 True  追加2本 ['leaf63', 'b7']
# ワールド0 の途中確認(本番を止めずに見た): leaf63_b7 −3.19/−2.15(seed組 s0/s1)、leaf63 −12.41 → 狙いの「得 2〜3」に入った

{'低雑音': 16, '小得': 12, '中雑音': 12}

=== 表1 真の差 T5 − T5(lgb_det)(×1e-4、seed 組 s0/s1 平均、ワールド平均±SE)と、lgb_det より真に良かった割合(ワールド×seed組) ===
kind                   低雑音           小得          中雑音  低雑音 良  小得 良  中雑音 良
cand                                                                    
b7                       -   +5.79±0.27            -   0.00  0.00   0.00
bag03_ff05     +15.52±0.46  +16.85±0.34  +17.49±0.34   0.00  0.00   0.00
bag05           +6.55±0.39   +6.07±0.38   +4.81±0.35   0.00  0.00   0.00
bag05_ff05     +11.06±0.28  +12.25±0.31  +12.13±0.28   0.00  0.00   0.00
bag08           +1.09±0.29   -0.13±0.21   -1.33±0.32   0.19  0.50   0.83
et             +11.08±0.70  +14.64±0.74  +17.17±0.68   0.00  0.00   0.00
ff05            +4.50±0.34   +5.76±0.28   +6.94±0.32   0.00  0.00   0.00
hgb_default     +0.35±0.23   -0.07±0.18   -0.11±0.28   0.44  0.67   0.50
leaf15_b7       +9.53±0.38  +15.02±0.38  +18.15±0.33   0.00  0.00   0.00
leaf63                   -  -12.72±0.29            -   0.00  1.00   0.00
leaf63_b7       +5.44±0.33   -2.47±0.37   -6.51±0.45   0.00  0.96   1.00
lgb_det         +0.00±0.00   +0.00±0.00   +0.00±0.00   0.00  0.00   0.00
lr05_it600_b7   +4.45±0.35   +4.49±0.25   +4.48±0.27   0.00  0.00   0.00

補正CV の lgb_det との差 − 真の差 の SD(×1e-4)
kind            低雑音    小得   中雑音
cand                           
b7              NaN  2.28   NaN
bag03_ff05     4.20  3.54  4.29
bag05          3.29  3.09  3.33
bag05_ff05     3.55  2.03  3.23
bag08          3.16  2.39  2.81
et             5.61  5.54  5.94
ff05           3.08  2.92  2.61
hgb_default    2.97  2.42  2.17
leaf15_b7      3.53  2.31  2.79
leaf63          NaN  3.46   NaN
leaf63_b7      4.13  2.94  3.62
lgb_det        0.00  0.00  0.00
lr05_it600_b7  3.60  1.82  2.90

=== 表2 余裕 m ごとの損(regret、×1e-4、ワールド平均±SE)と乗り換えの内訳 ===
[低雑音]
              regret  乗り換え  正しい  誤り  外れの損  見逃しの損  止めた良い候補   n
m                                                            
0          1.12±0.42    16    2  14  0.81   0.00        0  32
0.5        1.09±0.42    15    2  13  0.78   0.00        0  32
1          0.68±0.16    13    2  11  0.36   0.00        0  32
1.5        0.65±0.16    12    2  10  0.34   0.00        0  32
2          0.51±0.14     7    1   6  0.19   0.01        1  32
2.5        0.47±0.14     6    1   5  0.15   0.01        1  32
3          0.47±0.14     6    1   5  0.15   0.01        1  32
4          0.36±0.12     3    1   2  0.04   0.01        1  32
5          0.33±0.11     0    0   0  0.00   0.01        2  32
6          0.33±0.11     0    0   0  0.00   0.01        2  32
8          0.33±0.11     0    0   0  0.00   0.01        2  32
常にlgb_det  0.33±0.11     0    0   0  0.00   0.01        2  32
[小得]
              regret  乗り換え  正しい  誤り  外れの損  見逃しの損  止めた良い候補   n
m                                                            
0          0.55±0.26    23   20   3  0.10   0.00        0  24
0.5        0.53±0.26    22   20   2  0.07   0.00        0  24
1          0.61±0.26    20   18   2  0.07   0.08        2  24
1.5        0.63±0.27    18   16   2  0.07   0.10        4  24
2          0.67±0.28    15   14   1  0.03   0.18        6  24
2.5        0.67±0.28    15   14   1  0.03   0.18        6  24
3          0.78±0.31    14   13   1  0.03   0.30        7  24
4          0.98±0.35    12   11   1  0.03   0.49        9  24
5          1.77±0.29     5    5   0  0.00   1.32       15  24
6          2.26±0.38     2    2   0  0.00   1.80       18  24
8          2.36±0.37     1    1   0  0.00   1.90       19  24
常にlgb_det  2.49±0.35     0    0   0  0.00   2.03       20  24
[中雑音]
              regret  乗り換え  正しい  誤り  外れの損  見逃しの損  止めた良い候補   n
m                                                            
0          0.00±0.00    24   24   0   0.0   0.00        0  24
0.5        0.00±0.00    24   24   0   0.0   0.00        0  24
1          0.00±0.00    24   24   0   0.0   0.00        0  24
1.5        0.00±0.00    24   24   0   0.0   0.00        0  24
2          0.00±0.00    24   24   0   0.0   0.00        0  24
2.5        0.00±0.00    24   24   0   0.0   0.00        0  24
3          0.53±0.36    22   22   0   0.0   0.53        2  24
4          0.53±0.36    22   22   0   0.0   0.53        2  24
5          1.27±0.56    19   19   0   0.0   1.27        5  24
6          1.72±0.64    17   17   0   0.0   1.72        7  24
8          4.12±0.99     9    9   0   0.0   4.12       15  24
常にlgb_det  6.51±0.45     0    0   0   0.0   6.51       24  24

=== 表3 余裕(adv)と真の得(gain):補正CV最良(lgb_det 以外)の時点 ===
[低雑音] 選ばれた {np.str_('bag08'): 16, np.str_('hgb_default'): 15, np.str_('et'): 1}  真の最良 {np.str_('lgb_det'): 15, np.str_('hgb_default'): 12, np.str_('bag08'): 5}  adv 中央値 0.11  gain 中央値 -0.87
   adv (-99,0]: n=16  真に良い  6  gain 平均 -0.61
   adv (0,1]: n= 3  真に良い  0  gain 平均 -4.74
   adv (1,2]: n= 6  真に良い  1  gain 平均 -0.89
   adv (2,3]: n= 1  真に良い  0  gain 平均 -1.24
   adv (3,5]: n= 6  真に良い  1  gain 平均 -0.76
[小得] 選ばれた {np.str_('leaf63_b7'): 19, np.str_('bag08'): 3, np.str_('hgb_default'): 2}  真の最良 {np.str_('leaf63_b7'): 23, np.str_('lgb_det'): 1}  adv 中央値 3.84  gain 中央値 2.15
   adv (-99,0]: n= 1  真に良い  0  gain 平均 -0.22
   adv (0,1]: n= 3  真に良い  2  gain 平均 +0.49
   adv (1,2]: n= 5  真に良い  4  gain 平均 +0.27
   adv (2,3]: n= 1  真に良い  1  gain 平均 +2.75
   adv (3,5]: n= 9  真に良い  8  gain 平均 +2.64
   adv (5,99]: n= 5  真に良い  5  gain 平均 +3.45
[中雑音] 選ばれた {np.str_('leaf63_b7'): 24}  真の最良 {np.str_('leaf63_b7'): 24}  adv 中央値 7.07  gain 中央値 6.49
   adv (2,3]: n= 2  真に良い  2  gain 平均 +6.31
   adv (3,5]: n= 3  真に良い  3  gain 平均 +5.95
   adv (5,99]: n=19  真に良い 19  gain 平均 +6.61

=== 表4 各世界での超過損(その世界の最良 m との差)と、3世界の最悪の超過損(混ぜ方を知らないとき)===
kind       損 低雑音  損 小得  損 中雑音  超過 低雑音  超過 小得  超過 中雑音  超過 最悪(低・中の2世界)  超過 最悪(3世界)
m                                                                               
0           1.12  0.55   0.00    0.79   0.02    0.00            0.79        0.79
0.5         1.09  0.53   0.00    0.77   0.00    0.00            0.77        0.77
1           0.68  0.61   0.00    0.35   0.08    0.00            0.35        0.35
1.5         0.65  0.63   0.00    0.33   0.10    0.00            0.33        0.33
2           0.51  0.67   0.00    0.18   0.14    0.00            0.18        0.18
2.5         0.47  0.67   0.00    0.14   0.14    0.00            0.14        0.14
3           0.47  0.78   0.53    0.14   0.25    0.53            0.53        0.53
4           0.36  0.98   0.53    0.03   0.45    0.53            0.53        0.53
5           0.33  1.77   1.27    0.00   1.24    1.27            1.27        1.27
6           0.33  2.26   1.72    0.00   1.73    1.72            1.72        1.73
8           0.33  2.36   4.12    0.00   1.83    4.12            4.12        4.12
常にlgb_det   0.33  2.49   6.51    0.00   1.96    6.51            6.51        6.51

=== 表5 ワールド対の差(×1e-4、平均±SE)===
[低雑音] m=2 − m=0: -0.61±0.43
[低雑音] m=2 − m=1: -0.17±0.07
[低雑音] m=3 − m=2: -0.04±0.04
[低雑音] m=常にlgb_det − m=2: -0.18±0.11
[小得] m=2 − m=0: +0.12±0.07
[小得] m=2 − m=1: +0.06±0.03
[小得] m=3 − m=2: +0.11±0.11
[小得] m=常にlgb_det − m=2: +1.82±0.47
[中雑音] m=2 − m=0: +0.00±0.00
[中雑音] m=2 − m=1: +0.00±0.00
[中雑音] m=3 − m=2: +0.53±0.36
[中雑音] m=常にlgb_det − m=2: +6.51±0.45

=== 表6 葉の数と抜き出しの分離(小得ワールドのみ、真の差 T5 − T5(lgb_det)、×1e-4、ワールド平均±SE)===
  leaf63     -12.72±0.29   良い割合 1.00
  b7         +5.79±0.27   良い割合 0.00
  leaf63_b7  -2.47±0.37   良い割合 0.96
  交互作用 leaf63_b7 − leaf63 − b7 = +4.47±0.24
  補正CV ずれ SD: leaf63 3.46  b7 2.28

=== 表7 感度: 小得ワールドで候補を13本(leaf63・b7 も選べる)にしたときの m ごとの損 ===
m
0             0.00
0.5           0.00
1             0.00
1.5           0.00
2             0.00
2.5           0.00
3             0.00
4             0.00
5             0.00
6             0.00
8             0.95
常にlgb_det    12.72
選ばれた {np.str_('leaf63'): 24}  真の最良 {np.str_('leaf63'): 24}

# ---- extra174.py(Day173 追加2 の仮想 vs 今日の実測)----
小得ワールドの選ばれた候補の真の得 平均 1.93(中央値 2.15)、n=24
   仮想_損  仮想_見逃し  仮想_誤り  実測_損  実測_見逃し  実測_誤り   n
m                                              
0  0.33       4      2  0.55       0      3  24
1  0.40       7      2  0.61       2      2  24
2  0.54       8      2  0.67       6      1  24
3  1.27      14      1  0.78       7      1  24
4  1.29      15      1  0.98       9      1  24

# ---- 選択の上振れ(adv − gain、×1e-4、補正CV最良を選んだ時点)----
低雑音 adv-gain 平均 1.43 SD 3.46
小得 adv-gain 平均 1.51 SD 1.98
中雑音 adv-gain 平均 1.23 SD 3.62

読み方:

  • 表2 の低雑音・中雑音は Day173 の表2 と全行一致(m=0 低 1.12・m=3 中 0.53 など)。保存物の読み直しと集計が変わっていない証拠
  • 表1: 小得では leaf63_b7 が −2.47±0.37(ワールド×seed 組の 96% で良い)。狙った「得 2〜3」に入った。bag08 は −0.13 で五分五分
  • 表2 [小得]: m を上げると見逃しが増える(m=2 で 6/24、m=3 で 7/24、m=5 で 15/24)。m=0 でも誤りは 3/24(損 0.10)だけ。最良は m=0.5 の 0.53、m=2 は 0.67
  • 表3 [小得]: 補正CV最良が leaf63_b7 だったのは 19/24。残り 5 件は bag08・hgb_default で、真の得は ±0.5 程度。真の最良は 23/24 が leaf63_b7
  • 表4: 3 世界の最悪の超過損は m=2.5 が 0.14、m=2 が 0.18。差 0.04 は低雑音ワールドの1件(余裕が 2〜2.5 の帯の誤り)だけで決まっている
  • 表6: 葉の数の得 −12.72、抜き出しの損 +5.79、交互作用 +4.47。抜き出し 0.7 は、このデータでは単独でも組み合わせでも悪い
  • 表7: 候補に leaf63 を入れると、補正CV は 24/24 回 leaf63 を選び、それが真の最良。m=6 まで損 0、常に lgb_det だと 12.72 の損
  • 選択の上振れ: 3 種類の世界とも補正CV最良の余裕は真の得より平均 1.2〜1.5 大きい。m≈2 は、この上振れ(約 1.4)を相殺する線として働いている(後付けの解釈、事前に予想していない)

タスクC(戦略の模範)

  1. m=2 を据え置く。 3 世界の最悪の超過損で m=2(0.18)と m=2.5(0.14)の差は低雑音の1件で決まる差で、ワールド対の SE(0.04 前後)と同じ桁。下げる方向(m=1: 0.35、m=0.5: 0.77)はどちらも悪い。小得だけを見れば m=0.5 が最良だが、m=2 との差は 0.14±0.07 で、低雑音で増える外れの損(m=0.5 で +0.58)の方が大きい。Day173 の仮想が示した「得が小さい問題では m を下げる」は、実測では採るほどの差にならなかった
  2. 基準を固定しない。 表6・表7 で、m の調整で動く損(0.1〜0.2)より、候補に leaf63 があるかどうか(12.72)の方が約 60 倍大きい。基準 lgb_det(葉 31)は小得ワールドでは表現力不足の候補で、それを基準にした「乗り換えの余裕」は、悪い基準を守る仕組みになっていた。直し方は、乱数なしの候補(葉の数などの構造の違い)を先に補正CV で比べて基準を決め、そのあとで乱数ありの候補との乗り換えに m=2 を使う。乱数なしの候補どうしは seed の揺れが無いので、決定13 の補正CV だけで比べられる(表7 で 24/24 正しい)

タスクD(決定)

#決定採った案採らなかった案と理由前提が変わったら
14(据え置き)基準からの乗り換え基準より補正CV が 2×10⁻⁴ 以上良い乱数ありの候補だけを1本目に採る(Day173 と同じ)。3 世界での最悪の超過損 0.18 を根拠に添える(a) m=2.5(最悪の超過損 0.14 で表上は最小だが、m=2 との差は低雑音の1件で決まる)。(b) m=0.5(小得だけなら最良 0.53、低雑音で 0.77 超過)。(c) 世界ごとに m を変える(実戦ではどの世界か分からない)低雑音のような「良い候補が無い」問題が多いと分かったら m=2.5〜3 も同じくらい良い。系統の違う候補(et は 3 世界ともずれの SD が最大 5.5〜5.9)が最良になる問題では、その候補だけ m を上げる(未測定)
15(新設)基準の選び方基準は lgb_det に固定しない。乱数なしの候補(今は葉 31・63)を補正CV で比べ、良い方を基準にする。そのあと決定14 で乱数ありの候補と比べる(a) lgb_det 固定のまま(小得で 12.72 の損を見逃す)。(b) 乱数なし・ありを全部まとめて補正CV最良を採る(表7 では同じ結果だが、乱数ありは seed の揺れで余裕が上振れする=Day172 の外れ。乱数なしどうしなら揺れが無い)。(c) 葉の数を Optuna で探す(Day165 で探索の楽観を測った。候補2〜3本の比較で足りるうちは探索しない)低雑音で leaf63 が lgb_det より悪い(未測定、leaf63_b7 は +5.44)なら、決定15 は「どちらかを選ぶ」仕組みとして効くかを低雑音で確かめる必要がある。印は △

設計書の印の更新:

決定前後根拠
13(補正CV で比べる)◎◎小得でも真の最良を 23/24 の世界で leaf63_b7 と当て、13 本にすると 24/24 で leaf63
14(乗り換えに余裕 m=2)○○3 世界での最悪の超過損 0.18(低雑音で決まる)。◎ にしないのは、系統の違う候補が良い世界と実データを測っていないため
15(基準を乱数なし候補から選ぶ)なし△小得ワールドだけの証拠(表6・表7)。低雑音・中雑音で leaf63 を作っていない

📊 結果の可視化

3種類の世界の混ぜ方を知らないときの最悪の超過損(×10⁻⁴、短いほど良い)m≤2.5 は低雑音で、m≥3 は中雑音で最悪になる。小得はどの m でも最悪にならないm=00.79m=0.50.77m=10.35m=1.50.33m=20.18m=2.50.14m=30.53m=40.53m=51.27m=84.12m=常に基準6.51
葉の数 × 抜き出しの 2×2(小得ワールド、真の差 T5 − T5(lgb_det)、×10⁻⁴、左=本当に良い)12ワールド平均。足し算なら両方で −6.93 のはずが実測 −2.47 → 交互作用 +4.47±0.24leaf63(葉63・なし)-12.72leaf63_b7(葉63・0.7)-2.47b7(葉31・0.7)+5.79lgb_det(葉31・なし)+0.00抜き出し 0.7 は葉31でも葉63でも悪化。効いていたのは葉の数だけ(Day173 の推測「平均で丸めるのが効く」は外れ)
小得ワールドで動いた損の大きさ(×10⁻⁴)m=2 → 最良 m=0.50.14m=2 → 常に lgb_det1.82候補に leaf63 を入れる12.72

上: 3種類の世界での最悪の超過損。m=2 は 0.18(m=2.5 は 0.14、差は低雑音の1件で決まる)→ 決定14 は据え置き。中: 2×2 の分離。効いていたのは葉の数だけ。下: m の調整で動く損と、候補を1本足して動く損の大きさの比較(約60倍)→ 決定15 新設。

🪜 Step-by-Step 解説

Step 1: 雑音の大きさを決める

Day173 のパイロットでは、leaf63_b7 の真の差が雑音 0.05/0.03 で +5.44、0.15/0.10 で −6.51。この2点を直線で結ぶと、差 −2.5 は雑音(元)約 0.116 の位置。合成側も同じ比(1.5 倍)で 0.12/0.08 にした。別のパイロットは回さず、本番のワールド0 が終わった時点で −3.19/−2.15 を確かめて続けた(外れていたら止めるつもりだった)。

Step 2: 候補を足しても再現が崩れないことを確かめる

足す2本は末尾に置いた。LightGBM の乱数は random_state=1000*s+k で候補ごとに独立なので、末尾に足しても候補 0〜10 の予測は変わらないはず。雑音 0.05/0.03 で回し、Day172 と fold 別予測まで最大差 0.0 を確認した。

Step 3: 3 種類の世界を同じ集計に通す

低雑音・中雑音は作り直さず保存物を読み直し、まず Day173 の表2 と全行一致することを確かめた。そのうえで小得を足した。こうすると、表の差は「世界の違い」だけから来ると言える。

Step 4: 仮想と実測を並べる

Day173 の追加2 を、今日の平均得 1.93 に合わせて計算し直して並べた。m≤2 では仮想と実測の損が 0.1〜0.2 の差で合ったが、m=3 以上では仮想の見逃しが実測の 2 倍。小得ワールドでは補正CV のずれの SD が中雑音より小さい(leaf63_b7 で 2.94 対 3.62)ので、余裕の山が狭く、m の線を越えにくかった。仮想は「ずれは同じ」という置き方の分だけ悲観的だった。

Step 5: 2×2 で分ける

lgb_det・b7・leaf63・leaf63_b7 の 4 本で、葉の数の効果(−12.72)、抜き出しの効果(+5.79)、交互作用(+4.47)を出した。Day173 で「雑音があるときは平均で丸める仕組みが効く」と推測したが、少なくとも小得ワールドでは逆で、効いていたのは葉の数だけだった。

Step 6: 決定を書き直す

m の調整で動く損(0.1〜0.2)と、候補を1本足すだけで動く損(12.72)を並べ、決定15 を新設した。証拠は小得ワールドだけなので印は △ にした。

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

2×2 の分け方(料理の例え)。 塩(葉の数)とレモン(抜き出し)を、入れる/入れないの 4 通りで味見する。

抜き出しなし抜き出し 0.7
葉 31lgb_det(基準 0)b7 +5.79
葉 63leaf63 −12.72leaf63_b7 −2.47

足し算だけなら両方入れると −12.72 + 5.79 = −6.93 のはず。実際は −2.47 なので、4.47 だけ「組み合わせると余計に悪い」。これが交互作用。塩を足して良くなった料理に、レモンをかけると単独のとき以上に台無しになった、という形。

選択の上振れ(勝者の呪い)。 10 本の候補の補正CV には、それぞれ ±3 くらいのずれがある。一番良く見える1本を選ぶと、「本当に良い」に加えて「たまたま良く見えた」分も一緒に選んでしまう。今日は 3 種類の世界とも、選んだ1本の余裕が真の得より平均 1.2〜1.5 大きかった。m=2 はこの上振れをほぼ打ち消す高さに当たる。候補を増やすほど上振れは大きくなるので、候補数が変わると m も変わるはず(K 依存は未測定)。

🏆 Kaggleでの実践的な使い方

  • しきい値を詰める前に、候補の一覧を疑う。 今日、m の調整で動いた損は 0.1〜0.2、候補を1本足して動いた損は 12.72。Playground Series の上位解法でも、勝因は「何を候補に入れたか」(特徴量・モデルの系統)であることが多く、選び方の微調整ではない
  • 基準モデルは一度だけ決めて終わりにしない。 基準(lgb_det)が表現力不足だと、「乗り換えの余裕」は悪い基準を守る仕組みになる。葉の数・深さなど乱数の無い構造の違いを2〜3本並べて、補正CV で基準を選び直す
  • bagging・列抽出は「雑音が多ければ効く」とは限らない。 今日の小得ワールドでは、抜き出し 0.7 は葉 31 でも葉 63 でも悪かった(+5.79、交互作用 +4.47)。他人のノートブックの subsample=0.7, colsample_bytree=0.7 を既定値として持ち込まない
  • 「仮想で詰めた数字」は、実測で置き換える予定を書いておく。 Day173 の仮想は m≥3 の見逃しを 2 倍に見積もっていた

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
得が小さい問題では m を下げるべき見逃しの損だけを見る小得だけなら m=0.5 が最良だが、m=2 との差は 0.14±0.07。低雑音での外れの増加(m=0.5 で +0.58)の方が大きい
Day173 で効いた leaf63_b7 は抜き出しのおかげ「雑音には平均で丸める」という一般論小得では抜き出しは単独で +5.79、組み合わせでも +4.47 余計に悪い。効いたのは葉の数(−12.72)
仮想の計算は実測の代わりになる同じ形の表が出るので区別しなくなる仮想は「ずれの分布は変わらない」と置いた分だけ悲観的で、m=3 の見逃しを 14 対 7 と 2 倍に見積もった
基準モデルは固定しておく方が安全基準を動かすと比べる軸がぶれると考える表現力不足の基準を固定すると、どの m でも良い候補を逃す(常に lgb_det で 12.72)
補正CV最良の余裕はそのまま得の見積もりに使える補正CV で偏りを消したので「当たっている」と思う一番良く見えるものを選んだ時点で上振れが入る(平均 +1.2〜1.5)。補正CV は平均の偏りを消すが、選択の上振れは消さない

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

  • 低雑音・中雑音での leaf63・b7: 保存物に無い。作り直すと 28 ワールドで約 45 分かかるため、今日は小得だけで分離した。決定15 の印を △ にした理由。次回の最優先
  • 葉の数の上限(127・255)と min_child_samples: 葉 63 で −12.72 なら、さらに増やす方向も効く可能性がある。今日は 2×2 だけ
  • 候補数 K と選択の上振れの関係: 上振れ 1.2〜1.5 は候補 10 本のときの値。K を変えた測定は無い
  • 系統別の m(et): et のずれの SD は 3 世界とも最大(5.5〜5.9)だが、et が補正CV最良になったのは低雑音の1件だけ
  • 公開LB と 2 本目の枠、avg3(3 seed 平均)での損: 今日も測っていない(seed 2 組)
  • 実データ(S5E5): 縮小シミュレータの上の数字のみ。S5E5 のルール確認・公開割合・敵対的AUC は Day171 で利用者の作業として切り出したまま
  • 持ち越し(Day173 から): OOF 側と test 側の seed の揺れの分離、noKeytel・raw7 の補正後のずれ、補正CV と入れ子CV の比較、A_test の推定誤差、K > 13、Day170 のハイブリッド・狭い10本の出し直し、SE 個数差と分割の新しさ(Day165)、Day162 の選択バイアス、真の効果ちょうど0の偽採用率、Nadeau & Bengio・Krogh & Vedelsby 原典(今日も記憶による)

🚀 次のステップ

  • 発展: 低雑音(16)・中雑音(12)ワールドに乱数なしの leaf63・leaf127 を足し、決定15(基準を乱数なしの候補から補正CV で選ぶ)を 3 種類の世界で測る。低雑音で葉を増やすと悪くなるなら、決定15 の「選ぶ」部分が本当に効くかが分かる
  • 発展: 候補数 K を 5/10/20 に変え、選択の上振れ(adv − gain)と最適な m が K とともにどう動くかを測る
  • 次回予告: Day 175 — Playground Series 実戦投入⑩(決定15 を低雑音・小得・中雑音の 3 種類で確かめ、乱数なしの候補の葉の数 31/63/127 から基準を選ぶ損を測る)。月替わりテーマ3「画像(CV)系コンペへの応用」は 10/25 から

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

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