LESSON

学習ループ

田中VPoE:「データ、モデル、ときたら次は学習ループだ。PyTorch では学習のフローを自分で書く。面倒に思うかもしれないが、この透明性が PyTorch の強みでもある。」

あなた:「scikit-learn の .fit() 一行とは違うんですね。」

田中VPoE:「そうだ。でもその分、Early Stopping やカスタムメトリクスの計算など、自由自在にカスタマイズできる。実務では必ず必要になるスキルだ。」

学習ループの全体構造

import torch
import torch.nn as nn
from torch.utils.data import DataLoader

# === 準備 ===
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
criterion = nn.BCEWithLogitsLoss()  # 損失関数
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

# === 学習ループ ===
num_epochs = 50

for epoch in range(num_epochs):
    model.train()  # 学習モード(Dropout, BatchNorm が有効)
    train_loss = 0.0

    for batch_X, batch_y in train_loader:
        batch_X, batch_y = batch_X.to(device), batch_y.to(device)

        # 1. 勾配のリセット
        optimizer.zero_grad()

        # 2. 順伝播
        outputs = model(batch_X)

        # 3. 損失計算
        loss = criterion(outputs, batch_y)

        # 4. 逆伝播
        loss.backward()

        # 5. パラメータ更新
        optimizer.step()

        train_loss += loss.item()

    # エポックごとの平均損失
    avg_train_loss = train_loss / len(train_loader)
    print(f"Epoch [{epoch+1}/{num_epochs}] Train Loss: {avg_train_loss:.4f}")

Optimizer の選択

Optimizer特徴推奨場面
SGD基本的な勾配降下法CNN の学習(momentum付き)
Adam適応的学習率汎用的(最もよく使われる)
AdamWAdam + Weight DecayTransformer のFine-tuning
RMSprop適応的学習率RNN の学習
# SGD(モメンタム付き)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=1e-4)

# Adam(最も一般的)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)

# AdamW(Transformer 向け)
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=0.01)

損失関数の選択

タスク損失関数PyTorch クラス
二値分類Binary Cross-Entropynn.BCEWithLogitsLoss()
多クラス分類Cross-Entropynn.CrossEntropyLoss()
回帰MSEnn.MSELoss()
回帰(外れ値に強い)Huber Lossnn.HuberLoss()
# BCEWithLogitsLoss: Sigmoid + BCE を1つにまとめたもの(数値的に安定)
criterion = nn.BCEWithLogitsLoss()

# CrossEntropyLoss: Softmax + NLLLoss を1つにまとめたもの
criterion = nn.CrossEntropyLoss()

学習曲線の記録と可視化

import matplotlib.pyplot as plt

history = {'train_loss': [], 'val_loss': [], 'val_acc': []}

for epoch in range(num_epochs):
    # --- 学習 ---
    model.train()
    train_loss = 0.0
    for batch_X, batch_y in train_loader:
        batch_X, batch_y = batch_X.to(device), batch_y.to(device)
        optimizer.zero_grad()
        outputs = model(batch_X)
        loss = criterion(outputs, batch_y)
        loss.backward()
        optimizer.step()
        train_loss += loss.item()

    # --- 検証 ---
    model.eval()  # 評価モード(Dropout, BatchNorm が無効)
    val_loss = 0.0
    correct = 0
    total = 0

    with torch.no_grad():  # 勾配計算を無効化
        for batch_X, batch_y in val_loader:
            batch_X, batch_y = batch_X.to(device), batch_y.to(device)
            outputs = model(batch_X)
            loss = criterion(outputs, batch_y)
            val_loss += loss.item()

            predicted = (torch.sigmoid(outputs) > 0.5).float()
            total += batch_y.size(0)
            correct += (predicted == batch_y).sum().item()

    # 記録
    history['train_loss'].append(train_loss / len(train_loader))
    history['val_loss'].append(val_loss / len(val_loader))
    history['val_acc'].append(correct / total)

# 可視化
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
ax1.plot(history['train_loss'], label='Train Loss')
ax1.plot(history['val_loss'], label='Val Loss')
ax1.set_xlabel('Epoch')
ax1.set_ylabel('Loss')
ax1.legend()
ax1.set_title('学習曲線(損失)')

ax2.plot(history['val_acc'], label='Val Accuracy')
ax2.set_xlabel('Epoch')
ax2.set_ylabel('Accuracy')
ax2.legend()
ax2.set_title('検証精度')
plt.tight_layout()
plt.show()

Early Stopping の実装

検証損失が改善しなくなったら学習を停止し、過学習を防ぎます。

class EarlyStopping:
    """Early Stopping の実装"""

    def __init__(self, patience=10, min_delta=1e-4):
        self.patience = patience
        self.min_delta = min_delta
        self.counter = 0
        self.best_loss = float('inf')
        self.best_model_state = None

    def __call__(self, val_loss, model):
        if val_loss < self.best_loss - self.min_delta:
            self.best_loss = val_loss
            self.counter = 0
            self.best_model_state = model.state_dict().copy()
            return False  # 学習を続行
        else:
            self.counter += 1
            if self.counter >= self.patience:
                # ベストモデルを復元
                model.load_state_dict(self.best_model_state)
                return True  # 学習を停止
            return False

# 使用例
early_stopping = EarlyStopping(patience=10)

for epoch in range(num_epochs):
    # ... 学習と検証 ...

    if early_stopping(val_loss, model):
        print(f"Early Stopping at epoch {epoch+1}")
        break

学習曲線の診断

パターン症状対策
過学習train_loss↓, val_loss↑Dropout増加、データ拡張、正則化
学習不足両方とも高いままモデルを大きくする、学習率を上げる
良好両方とも低く、差が小さいこのまま学習を続行
発散損失が増加し続ける学習率を下げる

まとめ

  • 学習ループは「勾配リセット → 順伝播 → 損失計算 → 逆伝播 → パラメータ更新」の5ステップ
  • model.train()model.eval() で学習/評価モードを切り替える
  • Optimizer はタスクに応じて選択(汎用: Adam、Transformer: AdamW)
  • 学習曲線を記録・可視化して過学習を監視する
  • Early Stopping で最適なエポックで学習を停止する

チェックリスト

  • PyTorch の学習ループの5ステップを実装できる
  • model.train()model.eval() の違いを理解した
  • Optimizer と損失関数を適切に選択できる
  • Early Stopping を実装して過学習を防止できる

推定読了時間: 30分