📚 背景知識(読んでから問題へ)
今日の題材: mixup(Zhang et al., 2018, ICLR)の核心の数式:
x' = λxi + (1−λ)xj y' = λyi + (1−λ)yj
日本語で言い換えると:「2つのサンプルの特徴量をλ対(1−λ)で線形補間して新しい入力x'を作り、正解ラベルも同じ比率で混合ラベルy'を作る」。λは毎回Beta(α,α)からランダムに1個サンプリングする。レシピAとレシピBをランダムな配合比率で毎回混ぜて食べさせ続けるようなもの。
🪄 Mixupの適用ステップ(今日の学習ループの流れ)
①バッチをシャッフル
randpermで相手サンプルの並びindexを作る
②λをサンプリング
Beta(α,α)から混合比率λを1個取り出す
③x, yを線形補間
x'=λx+(1−λ)x[index]、y_a・y_bはそのまま保持
④混合損失を計算
λ·CE(pred,y_a)+(1−λ)·CE(pred,y_b)
⑤確信度も記録
test setのmean max softmax probabilityを比較
EMA(Day128)が「学習の全ステップをまたいで状態を蓄積する」のに対し、Mixupは「1バッチごとに独立に完結する処理」である点が対照的。SAM(Day127)と同じく1ステップ内で完結するが、SAMは勾配を、Mixupは入力データそのものを操作する。
🎯 問題
Day126〜128と同じ合成分類データに対し、通常の学習(Mixupなし)と、Mixupを使った学習を比較します。比較する軸は「test精度」だけでなく、モデルの自信度(確信度)が過剰でないかという新しい軸も見ます。
| 項目 | 値 |
|---|---|
| タスク | 5クラス分類(make_classification, n_samples=2400, n_features=20) |
| ラベルノイズ率 | flip_y=0.03(3%程度) |
| train / test | 先頭1800件 / 残り600件 |
| 学習設定 | epochs=60、lr=1e-3。baseline(Mixupなし)とmixup(alpha=0.2)を同条件で比較 |
| 比較対象 | baseline vs mixup(test精度 + mean max softmax probability) |
| 評価軸 | test精度・確信度(mean max prob)+ alpha感度(0.1/0.2/0.4/1.0) |
import numpy as np from sklearn.datasets import make_classification # --- 合成データ生成(5クラス分類。Day126/127/128と同系統) --- X, y = make_classification( n_samples=2400, n_features=20, n_informative=12, n_classes=5, n_clusters_per_class=1, random_state=42, flip_y=0.03, ) TRAIN_LEN = 1800 # 先頭1800件をtrain、残り600件をtest # epochs=60、lr=1e-3程度で、baseline(Mixupなし)とmixup適用ありを同条件で比較する
mixup_data(x, y, alpha)関数として実装するlam * loss_fn(pred, y_a) + (1 - lam) * loss_fn(pred, y_b)という形で計算するmixup_criterionを実装する💡 ヒント
Mixupの実装で最初につまずきやすいのは「混合ラベルy'をどう表現するか」です。素直に考えると`[0.6, 0, 0, 0.4, 0]`のような混合one-hotベクトルとして持ちたくなりますが、実装上はもっと簡単な近道があります。混合ラベルを明示的に作らず、「混合前の2つの整数ラベルy_i, y_j」と「混合比率lam」だけを持ち回り、損失計算の最後の一瞬だけlamで重み付けするという設計にすると、既存のCrossEntropyLoss(整数ラベルをそのまま受け取れる)がそのまま使えます。
- 交差エントロピー損失は正解分布に対して線形な演算なので、CE(pred, λyi+(1−λ)yj) = λ·CE(pred,yi) + (1−λ)·CE(pred,yj) という等式が成り立つ(交差エントロピーの線形性)。「混合ラベルに対する損失」は「混合前の2つのラベルそれぞれへの損失を、同じlamで加重平均するだけ」で正確に計算できる
mixup_dataでは、バッチ内のインデックスをtorch.randpermでシャッフルし、「元の順番のx, y」と「シャッフルした順番のx, y」の2組をlamで混ぜる- 「確信度(mean max softmax probability)」は
torch.softmax(logits, dim=1).max(dim=1).values.mean()で計算できる。1に近いほど「自信満々」、下がるほど「慎重」という意味になる - 確信度が高すぎる(over-confidence)ことは必ずしも良いモデルを意味しない。的中率と自信のズレはKaggleのLoglossベース評価指標のスコアを直接悪化させうる
import numpy as np import torch def mixup_data(x, y, alpha=0.2): lam = np.random.beta(alpha, alpha) if alpha > 0 else 1.0 batch_size = x.size(0) index = torch.randperm(batch_size) mixed_x = lam * x + (1 - lam) * x[index] y_a, y_b = y, y[index] return mixed_x, y_a, y_b, lam def mixup_criterion(loss_fn, pred, y_a, y_b, lam): return lam * loss_fn(pred, y_a) + (1 - lam) * loss_fn(pred, y_b) # 学習ループ内: # mixed_x, y_a, y_b, lam = mixup_data(xb, yb, alpha=0.2) # pred = model(mixed_x) # loss = mixup_criterion(loss_fn, pred, y_a, y_b, lam)
✅ 模範解答
記号↔変数の対応表
| 論文の記号 | 意味 | コード上の変数 |
|---|---|---|
| xi, yi | 元の順番のサンプルと整数ラベル | x, y_a (= y) |
| xj, yj | シャッフル後のサンプルと整数ラベル | x[index], y_b (= y[index]) |
| λ | Beta(α,α)からの混合比率 | lam |
| x' | 混合済みの入力 | mixed_x |
| (y'への損失) | 混合ラベルに対する交差エントロピー | mixup_criterion(...) |
import numpy as np import torch import torch.nn as nn from sklearn.datasets import make_classification from torch.utils.data import TensorDataset, DataLoader torch.manual_seed(0) # --- サンプルデータ生成(問題文と同じ) --- X, y = make_classification( n_samples=2400, n_features=20, n_informative=12, n_classes=5, n_clusters_per_class=1, random_state=42, flip_y=0.03, ) TRAIN_LEN = 1800 X_mean, X_std = X[:TRAIN_LEN].mean(axis=0), X[:TRAIN_LEN].std(axis=0) X_scaled = (X - X_mean) / X_std X_train = torch.tensor(X_scaled[:TRAIN_LEN], dtype=torch.float32) y_train = torch.tensor(y[:TRAIN_LEN], dtype=torch.long) X_test = torch.tensor(X_scaled[TRAIN_LEN:], dtype=torch.float32) y_test = torch.tensor(y[TRAIN_LEN:], dtype=torch.long) train_loader = DataLoader(TensorDataset(X_train, y_train), batch_size=32, shuffle=True) class MLP(nn.Module): def __init__(self, in_dim=20, hidden=32, num_classes=5): super().__init__() self.net = nn.Sequential( nn.Linear(in_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, num_classes), ) def forward(self, x): return self.net(x) loss_fn = nn.CrossEntropyLoss() # --- ステップ1/2: Mixupの数式をそのままコードに翻訳 --- def mixup_data(x, y, alpha=0.2): lam = np.random.beta(alpha, alpha) if alpha > 0 else 1.0 index = torch.randperm(x.size(0)) mixed_x = lam * x + (1 - lam) * x[index] y_a, y_b = y, y[index] return mixed_x, y_a, y_b, lam def mixup_criterion(loss_fn, pred, y_a, y_b, lam): # CE(pred, lam*y_i + (1-lam)*y_j) == lam*CE(pred,y_i) + (1-lam)*CE(pred,y_j)(線形性) return lam * loss_fn(pred, y_a) + (1 - lam) * loss_fn(pred, y_b) def evaluate(model): model.eval() with torch.no_grad(): logits = model(X_test) preds = logits.argmax(dim=1) acc = (preds == y_test).float().mean().item() conf = torch.softmax(logits, dim=1).max(dim=1).values.mean().item() return acc, conf # --- ステップ3: baseline / mixup 共通の学習ループ --- def train(epochs=60, lr=1e-3, use_mixup=False, alpha=0.2): torch.manual_seed(0) model = MLP() optimizer = torch.optim.Adam(model.parameters(), lr=lr) for epoch in range(epochs): model.train() for xb, yb in train_loader: optimizer.zero_grad() if use_mixup: mixed_x, y_a, y_b, lam = mixup_data(xb, yb, alpha=alpha) pred = model(mixed_x) loss = mixup_criterion(loss_fn, pred, y_a, y_b, lam) else: pred = model(xb) loss = loss_fn(pred, yb) loss.backward() optimizer.step() return evaluate(model) # --- ステップ4: baseline vs mixup(alpha=0.2) の比較 --- acc_base, conf_base = train(use_mixup=False) acc_mix, conf_mix = train(use_mixup=True, alpha=0.2) print(f"baseline : acc={acc_base:.4f} mean_max_prob(確信度)={conf_base:.4f}") print(f"mixup0.2 : acc={acc_mix:.4f} mean_max_prob(確信度)={conf_mix:.4f}") # 出力例(乱数・実行環境により変動): # baseline : acc=0.7150 mean_max_prob(確信度)=0.9350 # mixup0.2 : acc=0.7283 mean_max_prob(確信度)=0.8210 # → mixupの方がtest精度はやや高く、確信度(mean_max_prob)は明確に低い(=過剰な自信を抑えている) # --- ステップ5: alphaの感度確認 --- for alpha in [0.1, 0.2, 0.4, 1.0]: acc, conf = train(use_mixup=True, alpha=alpha) print(f"alpha={alpha:<4} acc={acc:.4f} mean_max_prob={conf:.4f}") # alpha=0.1 acc=0.720 mean_max_prob=0.885 (混合はやや弱く、baselineに近い挙動) # alpha=0.2 acc=0.728 mean_max_prob=0.821 (適度な混合。精度・確信度ともバランスが良い) # alpha=0.4 acc=0.726 mean_max_prob=0.760 (混合が強まり確信度はさらに低下、精度は横ばい) # alpha=1.0 acc=0.705 mean_max_prob=0.690 (lamが一様分布に近づき混合が強すぎ、精度がやや悪化)
📊 確信度の可視化(alpha別の test精度 vs mean max prob)
🪜 Step-by-Step 解説
1なぜ「混合one-hotラベル」を作らずに済むのか
def mixup_criterion(loss_fn, pred, y_a, y_b, lam): return lam * loss_fn(pred, y_a) + (1 - lam) * loss_fn(pred, y_b)
2mixup_dataのindex(シャッフル)が担う役割
index = torch.randperm(x.size(0)) mixed_x = lam * x + (1 - lam) * x[index]
3「確信度(mean max softmax probability)」という新しい評価軸の意味
4alphaがλの分布の形をどう変えるか
🧮 数学・統計の補足(文系向け)
赤色(RGB的には(1,0,0))と青色((0,0,1))を絵の具のように混ぜ合わせることを想像してください。赤を70%・青を30%の分量で混ぜると、0.7×(1,0,0) + 0.3×(0,0,1) = (0.7,0,0.3)という「赤紫っぽい色」ができます。これがx' = λxi + (1−λ)xjという線形補間の意味です。Mixupではこの「混色」を特徴量ベクトルに対して行い、正解ラベルも同じ配合比率で混ぜることで、モデルに「赤7割・青3割の色には、正解も『赤7割・青3割』くらいの気持ちで答えるべきだ」と教えているのです。
Beta(α,α)は0〜1の値だけを取る確率分布で、αが小さいほど「0付近か1付近(両端)」に出やすく偏り、αが大きい(特にα=1)ほど「0〜1の間のどの値も同じ確率」で出るようになります。αを「両端に偏った出目のコイン」から「完全に公平なサイコロ」へと連続的に変化させるダイヤルだと考えると、alphaの調整が「混合の強さ」をコントロールしていることが直感的に理解できます。
🏆 Kaggleでの実践的な使い方
よく使われるコンペカテゴリ: ☐ 表形式データ(Tabular) / ☐ 自然言語処理(NLP) / ☑ 画像認識(CV) / ☐ 時系列(Time Series)
🖼️ 画像分類・物体検出コンペ全般
上位解法でほぼ定番のaugmentation
特に訓練データが少ない・クラス間の境界があいまいなタスクで効果が出やすい。
✂️ CutMix / Manifold Mixup
同じ発想の応用形
CutMixは画像の一部領域を別画像で「切り貼り」して混ぜる。Manifold Mixupは中間層の特徴量にMixupを適用する。
📋 表形式(Tabular)での注意点
カテゴリ変数には慎重に
「性別=0.7×男+0.3×女」のような意味をなさない中間値を生むことがある。数値変数のみに限定するなどの工夫が必要。
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| 混合ラベルy'を毎回one-hotベクトルとして明示的に構築してしまう | 数式y' = λyi+(1−λ)yjを字面どおりに実装しようとする | 交差エントロピー損失の線形性を使えば、lam*loss(pred,y_a)+(1-lam)*loss(pred,y_b)という形で、整数ラベルのまま等価な計算ができる |
| Mixupを適用すればするほど精度が上がると考えてしまう | 「データ拡張は多いほど良い」という一般論からの類推 | alphaを大きくしすぎると混合が強くなりすぎ、元のサンプルの情報が薄まりかえって精度が落ちることがある(今日の実験でもalpha=1.0で悪化) |
| test精度だけを見て「Mixupに効果がない」と判断してしまう | Day126〜128と同じ評価軸(test精度)だけで比較しようとする | Mixupの効果はtest精度そのものよりもモデルの確信度(キャリブレーション)に強く表れることが多い。精度が横ばいでも確信度が改善していればLoglossベースの評価指標では有意な改善になりうる |
| カテゴリ変数を含む表形式データにも画像と同じ感覚でそのままMixupを適用してしまう | 「Mixupは万能なaugmentation」という思い込み | カテゴリ変数の線形補間は意味をなさない中間値を生むことがある。数値変数のみに限定する等の工夫が必要 |
🚀 次のステップ
- 発展: alphaごとの実験結果を、横軸alpha・縦軸mean_max_probの折れ線グラフとしてプロットし、「alphaが大きくなるほど確信度が単調に下がっていくか」を可視化してみましょう。単調でない箇所があれば、乱数・サンプル数による限界なのか、本質的な非単調性なのかを考えると良い訓練になります
- 次回予告: Day 130 — SOTA手法の追跡・論文実装⑤(Phase6テーマ1の最終日)。Label Smoothing(損失)・SAM(Optimizer)・EMA(重みの後処理)・Mixup(入力データ)と4つの異なる切り口を反復してきた総仕上げとして、Knowledge Distillation(教師モデルの出力分布を生徒モデルに温度付きKLダイバージェンスで学習させる手法)を扱い、「2つのモデルが協調して学習する」という5つ目の全く新しい切り口に挑みます
Phase 6 の学習マップ(全20テーマ)
Day126でLabel Smoothing(損失関数の改造)、Day127でSAM(Optimizerの改造)、Day128でEMA(学習ループ外での重みの後処理)、Day129でMixup(入力データそのものの改造)と、「論文→実装」ワークフローを異なる切り口で4回反復。次はいよいよテーマ1の最終日。
📝 自己評価(解いた後に記入)
自分の回答・気づき・メモ: