Day 180 — 2026-10-13 | Playground Series 実戦投入⑮ — 消えた分のやり直し(40ワールド)。決定15 が葉255 を選ぶ小得・中雑音でも5本平均の山は600本(9/12・11/12)で、決定19(葉255 以上は本数を補正CVで選ぶ)は +0.12/+0.06 と得が無く撤回。決定18(600本で作り直す)は3世界とも決定15 より −0.46〜−1.19(2SE 超)で印 ◎。Day179 の「補正CV は本数の比較で +2.4〜+5.3 偏る」は2ワールドの偶然で、16ワールドでは −0.4〜+0.9(×10⁻⁴)

2026-10-13 金 / Phase 6(月替わりテーマ2: Playground Series 実戦投入) コーディング→戦略 40ワールド・2,000 fit・壁時計18分(M5 Pro・12並行)

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

ここまでの決め方(CV設計書の該当部分)。

決定中身印(今日の前)
13候補は補正CV √(C² − A_test) で比べる。C は生の OOF RMSLE、A_test は test 2万行での5本の予測の分散(ラベル不要)葉511 まで ◎
15lr0.1・300本の乱数なし LightGBM 葉 31/63/127/255 を補正CV で比べ、端の255 が最良なら 511 を1本足す◎
18決定15 で選んだ葉を lr0.05・600本で作り直した1本を最終提出にする○
19作り直しの葉が255 以上なら、本数を 200〜600 から補正CV 最小で選ぶ(Day179、低雑音2ワールドだけ)△

Day179 で何が起きたか。 18ワールドを起動したが、低雑音 w0・w1 が終わった後に /private/tmp が OS 側で丸ごと片付けられ、Day175〜179 の保存物と venv が消えた。決定19 は「決定15 が葉255 を選ぶ世界(小得・中雑音)」で測れておらず、本題は未計測のまま残った。Day179 はほかに「補正CV は本数の比較で少ない本数を悪く見せる(+2.4〜+5.3)」とも書いたが、これも2ワールドだけの値で、SE を出していなかった。

今日の作り方。 保存先を ~/AI/lab/学習/Engineer/DataScience/Kaggle/work/d180/ に移し、新しい uv venv(Python 3.13.16・lightgbm 4.7.0・scikit-learn 1.9.1・numpy 2.5.4・pandas 3.0.6)を作った。マシンも M1(8GB)から M5 Pro(15コア・48GB)に変わった。1ワールドで lr0.1・300本の葉5種と lr0.05・600本の葉5種を作り、lr0.05 は木の塊ごとに予測を足して途中本数12点(50〜600本)の OOF と 5本平均を作る(Day179 と同じ方法)。

マシンと環境が変わったので、最初に再現を確かめる。 Day178 の表(同じ w0-5 の18ワールド)と Day179 の表(低雑音 w0・w1)は数値が公開されているので、今日の生成でそれを小数2桁まで出し直せれば、生成器・分割・LightGBM の結果がマシンをまたいで同じだと言える(決定9)。

用語整理:

用語意味
leaf31〜leaf511学習率 0.1・300本の乱数なし LightGBM(決定15 の葉選びに使う)
s31〜s511学習率 0.05・最大600本(決定18 の作り直しに使う)
途中本数 n50, 100, …, 600 の12点。学習し直さず start_iteration で塊ごとに足して作る
T55本平均の truth 60,000行 RMSLE(真)
損方針で選んだ1本の T5 − その世界の lr0.05 の「5葉×12本=60点」の T5 の最良
項1・項2・項3Ca² − T5² = (C² − T1²) + (A_truth − A_test) + (T1² − T5² − A_truth) の3つ(Day177 の恒等式)。T1 は1 fold モデルの truth RMSLE の平均

数値はすべて縮小シミュレータの上の実測(S5E5 の構造を写した合成データ、Day163〜)。

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

名前形・型意味・作り方使う場所
Sex … Body_Temp, Dur_x_HR, Keytel_kcalint / float(9列)Day163〜179 と同じ合成(ワールドの乱数 3000+番号)。低雑音(Calories の雑音 0.05/0.03)16・小得(0.12/0.08)12・中雑音(0.15/0.10)12全候補
oof1 / ev1(5, 30000) / (5, 5, 80000) float32lr0.1・300本の葉31〜511 の OOF と fold 別予測(test 2万+truth 6万)決定15 の葉選び・再現
oof5(5, 12, 30000) float32lr0.05 の葉5種 × 途中本数12点の OOF(塊ごとに加算)生CV・補正CV
S5 / S1(5, 12, 4) / (5, 4)T5²・T1²・A_test・A_truth(3項分解に要る量)表1〜5
vcurve(5, 5, 600)fold ごとの val l2 を1本ごと表6(早期打ち切りの止まる本数)
保存先work/d180/r/{low,sm,mid}/raw180_w*.npz(計 809MB)tmp の外(Day179 の消失を受けて)次回以降

評価指標(RMSLE)と、今日の問い

RMSLE は log1p 空間の RMSE(小さいほど良い)。差はすべて ×10⁻⁴。下の図は、提出と同じ5本平均で本数を変えたときの真の誤差。決定15 が実際に選ぶ葉255(実線)は小得・中雑音で600本が最良。600本より良い本数があるのは葉511(破線)だけで、その葉は一度も選ばれない。

5本平均の真の RMSLE − 同じ葉の600本(×10⁻⁴、下ほど良い、40ワールド平均)実線=葉255(決定15 が小得・中雑音で選ぶ葉)、破線=葉511(一度も選ばれない)。上端 +20 で切った-5+0+5+10+15+20100本200本300本400本500本600本━ 低雑音 葉255(山 400本)━ 小得 葉255(山 600本)━ 中雑音 葉255(山 600本)╍ 低雑音 葉511(山 200本)╍ 小得 葉511(山 300本)╍ 中雑音 葉511(山 400本)

❓ 問題

タスクA(予想を確かめる)

Day179 は測る前にこう書いていた(Day179 タスクC・決定表)。今日の結果で当たり外れを判定する。

  1. 決定15 が葉255 を選ぶ小得・中雑音で、本数を補正CV で選ぶと 600本固定より −0.5 以上得する(そうなら決定18 の「600本」を「葉255 以上は補正CV で選ぶ」に替える)
  2. 補正CV は本数の比較で少ない本数を悪く見せる偏りがある(+2.4〜+5.3、低雑音2ワールド)
  3. 5本平均の山は1 fold の曲線の山より後ろにある

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

  1. 生成部を Day175 と1文字も変えずに、1ワールドで lr0.1 の葉5種と lr0.05 の葉5種×途中本数12点を作る day180.py を書く。保存先は tmp の外
  2. Day178 表1・表2・表5 と Day179 表1・表3・表5 の公開値を出し直し、マシンと venv が変わっても結果が同じかを確かめる
  3. analyze180.py で、本数曲線・決定15 の葉・葉ごとの本数選びの規則・本数方向の CV の偏りの3項分解・方針全体の損・早期打ち切りの止まる本数・費用を出す

タスクC(戦略)

  1. 決定18 の「600本」を替えるか。決定19 を残すか
  2. Day179 の「補正CV は本数の比較で偏る」を CV設計書に書き足すべきか
  3. 2ワールドの暫定値から何を学ぶか(どんな書き方なら防げたか)

💡 ヒント

ヒント1(方向性)

葉ごとの表(その葉で本数を選ぶと得か)と、方針の表(決定15 が実際にその葉を選ぶか)を分ける。得が出る葉が、実際には選ばれないことがある。

ヒント2(アプローチ)

1ワールドは独立なので、ワールドを1ジョブにして xargs -P で並べる。OMP_NUM_THREADS=1・n_jobs=1 にすれば結果はスレッド数に依存しない。macOS の LightGBM wheel は libomp を探すので、scikit-learn 同梱の sklearn/.dylibs を DYLD_LIBRARY_PATH に入れる。

ヒント3(コード骨格)

for k, (tr, va) in enumerate(KFold(5, shuffle=True, random_state=0).split(train)):
    for j, L in enumerate(LEAVES):
        m = lgb.LGBMRegressor(**LG1, num_leaves=L, random_state=k).fit(X, y)          # 決定15 用
        oof1[j, va] = m.predict(XV); ev1[j, k] = m.predict(XE)
        m = lgb.LGBMRegressor(**LG5, num_leaves=L, random_state=k).fit(X, y, eval_set=[(XV, yv)], ...)
        a = 0
        for i, b in enumerate(IT):                                                       # 塊ごとに足す
            sv += m.predict(XV, start_iteration=a, num_iteration=b - a, raw_score=True)
            ...

✅ 模範解答

タスクA(判定)

  1. 外れ。 決定15 が葉255 を選んだのは小得 9/12・中雑音 11/12 ワールド。その葉255 の5本平均の山は小得 9/12・中雑音 11/12 で 600本(表1)。補正CV で本数を選ぶと 575〜600本を選び、決定18 より小得 +0.12±0.09・中雑音 +0.06±0.06 とわずかに悪い(表5)。得が出るのは葉511(−2.8〜−4.9)だけで、決定15 は40ワールドで一度も葉511 を選ばなかった
  2. 外れ(2ワールドの偶然)。 今日の生成で Day179 の w0・w1 を出し直すと +2.38/+4.14/+5.27/+2.55/+3.04 が小数2桁まで一致した(計算は正しかった)。16ワールドに増やすと補正CV の偏りは −0.42〜+0.11、小得・中雑音を合わせた15組すべてが 2SE 未満(最大 小得 葉63 +0.87±0.81)。ワールドごとの値の SD は約3.0〜3.4 で、w0・w1 は葉63 で16ワールド中の上位2つ(+4.3・+4.0)だった
  3. 当たり(強まった)。 1 fold の早期打ち切り(忍耐∞)が選ぶ本数の中央値は、中雑音 葉255 で393本、5本平均の山は600本(表6)

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

day180.py(生成部 keytel_rate・make_original・synth_from・add_feats・BASE は Day175 と1文字も同じなので省略。全文は work/d180/day180.py):

LG1 = dict(objective="regression", learning_rate=0.1, n_estimators=300, min_child_samples=20, n_jobs=1, verbose=-1)
LG5 = dict(objective="regression", learning_rate=0.05, n_estimators=600, min_child_samples=20, n_jobs=1, verbose=-1)
LEAVES = [31, 63, 127, 255, 511]
IT = list(range(50, 601, 50))
nM, nI = len(LEAVES), len(IT)
# ...生成(seed 3000+W、雑音 NO/NS)、train 3万行・evalset 8万行(先頭2万 = test、残り6万 = truth)...

def lp_clip(lp):  # 提出は 1kcal 以上に丸める(Day163〜)
    return np.log1p(np.clip(np.expm1(lp), 1, None))

oof1 = np.zeros((nM, N_TRAIN)); ev1 = np.zeros((nM, 5, N_TEST + N_TRUTH), np.float32)
oof5 = np.zeros((nM, nI, N_TRAIN), np.float32)
ev5 = np.zeros((nM, nI, 5, N_TEST + N_TRUTH))              # 集計だけ残す(全部保存すると1ワールド 190MB)
vcurve = np.zeros((nM, 5, 600))
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
    yv = np.log1p(train.Calories.values[va])
    for j, L in enumerate(LEAVES):
        m = lgb.LGBMRegressor(**LG1, num_leaves=L, random_state=k).fit(X, y)   # Day175/176 と同じ random_state
        oof1[j, va] = m.predict(XV); ev1[j, k] = m.predict(XE)
        rec = {}
        m = lgb.LGBMRegressor(**LG5, num_leaves=L, random_state=k).fit(
            X, y, eval_set=[(XV, yv)], eval_metric="l2", callbacks=[lgb.record_evaluation(rec)])
        vcurve[j, k] = rec["valid_0"]["l2"]
        sv, se_ = np.zeros(len(va)), np.zeros(len(XE)); a = 0
        for i, b in enumerate(IT):
            sv += m.predict(XV, start_iteration=a, num_iteration=b - a, raw_score=True)
            se_ += m.predict(XE, start_iteration=a, num_iteration=b - a, raw_score=True)
            oof5[j, i, va] = sv; ev5[j, i, k] = se_; a = b
        if k == 0:
            rep[j] = np.abs(m.predict(XE) - se_).max()     # 塊の和 対 一括予測

def agg(E):  # (5, N_TEST+N_TRUTH) -> T5², T1², A_test, A_truth(Day177 の3項分解に要る量)
    t5 = ((lp_clip(E[:, N_TEST:].mean(0)) - ltru) ** 2).mean()
    t1 = ((lp_clip(E[:, N_TEST:]) - ltru) ** 2).mean()
    return t5, t1, E[:, :N_TEST].var(axis=0).mean(), E[:, N_TEST:].var(axis=0).mean()

S5 = np.array([[agg(ev5[j, i]) for i in range(nI)] for j in range(nM)])     # (葉, 本数, 4)
S1 = np.array([agg(ev1[j].astype(np.float64)) for j in range(nM)])
np.savez_compressed(f"{OUT}/raw180_w{W}.npz", oof1=oof1, ev1=ev1, oof5=oof5, ev600=ev5[:, -1].astype(np.float32),
                    S5=S5, S1=S1, vcurve=vcurve, ...)

run.sh と実行:

# run.sh: 1ワールドを1スレッドで作る。scikit-learn 同梱の libomp を LightGBM に使わせる
export OMP_NUM_THREADS=1 DYLD_LIBRARY_PATH="$PWD/.venv/lib/python3.13/site-packages/sklearn/.dylibs"
.venv/bin/python day180.py "$1" "$2" "$3" "r/$4" > "log/$4_w$1.log" 2>&1
# 1回目: 低雑音 w0(再現確認を先に)→ 小得・中雑音 w0-5 → 低雑音 w1-5 の18ワールドを12並行
xargs -P 12 -L 1 ./run.sh < jobs.txt
# 2回目: 小得・中雑音 w6-11、低雑音 w6-15 の22ワールドを12並行(Day177 と同じ 16/12/12 の40ワールドにそろえる)
xargs -P 12 -L 1 ./run.sh < jobs2.txt
# 集計
.venv/bin/python analyze180.py r

analyze180.py(要点。全文は work/d180/analyze180.py):

def pick15(g1):                                   # 決定15: lr0.1 の補正CV、L4+端なら511
    ca = g1.set_index("leaf").Ca
    L = ca[[31, 63, 127, 255]].idxmin()
    if L == 255: L = ca[LEAVES].idxmin()
    return L

def choose(gl, col, lo=50):                       # 本数を CV(Ca か C)の最小で選ぶ
    gg = gl[gl.it >= lo]; return gg.loc[gg[col].idxmin()]

# 方針(1ワールド)。損は lr0.05 の60点の真の最良との差
d19 = choose(gl, "Ca", 200).T5 if L >= 255 else gl[gl.it == 600].T5.iloc[0]
# 3項分解: Ca−T5 = (項1 + 項2 + 項3) / (Ca+T5) を 200本と600本で引く(恒等式なので和は必ず一致)
g["e1"] = (g.c2 - g.t1sq) / den; g["e2"] = (g.atr - g.ate) / den; g["e3"] = (g.t1sq - g.t5sq - g.atr) / den

実行結果(実測)

# ============ 実行環境: macOS・Apple M5 Pro(15コア・48GB)・uv venv(Python 3.13.16・lightgbm 4.7.0・scikit-learn 1.9.1・
#   numpy 2.5.4・pandas 3.0.6)。OMP_NUM_THREADS=1・n_jobs=1、12並行
#   40ワールド(低雑音16・小得12・中雑音12)× 10本(lr0.1 葉5種+lr0.05 葉5種)× 5 fold = 2,000 fit
#   壁時計 1回目 18ワールド 9分07秒(05:39:38→05:48:45)、2回目 22ワールド 9分01秒。1ワールド 230〜296秒
#   保存物 r/{low,sm,mid}/raw180_w*.npz 計 809MB(work/d180、tmp の外)
ワールド数: {'中雑音': 12, '低雑音': 16, '小得': 12}
塊の和 対 一括予測(fold 0、最大絶対差): 2.9e-14

=== 表0 再現(マシン・venv が変わっても同じか)===
Day178 表1・表2(w0-5 の18ワールド)を出し直した → 42個(表1 の27個・表2 の15個)が小数2桁まで全部一致
  例 [中雑音] lr0.05: s255 -38.50±0.58  s511 -30.16±1.61   lr0.05−lr0.1: 葉255 -1.62±0.24
     [低雑音] lr0.1 : leaf63 -3.36±0.49  leaf511 +9.40±0.79
Day178 表5(w0-5): 決定15 低 2.57・小得 0.91・中 2.23、作り直し 1.52・0.62・0.78、作り直し−決定15 -1.05±0.38/-0.29±0.27/-1.46±0.33 → 全部一致
Day179(低雑音 w0・w1): 表1 の25個(例 葉511 +3.86 -4.80 -4.32 -2.93 -1.44)・表3 の10個(補正CV +2.38 … +3.04、生CV -2.68 … -6.77)・
  表5 の決定18 の損 1.46・決定15 の葉 w0=63/w1=127 → 全部一致

=== 表1 5本平均の真の RMSLE の本数曲線(同じ葉の600本との差、×1e-4、ワールド平均)と、600本が最良のワールド数 ===
[低雑音 n=16]   100     200     300     400     500    600   600本が最良  最良本数の範囲
  葉31        +22.00  +10.55   +6.16   +3.31   +1.39   0.00   16/16
  葉63        +19.07   +8.79   +4.83   +2.41   +0.97   0.00   16/16
  葉127       +16.21   +5.90   +2.70   +1.11   +0.31   0.00   11/16        500〜600
  葉255       +11.30   +1.24   -0.61   -0.92   -0.63   0.00    0/16        300〜500
  葉511        +3.29   -5.17   -4.44   -3.04   -1.48   0.00    0/16        200〜300
[小得 n=12]
  葉31〜127   すべて 600本が最良(12/12)。200本で +17.95〜+20.03
  葉255       +33.45  +12.79   +5.43   +2.05   +0.48   0.00    9/12        550〜600
  葉511       +20.69   +0.94   -2.64   -2.64   -1.61   0.00    0/12        300〜400
[中雑音 n=12]
  葉31〜127   すべて 600本が最良(12/12)
  葉255       +43.07  +17.77   +8.07   +3.28   +0.96   0.00   11/12        550〜600
  葉511       +27.86   +3.15   -2.18   -2.88   -1.77   0.00    0/12        350〜450

=== 表2 決定15(lr0.1 の補正CV、L4+端なら511)が選んだ葉(ワールド数)と、その葉の5本平均で600本が最良だった数 ===
  [低雑音] {31: 1, 63: 13, 127: 2}   600本が最良 16/16(600本固定の損 平均 0.00)
  [小得]   {127: 3, 255: 9}          600本が最良  9/12(同 0.03、最大 0.24)
  [中雑音] {127: 1, 255: 11}         600本が最良 11/12(同 0.002、最大 0.03)
  → 40ワールドで葉511 は一度も選ばれない

=== 表3 葉ごとに本数を選ぶ規則(600本固定との対の差±SE、×1e-4、[ ]は選んだ本数の中央値)===
                      葉31          葉63          葉127         葉255          葉511
[低雑音] 補正CV(200-600) +0.12±0.07   +0.22±0.15   +0.41±0.24   -0.52±0.29[375] -4.85±0.29[200]
         生CV(50-600)    +1.21±0.45   +2.34±0.63   +2.77±0.50   +1.01±0.43[200] -3.70±0.70[150]
[小得]   補正CV(200-600)  0.00         0.00         0.00        +0.16±0.10[600] -2.78±0.19[350]
         生CV(50-600)     0.00         0.00        +0.34±0.14   +3.62±0.66[350] -0.35±0.48[225]
[中雑音] 補正CV(200-600)  0.00         0.00         0.00        +0.14±0.10[600] -2.80±0.27[375]
         生CV(50-600)     0.00         0.00        +0.22±0.15   +4.16±0.76[400] -0.44±0.61[250]
(補正CV の 50-600 は 200-600 と葉511 低雑音以外同じ。低雑音 葉511 は -4.72±0.33)

=== 表4 本数方向の CV の偏り: (CV[200]−CV[600]) − (真[200]−真[600])(×1e-4、平均±SE)と補正CV の3項分解 ===
           補正CV                                   生CV
           葉31       葉63       葉127      葉255      葉511       葉31〜葉511
[低雑音]  +0.11±0.70 +0.05±0.83 -0.01±0.85 -0.28±0.76 -0.42±0.85   -5.01 -6.51 -7.90 -9.22 -10.14(すべて SE 0.7〜0.9)
[小得]    +0.51±0.61 +0.87±0.81 +0.25±0.70 +0.35±1.02 +0.66±0.89   -4.65 -6.60 -9.80 -11.87 -12.38
[中雑音]  +0.46±0.70 +0.32±0.79 +0.41±0.70 -0.00±1.10 +0.41±0.87   -5.17 -8.01 -11.11 -13.92 -14.52
3項分解(補正CV の偏りの内訳): 項1(OOF 行と truth 行の違い)がほぼ全部。項2 は |平均| ≤ 0.09、項3 は ≤ 0.01
補正CV の偏りのワールドごとの SD(低雑音): 葉63 3.31・葉127 3.38・葉255 3.03
  低雑音 葉63 の16ワールドの値: +4.3 +4.0 +0.4 +0.8 -6.5 +0.4 +0.6 +1.6 +3.3 -0.7 -6.9 +3.7 -1.0 +0.7 -2.9 -1.0(先頭2つが Day179 の w0・w1)

=== 表5 方針全体の損(×1e-4、対 その世界の lr0.05 の60点の真の最良)と決定18 との対の差(平均±SE、良くなった/悪くなったワールド数)===
  方針                                              低雑音                小得                  中雑音               最悪
  決定15 のまま(lr0.1・300本)                     1.64 (+0.56±0.23) 4/12  1.33 (+0.46±0.23) 6/6   1.58 (+1.19±0.21) 0/12  1.64
  決定18(葉は決定15、lr0.05・600本)               1.08                    0.87                    0.39                    1.08
  決定19(葉255以上なら本数200-600を補正CVで)      1.08 (+0.00) 0/0        0.99 (+0.12±0.09) 1/3   0.45 (+0.06±0.06) 0/1   1.08
  決定18+本数を補正CVで(50-600、全部の葉)          1.33 (+0.25±0.15) 0/5   0.99 (+0.12±0.09) 1/3   0.45 (+0.06±0.06) 0/1   1.33
  決定18+本数を生CVで(50-600)                      3.49 (+2.41±0.62) 0/14  3.51 (+2.64±0.74) 0/10  3.63 (+3.24±0.52) 0/11  3.63
  葉も本数も補正CVで(60点)                          0.84 (-0.24±0.45) 6/3   0.82 (-0.04±0.35) 2/3   0.45 (+0.06±0.06) 0/1   0.84
  常に s255・600本                                  5.00 (+3.92±0.55) 1/15  0.03 (-0.84±0.44) 3/0   0.00 (-0.39±0.39) 1/0   5.00

=== 表6 fold ごとの早期打ち切りが止まる本数(val l2 の記録から再現、fold×ワールドの中央値[最小-最大])と 5本平均の山(中央値)===
            忍耐50              忍耐∞   5本平均の山
[低雑音] 葉63   281[107-600]   461     600
         葉255  189[92-388]    208     400
         葉511  157[90-319]    162     200
[小得]   葉127  408[183-600]   551     600
         葉255  304[189-587]   376     600
[中雑音] 葉127  494[272-600]   571     600
         葉255  348[210-533]   393     600
         葉511  239[174-368]   244     400

=== 表7 費用(1ワールド5fold、中央値秒。M5 Pro・12並行)===
         lr0.1 学習 / 予測    lr0.05 学習 / 予測(途中本数12点込み)
  葉31      1.1 / 2.8            2.4 / 5.5
  葉63      1.4 / 4.3            3.3 / 8.6
  葉127     2.1 / 7.6            5.0 / 15.7
  葉255     3.4 / 15.8           8.4 / 34.7
  葉511     6.5 / 41.5          16.3 / 78.3
  (Day178 の M1・同居環境では lr0.05 の s255 が 147秒、s511 が 343秒。今日は 43秒・95秒)

タスクC(戦略の模範)

  1. 決定18 の「600本」は替えない。決定19 は撤回する。 本数選びの得が出るのは葉511 だけ(3世界で −2.8〜−4.9、40/40 で山が600本より前)。しかし決定15 は40ワールドで一度も葉511 を選ばない(表2)。実際に選ばれる葉255 の5本平均の山は、小得・中雑音では 9/12・11/12 で600本で、補正CV で本数を選ぶと +0.12±0.09・+0.06±0.06 とわずかに損(表5)。決定18 は3世界とも決定15 のまま提出するより良く(低 −0.56±0.23・小得 −0.46±0.23・中 −1.19±0.21、40ワールド)、600本固定の損は選ばれた葉の12点の最良に対して平均 0.00〜0.03。決定18 を ○ → ◎、決定19 を △ → ✕(撤回)。ただし「決定15 が葉511 を選んだら、本数を 200〜600 から補正CV で選ぶ」だけは休眠の規則として残す(葉511 での得は40ワールドで確か)
  2. Day179 の「補正CV は本数の比較で偏る」は撤回し、逆の内容を書き足す。 40ワールドで補正CV の偏りは −0.42〜+0.87、15組すべて 2SE 未満。中身はほぼ全部が項1(OOF 行と truth 行の違い)で、項2・項3 は 0.1 以下(Day177 の葉の比較と同じ構造)。決定13 は「本数の比較(200 対 600本)」にも使える(○)。一方、生CV は本数を減らすほど良く見えすぎる(−4.65〜−14.52、すべて 5SE 超)ので、本数を生CV で選ぶ方針は最悪 3.63 で一番悪い
  3. 2ワールドの値を書くときは SE とワールドごとの値を必ず添える。 Day179 の +2.4〜+5.3 は計算の誤りではなく(今日、小数2桁まで再現した)、16ワールドで上位2つの世界をたまたま引いたこと。ワールドごとの SD は約3.3 なので、2ワールドの平均の SE は約2.3。+4.14 は 1.8SE で、決定17(2SE 未満は決め手にしない)に照らせば最初から「判定不能」と書くべきだった。Day179 の表3 には SE が無かった。決定17 に「表に出す差には SE(世界ペア)を必ず並べる。SE が出せない2ワールド以下の値は『暫定』ではなく『判定不能』と書く」を足す

タスクD(決定)

#決定採った案採らなかった案と理由前提が変わったら
18(○ → ◎)最終提出は決定15 の葉を lr0.05・600本で作り直した1本40ワールドで決定15 のままより 低 −0.56±0.23・小得 −0.46±0.23・中 −1.19±0.21。選ばれた葉の5本平均は 36/40 で600本が最良、残り4つも損 ≤0.24(a) 葉も本数も60点から補正CV で選ぶ: 最悪 0.84 対 1.08 で良く見えるが、対の差は −0.24±0.45・−0.04±0.35・+0.06±0.06 で全部 2SE 未満。費用は lr0.05 を5葉ぶん作る(M5 Pro で1ワールド約180秒 対 決定18 の約130秒=lr0.1 の5葉 約87秒+選んだ1本)。(b) 常に s255: 低雑音で 5.00学習率を 0.02 以下にする、または本数の上限を変えるなら「600本」の位置を測り直す(今日の山は600本の端なので、もっと後ろに山がある可能性は残る)
19(△ → ✕、撤回。休眠の一部だけ残す)葉255 で本数を選ぶのはやめる。決定15 が葉511 を選んだときだけ 200〜600本から補正CV で選ぶ(休眠)葉255: 補正CV の本数選びは小得 +0.16±0.10・中 +0.14±0.10(悪化)。葉511: −2.78〜−4.85(40/40 で山が600本より前)葉255 以上すべてで選ぶ(Day179 の案): 決定15 が実際に選ぶのは葉255 で、その山は600本。得の出る葉511 は選ばれない決定15 が葉511 を選ぶ世界(雑音がさらに大きい・行数が多い)が出たら休眠を起こして測る
13(追記)補正CV は本数の比較(200 対 600本)にも使える(○)偏り −0.42〜+0.87、15組すべて 2SE 未満。項1 がほぼ全部生CV で本数を比べる: 少ない本数を −4.65〜−14.52 良く見せる50〜150本の比較は測っていない(200 対 600 だけ)
17(追記)表に出す差には世界ペアの SE を並べる。2ワールド以下の値は「判定不能」と書くDay179 の 2ワールドの偏り +4.14 は 1.8SE で、16ワールドでは +0.05「暫定値」と書いて決定を置く(Day179 のやり方): 撤回が要る決定(19)を1つ生んだ—

📊 結果の可視化

40ワールド(低雑音16・小得12・中雑音12)。Day178・179 の公開値を小数2桁まで再現したうえでの数字。
決定18(600本で作り直す)との対の差(×10⁻⁴、右ほど悪い、ひげ=±1SE、40ワールド)0 の線より左なら決定18 より良い。決定19 と60点同時はどれも 2SE 未満0-1+1+2+3+4決定19(葉255以上は本数を補正CVで)低雑音 +0.00±0.00小得 +0.12±0.09中雑音 +0.06±0.06葉も本数も補正CVで(60点)低雑音 -0.24±0.45小得 -0.04±0.35中雑音 +0.06±0.06決定15 のまま(lr0.1・300本)低雑音 +0.56±0.23小得 +0.46±0.23中雑音 +1.19±0.21本数を生CVで低雑音 +2.41±0.62小得 +2.64±0.74中雑音 +3.24±0.52
補正CV の本数方向の偏り(低雑音 葉63、×10⁻⁴): 16ワールドの1つずつ橙=Day179 が見た w0・w1(平均 +4.14)。16ワールドの平均は +0.05±0.83+4.3w0+4.0w1+0.4w2+0.8w3-6.5w4+0.4w5+0.6w6+1.6w7+3.3w8-0.7w9-6.9w10+3.7w11-1.0w12+0.7w13-2.9w14-1.0w152ワールド平均 +4.1416ワールド平均 +0.05

1枚目: 決定19 の得は 0〜+0.12(悪化側)で、決定18 を替える理由にならない。本数を生CV で選ぶと全世界で 2.4〜3.2 悪い。2枚目: Day179 が「補正CV は本数の比較で偏る」とした根拠は、16ワールドのうち上位2つを引いた偶然だった。

🪜 Step-by-Step 解説

Step 1: 保存先と環境を作り直す

work/d180/ に uv venv を作った。最初の import は libomp.dylib が見つからず失敗した(macOS の LightGBM wheel は Homebrew の libomp を探す)。Homebrew の libomp を入れる代わりに、scikit-learn 同梱の sklearn/.dylibs/libomp.dylib を DYLD_LIBRARY_PATH で渡した(Day177 と同じ方法、システムを変えない)。

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

低雑音 w0 を先頭に並べて起動し、Day178 表1・表2・表5 と Day179 の表を出し直した。マシン(M1 → M5 Pro)、Python(3.13.12 → 3.13.16)、pandas が変わっても小数2桁まで一致した。OMP_NUM_THREADS=1・n_jobs=1・乱数なしの設定にしておいた効果。これで、消えた保存物を今日の物で置き換えてよいと言える。

Step 3: 葉ごとの得と、選ばれる葉を分ける

表3 だけ見ると「葉511 で本数を選ぶと −4.85」と言いたくなる。表2 を見ると決定15 は葉511 を一度も選ばない。表5(方針全体)で決定19 は得0か少し損。Day179 と同じ「葉ごとの得 ≠ 方針の得」を、今度は本題の世界で確かめた。

Step 4: Day179 の偏りを撤回する

w0・w1 の値は再現したので計算は正しい。ワールドを16に増やすと平均は0付近に寄り、ワールドごとの値を並べると w0・w1 が上位2つだった。偶然の上振れを引いていた。

Step 5: 3項分解で補正CV の残りの中身を見る

本数方向でも、項2(A_truth − A_test、test 行と truth 行の分散の違い)と項3(切り詰め)は 0.1 以下で、残りは項1(OOF 行と truth 行の違い)だけ。項1 はワールドごとに ±3 揺れる、データの偶然の成分。

Step 6: 費用と止め方を記録する

40ワールドを18分で回せた(Day178 の M1 では18ワールドに約2時間)。保存物は 809MB を tmp の外に置いた。

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

なぜ2ワールドで +4 が出て、16ワールドで 0 になるのか。 ワールドごとの値は ±3 くらい揺れる(SD 3.3)。2つの平均の揺れは 3.3 ÷ √2 ≈ 2.3。+4 は「揺れ2つ分」に届かない。サイコロを2回振って両方5以上が出ても「このサイコロは大きい目が出やすい」とは言えないのと同じ。16回振ると平均は真ん中に寄る。

なぜ大きい葉だけ本数を減らすと得なのか。 葉が大きい木は1本でたくさん覚える。本数が増えると、データの偶然まで覚える段階に早く入る。葉511 は200〜400本でもう覚えすぎが始まり、葉255 以下は600本でもまだ「本当の形」を覚えている途中。5本平均にすると偶然の部分が打ち消し合うので、1本で見たときより山が後ろにずれる(表6: 1本の早期打ち切りは中雑音 葉255 で393本、5本平均の山は600本)。

生CV が少ない本数を良く見せる理由。 生CV は1本ずつの誤差、提出は5本平均の誤差。少ない本数ほど5本が似ていて平均の得が小さい。生CV はこの「平均の得」を測れないので、本数が多いときの得を見落とし、少ない本数を相対的に良く見せる。補正CV は5本のばらつき(A_test)を引いて平均の得を足し戻すので、この偏りが消える。

🏆 Kaggleでの実践的な使い方

  • 本数は「提出と同じ形(fold 平均)」で、しかも補正した CV で選ぶ。 生の OOF で本数を選ぶと最悪 3.63×10⁻⁴ 損する(今日の方針で最悪)。fold ごとの早期打ち切りも本数を早く止めすぎる(中雑音 葉255: 348本 対 山600本)
  • 調整の得が出る設定と、実際に選ぶ設定が同じかを先に確かめる。 葉511 での本数選びは確実に得だが、自分の選び方がそもそも葉511 を選ばないなら、その規則は使われない
  • 少数の試行で出た偏りは、SE とワールドごとの値を並べてから信じる。 2ワールドの +4 は16ワールドで0になった
  • 実験の保存物と環境は tmp の外に置き、再現テストを先頭に並べる。 今日は Day178・179 の公開値を最初に出し直したので、マシンが変わっても同じ結果だと言えた

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
大きい木では本数を減らすべき1 fold の学習曲線で山が早く来る5本平均では葉255 の山は600本(小得 9/12・中雑音 11/12)。山が600本より前なのは葉511 だけ
2ワールドで出た偏りは「暫定値」として使える数字が出ると何かに使いたくなるSE 2.3 に対して +4 は判定不能。16ワールドで +0.05 になり、決定19 を撤回することになった
マシンが変わると結果は合わない浮動小数点やライブラリの違いを心配する乱数なし・1スレッドにすれば、M1 → M5 Pro、別 venv でも小数2桁まで一致した
補正CV は葉の比較用で、本数の比較には使えないDay179 がそう書いた40ワールドで本数方向の偏りは全部 2SE 未満。偏るのは生CV(−4.65〜−14.52)
60点から全部比べるのが一番良い候補が多いほど良い物が入る決定18 との差は全部 2SE 未満で、費用は約1.4倍

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

  • fold ごとの早期打ち切りの真の損: 今日は止まる本数(val l2 の記録から再現)だけ出し、早期打ち切りで止めたモデルの予測は作らなかった(予測を2通り足すと1ワールドの予測時間が約1.5倍になるため)。Day179 の「忍耐50 で +2.17 悪化」は2ワールドの値のままで、表6 の止まる本数(山より約40〜320本早い)からは悪化の向きは同じと推測できるが、大きさは未計測
  • 600本より後ろ: 葉31〜255 の山は600本の端にある(表1)。800〜1,200本でさらに良くなるかは測っていない。決定18 の「600本」は「600本までの中で最良」であって「本数の山」ではない
  • 50〜150本での補正CV の偏り: 表4 は200 対 600本だけ
  • Day178 の残り22ワールド(lr0.1 対 lr0.05 の比較): 今日の40ワールドで Day178 の表1・表3 も出し直せるが、今日の問い(本数)に関係する表5 の再現だけにした
  • 持ち越し: 学習率 0.02 以下、seed を変えた独立な10分割、mcs 5・10、乱数ありの候補(決定14)、決定17〜19 の CV設計書への反映、候補数 K と選択の上振れ、系統別の m(et)、公開LB と2本目の枠、avg3、OOF側とtest側の seed の揺れ、noKeytel・raw7、入れ子CV との比較、Day170 の出し直し、Day165 の SE 個数差、Day162 の選択バイアス、真の効果0の偽採用率
  • 原典照合: Nadeau & Bengio・Krogh & Vedelsby は未照合のまま。LightGBM 4.7.0 では eval_set 引数に「deprecated, use 'eval_X' and 'eval_y' instead」の警告が出た(今日のログで確認、結果には影響しない)
  • 実データ(S5E5): 縮小シミュレータの上の数字のみ

🚀 次のステップ

  • 発展: 葉63〜255 で 600本の先(800・1,000・1,200本)に山があるか。あれば決定18 の本数を伸ばす得と費用の釣り合いを測る
  • 発展: fold ごとの早期打ち切りの止める本数を「別の分割の OOF」で決めると、早すぎる停止が消えるか
  • 次回予告: Day 181 — Playground Series 実戦投入⑯(決定18 の作り直しを 1,200本まで伸ばし、5本平均の山が600本の後ろにあるかを、今日の40ワールドの保存物と同じ生成で測る。あわせて CV設計書に決定13〜19 の今日の改訂を反映する)。月替わりテーマ3「画像(CV)系コンペへの応用」は 10/25 から

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

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