学習ループ
田中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 | 適応的学習率 | 汎用的(最もよく使われる) |
| AdamW | Adam + Weight Decay | Transformer の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-Entropy | nn.BCEWithLogitsLoss() |
| 多クラス分類 | Cross-Entropy | nn.CrossEntropyLoss() |
| 回帰 | MSE | nn.MSELoss() |
| 回帰(外れ値に強い) | Huber Loss | nn.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分