📚 背景知識(読んでから問題へ)
ここまでの決め方(CV設計書の該当部分)。
| 決定 | 中身 | 印(今日の前) |
|---|---|---|
| 13 | 候補は補正CV √(C² − A_test) で比べる。C は生の OOF RMSLE、A_test は test 2万行での5本の予測の分散(ラベル不要) | 葉511 まで ◎ |
| 15 | lr0.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 の作り直しに使う) |
| 途中本数 n | 50, 100, …, 600 の12点。学習し直さず start_iteration で塊ごとに足して作る |
| T5 | 5本平均の truth 60,000行 RMSLE(真) |
| 損 | 方針で選んだ1本の T5 − その世界の lr0.05 の「5葉×12本=60点」の T5 の最良 |
| 項1・項2・項3 | Ca² − 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_kcal | int / 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) float32 | lr0.1・300本の葉31〜511 の OOF と fold 別予測(test 2万+truth 6万) | 決定15 の葉選び・再現 |
| oof5 | (5, 12, 30000) float32 | lr0.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(破線)だけで、その葉は一度も選ばれない。
❓ 問題
タスクA(予想を確かめる)
Day179 は測る前にこう書いていた(Day179 タスクC・決定表)。今日の結果で当たり外れを判定する。
- 決定15 が葉255 を選ぶ小得・中雑音で、本数を補正CV で選ぶと 600本固定より −0.5 以上得する(そうなら決定18 の「600本」を「葉255 以上は補正CV で選ぶ」に替える)
- 補正CV は本数の比較で少ない本数を悪く見せる偏りがある(+2.4〜+5.3、低雑音2ワールド)
- 5本平均の山は1 fold の曲線の山より後ろにある
タスクB(コーディング)
- 生成部を Day175 と1文字も変えずに、1ワールドで lr0.1 の葉5種と lr0.05 の葉5種×途中本数12点を作る
day180.pyを書く。保存先は tmp の外 - Day178 表1・表2・表5 と Day179 表1・表3・表5 の公開値を出し直し、マシンと venv が変わっても結果が同じかを確かめる
analyze180.pyで、本数曲線・決定15 の葉・葉ごとの本数選びの規則・本数方向の CV の偏りの3項分解・方針全体の損・早期打ち切りの止まる本数・費用を出す
タスクC(戦略)
- 決定18 の「600本」を替えるか。決定19 を残すか
- Day179 の「補正CV は本数の比較で偏る」を CV設計書に書き足すべきか
- 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(判定)
- 外れ。 決定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ワールドの偶然)。 今日の生成で 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)だった
- 当たり(強まった)。 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(戦略の模範)
- 決定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ワールドで確か)
- 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 で一番悪い
- 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つ生んだ | — |
📊 結果の可視化
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 から
📝 自己評価(解いた後に記入)
自分の回答・気づき・メモ: