LESSON

深層学習のデバッグ

田中VPoE:「深層学習では、コードは動くのに精度が出ないということがよくある。バグがエラーとして表出せず、黙って精度劣化するのが厄介なんだ。」

あなた:「学習曲線がおかしいとか、損失が下がらないとか、そういう問題ですね。」

田中VPoE:「そうだ。よくある落とし穴とその診断方法を知っておけば、問題の切り分けが格段に速くなる。」

勾配消失と勾配爆発

勾配消失(Vanishing Gradients)

深い層ほど勾配が小さくなり、浅い層が学習しなくなる問題です。

出力層の勾配: 1.0
   ↓ 逆伝播
層4の勾配: 0.1
層3の勾配: 0.01
層2の勾配: 0.001
層1の勾配: 0.0001  ← ほぼ更新されない
原因対策
Sigmoid/Tanh の使用ReLU 系に変更
深すぎるネットワークResidual Connection(スキップ接続)
不適切な初期化Kaiming/Xavier 初期化

勾配爆発(Exploding Gradients)

勾配が層を通るたびに増幅され、パラメータが発散する問題です。

原因対策
学習率が大きすぎる学習率を下げる
重みの初期値が大きい適切な初期化
長い系列(RNN)Gradient Clipping

勾配のモニタリング

def check_gradients(model):
    """各層の勾配の大きさを確認"""
    for name, param in model.named_parameters():
        if param.grad is not None:
            grad_norm = param.grad.norm().item()
            if grad_norm < 1e-7:
                print(f"[警告] 勾配消失: {name} (norm={grad_norm:.2e})")
            elif grad_norm > 100:
                print(f"[警告] 勾配爆発: {name} (norm={grad_norm:.2e})")

# 学習ループ内で使用
# loss.backward()
# check_gradients(model)

学習曲線の診断

学習曲線のパターン診断対策
train_loss が下がらない学習率が小さすぎる / モデルが小さすぎる学習率を上げる / モデルを大きくする
train_loss が発散する学習率が大きすぎる学習率を下げる
train_loss↓ val_loss↑過学習正則化、データ拡張、Early Stopping
両方がプラトー学習率が高すぎて最適解周辺で振動学習率を減衰させる
train_loss に NaN数値的な不安定性学習率を下げる、勾配クリップ

よくある落とし穴

1. model.train() / model.eval() の切り替え忘れ

# 間違い: 評価時に train モードのまま
for batch in val_loader:
    outputs = model(batch)  # Dropout が有効なまま → 不安定な評価

# 正しい
model.eval()
with torch.no_grad():
    for batch in val_loader:
        outputs = model(batch)

2. 勾配のリセット忘れ

# 間違い: 勾配が累積される
for batch in train_loader:
    outputs = model(batch)
    loss = criterion(outputs)
    loss.backward()  # 前のバッチの勾配に加算されてしまう
    optimizer.step()

# 正しい
for batch in train_loader:
    optimizer.zero_grad()  # 必ずリセット
    outputs = model(batch)
    loss = criterion(outputs)
    loss.backward()
    optimizer.step()

3. データの前処理ミスマッチ

# 間違い: 学習時と推論時の前処理が異なる
# 学習時に正規化あり、推論時に正規化なし → 精度が大幅に低下

# 正しい: 同じ前処理を統一的に適用

4. ラベルの不整合

# 間違い: BCEWithLogitsLoss に対して Sigmoid を適用済みの出力を渡す
model_output = torch.sigmoid(logits)  # 既に Sigmoid 適用
loss = nn.BCEWithLogitsLoss()(model_output, labels)  # 二重に Sigmoid

# 正しい: BCEWithLogitsLoss は内部で Sigmoid を適用する
loss = nn.BCEWithLogitsLoss()(logits, labels)  # logits をそのまま渡す

5. デバイスの不一致

# 間違い: モデルが GPU、データが CPU
model = model.to('cuda')
outputs = model(cpu_tensor)  # エラー

# 正しい: 同じデバイスに揃える
outputs = model(cpu_tensor.to('cuda'))

デバッグチェックリスト

まず確認すべきこと:

  • 小さなデータセット(10サンプル程度)で損失が0に近づくか? → モデルの学習能力を確認
  • 学習率を変えて損失の変化を確認したか?
  • 入力データの形状とラベルの対応は正しいか?
  • 前処理は学習時と推論時で統一されているか?
  • model.train() / model.eval() の切り替えは正しいか?
  • 勾配の大きさは正常か?(消失/爆発していないか)

まとめ

  • 勾配消失は ReLU、スキップ接続、適切な初期化で対策する
  • 勾配爆発は学習率の低減と Gradient Clipping で対策する
  • 学習曲線のパターンから問題を診断できる
  • よくある落とし穴(train/eval 切替、勾配リセット、前処理ミスマッチ)を把握しておく
  • 小さなデータで過学習できるか確認するのが最初のデバッグステップ

チェックリスト

  • 勾配消失と勾配爆発の原因と対策を説明できる
  • 学習曲線のパターンから問題を診断できる
  • よくある落とし穴を5つ以上列挙できる
  • デバッグの手順(小データで過学習確認→段階的に拡大)を理解した

推定読了時間: 15分