Day 141 — コンペ戦略① — 提出予算の配分と「CVを信じよ」を実測で裏付ける

2026-08-31 金 / Phase 6(Grandmaster、テーマ4群 の 1日目) 戦略 提出予算配分 / CV vs Public LB / 最終提出2枠の選び方

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

🏁
Phase6テーマ4「コンペ戦略・Gold 3枚達成プラン」の1日目です。Day126-140で積み上げたSOTA手法追跡・カスタムアーキテクチャ・高度なアンサンブルという技術実験を離れ、今日から5日間は「提出回数をどう配分するか」「CVとPublic LBが食い違ったらどちらを信じるか」「最終提出2つをどう選ぶか」という、技術力だけでは解決しない戦略的な意思決定を扱います。
🧩
今日の核心 — 「CVを信じよ」を精神論ではなく数値で裏付ける: CV・Public LB・Private LBはどれも「モデルの本当の実力」を、違うサンプルサイズのデータで測った推定値にすぎない。正解率のような指標の測定ノイズはおおよそ sqrt(p(1-p)/n) でサンプルサイズnに依存するため、テストデータのごく一部しか使わないPublic LBはCVよりノイズが大きい。この差をシミュレーションで再現し、「CV1つ+LB1つ」の定石ミックス戦略が本当に最適かを検証する。
キーワード直感的な説明
提出予算(submission budget)コンペ期間中に使える提出回数の総量。探索・検証・最終選定への配分を計画すること
Public LB / Private LBテストデータを分割し、一部は提出のたびに即時採点・公開(Public)、残りはコンペ終了後のみ採点・公開(Private)される仕組み
シェイクアップ(shake-up)コンペ終了時にPublic LBとPrivate LBの順位が大きく入れ替わる現象。Public LBへの過学習が主因
最終提出2枠(final submissions)コンペ終了までに選ぶ、Private LB成績に使われる最大2つの提出。選ばれた2つのうち良い方が採用される
標準誤差 sqrt(p(1-p)/n)正解率をサンプルで測定するときの測定ノイズの目安。サンプルサイズnが小さいほどノイズが大きい

🎯 問題

タスクA(戦略・記述)

30日間のコンペ、1日最大5回・合計150回の提出枠があるが実際に使い切る参加者は少なく、上位陣は合計30〜50回程度に留める傾向があるとする。以下の3フェーズに提出予算をどう配分するか計画し、「1日1提出ルール」の狙いを今日のノイズの話と関連づけて説明する。

1
探索フェーズ(序盤〜中盤): 特徴量・モデルの方向性を広く試す
2
検証フェーズ(中盤〜終盤): 有望な方向をCVで固め、アンサンブルを試す
3
最終選定フェーズ(終盤〜締切直前): 最終提出2枠を確定させる

タスクB(実装・シミュレーション)

項目値
候補モデル数N_CAND = 30(30日コンペで試した候補モデル)
本当の実力の分布true_skill ~ N(0.800, 0.010)
ノイズの標準偏差se(n) = sqrt(0.8 * 0.2 / n)
サンプルサイズCV実質8000件 / Private 4000件 / Public 1000件
比較する戦略CV一択 / Public LB一択 / 定石ミックス(CV1+LB1) / CV1枠のみ
反復回数N_SIM = 20000
import numpy as np
rng = np.random.default_rng(0)

N_CAND = 30
N_SIM = 20000
TRUE_MEAN = 0.800
TRUE_STD  = 0.010

def se(n, p=0.80):
    """サンプルサイズnでの正解率測定の標準誤差"""
    return np.sqrt(p * (1 - p) / n)

N_CV_EFFECTIVE = 8000
N_PRIVATE = 4000
N_PUBLIC  = 1000
CV_STD = se(N_CV_EFFECTIVE)
PRIVATE_STD = se(N_PRIVATE)
PUBLIC_STD = se(N_PUBLIC)
B1
各候補のtrue_skillを生成し、CV/Public/Privateそれぞれ別ノイズを加えた3つの観測値を作る
B2
戦略A「CV一択」B「Public LB一択」C「定石ミックス」D「CV1枠のみ」を実装し、それぞれの最終private_scoreを記録する
B3
N_SIM回繰り返し、各戦略の平均private_scoreと「神視点の理論上限」からのregret(取りこぼし)を求める
B4
「真に実力最良のモデル」が最終2枠に含まれた確率、「採用した提出が真の下位半分由来」だった確率を戦略ごとに集計する
B5
(考察): 定石ミックスはCV一択より優れていたか、今日のノイズ設定とどう関係するかを150字程度で結論づける

💡 ヒント

ヒント1方向性

タスクAは正解が1つに決まる問題ではない。「なぜその回数配分にするか」をコンペの進行段階の役割(広く探る/絞り込む/安全に選ぶ)と紐づけて説明できることが重要。タスクBは「同じtrue_skillから3種類のノイズ付き観測値を作り、その観測値だけを頼りに意思決定した場合どれだけ取りこぼすか」を測るシミュレーションで、CVは学習データ全体を使えるためノイズが小さく、Public LBはテストのごく一部しか使えないためノイズが大きいというサンプルサイズの非対称性を乱数の標準偏差の違いとして表現する。

ヒント2アプローチ
  • se(n, p=0.80)関数はどの観測値のノイズにも共通して使う。N_CV_EFFECTIVE > N_PRIVATE > N_PUBLICの順にサンプルサイズが大きい(=ノイズが小さい)ことを先に確認する
  • np.argsort(-cv_score)[:2]のようにインデックスを取得すれば「上位2件」が得られる
  • 戦略C(定石ミックス)は「CV最高」と「Public LB最高」が同じモデルを指すことがある。その場合は2枠目をCV2位で埋める
  • 「選ばれた2枠のうちprivate_scoreが高い方」=private_score[picks].max()。Kaggleの「最終2つのうち良い方が採用される」ルールに対応
  • 「本当に実力最良のモデル」はnp.argsort(-true_skill)[0]。これが最終2枠のインデックスに含まれるかをinで判定する
ヒント3コード骨格
results = {"cv_cv": [], "lb_lb": [], "mixed": [], "single_cv": []}
best_possible, truebest_private = [], []
hit_truebest = {"cv_cv": 0, "lb_lb": 0, "mixed": 0}

for sim in range(N_SIM):
    true_skill = rng.normal(TRUE_MEAN, TRUE_STD, N_CAND)
    cv_score = true_skill + rng.normal(0, CV_STD, N_CAND)
    public_score = true_skill + rng.normal(0, PUBLIC_STD, N_CAND)
    private_score = true_skill + rng.normal(0, PRIVATE_STD, N_CAND)

    order_true = np.argsort(-true_skill)
    best_possible.append(private_score.max())
    truebest_private.append(private_score[order_true[0]])

    top2_cv = np.argsort(-cv_score)[:2]
    results["cv_cv"].append(private_score[top2_cv].max())
    if order_true[0] in top2_cv: hit_truebest["cv_cv"] += 1

    top2_lb = np.argsort(-public_score)[:2]
    results["lb_lb"].append(private_score[top2_lb].max())
    if order_true[0] in top2_lb: hit_truebest["lb_lb"] += 1

    best_cv_idx = np.argsort(-cv_score)[0]
    best_lb_idx = np.argsort(-public_score)[0]
    picks = [best_cv_idx, np.argsort(-cv_score)[1]] if best_cv_idx == best_lb_idx else [best_cv_idx, best_lb_idx]
    results["mixed"].append(private_score[picks].max())
    if order_true[0] in picks: hit_truebest["mixed"] += 1

    results["single_cv"].append(private_score[best_cv_idx])

✅ 模範解答

# --- ノイズの大きさを確認 ---
print(f"CV_STD={CV_STD:.5f}  PRIVATE_STD={PRIVATE_STD:.5f}  PUBLIC_STD={PUBLIC_STD:.5f}")
# 実測: CV_STD=0.00447  PRIVATE_STD=0.00632  PUBLIC_STD=0.01265
# → Public LBのノイズはCVの約2.8倍、Privateの約2.0倍

# --- モンテカルロシミュレーション本体(ヒント3のループを実行) ---
best_possible = np.array(best_possible)
for k, arr in results.items():
    arr = np.array(arr)
    regret = (best_possible - arr).mean()
    print(f"{k:10s} mean_private={arr.mean():.5f}  regret={regret:.5f}")

# ============ 実測結果 ============
# [参考] 神視点の理論上限 best_possible_private mean = 0.82423
# [参考] 真に実力最良のモデルを選んだ場合の private mean = 0.82048
#
# cv_cv      mean_private=0.82147  regret_vs_bestpossible=0.00276
# mixed      mean_private=0.82064  regret_vs_bestpossible=0.00359
# single_cv  mean_private=0.81866  regret_vs_bestpossible=0.00557
# lb_lb      mean_private=0.81742  regret_vs_bestpossible=0.00681
#
# cv_cv      P(真に実力最良のモデルを最終2枠に含める)=0.774
# mixed      P(真に実力最良のモデルを最終2枠に含める)=0.690
# lb_lb      P(真に実力最良のモデルを最終2枠に含める)=0.401
#
# cv_cv      P(採用した最終提出が真の下位半分由来)=0.000
# mixed      P(採用した最終提出が真の下位半分由来)=0.003
# lb_lb      P(採用した最終提出が真の下位半分由来)=0.020
🧭
タスクB-5: 考察 — 定石ミックス(regret=0.00359)はCV一択(regret=0.00276)に勝てなかった。「真に実力最良のモデルを最終2枠に含める確率」もCV一択77.4%に対しミックスは69.0%と低い。原因はPublic LBの標準偏差(0.01265)がCVの標準偏差(0.00447)より約2.8倍大きいこと。ミックス戦略の2枠目はPublic LB最高のモデルを機械的に採用するが、そのモデルは「本当に実力がある」のではなく「たまたまPublic LBで得をした」可能性が無視できない。CVを2回信じる方が、ノイズの少ない情報源を2回使うことになり結果的に安定した。ただしこの結果は「Public LBのノイズがCVよりかなり大きい」という設定に依存しており、後述の追加検証で条件付きの結論であることを確認する。

📊 結果の可視化

戦略ごとの平均 private_score(理論上限との比較) 0.812 理論上限 .82423 .82147CV一択 .82064定石ミックス .81866CV1枠のみ .81742Public LB一択 → CV一択が最良。定石とされるミックスも僅差で及ばず、Public LB一択が最も理論上限から遠い

戦略ごとの「真に実力最良のモデルを最終2枠に含める確率」:

CV一択
77.4%
定石ミックス
69.0%
Public LB一択
40.1%

「採用した最終提出が真の下位半分由来だった」確率: CV一択 0.0% / 定石ミックス 0.3% / Public LB一択 2.0%(シェイクアップで沈む典型パターンに対応)

戦略平均privateregret真ベストを含む確率
神視点(理論上限)0.824230(基準)—
CV一択0.821470.0027677.4%
定石ミックス0.820640.0035969.0%
CV1枠のみ0.818660.00557—
Public LB一択0.817420.0068140.1%

🪜 Step-by-Step 解説

1なぜ同じtrue_skillから3種類の観測値を作るのか

true_skill = rng.normal(TRUE_MEAN, TRUE_STD, N_CAND)
cv_score = true_skill + rng.normal(0, CV_STD, N_CAND)
public_score = true_skill + rng.normal(0, PUBLIC_STD, N_CAND)
private_score = true_skill + rng.normal(0, PRIVATE_STD, N_CAND)
📐
なぜこうするか: 現実のKaggleでも、CV・Public LB・Private LBの3つのスコアは「同じモデルの本当の実力」を、違うデータ(学習データのOOF・テストの一部・テストの残り)で測った別々の推定値にすぎない。3つとも同じtrue_skillを土台にしつつサンプルサイズの違いによるノイズの大きさだけが異なる構造にすることで、CVとPublic LBの順位がずれる原因を「モデルの気まぐれ」ではなく測定誤差の大きさの違いとして説明できる。

2なぜCV一択がミックス戦略に勝つことがあるのか

# cv_cv      regret_vs_bestpossible=0.00276
# mixed      regret_vs_bestpossible=0.00359
# lb_lb      regret_vs_bestpossible=0.00681
🎲
なぜこうするか: 定石ミックス(CV1つ+LB1つ)は多様な情報源を混ぜる狙いがあるが、保険として使う情報源自体のノイズが大きすぎると保険は「安全策」ではなく「ノイズの多い賭け」になる。Public LBの標準偏差(0.01265)がCVの標準偏差(0.00447)よりかなり大きいため、Public LB由来の1枠は「本当に実力があるから選ばれた」のではなく「ノイズで得をしたから選ばれた」割合が大きくなり、regretを押し上げる方向に働いた。

3なぜ「下位半分から選んでしまう確率」がCV一択でほぼ0なのか

# cv_cv      P(下位半分由来)=0.000
# mixed      P(下位半分由来)=0.003
# lb_lb      P(下位半分由来)=0.020
🔍
なぜこうするか: CVのノイズ(0.00447)はTRUE_STD(0.010)よりかなり小さいため、CVスコアの順位は真の実力の順位をかなり忠実に反映し「下位半分なのに選ばれる」ことはほぼ起きない。一方Public LBはノイズがTRUE_STDに対して相対的に大きいため、稀に「本当は下位半分の弱いモデルが、たまたまPublic LBで良い数字を出して選ばれてしまう」というシェイクアップの典型パターンが再現される。

🔁 追加検証(Public LBのサンプルサイズを大きくした場合)

「CV一択がミックスに勝つ」という結論は、Public LBのノイズがCVよりかなり大きいという前提に強く依存する。この前提を崩した場合(Public/Privateの分割比率を逆転させ、Publicの取り分を大きくしてノイズを減らす)に結論が変わるかを確認した。

# N_PUBLIC=3000, N_PRIVATE=2000 のケース
# CV_STD=0.00447  PRIVATE_STD=0.00894  PUBLIC_STD=0.00730
#
# cv_cv   mean_private=0.82258
# mixed   mean_private=0.82242
# lb_lb   mean_private=0.82116
📏
Public LBのノイズがCVに近づく(0.00730 vs 0.00447)と、CV一択とミックスの差はほぼ誤差の範囲まで縮まった(差0.00016、メイン設定での差0.00083よりさらに小さい)。CV一択がわずかに上回る傾向は変わらなかったが、「CVを信じよ」はPublic LBのサンプルサイズが極端に小さいコンペほど強く成り立ち、Public LBが十分大きいコンペでは差がほとんど消える、という条件付きの結論として理解するのが実測に忠実である。

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

🗳️
「サンプルサイズとノイズの関係」を、世論調査の例えで理解する:

選挙の当落を予想する世論調査で、10人にしか聞かなかった調査と、10,000人に聞いた調査があったら、どちらを信じるべきかは直感的に分かるはずです。10人の調査はたまたま偏った10人に当たっただけで結果が大きくブレる可能性があります。今日のPublic LB(1000件)とCV(実質8000件)の関係もこれと同じで、Public LBは「一部の人にしか聞いていない世論調査」、CVは「もっと多くの人に聞いた世論調査」に相当し、後者の方が本当の実力に近い数字を返します。
🎟️
「regret(取りこぼし)」を、宝くじ売り場の例えで理解する:

30枚の宝くじがあり、それぞれ当たりやすさ(true_skill)が少しずつ違うとします。中身は直接見えず、「うわさ話(CV)」と「近所の人の体験談(Public LB)」という間接情報しか得られません。うわさ話の方が多くの人から集めた情報なので信頼度が高く、体験談は少人数の話なので当てにならないことがある、という状況で最終的にどの2枚を買うかを決めるのが今日のシミュレーションです。regretは「中身が最初から見えていたら得られたはずの当たり」と「実際に選んだ2枚での当たり」の差、つまり情報不足によって取りこぼした分を表します。

🏆 Kaggleでの実践的な使い方

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

原則

📉 「CVを信じよ」は条件付きの経験則

Public LBのサンプルサイズが小さいほど強く成り立つ

コンペのDiscussionタブでPublic/Privateの分割比率が公開されていれば、その比率から「今回はどれくらいCVを信じるべきか」を数値的に見積もることができる。

実例

🎫 最終提出2枠は「保険」の質を吟味する

LB側の情報が信頼できる場合にこそ機能する定石

「CV1つ+LB1つ」の定石は万能ではない。テストデータが小さいコンペや、CVとLBの乖離が大きいコンペでは、あえて「CV上位2つ」を選ぶ判断も選択肢に入れるべき。

実務

🗓️ 提出予算は3フェーズで配分する

探索→検証→最終選定

序盤に手数を使いすぎると、終盤の最終選定に使える回数が減り、CVとPublic LBの乖離を落ち着いて検討する余裕がなくなる。

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
Public LBの順位が上がったら、モデルが本当に改善したと思う目に見える数字が上がるとつい信じたくなる今日の実測のようにPublic LBはCVよりノイズが大きい。小さな順位変動は測定誤差の範囲内であることが多い
「CV1つ+LB1つ」の定石ミックスは常にCVだけより優れていると思い込む情報源を混ぜれば必ず安全になるという直感今日の実測ではCV一択(regret 0.00276)がミックス(regret 0.00359)を上回った。混ぜる情報源自体のノイズが大きい場合、混ぜることが逆に不利になることがある
提出回数は多いほど良い結果につながると思う「試行回数=改善の機会」という単純な発想提出回数を使う「タイミング」(探索か検証か最終選定か)の方が回数そのものより重要。終盤にむやみに提出を重ねるとPublic LBへの過学習を招きやすい
CVスコアさえ良ければPrivate LBも必ず良いと思うCVは「万能な予言者」だという期待今日の実測でもCV一択のregretは0ではない(0.00276)。CVもサンプルサイズに応じたノイズを持つ推定値の一つであり、完璧な予言者ではない

🚀 次のステップ

  • 発展: N_CAND=30を10個や100個に変えた場合、また候補モデル間の実力差TRUE_STDを今日より小さく・大きくした場合に、CV一択とミックス戦略の優劣がどう変わるか試してみましょう
  • 次回予告: Phase6テーマ4「コンペ戦略・Gold 3枚達成プラン」の2日目に入ります。今日はコンペ終了時点の「最終提出の選び方」を扱いましたが、次回はコンペ進行中の意思決定、CVスコアが伸び悩んだときに新しい特徴量を試すべきか、アンサンブルの微調整に留めるべきかという探索と活用のトレードオフを、限られた提出予算のもとでどう判断するかを扱う予定です

提出予算配分プラン(タスクA・例)

探索 15-20回
検証 10-15回
最終選定 3-5回

最終選定フェーズで初めてPublic LBを「保険」として使う。それ以外のフェーズはCVの安定した改善だけを判断材料にする。

Phase 6 の学習マップ(全20テーマ)

1-5
SOTA手法追跡・論文実装(完了)
6-10
カスタムアーキテクチャ設計(完了)
11-15
高度なアンサンブル(完了)
16-20
コンペ戦略・Gold3枚達成プラン(本日1/5)

Day141で最終提出の選び方(CV vs Public LB)を実測。次回以降、コンペ進行中の探索/活用トレードオフや提出予算の実務判断に進む。

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

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