深層学習のデバッグ
田中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分