Day 149 — Phase 6 総復習 — 6テーマ横断のROIランキングと次の90日の時間配分

2026-09-08 金 / Phase 6(Grandmaster、総復習・区切り回) 分析→戦略→コーディング ROIランキング / 予算配分アルゴリズム

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

🗺️
Day1からDay148まででPhase1〜Phase6を一巡した。roadmap.md記載のPhase6主要テーマ6項目(SOTA手法追跡・カスタムアーキテクチャ・高度なアンサンブル・コンペ分析・TTA/TTAアンサンブル・解法公開/フォーラム)はDay126-148で全て着手済み。今日はその総復習として、5グループの実測改善幅と投入時間を1つの表にまとめ、「次の90日、どこに時間を張るべきか」をROI(投資対効果)という数値で決定する。

Phase6で学んだ5グループは、どれも「やった方がいい」ことに見える。しかし限られた時間で全部を同じ熱量でやろうとすると、実際にはメダルへの寄与が小さい作業に時間を吸われがちである。「改善幅 ÷ 投入時間 = ROI」という単一の指標に翻訳することで、初めて「どこに時間を張るべきか」を機械的に決定できる。

Phase6のグループ該当Day学んだこと(概要)
SOTA手法追跡・論文実装126-130Label Smoothing・SAM・EMA・Mixup・Knowledge Distillation
カスタムアーキテクチャ設計131-135Residual MLP・Gated MLP・Squeeze-and-Excite・Multi-Head Attention・Hybrid Block
高度なアンサンブル136-140Pseudo Labeling・TTA(ノイズ摂動・Feature Masking)・Stacking基礎
コンペ戦略141-145提出予算配分・探索と活用・チームマージ・メダル圏内距離・Gold3枚統合シミュレーション
解法公開・フォーラム146-148上位解法の読み方・write-upの信頼度判定(trust_score)・自分のwrite-up自動生成
⚖️
1点だけ質的に扱いが異なるグループがある: 「解法公開・フォーラム」はCVスコアを直接改善する活動ではない(write-upを書いてもCVは1ミリも変わらない)。しかしDay147-148で見た通り、コミュニティでの信頼構築・チームマージの誘い・将来の自分の記録という別軸の価値がある。ROI計算に無理やり混ぜると「予算ゼロでよい」という誤った結論になりかねないため、今日はROIで比較できる4グループと分離し、最低保証時間(フロア予算)を別枠で与える設計にする。

🗂️ Phase6ログの構造化データ

変数名型内容
phase6_loglist[dict]{"theme": グループ名, "days": 該当Day, "avg_cv_gain": 平均CV改善幅(質的グループはNone), "hours": 平均投入時間} を5件
phase6_log = [
    {"theme": "SOTA手法追跡", "days": "126-130", "avg_cv_gain": 0.0091, "hours": 14},
    {"theme": "カスタムアーキテクチャ", "days": "131-135", "avg_cv_gain": 0.0043, "hours": 22},
    {"theme": "高度なアンサンブル", "days": "136-140", "avg_cv_gain": 0.0158, "hours": 9},
    {"theme": "コンペ戦略", "days": "141-145", "avg_cv_gain": 0.0067, "hours": 5},
    {"theme": "解法公開・フォーラム", "days": "146-148", "avg_cv_gain": None, "hours": 12},
]

🎯 問題

タスクA(理論・戦略)

以下2点をそれぞれ2〜3文で説明する。

1
「改善幅÷投入時間」というROI指標だけで次の90日の時間配分を100%決めてしまうことの危険性を、Phase6の5グループのうちどれか1つを具体例に挙げて説明する
2
「解法公開・フォーラム」のように、ROIでは測れないがKaggle学習者にとって重要な活動を、フロア予算(最低保証時間)という形で別枠にする設計判断のメリットを説明する

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

B1
phase6_logを受け取り、avg_cv_gainが数値のグループについてroi = avg_cv_gain / hoursを計算しROI降順にソートしたDataFrameと、avg_cv_gainがNone(質的なグループ)のDataFrameを分けて返す関数 compute_roi(phase6_log) -> tuple[pd.DataFrame, pd.DataFrame] を実装する
B2
次の90日の総時間予算をtotal_hours=200とし、質的グループには1テーマあたりfloor_hours=15時間を最低保証として先に割り当て、残りをROIの大きさに比例して数値グループに配分する関数 allocate_budget(numeric_df, qualitative_df, total_hours=200, floor_hours=15) -> dict を実装する
B3
compute_roiの出力がROI降順になっていることをassert文で確認する
B4
allocate_budgetが返す配分の合計がtotal_hours(誤差0.5時間以内)に一致することをassert文で確認する
B5
(考察): ROIランキングの1位と最下位を挙げ、Phase4までのGBDT・アンサンブル系技術とPhase5-6のディープラーニング系技術(カスタムアーキテクチャ等)のどちらが時間対効果が高かったかを100〜150字程度で述べる

💡 ヒント

ヒント1方向性

タスクAの1.は、投入時間が極端に小さいテーマ(例えば5時間しか使っていないのにたまたま改善幅が大きかったテーマ)のROIが不自然に高く出てしまう可能性を考えるとよい(分散の小さいサンプルほど極端な比率が出やすい、という一般的な注意点)。タスクBは、Day145の「Gold3枚達成プラン統合シミュレーション」やDay146の「write-up技術のROIランキング」の延長線上にあるが、今日は「技術1つずつ」ではなく「Phase6のグループ単位」でROIを計算する点が異なる。

ヒント2アプローチ
  • compute_roi: avg_cv_gainがNoneかどうかを判定し、DataFrameを2つに分割する。ROIは列同士の演算で一括計算できる
  • allocate_budget: まず質的グループの人数 × floor_hoursをtotal_hoursから引いて「数値グループに配れる残り時間」を求める
  • 各数値グループの配分時間 = 残り時間 × (そのグループのROI ÷ 全数値グループのROI合計) という比例配分の式を使う
  • 合計がtotal_hoursにほぼ一致することの確認には、丸め誤差を許容するためabs(合計 - total_hours) < 0.5のような許容範囲を使う
ヒント3コード骨格
import pandas as pd

def compute_roi(phase6_log):
    df = pd.DataFrame(phase6_log)
    df["roi"] = df.apply(
        lambda r: (r["avg_cv_gain"] / r["hours"]) if r["avg_cv_gain"] is not None else None,
        axis=1,
    )
    numeric = df[df["roi"].notna()].sort_values("roi", ascending=False).reset_index(drop=True)
    qualitative = df[df["roi"].isna()].reset_index(drop=True)
    return numeric, qualitative

def allocate_budget(numeric_df, qualitative_df, total_hours=200, floor_hours=15):
    qual_total = floor_hours * len(qualitative_df)
    remaining = total_hours - qual_total
    weight_sum = numeric_df["roi"].sum()
    alloc = {}
    for _, row in numeric_df.iterrows():
        alloc[row["theme"]] = round(remaining * row["roi"] / weight_sum, 1)
    for _, row in qualitative_df.iterrows():
        alloc[row["theme"]] = float(floor_hours)
    return alloc

# ... assert でROI降順・合計200hを確認 ...

✅ 模範解答

import pandas as pd

phase6_log = [
    {"theme": "SOTA手法追跡", "days": "126-130", "avg_cv_gain": 0.0091, "hours": 14},
    {"theme": "カスタムアーキテクチャ", "days": "131-135", "avg_cv_gain": 0.0043, "hours": 22},
    {"theme": "高度なアンサンブル", "days": "136-140", "avg_cv_gain": 0.0158, "hours": 9},
    {"theme": "コンペ戦略", "days": "141-145", "avg_cv_gain": 0.0067, "hours": 5},
    {"theme": "解法公開・フォーラム", "days": "146-148", "avg_cv_gain": None, "hours": 12},
]

def compute_roi(phase6_log):
    df = pd.DataFrame(phase6_log)
    df["roi"] = df.apply(
        lambda r: (r["avg_cv_gain"] / r["hours"]) if r["avg_cv_gain"] is not None else None,
        axis=1,
    )
    numeric = df[df["roi"].notna()].sort_values("roi", ascending=False).reset_index(drop=True)
    qualitative = df[df["roi"].isna()].reset_index(drop=True)
    return numeric, qualitative

def allocate_budget(numeric_df, qualitative_df, total_hours=200, floor_hours=15):
    qual_total = floor_hours * len(qualitative_df)
    remaining = total_hours - qual_total
    weight_sum = numeric_df["roi"].sum()
    alloc = {}
    for _, row in numeric_df.iterrows():
        alloc[row["theme"]] = round(remaining * row["roi"] / weight_sum, 1)
    for _, row in qualitative_df.iterrows():
        alloc[row["theme"]] = float(floor_hours)
    return alloc

numeric_df, qualitative_df = compute_roi(phase6_log)
alloc = allocate_budget(numeric_df, qualitative_df, total_hours=200, floor_hours=15)
total = sum(alloc.values())

assert list(numeric_df["theme"]) == list(numeric_df.sort_values("roi", ascending=False)["theme"]), "ROI降順になっていません"
assert abs(total - 200) < 0.5, f"配分合計が200時間から乖離しています: {total}"
print("[OK] ROI降順ソート かつ 配分合計200h(誤差0.5h以内) を満たす")

# ============ 実測結果(実際にPython 3で実行して確認済み) ============
#          theme  avg_cv_gain  hours       roi
# 0    高度なアンサンブル       0.0158      9  0.001756
# 1        コンペ戦略       0.0067      5  0.001340
# 2     SOTA手法追跡       0.0091     14  0.000650
# 3  カスタムアーキテクチャ       0.0043     22  0.000195
#
#         theme  avg_cv_gain  hours
# 0  解法公開・フォーラム          NaN     12
#
# 高度なアンサンブル 82.4h / コンペ戦略 62.9h / SOTA手法追跡 30.5h /
# カスタムアーキテクチャ 9.2h / 解法公開・フォーラム 15.0h(フロア予算)
# 合計: 200.0h
# [OK] ROI降順ソート かつ 配分合計200h(誤差0.5h以内) を満たす
🧭
タスクA-1: 「コンペ戦略」グループは投入時間がわずか5時間しかないため、ROI(0.00134)は投入時間の小ささに引っ張られて大きく出やすい。投入時間が極端に短いテーマのROIは、サンプル数が少ない実験の結果と同様に振れ幅が大きく、鵜呑みにせず継続的に計測し直す必要がある。
🧭
タスクA-2: 「解法公開・フォーラム」はROIで測ればCV改善への寄与がゼロに見え、放っておくと予算配分がゼロになりかねない。しかし丁寧なwrite-upは将来の自分の学習記録・コミュニティでの信頼・チームマージの機会につながる別軸の価値を持つ。フロア予算という下限を機械的に設けることで、短期的なCV改善だけに時間配分が偏ることを防げる。
🧭
タスクB-5: 考察 — ROI1位は「高度なアンサンブル」(0.001756)、最下位は「カスタムアーキテクチャ」(0.000195)だった。Phase4までで培ったGBDT・アンサンブル寄りの技術を応用した「高度なアンサンブル」の方が、Phase5-6で新しく学んだディープラーニング系の「カスタムアーキテクチャ自作」よりも、同じ時間を投じたときの時間対効果が高いという結果になった。

📊 結果の可視化

① Phase6ログの棚卸し

phase6_log に5グループの平均CV改善幅・投入時間を構造化して保持

② ROI計算・降順ランキング

compute_roi() が数値グループと質的グループを分け、ROI降順にソート

③ 90日・200時間の配分

allocate_budget() がフロア予算+ROI比例配分で次の時間の使い方を決定

ROIランキング(改善幅 ÷ 投入時間、降順) 高度なアンサンブル 0.001756 コンペ戦略 0.001340 SOTA手法追跡 0.000650 カスタムアーキテクチャ 0.000195 解法公開・フォーラムはCVに直接効かないためROI計算の対象外(別枠のフロア予算15h) Phase4系(GBDT/アンサンブル)がPhase5-6系(DL/カスタムアーキ)よりROIが高い結果
次の90日・200時間の配分結果(合計200.0h) 高度なアンサンブル 82.4h コンペ戦略 62.9h SOTA手法追跡 30.5h 解法公開・フォーラム 15.0h(フロア予算) カスタムアーキテクチャ 9.2h 解法公開・フォーラムはROI外だが、フロア予算15hを先に確保した上で残り185hをROI比例配分
グループavg_cv_gainhoursroi配分(次の90日)
高度なアンサンブル0.015890.001756(1位)82.4h
コンペ戦略0.006750.00134062.9h
SOTA手法追跡0.0091140.00065030.5h
解法公開・フォーラムNone(質的)12対象外15.0h(フロア)
カスタムアーキテクチャ0.0043220.000195(最下位)9.2h

🪜 Step-by-Step 解説

1なぜ「グループ単位」でROIを計算するのか(Day146との違い)

phase6_log = [
    {"theme": "SOTA手法追跡", "days": "126-130", ...},
    ...
]
🗂️
なぜこうするか: Day146では「上位解法に書かれた個々の技術」1つずつのROIを扱ったが、今日は「Phase6の5日間ずつのグループ」というより粗い単位でROIを計算している。次の90日の計画を立てる段階では「MixUpを使うかSAMを使うか」という技術単位の意思決定より先に、「そもそもSOTA手法の勉強にどれだけ時間を割くべきか」というグループ単位の大きな配分を先に決める必要があるためである。

2なぜ質的なグループをROI計算から除外するのか

df["roi"] = df.apply(
    lambda r: (r["avg_cv_gain"] / r["hours"]) if r["avg_cv_gain"] is not None else None,
    axis=1,
)
⚖️
なぜこうするか: もし「解法公開・フォーラム」のavg_cv_gainを無理やり0として計算すると、ROIも自動的に0になり、他の4グループと同じ土俵で比較されて「予算配分ゼロ」という結論に流れてしまう。Noneのまま扱い、ROI計算の対象から明示的に除外することで、「measurement対象外である」という事実そのものをコードの構造で表現できる。これはDay147で「red flagをtrust_scoreの4基準に含めず別枠にした」判断と同じ設計思想である。

3なぜフロア予算を先に引いてから、残りを比例配分するのか

qual_total = floor_hours * len(qualitative_df)
remaining = total_hours - qual_total
weight_sum = numeric_df["roi"].sum()
alloc[row["theme"]] = round(remaining * row["roi"] / weight_sum, 1)
🧮
なぜこうするか: 先にフロア予算(解法公開・フォーラムの15時間)を確保してから、残りの185時間をROIに応じて比例配分することで、「質的な価値を持つ活動には必ず時間が残る」ことを数式のレベルで保証できる。もし先に比例配分をしてからフロア予算を足すと、total_hoursの合計が200時間を超えてしまい、assertが失敗する。「最低保証」と「ROI比例」という2種類の異なるロジックを、計算の順番で両立させている点がこのコードの設計上のポイントである。

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

💴
ROIを「時給」として理解する:

roi = avg_cv_gain / hoursという式は、アルバイトの時給計算「もらえた金額 ÷ 働いた時間」と全く同じ構造をしている。「高度なアンサンブル」は9時間で0.0158点分のCV改善を得たので、いわば「1時間あたり0.00176点」を稼ぐ"時給"が高い作業だったと言える。一方「カスタムアーキテクチャ」は22時間かけて0.0043点しか改善しておらず、"時給"は約9分の1(0.000195点/時間)しかない。同じ「頑張った」でも、時給換算すると得意分野と不得意分野がはっきり分かれることが分かる。
🍕
比例配分の式を「ピザの取り分」として理解する:

remaining * row["roi"] / weight_sumという式は、「185時間分のピザを、ROIという"お腹の空き具合"に応じて切り分ける」計算だと考えると分かりやすい。4人がそれぞれのお腹の空き具合(ROI)を申告し、それぞれの空き具合を全員の合計で割った"割合"だけピザ(残り時間)を受け取る。空き具合が2倍の人は2倍の分量を受け取り、全員の受け取り分を合計すると必ずピザ全体(185時間)に一致する、という比例配分の基本的な考え方である。

🏆 Kaggleでの実践的な使い方

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

原則

⏱️ コンペ中の時間配分を定量的に決める

感覚ではなく実測データに基づく判断

特徴量エンジニアリング・モデル選定・アンサンブル・write-upそれぞれの過去の改善幅と投入時間を記録しておけば、新しいコンペでも今日と同じROI計算で「今回はどこに時間を張るべきか」を機械的に決められる。

実例

🤝 チームでの役割分担の根拠にする

得意分野の可視化

チームメンバーそれぞれの得意分野のROIを比較することで、「誰がアンサンブル担当、誰が特徴量エンジニアリング担当か」という役割分担を実測データに基づいて決められる。

実務

🔍 「勉強のための勉強」の自己点検

学習目的とスコア目的のバランス

カスタムアーキテクチャの自作のように「勉強にはなるがROIは低い」活動と、アンサンブルのように「即座にスコアへ跳ね返るがROIが高い」活動を切り分けて記録することで、時間の使い方を意識的に選べるようになる。

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
ROIが一番高い活動だけに全ての時間を使えばよいと考える「一番効率がいいものに全振りすべき」という単純な最適化発想投入時間がゼロに近づくとその技術の再現性や汎用性の検証が不十分になる。ROI最大化と「様々な技術の引き出しを持つこと」はトレードオフであり、複数グループへ分散配分する設計が現実的である
CVスコアに効かない活動には時間を使う価値がないと考える「測れるものだけが価値」という数値至上主義解法公開・フォーラムのように、短期的なCVには効かなくても中長期的な学習効率・コミュニティでの信頼という別軸の価値を持つ活動がある。フロア予算のような仕組みで意図的に確保する必要がある
過去のROIが将来も同じ値で続くと思い込む一度計測した数値を固定的な真実として扱ってしまう今日のROIはあくまで過去5グループ・Day126-148の実測に基づく暫定値である。新しいコンペのドメインが変われば効果は変動しうるため、次のサイクルでも実測し直して更新する必要がある
投入時間が短いテーマのROIをそのまま信頼してしまう分母が小さいほど比率が不安定になることを意識しない投入時間が5時間しかない「コンペ戦略」のROIは振れ幅が大きい可能性がある。投入時間そのものも記録し、極端に短い時間で計算されたROIには注意書きを添えるなどの工夫が必要である

🚀 次のステップ

  • 発展: 今日は「Phase6の5グループ」という粗い粒度でROIを計算したが、実際のコンペではさらに細かく「特徴量エンジニアリングのうちどのサブカテゴリが効くか」「アンサンブルのうちどの重み付け手法が効くか」という階層的なROI計算に発展させられる。phase6_logのような構造化データを、より細かい粒度でも同じ形式で残しておく習慣が土台になる
  • 次回予告: Phase6の総復習が一区切りついたため、次回はPhase6の中でまだ深掘りしていない周辺テーマ(NLP系コンペへの応用、あるいは実在のPlayground Series/Tabularコンペへの実践投入を想定したシミュレーション)に取り組む予定。テーマ選定はDay150開始時に決定する

Phase 6 の学習マップ(総復習・Day149で一区切り)

1-5
SOTA手法追跡・論文実装(完了)
6-10
カスタムアーキテクチャ設計(完了)
11-15
高度なアンサンブル(完了)
16-20
コンペ戦略・Gold3枚達成プラン(完了)
21-23
解法公開・フォーラム(完了)
総復習
ROIランキング・時間配分の意思決定(Day149で完了)

roadmap.md記載のPhase6主要テーマ6項目はDay126-148で全て着手完了した。Day149の今日はそれらを1つの表に統合し、「次の90日、どこに時間を張るか」をROI計算と予算配分アルゴリズムという形で数値化した。ROI1位は高度なアンサンブル・最下位はカスタムアーキテクチャという結果を得た。

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

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