📚 背景知識(読んでから問題へ)
| キーワード | 直感的な説明 |
|---|---|
| 探索(EXPLORE) | 未知の新しいアイデア(特徴量・モデル)を試す行動。当たれば大きな改善、外れれば提出を消費するだけで終わる |
| 活用(EXPLOIT) | 既知の枠組みの中でパラメータ・重みを微調整する行動。改善幅は小さいが確実。同じ枠組みを続けるほど逓減する |
| 多腕バンディット問題 | 未知の当たりやすさを持つ複数の選択肢から、限られた試行回数で最大の報酬を得るための配分を決める意思決定問題 |
| 逓減(diminishing returns) | 同じ種類の改善を繰り返すほど、1回あたりの効果が小さくなっていくこと |
| 適応的ルール(adaptive policy) | あらかじめ決めたスケジュールではなく、観測された結果に応じてその都度、次の行動を決める戦略 |
🎯 問題
タスクA(戦略・記述)
検証フェーズ(残り提出予算20回)に入ったところ、直近5回の提出でCVスコアがほぼ横ばい(停滞)になっているとする。次の1回で「探索」と「活用」のどちらを選ぶかを、以下3点を踏まえて3〜4文で説明する。
タスクB(実装・シミュレーション)
| 項目 | 値 |
|---|---|
| 残り提出予算 | N_STEPS = 20 |
| 開始CVスコア | BASE = 0.800(停滞している状態) |
| 探索の当たり確率・改善幅 | P_HIT = 0.20 / Uniform(0.003, 0.008) |
| 活用の初回改善幅・逓減係数 | EXPLOIT_BASE_GAIN = 0.0015 / EXPLOIT_DECAY = 0.75 |
| 適応的ルールの切替閾値 | K_ADAPTIVE = 3(連続活用がこの回数で探索へ切替) |
| 比較する戦略 | 常に活用 / 常に探索 / 固定半々 / 適応的ルール |
| 反復回数 | N_SIM = 20000 |
import numpy as np rng = np.random.default_rng(0) N_STEPS = 20 N_SIM = 20000 BASE = 0.800 P_HIT = 0.20 EXPLORE_GAIN_LOW, EXPLORE_GAIN_HIGH = 0.003, 0.008 EXPLOIT_BASE_GAIN = 0.0015 EXPLOIT_DECAY = 0.75 K_ADAPTIVE = 3
cv(現在のCVスコア)とexploit_streak(連続で活用した回数)の2つで表現するcvを記録するK_ADAPTIVEを1〜10で変化させ、平均改善幅と標準偏差の変化を調べる💡 ヒント
タスクAは「停滞は今の枠組みの伸びしろが尽きたシグナル」と「残り予算が少ない終盤ではリスクを取りにくい」という2つの軸を分けて考えることがポイント。タスクBは「探索」と「活用」をそれぞれ確率的な報酬モデルとして表現し、固定スケジュールで割り振る場合と、観測された状態(連続活用回数)に応じて動的に切り替える場合とでどれだけ差が出るかを測るシミュレーション。活用の効果がEXPLOIT_DECAYで逓減する設計になっている点がポイント。
- 探索が当たると
exploit_streakは0にリセットされる点を忘れないこと(新しい特徴量の周辺には未チューニングの伸びしろが生まれる) - 戦略C「固定半々」は
step < N_STEPS // 2かどうかで探索/活用を切り替える - 戦略D「適応的ルール」は
exploit_streak >= K_ADAPTIVEかどうかだけを見る。初手はexploit_streak=0なので必ず活用になる - 「探索を何回選んだか」「探索が何回当たったか」は別々のカウンタで集計する
rng.uniform(low, high)は一様分布サンプリング、rng.random() < P_HITは確率P_HITでの成功判定に使う
def run_policy(policy, rng): cv = BASE exploit_streak = 0 n_explore = 0 n_hit = 0 for step in range(N_STEPS): if policy == "always_exploit": action = "exploit" elif policy == "always_explore": action = "explore" elif policy == "fixed_half": action = "explore" if step < N_STEPS // 2 else "exploit" elif policy == "adaptive": action = "explore" if exploit_streak >= K_ADAPTIVE else "exploit" if action == "explore": n_explore += 1 if rng.random() < P_HIT: cv += rng.uniform(EXPLORE_GAIN_LOW, EXPLORE_GAIN_HIGH) n_hit += 1 exploit_streak = 0 # 外れた場合はcv変化なし else: cv += EXPLOIT_BASE_GAIN * (EXPLOIT_DECAY ** exploit_streak) exploit_streak += 1 return cv, n_explore, n_hit
✅ 模範解答
# --- 4戦略×N_SIM回のモンテカルロシミュレーション(ヒント3のrun_policyを実行) --- policies = ["always_exploit", "always_explore", "fixed_half", "adaptive"] results = {p: [] for p in policies} n_explore_used = {p: [] for p in policies} n_hit_used = {p: [] for p in policies} for sim in range(N_SIM): for p in policies: cv, n_explore, n_hit = run_policy(p, rng) results[p].append(cv) n_explore_used[p].append(n_explore) n_hit_used[p].append(n_hit) for p in policies: arr = np.array(results[p]) gain = arr - BASE print(f"{p:16s} mean_gain={gain.mean():.5f} std={arr.std():.5f}") # ============ 実測結果 ============ # always_exploit mean_final_cv=0.80598 mean_gain=0.00598 n_explore=0.00 n_hit=0.00 std=0.00000 # always_explore mean_final_cv=0.82201 mean_gain=0.02201 n_explore=20.00 n_hit=4.00 std=0.01034 # fixed_half mean_final_cv=0.81662 mean_gain=0.01662 n_explore=10.00 n_hit=1.99 std=0.00723 # adaptive mean_final_cv=0.82257 mean_gain=0.02257 n_explore=11.10 n_hit=2.22 std=0.00781
📊 結果の可視化
戦略ごとの平均探索回数(探索に使った提出回数 / 全20回中):
探索の「当たり」回数(平均): 適応的ルール 2.22回 / 常に探索 4.00回 / 固定半々 1.99回。少ない探索回数でも当たりを効率よく拾えている
| 戦略 | 平均gain | 平均探索回数 | 平均当たり回数 | std(リスク) |
|---|---|---|---|---|
| 適応的ルール | 0.02257 | 11.10 | 2.22 | 0.00781 |
| 常に探索 | 0.02201 | 20.00 | 4.00 | 0.01034 |
| 固定半々 | 0.01662 | 10.00 | 1.99 | 0.00723 |
| 常に活用 | 0.00598 | 0.00 | 0.00 | 0.00000 |
🪜 Step-by-Step 解説
1なぜ「活用」に逓減を組み込むのか
cv += EXPLOIT_BASE_GAIN * (EXPLOIT_DECAY ** exploit_streak)
exploit_streak += 1
EXPLOIT_DECAY ** exploit_streakという等比数列の形で「同じ行動を繰り返すほど価値が減る」直感をコードに落とし込んでいる。2なぜ探索の当たりでexploit_streakをリセットするのか
if rng.random() < P_HIT:
cv += rng.uniform(EXPLORE_GAIN_LOW, EXPLORE_GAIN_HIGH)
n_hit += 1
exploit_streak = 0
3なぜ適応的ルールが常に探索する戦略に勝てるのか
# always_explore mean_gain=0.02201 mean_n_explore=20.00 mean_n_hit=4.00
# adaptive mean_gain=0.02257 mean_n_explore=11.10 mean_n_hit=2.22
P_HIT=0.20なので20回中平均4回しか当たらず、残り16回は提出を消費するだけで終わる。適応的ルールは活用の伸びしろが逓減しきった(exploit_streak>=3)タイミングだけ探索に切り替えるため、探索回数自体は少ない(平均11.10回)にもかかわらず、合間の活用フェーズで着実に小さな改善を積み重ねている。「探索の回数を増やせば良い」のではなく「稼げるうちは活用し、稼げなくなったら探索する」メリハリの方が効率的という結果になった。🔁 追加検証(K_ADAPTIVEを変化させた場合)
適応的ルールの「何回連続で活用したら探索に切り替えるか」を決めるK_ADAPTIVEを1〜10まで変化させ、平均改善幅と標準偏差(リスク)がどう変化するかを確認した。
# K= 1 mean_gain=0.02371 std=0.00967
# K= 2 mean_gain=0.02348 std=0.00866
# K= 3 mean_gain=0.02254 std=0.00776 ← 今日のメイン設定
# K= 4 mean_gain=0.02137 std=0.00696
# K= 5 mean_gain=0.02010 std=0.00618
# K= 7 mean_gain=0.01761 std=0.00520
# K=10 mean_gain=0.01474 std=0.00361
🧮 数学・統計の補足(文系向け)
初心者がジョギングを始めると、最初の数週間はタイムがどんどん縮みます。しかしある程度速くなると、同じ練習を続けてもタイム短縮のペースはどんどん小さくなっていきます。今日の「活用」の
EXPLOIT_DECAY ** exploit_streakはこれと同じ構造で、同じ種類の練習(アンサンブル調整)を繰り返すほど、1回あたりの伸びが指数関数的に小さくなっていくことを表しています。
活用だけをずっと続けた場合の合計改善幅は、次の等比数列の和で計算でき、実測
mean_gain=0.00598と一致することが確認できます。
total = EXPLOIT_BASE_GAIN * sum(EXPLOIT_DECAY**i for i in range(N_STEPS))
= 0.0015 * (1 - 0.75**20) / (1 - 0.75)
= 0.0015 * 3.9873...
= 0.005981...
「1回目は満額、2回目は0.75倍、3回目は0.75×0.75倍…」と効果が等比数列的に小さくなっていくとき、無限に続けても合計は無限に大きくならず、ある一定の値(EXPLOIT_BASE_GAIN / (1 - EXPLOIT_DECAY) = 0.006)に近づいて頭打ちになります。中学・高校で習う等比数列の和の公式が、そのままKaggleの「チューニングの限界」の説明に使えます。
🏆 Kaggleでの実践的な使い方
よく使われるコンペカテゴリ: ☑ 表形式データ(Tabular) / ☑ 自然言語処理(NLP) / ☑ 画像認識(CV) / ☑ 時系列(Time Series)
🚦 停滞したら探索、まだ伸びるなら活用
状態依存の判断が固定スケジュールより効率的
「直近N回の提出でCVが改善しなかったら、次は毛色の違う特徴量を試す」という自分ルールを事前に決めておくと、感覚的な判断のブレを減らせる。
⏳ 残り予算とKの関係
終盤は探索への切替を遅らせる
探索への切り替えを早める(Kを小さくする)ほど平均は良くなるがばらつきも増える。終盤で順位を安定させたい局面では、あえて活用寄りに倒す判断が合理的。
🌱 探索の当たりは次の伸びしろを生む
見つけたらすぐ固定せず、周辺を再調整する
新しい特徴量やモデルを見つけたら、その周辺の重み・パラメータを改めてチューニングする余地があることを意識する。exploit_streakのリセットはこの相乗効果を表す。
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| CVが停滞したら、とにかく新しいことを試すべきだと思い込む | 「停滞=行き詰まり」という直感的な焦り | 今日の実測では常に探索する戦略(mean_gain 0.02201)は適応的ルール(0.02257)にわずかに劣り、探索だけに頼ると外れの多さ(20回中平均16回が外れ)で提出を浪費する |
| 探索の回数を増やせば増やすほど平均的な結果は良くなると思う | 「試行回数=改善のチャンス」という単純化 | 適応的ルールは探索回数が平均11.10回と常に探索する戦略の20回より少ないのに、平均改善幅は上回った。回数より「稼げなくなったタイミングで切り替える」判断の質が重要 |
| 「前半探索・後半活用」という固定スケジュールは十分合理的だと思う | カレンダー的な区切りは分かりやすく実践しやすい | 今日の実測で固定半々(mean_gain 0.01662)は適応的ルール(0.02257)や常に探索(0.02201)より明確に劣った。実際に停滞しているかを見ずに時期だけで判断すると、活用で稼げる伸びしろを無駄にする |
| リスクを気にせず、平均改善幅が一番良い戦略(小さいK)を常に選ぶべきだと思う | 平均値だけを見て判断してしまう | 追加検証の通り、平均改善幅が良い戦略ほど標準偏差(ばらつき)も大きい。残り予算が少ない終盤など安定した結果が必要な局面では、平均だけでなくばらつきも判断材料にすべき |
🚀 次のステップ
- 発展: 今日は
P_HIT=0.20(探索の当たり確率)を固定したが、コンペの種類によってはもっと低い場合もある。P_HITを0.05〜0.30の範囲で変化させたとき、常に探索する戦略と適応的ルールの優劣がどう変わるか試してみましょう。探索が外れた場合に何らかのペナルティ(余分な提出消費・作業時間の圧迫)を加えたシミュレーションへの拡張も可能です - 次回予告: Phase6テーマ4「コンペ戦略・Gold 3枚達成プラン」の3日目に入ります。次回はチーム戦(チームマージ)の意思決定、1人で最後まで戦うか途中で他の参加者とチームを組むかの判断を、スコアの伸びと労力配分の観点から扱う予定です
Phase 6 の学習マップ(全20テーマ)
Day141で最終提出の選び方(CV vs Public LB)、Day142で進行中の探索/活用トレードオフを実測。次回以降、チームマージ判断や提出予算の実務判断に進む。
📝 自己評価(解いた後に記入)
自分の回答・気づき・メモ: