Day 129 — SOTA手法の追跡・論文実装④ — Mixupで「境界のあいまいさ」を教える

2026-08-19 金 / Phase 6(Grandmaster、テーマ1/5 の 4日目) 理論→コーディング 数式→実装の翻訳 / データ拡張 / 確信度(キャリブレーション)の比較

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

🔁
Day126でLabel Smoothing(損失関数)、Day127でSAM(Optimizer)、Day128でEMA(学習ループ外での重みの後処理)を扱いました。今日のMixupはこれらのどれとも違い、「入力データそのもの」を改造するという4つ目の全く別の切り口です。同じ「論文の数式→実装」ワークフローを、4回目の異なる題材で反復して、パターンを体に染み込ませます。
🖼️
なぜ「2つのサンプルを混ぜる」だけで効果があるのか: 通常の学習では、1枚の訓練データには「これはクラスA」という1つの正解ラベル(one-hot)しか対応しません。そのためモデルは「クラスAの典型例」と「クラスBの典型例」しか見ておらず、その中間にあるようなあいまいなテストデータに対しても過剰に自信満々に答えてしまう傾向があります。犬の写真と猫の写真を透明度40%で重ね合わせると「6割猫・4割犬」に見える合成写真ができるように、Mixupはこの合成写真を訓練データとして使い、正解ラベルも同じ比率で混合するという発想です。
x_i(クラスA)
+
x_j(クラスB)
→
x'(λ=0.6)

今日の題材: mixup(Zhang et al., 2018, ICLR)の核心の数式:

x' = λxi + (1−λ)xj  y' = λyi + (1−λ)yj

日本語で言い換えると:「2つのサンプルの特徴量をλ対(1−λ)で線形補間して新しい入力x'を作り、正解ラベルも同じ比率で混合ラベルy'を作る」。λは毎回Beta(α,α)からランダムに1個サンプリングする。レシピAとレシピBをランダムな配合比率で毎回混ぜて食べさせ続けるようなもの。

🪄 Mixupの適用ステップ(今日の学習ループの流れ)

1

①バッチをシャッフル

randpermで相手サンプルの並びindexを作る

2

②λをサンプリング

Beta(α,α)から混合比率λを1個取り出す

3

③x, yを線形補間

x'=λx+(1−λ)x[index]、y_a・y_bはそのまま保持

4

④混合損失を計算

λ·CE(pred,y_a)+(1−λ)·CE(pred,y_b)

5

⑤確信度も記録

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適用ありを同条件で比較する
1
数式→コードの翻訳(メイン): Mixupの数式を、ミニバッチ(x,y)を受け取り混合済み入力x'・2種類のラベルy_a/y_b・混合比率lamを返すmixup_data(x, y, alpha)関数として実装する
2
Mixup用の損失計算: 混合ラベルに対する交差エントロピーをlam * loss_fn(pred, y_a) + (1 - lam) * loss_fn(pred, y_b)という形で計算するmixup_criterionを実装する
3
2種類の学習: epochs=60で、baseline学習とalpha=0.2のmixup学習をそれぞれ行う
4
比較実験: test精度に加え、test setに対するモデルの確信度(mean max softmax probability)をbaseline/mixup両方で計算し比較する
5
alphaの感度確認: alphaを[0.1, 0.2, 0.4, 1.0]の4通りで試し、test精度と確信度の変化を確認する
6
(考察): Mixupがtest精度と確信度にそれぞれどう影響するかを100字程度で結論づける

💡 ヒント

ヒント1方向性

Mixupの実装で最初につまずきやすいのは「混合ラベルy'をどう表現するか」です。素直に考えると`[0.6, 0, 0, 0.4, 0]`のような混合one-hotベクトルとして持ちたくなりますが、実装上はもっと簡単な近道があります。混合ラベルを明示的に作らず、「混合前の2つの整数ラベルy_i, y_j」と「混合比率lam」だけを持ち回り、損失計算の最後の一瞬だけlamで重み付けするという設計にすると、既存のCrossEntropyLoss(整数ラベルをそのまま受け取れる)がそのまま使えます。

ヒント2アプローチ
  • 交差エントロピー損失は正解分布に対して線形な演算なので、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ベース評価指標のスコアを直接悪化させうる
ヒント3コード骨格
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が一様分布に近づき混合が強すぎ、精度がやや悪化)
🧭
タスク6・考察: Mixupを使うとtest精度はbaselineと同等かやや上回りつつ、モデルの確信度(mean max softmax probability)は明確に低下し、過剰な自信を抑える効果が確認できた。ただしalphaを大きくしすぎる(1.0)と混合が強くなりすぎてかえって精度が落ちており、alpha=0.2前後が精度と確信度のバランスとして妥当だと分かった。

📊 確信度の可視化(alpha別の test精度 vs mean max prob)

alpha別: test精度(青)と 確信度mean max prob(橙) 0.0 1.0 base 0.1 0.2 0.4 1.0 test精度 確信度 → alphaが大きいほど確信度(橙)は単調に低下、精度(青)はalpha=0.2付近でピークを迎え1.0でやや低下
🔍
この図は模式的なイメージ(実際の数値は実行環境により変動)。重要なのは「確信度が右に行くほど下がり続ける」のに対し「精度は途中でピークを打つ」という、2つの指標が同じ方向には動かない点。Mixupの強さは精度と確信度のトレードオフとして選ぶ必要がある。

🪜 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)
🔗
なぜこうするか: 交差エントロピー損失は正解分布に対して線形な演算である(CE(p, λqi+(1−λ)qj) = λ·CE(p,qi) + (1−λ)·CE(p,qj))。この数学的な性質のおかげで、混合ラベルy'を実際にベクトルとして構築する必要がなく、混合前の2つの整数ラベルy_i, y_jそれぞれに対する通常のCrossEntropyLossを、同じlamで加重平均するだけで数式どおりの結果が得られる。字面どおり実装すると遠回りになりがちだが、損失関数の性質を理解していると近道が見える典型例である。

2mixup_dataのindex(シャッフル)が担う役割

index = torch.randperm(x.size(0))
mixed_x = lam * x + (1 - lam) * x[index]
🧩
なぜこうするか: Mixupは「ミニバッチの中からランダムに2つのサンプルを選んで混ぜる」操作である。torch.randpermでバッチのインデックスをシャッフルし、「元の順番のバッチ」と「シャッフルされたバッチ」を組み合わせることで、各サンプルはバッチ内の別のランダムなサンプルと混ぜられる。N回のペア選択とシャッフルを1回のpermutationだけで同時に済ませている効率的なやり方である。

3「確信度(mean max softmax probability)」という新しい評価軸の意味

🎯
なぜこうするか: Day126〜128では「test精度」という1つの軸だけで評価してきたが、Mixupの主な効果は精度そのものよりもモデルの判断の"慎重さ"に表れやすい。torch.softmax(logits, dim=1).max(dim=1).valuesは「各サンプルについてモデルが最も自信を持ったクラスの確率」を表し、test set全体で平均して1に近いほど「常に断定的」、1から離れるほど「慎重(あいまいさを認めている)」ということになる。Mixupで学習したモデルのこの値がbaselineより低く出ることを確認することで、「Mixupは精度だけでなく、モデルの意思決定の性質そのものを変える」という論文の主張を数値で追体験できる。

4alphaがλの分布の形をどう変えるか

⚖️
なぜこうするか: alphaはBeta(α,α)の形状パラメータであり、λがどのあたりの値になりやすいかを決める。alphaが小さい(例: 0.1)とλは0か1の近くに偏りやすく(混合がほぼ「どちらか一方寄り」=baselineに近い挙動)、alpha=1.0だとλは0〜1の一様分布になり(「半々の完全な混合」が頻繁に起こる=元の情報が失われやすい)、中間のalpha(0.2〜0.4程度)が実務でよく使われる。数式のパラメータを動かして効き方を体感することが、論文を読むだけでは得られない「チューニングの土地勘」になる。

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

🎨
「線形補間」を、色の混色で理解する:

赤色(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分布」を、コイン投げの偏りぐあいで理解する:

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テーマ)

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

Day126でLabel Smoothing(損失関数の改造)、Day127でSAM(Optimizerの改造)、Day128でEMA(学習ループ外での重みの後処理)、Day129でMixup(入力データそのものの改造)と、「論文→実装」ワークフローを異なる切り口で4回反復。次はいよいよテーマ1の最終日。

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

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