Day 144 — コンペ戦略④ — メダル圏内までの距離の見積もり

2026-09-03 金 / Phase 6(Grandmaster、テーマ4群 の 4日目) 戦略→コーディング シェイクアップ / カットオフライン / 逆算

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

🔁
Day141「最終提出2枠の選び方」、Day142「探索と活用のトレードオフ」、Day143「チームマージの意思決定」に続き、今日はこれらの土台となる問い — 「自分は今どれだけメダルに近いのか、残り日数で何点伸ばせば間に合うのか」 — を数値で見積もる。「今のPublic LB順位は良さそうだから大丈夫」という感覚を、シェイクアップとカットオフラインの変動を組み込んだシミュレーションで検証する。
⚠️
重要な事実: Public LB(期間中に見える暫定順位表)とPrivate LB(終了時に公開される最終順位表)は異なるデータサンプルで計算されるため、必ずズレが生じる。このズレで最終順位が期間中の順位から変動する現象をシェイクアップと呼ぶ。しかもこのズレは自分のスコアだけでなく、Gold圏内に入るためのカットオフライン自体にも同じように作用する上に、残り期間で他の参加者も改善を続けるため、カットオフラインは平均的に上昇していく。
キーワード直感的な説明
Public LB / Private LB期間中に見える暫定順位表(Public)と、終了時に公開される最終順位表(Private)。異なるデータサンプルで計算される
シェイクアップ(shake-up)Public LBとPrivate LBのスコアのズレによって、最終順位が期間中の順位から変動する現象
カットオフライン(cutoff score)メダル圏内(今日はGold)に入るために必要な最低スコアのライン。これも変動する
逆算(back-calculation)「目標とする到達確率」から出発し、それを満たすために必要な改善幅を求める考え方。Day141-143の「積み上げ型」に対し今日は「目標から逆に必要量を求める」

🎯 問題

タスクA(戦略・記述)

コンペ終了まで残り12日、現在のPublic LBスコアは0.815、Goldカットオフラインはおおよそ0.825あたりと見立てているが確信はない。以下3点をそれぞれ2〜3文で説明する。

1
「現在のPublic LBスコア − 推定カットオフスコア」という素朴な引き算だけでは判断材料として不十分な理由は何か(シェイクアップの影響を自分側だけでなくカットオフ側にも置くべき理由)
2
残り日数が少なくなるほど(自然に稼げる改善幅が小さくなるほど)、目標到達確率を満たすために追加で必要な上乗せ幅はどう変わるか
3
逆算した「必要な上乗せ幅」が自然な改善カーブだけでは届かない大きさだった場合、どう判断を変えるべきか(Day142の探索、Day143のチームマージとどうつながるか)

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

項目値
残り日数・現在のPublic LBD_REMAINING=12 / PUBLIC_LB_NOW=0.815
自然な逓減改善曲線CV_GAIN_BASE=0.0018 / CV_DECAY=0.88
Goldカットオフの推定値GOLD_CUTOFF_PUBLIC_EST=0.825
シェイクアップ幅(自分・カットオフ双方に作用)SHAKEUP_STD=0.006
カットオフラインの上昇CUTOFF_DRIFT_MEAN=0.003 / CUTOFF_DRIFT_STD=0.004
目標到達確率・反復回数TARGET_PROB=0.70 / N_SIM=20000
import numpy as np

N_SIM = 20000
D_REMAINING = 12

PUBLIC_LB_NOW = 0.815
CV_GAIN_BASE = 0.0018
CV_DECAY = 0.88

GOLD_CUTOFF_PUBLIC_EST = 0.825

SHAKEUP_STD = 0.006

CUTOFF_DRIFT_MEAN = 0.003
CUTOFF_DRIFT_STD = 0.004

TARGET_PROB = 0.70
B1
gain(base, decay, start, length)(Day142/143と同じ)で自然な改善幅planned_gainを計算する
B2
シェイクアップを考慮しないnaive_gap = GOLD_CUTOFF_PUBLIC_EST - PUBLIC_LB_NOW - planned_gainを計算する
B3
上乗せ改善deltaを引数に取るsimulate_p_medal(delta, rng)を実装。own/cutoffともに独立なシェイクアップノイズを加えP(own_private > cutoff_private)を返す
B4
delta=0.000〜0.020を0.001刻みでP(medal)を計算する。delta=0でのP(medal)がnaive_gapの符号から想像される値とどれだけ違うかに注目する
B5
二分探索でP(medal)>=TARGET_PROBを満たす最小のrequired_deltaを求め、naive_gapと比較する
B6
(追加検証)SHAKEUP_STD(0.002〜0.014)とD_REMAINING(4〜20)を変化させ、required_deltaの変化を調べる
B7
(考察): naive_gapで判断した場合とrequired_deltaで判断した場合とで結論はどう変わったか、実測を根拠に150字程度で結論づける

💡 ヒント

ヒント1方向性

タスクAの1.は、自分のスコアとカットオフラインの両方が同じ大きさの不確実性を持って独立に動くという点に気づけるかがポイント。2.はDay141-143の逓減する改善曲線から、残り日数が減るほど自然に稼げるplanned_gainが小さくなることを考える。タスクBは、「自分のスコア」と「カットオフライン」という2つの確率変数の差を扱う点が新しい。どちらも独立にシェイクアップの影響を受けるため、2つの不確実性の分散が合成されて、素朴な点差からは想像しにくいほど確率が動く。

ヒント2アプローチ
  • simulate_p_medalの中でown_privateとcutoff_privateはどちらもN_SIM件のnumpy配列として一括生成し、(own_private > cutoff_private).mean()で比率を出す
  • 二分探索は「deltaが大きいほどP(medal)は単調に増加する」性質を利用し、p < TARGET_PROBならlo=mid、そうでなければhi=midと区間を狭める
  • 比較を安定させるため、パラメータを変えて評価するたびにrng = np.random.default_rng(0)と同じシードから作り直すと、乱数のブレでなくパラメータの違いが見やすくなる(Day143と同じ工夫)
  • 感度分析はSHAKEUP_STD・D_REMAININGそれぞれ他の設定を固定し、二分探索を繰り返せばよい
ヒント3コード骨格
def gain(base, decay, start, length):
    if length <= 0:
        return 0.0
    i = np.arange(start, start + length)
    return np.sum(base * (decay ** i))

def simulate_p_medal(delta, rng, n_sim=N_SIM,
                      shakeup_std=SHAKEUP_STD,
                      cutoff_drift_mean=CUTOFF_DRIFT_MEAN,
                      cutoff_drift_std=CUTOFF_DRIFT_STD):
    planned_gain = gain(CV_GAIN_BASE, CV_DECAY, 0, D_REMAINING)
    own_base = PUBLIC_LB_NOW + planned_gain + delta
    own_private = own_base + rng.normal(0, shakeup_std, n_sim)
    cutoff_private = (GOLD_CUTOFF_PUBLIC_EST
                       + rng.normal(cutoff_drift_mean, cutoff_drift_std, n_sim)
                       + rng.normal(0, shakeup_std, n_sim))
    return (own_private > cutoff_private).mean()

# 二分探索でrequired_deltaを求める
lo, hi = 0.0, 0.05
for _ in range(40):
    mid = (lo + hi) / 2
    rng = np.random.default_rng(0)
    p = simulate_p_medal(mid, rng)
    if p < TARGET_PROB:
        lo = mid
    else:
        hi = mid
required_delta = hi

✅ 模範解答

for delta in np.round(np.arange(0.000, 0.021, 0.001), 3):
    rng = np.random.default_rng(0)
    p = simulate_p_medal(delta, rng)
    print(f"delta={delta:.3f}  P(medal)={p:.3f}")

# ============ 実測結果 ============
# planned_gain(natural, 12days)=0.01176
# naive_gap = -0.00176   (素朴な点差だけ見ると「自然な伸びだけで届く」ように見える)
#
# delta=0.000  P(medal)=0.445   ← naive_gapは負(=届いて見える)なのにP(medal)は半分以下
# delta=0.003  P(medal)=0.575
# delta=0.006  P(medal)=0.700   ← ここでTARGET_PROB=0.70に到達
# delta=0.010  P(medal)=0.828
# delta=0.015  P(medal)=0.929
# delta=0.020  P(medal)=0.977
#
# required_delta = 0.00601  (このdeltaでP(medal)=0.7000)
# naive_gap = -0.00176 → required_delta / naive_gap = -3.406倍(符号も大きさも一致しない)
🧭
タスクB-7: 考察 — naive_gap(素朴な点差、-0.00176)だけ見ると「自然な伸びのままで十分届く」という結論になるが、シェイクアップとカットオフの変動を組み込んだrequired_delta(0.00601)で判断すると、実際には自然な伸びに加えさらに約0.006の上乗せが必要という、符号も大きさもまったく異なる結論になった。自分のスコアとカットオフラインの双方が独立に不確実性を持つため、小さい(あるいは負の)点差はその不確実性の広がりに対してほとんど意味を持たない。

📊 結果の可視化

上乗せ改善 delta を増やしたときの P(medal) 0 1.0 0.000 0.020 TARGET_PROB=0.70 required_delta=0.00601 delta=0 → 0.445 → naive_gapは負(点差上は届いて見える)なのに、delta=0ではP(medal)は半分以下。0.006の上乗せで初めて70%に到達する

naive_gap(素朴な点差)と required_delta(不確実性を考慮した必要上乗せ幅)の符号の違い:

0 naive_gap = −0.00176 (点差だけ見ると「届いて見える」) required_delta = +0.00601 (実際にはさらに上乗せが必要)
指標値意味
planned_gain0.01176残り12日で自然に稼げる改善幅
naive_gap−0.00176不確実性を無視した素朴な点差(負=届いて見える)
P(medal) at delta=00.445実際には半分を下回る
required_delta+0.00601TARGET_PROB=0.70を満たす必要な上乗せ改善

🪜 Step-by-Step 解説

1なぜdelta=0でもnaive_gapの符号から想像できないほどP(medal)が低いのか

# naive_gap = -0.00176   (点差の上では届いているように見える)
# delta=0.000  P(medal)=0.445
🎯
なぜこうするか: own_privateとcutoff_privateはそれぞれ独立にNormal(0, SHAKEUP_STD)のノイズを持つため、両者の差の標準偏差は単純なSHAKEUP_STDではなく、sqrt(2*SHAKEUP_STD**2 + CUTOFF_DRIFT_STD**2) ≈ 0.0094という、naive_gapの絶対値(0.00176)よりずっと大きな広がりになる。さらにCUTOFF_DRIFT_MEAN=0.003(他の参加者も伸びる分、カットオフラインが平均的に上がる効果)が上乗せされ、実質的な期待差はnaive_gap - CUTOFF_DRIFT_MEAN ≈ -0.0048とさらに不利な方向にずれる。点差が広がりに対して小さすぎるほど、確率はコイントス(50%)に近づく。

2なぜ残り日数が少ないほどrequired_deltaが大きくなるのか

# D_REMAINING= 4  planned_gain=0.00600  required_delta=0.01177
# D_REMAINING=20  planned_gain=0.01384  required_delta=0.00394
⏳
なぜこうするか: required_deltaは「目標到達確率70%を満たすために、自然な伸び(planned_gain)を超えて追加で必要な上乗せ幅」である。残り日数が少ないほど逓減する改善カーブによってplanned_gain自体が小さくなるため、同じカットオフラインに届くにはその分をdelta側で補わなければならない。「残り日数が少ない」ことは、伸ばせる絶対量が減るだけでなく、目標達成に必要な一手の大きさを増加させる二重の不利をもたらす。

3なぜSHAKEUP_STDが大きいコンペほどrequired_deltaが大きくなるのか

# SHAKEUP_STD=0.002  required_delta=0.00379
# SHAKEUP_STD=0.014  required_delta=0.01153
📐
なぜこうするか: SHAKEUP_STDが大きいということは、Public LBとPrivate LBのズレが大きい(評価データがノイズに敏感、あるいはテストサンプルが小さい)ことを意味する。このズレが自分のスコアとカットオフラインの両方に独立にのしかかるため、SHAKEUP_STDが大きくなるほど「同じ点差でもどちらに転ぶか分からない」度合いが増し、目標確率を確保するための上乗せrequired_deltaも比例して大きくなる。過去の類似コンペのシェイクアップの大きさを把握しておくことが、このSHAKEUP_STDの見積もり精度に直結する。

🔁 追加検証(SHAKEUP_STD・D_REMAININGを変化させた場合)

それぞれ他の設定を固定し、SHAKEUP_STDとD_REMAININGを個別に変化させてrequired_deltaを求めた。

# --- SHAKEUP_STDを変えた場合 ---
# SHAKEUP_STD=0.002  required_delta=0.00379
# SHAKEUP_STD=0.004  required_delta=0.00481
# SHAKEUP_STD=0.006  required_delta=0.00601   ← 今日のメイン設定
# SHAKEUP_STD=0.008  required_delta=0.00731
# SHAKEUP_STD=0.010  required_delta=0.00873
# SHAKEUP_STD=0.012  required_delta=0.01011
# SHAKEUP_STD=0.014  required_delta=0.01153

# --- D_REMAINING(残り日数)を変えた場合 ---
# D_REMAINING= 4  planned_gain=0.00600  naive_gap=0.00400   required_delta=0.01177
# D_REMAINING= 6  planned_gain=0.00803  naive_gap=0.00197   required_delta=0.00974
# D_REMAINING= 8  planned_gain=0.00961  naive_gap=0.00039   required_delta=0.00817
# D_REMAINING=10  planned_gain=0.01082  naive_gap=-0.00082  required_delta=0.00695
# D_REMAINING=12  planned_gain=0.01176  naive_gap=-0.00176  required_delta=0.00601   ← 今日のメイン設定
# D_REMAINING=16  planned_gain=0.01306  naive_gap=-0.00306  required_delta=0.00472
# D_REMAINING=20  planned_gain=0.01384  naive_gap=-0.00384  required_delta=0.00394
SHAKEUP_STDは増えるほど、D_REMAININGは増えるほどrequired_deltaが動く(逆方向) 小さい 大きい SHAKEUP_STD軸 D_REMAINING軸
🎚️
SHAKEUP_STDを増やすほどrequired_deltaは単調に増加し(0.00379→0.01153)、D_REMAININGを増やすほどrequired_deltaは単調に減少した(0.01177→0.00394、ただし方向が逆の意味を持つ点に注意)。評価指標がノイズに敏感なコンペほど、そして残り日数が少ない終盤ほど、必要な上乗せ改善は大きく見積もっておくべきだと言える。

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

🎯
「2つの独立な不確実性が重なると、差の広がりはどちらか一方より大きくなる」を、2つの的当てゲームの例えで理解する:

自分の矢が的の中心から平均0.5cmだけ的に近い場所に飛ぶとします。しかし風でブレて実際にはプラスマイナス3cmくらい散らばります。一方、的自体もわずかに揺れる台の上にあり、平均的な位置はほぼ動かないものの、これもプラスマイナス2cmくらい散らばるとします。「自分の矢の平均位置」と「的の平均位置」の差は0.5cmしかありませんが、矢のブレと的のブレの両方が重なった結果、実際に矢が的の内側に入るかどうかは、単純に0.5cmという数字だけからは想像できないほど五分五分に近づきます。今日のシミュレーションのown_privateとcutoff_privateは、まさにこの「両方が独立に揺れる」構造を再現したものです。
➕
「合成された分散」を数式で理解する:

2つの独立な確率変数の差の分散は、それぞれの分散の和になります(標準偏差は単純な足し算ではなく、2乗和のルート):
combined_std = np.sqrt(2 * SHAKEUP_STD**2 + CUTOFF_DRIFT_STD**2)
# = sqrt(2 * 0.006**2 + 0.004**2) = sqrt(0.000088) ≈ 0.00938
「点差(naive_gap)は0.00176しかないのに、広がり(combined_std)は0.0094もある」という状態は、点差が広がりの5分の1程度しかないことを意味します。1つの変数のバラつきを表す標準偏差に対し、2つの変数の差を考えるときはそれぞれのバラつきが両方効いてくる、という点が今日の実測のポイントです。

🏆 Kaggleでの実践的な使い方

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

原則

🚫 順位だけでメダルを確信しない

点差がプラスに見えても油断しない

今日の実測が示す通り、素朴な点差がわずかにプラス方向でも実際の到達確率は五分五分を大きく下回りうる。過去の類似コンペのシェイクアップ幅(Meta Kaggleデータなど)を事前に把握しておく。

実例

📅 残り日数と必要な一手をセットで見積もる

定期的にrequired_deltaを逆算し直す

残り日数が少なくなるほど自然な伸びで補える割合は下がり、必要な上乗せ幅は増える。「あと何日で何点必要か」を定期的に見直し、Day142の探索やDay143のマージへの切り替えを早める。

実務

👀 カットオフラインの動きも観測する

自分だけでなく相手側の伸びも見る

自分のスコアだけでなく、他の参加者のPublic LBの伸びのペースもLB履歴やDiscussionで定点観測し、CUTOFF_DRIFT_MEANに相当する上昇ペースを主観的にでも見積もる。

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
Public LBで現在カットオフラインより上にいれば安心だと思う見た目の順位・点差をそのまま信じてしまう今日の実測ではnaive_gapが負(=点差の上では届いて見える)でも、P(medal)=44.5%と半分を下回った。シェイクアップとカットオフの変動、両方を考慮しないと過信になる
残り日数が少なくなるほど、必要な上乗せ幅は小さくなると思う(もう十分伸ばしたから)「これまでの進捗」に安心してしまう直感実測では逆に、残り日数が少ないほど自然な改善(planned_gain)が小さくなる分、必要な上乗せ幅(required_delta)は増えた(D=4で0.01177 vs D=20で0.00394)
シェイクアップは自分のスコアだけに影響すると思いがち「自分が主役」という視点で不確実性を捉えてしまう実際にはカットオフライン側にも同じ大きさの不確実性がある。両方の不確実性が独立に重なるため、実測のrequired_deltaは点差の大きさだけでは説明できない値になった
必要な改善幅(required_delta)を満たせば確実にGoldに届くと思う「目標を満たした=保証された」という誤解required_deltaはTARGET_PROB=0.70を満たす値であり、それでも30%の確率で外れる。これは「必要な投資」であって「保証」ではない

🚀 次のステップ

  • 発展: 今日は自分のシェイクアップノイズとカットオフラインのシェイクアップノイズを独立とみなしたが、実際には同じテストデータの分割から生まれるズレである以上、両者にはある程度の正の相関がある可能性がある。rng.multivariate_normalなどで相関を持たせたノイズに拡張し、相関がrequired_deltaをどう変えるか試してみましょう。CUTOFF_DRIFT_MEAN(他の参加者の伸びペース)を0〜0.010の範囲で変化させた場合の感度も確認する価値があります
  • 次回予告: Phase6テーマ4「コンペ戦略・Gold 3枚達成プラン」の5日目・最終日に入ります。次回はDay141〜144で扱った4つの意思決定(提出予算配分・探索と活用・チームマージ・メダル圏内までの距離の見積もり)を統合し、一つの「Gold 3枚達成プラン」として総括する予定です

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

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

Day141で最終提出の選び方、Day142で探索/活用トレードオフ、Day143でチームマージ、Day144でメダル圏内までの距離の見積もりを実測。次回、5日間の総括に進む。

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

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