📚 背景知識(読んでから問題へ)
①データの量とクラス不均衡: 少ない・不均衡が強いほど、正則化(Augmentation)とFeature Extraction(凍結)が有効になりやすい
②計算予算: 少ないほど「軽いバックボーン → Feature Extraction → 段階的に重くする」の順で試す
③過学習の兆候: train/val lossの乖離(Day116)を見て、Augmentation強化・データ追加・モデル縮小のどれを選ぶか判断する
🗂️ CVブロック6日間まとめ(Day114〜119)
| Day | テーマ | 身についた武器 |
|---|---|---|
| 114 | CNN入門①(畳み込み・プーリング) | 画像がなぜCNNで扱えるか、nn.Conv2d/nn.MaxPool2dの直感 |
| 115 | CNN入門②(DataLoader・学習ループ) | torchvision.datasets・transforms・学習/評価ループの型 |
| 116 | CNN入門③(Augmentation基礎・過学習検知) | 基本Augmentation、学習曲線から過学習を見抜く目 |
| 117 | 転移学習入門(ResNet) | 事前学習済みモデルの再利用、Feature Extraction と Fine-tuning の違い |
| 118 | EfficientNet・timm入門 | timm.create_modelの統一API、compound scaling |
| 119 | Augmentation応用(RandAugment・Mixup・CutMix) | 強い正則化でCV-LB乖離を抑える、エポック数とのトレードオフ |
今日(Day120)はこの6日間を1つの意思決定フローに統合し、CVブロック(テーマ11-15相当)を完走する。
🎯 問題
架空のKaggle画像コンペ 「Plant Pathology 2026」(葉の病気を12クラスに分類、評価指標はmacro F1)に参加しています。
| 項目 | 値 |
|---|---|
| train | 6,500枚 / 12クラス(不均衡: 最小80枚・最大1,400枚) |
| test | 2,800枚 |
| 評価指標 | macro F1 |
| 実行制限 | 1回の実行につきGPU連続9時間まで |
| 今週のGPU残り時間 | 24時間 |
| コンペ残り日数 | 12日 |
recommend_cv_experiment_plan(...)を実装する。不均衡比率10倍以上でRandAugment開始、平均枚数300枚未満でFeature Extraction開始、Mixup/CutMixはepochs≥5限定、予算超過は提案しない、の4ルールを満たすefficientnet_b0+Feature Extraction+RandAugment、3epoch)の学習曲線から、Fine-tuning切替・Mixup/CutMix追加・バックボーン大型化のどれを最優先すべきか、根拠つきで結論を出す💡 ヒント
新しい技術は何も出てきません。「Day114〜119の各技術を、いつ・どの順番で・どんな条件で使うか」という意思決定のルールを言語化し、コードのif分岐として表現する回です。タスク3は「train_lossは順調に下がっているのにval_lossが下がらない/悪化している」というパターンをどう解釈するかがポイントです。
- 不均衡比率は
max_class_count / min_class_countで計算し、10倍を「一部のクラスがほぼ無視される」目安として使う - 平均枚数
num_train_images / num_classesが300枚未満なら、Day117の「データが少ないほどFeature Extractionを優先」をそのままコード化する - タスク3では、Epoch2以降val_lossが下がらずtrain_lossだけ下がり続けている点(Day116の過学習の兆候)と、今のモードがすでにFeature Extraction(凍結)である点を突き合わせて考える
- 「Fine-tuning」「Mixup/CutMix」「バックボーン大型化」のどれが表現力を上げる方向で、どれが正則化を強める方向かを整理してから、今の症状に合う方を選ぶ
def recommend_cv_experiment_plan(num_train_images, num_classes, min_class_count, max_class_count, gpu_hours_left): plan = [] imbalance_ratio = max_class_count / min_class_count avg_per_class = num_train_images / num_classes first_mode = "feature_extraction" if avg_per_class < 300 else "fine_tuning" first_aug = "randaugment" if imbalance_ratio >= 10 else "basic" # 実験1: 最も軽い efficientnet_b0 のベースライン確認 plan.append({ "backbone": "efficientnet_b0", "mode": first_mode, "augmentation": first_aug, "epochs": 3, "est_hours": 3.0, "reason": "最も安い実験でまず当たりを付ける(Day118: 軽量・高効率バックボーン)", }) # 実験2以降は残り予算を見ながら追加していく # ... Mixup/CutMixはepochs>=5の実験にのみ提案する、大きいバックボーンは2倍のest_hoursで見積もる ... return plan
✅ 模範解答
タスク1: 優先度順の実験リスト(GPU予算24時間を前提)
- 1efficientnet_b0 + Feature Extraction + 基本/RandAugment、3epoch — 最も安い実験でパイプライン全体が動くこと・ベースラインF1を確認する(Day118: 軽量バックボーン、Day117: 凍結で高速)
- 2実験1の学習曲線を見て過学習の有無を判定(Day116) — 新規学習コストゼロで、次の一手のボトルネック(データ不足かモデル力不足か)を切り分ける
- 3Fine-tuningへ切り替えて再学習(Day117) — Feature Extractionで頭打ちなら学習対象パラメータを増やす。ただし過学習の兆候が強い場合はAugmentation強化を先に行う
- 4RandAugment→Mixup/CutMixへ段階的に強化(epochs≥5、Day119) — 正則化を一段ずつ強め、val_f1の頭打ちが過学習由来であることを確認しながら潰す
- 5予算に余裕があればefficientnet_b3等へスケールアップ(Day118) — データ・正則化側の改善余地を使い切ってから、計算コストの高い打ち手を最後に残り予算の範囲で試す
タスク2: recommend_cv_experiment_plan の実装
def recommend_cv_experiment_plan( num_train_images: int, num_classes: int, min_class_count: int, max_class_count: int, gpu_hours_left: float, ) -> list[dict]: """Day114〜119の判断基準をコード化し、次に試すべき実験を優先度順に返す。""" plan = [] imbalance_ratio = max_class_count / min_class_count avg_per_class = num_train_images / num_classes # --- ルール1: 平均枚数300枚未満ならFeature Extractionから開始(Day117) --- first_mode = "feature_extraction" if avg_per_class < 300 else "fine_tuning" # --- ルール2: 不均衡比率10倍以上ならRandAugmentから開始(Day119) --- first_aug = "randaugment" if imbalance_ratio >= 10 else "basic" remaining = gpu_hours_left # --- 実験1: 必ずefficientnet_b0のベースライン確認から(Day118) --- exp1_hours = 3.0 if exp1_hours <= remaining: plan.append({ "backbone": "efficientnet_b0", "mode": first_mode, "augmentation": first_aug, "epochs": 3, "est_hours": exp1_hours, "reason": "最も安い実験で当たりを付ける(軽量バックボーン・Day118)", }) remaining -= exp1_hours # --- 実験2: Fine-tuningへの切り替え(実験1がfeature_extractionの場合のみ意味を持つ) --- exp2_hours = 4.0 if first_mode == "feature_extraction" and exp2_hours <= remaining: plan.append({ "backbone": "efficientnet_b0", "mode": "fine_tuning", "augmentation": first_aug, "epochs": 4, "est_hours": exp2_hours, "reason": "Feature Extractionで頭打ちの場合に学習対象パラメータを増やす(Day117)", }) remaining -= exp2_hours # --- 実験3: Mixup/CutMixはepochs>=5の実験としてのみ提案(Day119の教訓) --- exp3_hours = 5.5 if exp3_hours <= remaining: plan.append({ "backbone": "efficientnet_b0", "mode": "fine_tuning", "augmentation": "mixup_cutmix", "epochs": 5, "est_hours": exp3_hours, "reason": "十分なエポック数を確保したうえで強い正則化を試す(Day119: 3epochでは未収束)", }) remaining -= exp3_hours # --- 実験4: 予算に余裕があれば重いバックボーンへスケールアップ(学習時間は実験1の2倍と見積もる) --- exp4_hours = exp1_hours * 2 if exp4_hours <= remaining: plan.append({ "backbone": "efficientnet_b3", "mode": "fine_tuning", "augmentation": first_aug, "epochs": 4, "est_hours": exp4_hours, "reason": "軽量・正則化側の改善余地を使い切ってから、計算コストの高いスケールアップを最後に試す(Day118)", }) remaining -= exp4_hours return plan # --- 動作確認: Plant Pathology 2026設定 --- plan = recommend_cv_experiment_plan( num_train_images=6500, num_classes=12, min_class_count=80, max_class_count=1400, gpu_hours_left=24.0, ) for i, exp in enumerate(plan, 1): print(f"実験{i}: {exp['backbone']} / {exp['mode']} / {exp['augmentation']} / {exp['epochs']}epoch / 見積{exp['est_hours']}h") # 出力例: # 実験1: efficientnet_b0 / feature_extraction / randaugment / 3epoch / 見積3.0h # 実験2: efficientnet_b0 / fine_tuning / randaugment / 4epoch / 見積4.0h # 実験3: efficientnet_b0 / fine_tuning / mixup_cutmix / 5epoch / 見積5.5h # 実験4: efficientnet_b3 / fine_tuning / randaugment / 4epoch / 見積6.0h # (合計 18.5h ≤ 24h、予算内に収まっている)
avg_per_class = 6500/12 ≈ 541.7枚(300枚以上)に対しimbalance_ratio = 1400/80 = 17.5倍(10倍以上)が同時に成立する。今回のルール設計は平均枚数のみでモードを決めるため機械的にはfine_tuningになるが、タスク3の出題設定は「80枚しかない少数クラスが過学習しやすい」という追加リスクを人間側が読み取り、あえてFeature Extractionから始めたケースとして提示している。関数の機械的な判定と、人間が状況を見て下す最終判断は必ずしも一致しなくてよく、関数はあくまで「たたき台」である点が今回の設計上の重要な学び。タスク3: 実験1の結果から次の一手を判断する
Epoch 1: train_loss=1.82, val_loss=1.61, train_f1=0.41, val_f1=0.39 Epoch 2: train_loss=1.20, val_loss=1.55, train_f1=0.63, val_f1=0.42 # val_f1のピーク Epoch 3: train_loss=0.85, val_loss=1.58, train_f1=0.76, val_f1=0.41 # val_lossが悪化
| 選択肢 | 適合度 | 理由 |
|---|---|---|
| Fine-tuningに切り替える | ✗ 低い | 現状はすでに過学習気味。学習対象パラメータを増やす方向は過学習をさらに悪化させるリスクが高い |
| Mixup/CutMixを追加する | ◎ 最優先 | 過学習を抑える最も直接的な打ち手。ただしエポック数を5以上に増やしたうえで導入する必要がある(Day119) |
| バックボーンを大きくする | ✗ 低い | 表現力不足が原因である証拠はまだない。正則化側を先に試すべき段階 |
📊 GPU予算配分の可視化(合計24時間のうち何にいくら使うか)
安い実験(実験1)から高い実験(実験4)へ積み上げ、合計18.5h/24hに収める。残り5.5hはタスク3で判明した「想定外の過学習」への対応やコンペ終盤の再実行用バッファとして残す。
📉 学習曲線の読み解き(実験1の結果)
🪜 Step-by-Step 解説
1判断基準を「閾値付きのif分岐」として明文化する
first_mode = "feature_extraction" if avg_per_class < 300 else "fine_tuning" first_aug = "randaugment" if imbalance_ratio >= 10 else "basic"
2予算を消費しながら実験を積み上げる
remaining = gpu_hours_left if exp1_hours <= remaining: plan.append({...}) remaining -= exp1_hours
3Mixup/CutMixにはエポック数の下限を設ける
exp3_epochs = 5 if exp3_hours <= remaining: plan.append({..., "augmentation": "mixup_cutmix", "epochs": exp3_epochs, ...})
4学習曲線のパターンから原因を切り分ける
train_loss: 1.82 → 1.20 → 0.85(下がり続ける) val_loss: 1.61 → 1.55 → 1.58(Epoch3で悪化)
🧮 数学・統計の補足(文系向け)
Expected Value = P(改善する) × 改善幅 − 実験コスト→ これは「探索と活用のトレードオフ(Exploration vs Exploitation)」を数式にしたもの。今日のタスク1〜2は、
P(改善する)や改善幅を正確に見積もる代わりに、「安くて情報量の多い実験を先に、コストの高い実験を後に」という経験則ベースの近似解で意思決定している、と理解すると良い。
🏆 Kaggleでの実践的な使い方
よく使われるコンペカテゴリ: ☑ 画像認識(CV) / ☐ 表形式データ(Tabular) / ☐ 自然言語処理(NLP) / ☐ 時系列(Time Series)
📋 「実験計画表」はコンペ参加者の必需品
試す順番を書き出してから実装を始める
上位入賞者の多くはNotebookを書き始める前に、今日のタスク1のような「試す順番のメモ」をスプレッドシートやREADMEに書き出している。GPU予算が有限な個人参加者ほど、計画的な実験順序が最終スコアに直結する。
🩺 学習曲線の自動判定をサブミット前に
過学習の兆候を見逃さない簡易チェック
タスク3のような「train/valの乖離パターンから次の一手を選ぶ」ロジックは、簡易チェックとしてノートブックに常備しておくと、過学習の兆候を見逃さずに済む。
🗃️ 自作の意思決定関数は実験管理の第一歩
MLflow/W&Bの前段階として有効
本格的なコンペではMLflowやWeights & Biasesを使うが、個人開発の初期段階では今日作った自作の意思決定関数でも十分に「何を・なぜ試したか」を後から振り返れる仕組みになる。
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| 過学習の兆候が出たら、とりあえずモデルを大きくして精度を上げようとする | 「モデルが弱いから精度が伸びない」という直感に頼ってしまう | モデルを大きくするのは表現力不足のケースに有効。train_lossだけ下がりval_lossが頭打ちのケース(過学習)ではむしろ逆効果になりやすい |
| GPU予算を考えず、最初から一番強い設定で実験を始める | 「強い設定の方が良い結果が出るはず」と考え、段階を踏むのを面倒に感じる | 1回の実験に予算を使い切ると、何が効いたのか切り分けられなくなる。安い実験から始める方が限られた予算内で学びが多い |
| Mixup/CutMixのようなAugmentationは「多いほど良い」と考え常に全部盛りにする | 正則化を「品質向上のオプション」程度に軽く見てしまう | 強い正則化はエポック数など前提条件が揃って初めて効果が出る(Day119)。前提を無視すると単なる学習不足の原因になる |
| 意思決定関数の出力を絶対的な正解として鵜呑みにする | コードで出力された結果は「機械的で正しいはず」と思い込む | 関数はあくまで「たたき台」。クラス不均衡という追加リスクを人間側が読み取り、機械的な判定とは異なる選択をすることは十分にあり得る |
🚀 次のステップ
- 発展:
recommend_cv_experiment_planに、Day116で学んだ「学習曲線の乖離幅」を入力として受け取り、乖離が大きいほど自動的にAugmentationレベルを引き上げる(basic→randaugment→mixup_cutmix)ロジックを追加してみましょう。タスク3のような判断を関数の中に組み込んで自動化する第一歩になります - 次回予告: Day 121 — 時系列ディープラーニング入門(LSTM)。CVブロック(Day114〜120)が完了したので、次は「16-18 時系列(LSTM・Prophet・Temporal Fusion Transformer)」ブロックへ進みます。GBDTベースの時系列特徴量(Day098-100)とは異なる、ニューラルネットワークならではの時系列の扱い方を学びます
Phase 5 の学習マップ(全20テーマ予定)
Day114〜120の7日間でCNN基礎・転移学習・EfficientNet/timm・Augmentation応用・実験戦略までを一通り学び、CVブロックが完了した。次は時系列ブロック(16-18)へ進む。
📝 自己評価(解いた後に記入)
自分の回答・気づき・メモ: