こんにちは!日々のディープラーニング(深層学習)の開発、本当にお疲れ様です。
PyTorchやTensorFlowを使っていて、こんな絶望的な状況に直面したことはありませんか?
- 「なんだこのエラーは……。`RuntimeError: size mismatch` って、一体どこのテンソルとどこのテンソルが噛み合ってないんだ?」
- 「損失(Loss)が全然下がらない。順伝播(Forward)の途中で、どこの層に変な値(NaNやゼロ)が混入しているんだ……?」
- 「ネットから拾ってきた複雑なTransformerのモデル、自分のカスタムデータを入れた途端に落ちるけど、内部のテンソル形状(Shape)がどう変化しているのか全く追えない……」
こういう時、とりあえず `print(tensor.shape)` をあちこちに埋め込んで、コードを書いては実行し、書いては実行し……なんて泥臭いデバッグをしていませんか?
実は、Python標準のデバッガである `pdb`(あるいはその超強力な進化系である `IPdb`)を使いこなせば、「計算グラフの任意の場所でプログラムをピタッと止め、動いているその瞬間のテンソル形状や中身をリアルタイムに覗き見する」 ことが簡単にできてしまいます。
今回は、初心者の方でも今日からすぐに実践できるように、AIモデルの推論・学習過程を手の内に入れるための「pdb/IPdbの極意」を、優しく丁寧にお伝えします。これをマスターすれば、毎日のエラーとの格闘劇が劇的に楽になりますよ!
—
1. なぜAI開発に `pdb / IPdb` が最強の武器になるのか?
深層学習フレームワークは、私たちが書いたコードを内部で「計算グラフ(Computation Graph)」にコンパイルして実行します。そのため、エラーが発生した時にトレースバック(スタックトレース)を見ても、「実際にその時、各テンソルがどんな形状(Shape)をしていて、どんな数値を持っていたのか」までは見えてきません。
ここで `print` デバッグの限界が来ます。モデルが何十層もある場合、すべての層の出力を print していたらコンソールが情報の海になり、肝心なバグを見落とします。
pdb と IPdb の違い
- `pdb`: Python標準のデバッガ。追加インストール不要でどこでも動くのが強みですが、画面が少し地味です。
- `IPdb`: IPythonベースのインタラクティブデバッガ。シンタックスハイライト、タブ補完、そして何より強力なオブジェクトのインスペクション機能が使えます。AI開発では、圧倒的に `ipdb` がおすすめです。
—
2. 導入と基礎セットアップ
まずは、最強の相棒である `ipdb` をインストールしましょう。あわせて、Jupyter環境ではなく、純粋なPythonスクリプト(`.py`ファイル)をデバッグする前提で話を進めます。
インストールコマンド
ターミナル(端末)を開いて、以下のコマンドを実行してください。
IPythonと統合された強力なデバッガである ipdb をインストール
pip install ipdb
たったこれだけです。準備は整いました。
—
3. 【実践】PyTorchのモデルでテンソル形状をリアルタイム監視する
百聞は一見に如かず。実際に小さなPyTorchのCNN(畳み込みニューラルネットワーク)モデルを題材にして、推論の途中で処理を止め、テンソルを覗いてみましょう。
以下のコードを `debug_sample.py` という名前で保存してください。
import torch
import torch.nn as nn
import ipdb # ← IPdbをインポート
サンプルのための簡単なCNNモデルを定義
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
# チャンネル数を 1 -> 8 に変換する畳み込み層
self.conv1 = nn.Conv2d(in_channels=1, out_channels=8, kernel_size=3, stride=1, padding=1)
# 2次元のプーリング層
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
# 全結合層(画像サイズがどう変わるか計算が必要な厄介な場所)
self.fc1 = nn.Linear(8 14 14, 10)
def forward(self, x):
print(f”入力直後のShape: {x.shape}”)
# ———————————————————
# 【重要】ここでプログラムを強制停止させるブレークポイントを設置
# ———————————————————
ipdb.set_trace()
x = self.conv1(x)
x = torch.relu(x)
x = self.pool(x)
# ここで形状が変わっているはず!
x = x.view(-1, 8 14 14)
x = self.fc1(x)
return x
if __name__ == “__main__”:
# ダミーの入力データを作成 (バッチサイズ=1, チャンネル=1, 高さ=28, 幅=28)
dummy_input = torch.randn(1, 1, 28, 28)
# モデルのインスタンス化
model = SimpleCNN()
# 推論(順伝播)を実行
output = model(dummy_input)
print(f”出力結果のShape: {output.shape}”)
実行とデバッグ操作の流れ
ターミナルからこのスクリプトを実行してみます。
python debug_sample.py
実行すると、次のような画面(IPdbのプロンプト)で処理がピタッと止まります。
入力直後のShape: torch.Size([1, 1, 28, 28])
> /path/to/debug_sample.py(20)forward()
-> x = self.conv1(x)
(Pdb)
この `(Pdb)` と書かれたプロンプトから、私たちはPythonの神様になったかのように、停止した瞬間の世界を自由に調べられます。
1. テンソルの形状(Shape)と中身を確認する
プロンプトに `x` や `x.shape` と打ち込んでみてください。
(Pdb) x.shape
torch.Size([1, 1, 28, 28])
(Pdb) x.device
device(type=’cpu’)
おお、現在の `x` がどんな形状で、どのデバイス(CPU/GPU)に乗っているのかが一目で分かります!
2. 次の行へ進む (`n` コマンド)
次の処理(`x = self.conv1(x)`)を実行して、畳み込み後の世界を見てみましょう。`n`(next)または `step` の `s` を入力します。
(Pdb) n
> /path/to/debug_sample.py(21)forward()
-> x = torch.relu(x)
そして、もう一度 `x.shape` を確認してみます。
(Pdb) x.shape
torch.Size([1, 8, 28, 28])
「なるほど、チャンネル数が `1` から `8` に増えているな!」というのが、リアルタイムに確認できました。
3. デバッグを終了して処理を再開する (`c` コマンド)
調査が終わったら、`c`(continue)と入力すれば、プログラムが通常の速度で最後まで実行されます。
(Pdb) c
出力結果のShape: torch.Size([1, 10])
これだけでも `print` デバッグ生活とはおさらばできるレベルの快適さですが、実務ではさらに一歩進んだテクニックが必要です。次を見ていきましょう。
—
4. プロフェッショナルな条件付きブレークポイントの極意
「バッチサイズが 32 のときはうまくいくのに、最後の余ったバッチ(例えばサイズが 5 とか)の時にだけ `RuntimeError` で落ちるんだよな……」
こんな現象、ディープラーニングでは本当によくあります。全ループでブレークポイントが発動してしまっては、何回 `c` を叩けばいいのか分からなくなってしまいますよね。
そんな時は、「特定の条件を満たした時だけ止める」 という技を使いましょう。
`ipdb.set_trace()` には条件式を直接渡すことはできませんが、Pythonの条件分岐と組み合わせることで、スマートに実現できます。
def forward(self, x):
# バッチサイズが変則的な時(例: 予期せぬサイズ)だけデバッガを起動する
if x.shape[0] != 32:
print(f”警告: 予期せぬバッチサイズを検知しました -> {x.shape}”)
ipdb.set_trace()
x = self.conv1(x)
# … 以下略
または、IPdbのプロンプト内で条件分岐させることも可能ですし、次のような強力なコマンドを pdb プロンプトから実行することもできます。
知っておくと一生モノの pdb 内部コマンド集
pdbのプロンプトに入った後、以下のコマンドを覚えているだけで、デバッグスピードが10倍になります。
| コマンド | 短縮形 | 役割 |
| :— | :— | :— |
| `next` | `n` | 関数に入らずに、次の行へ進む(ストップオーバー) |
| `step` | `s` | 関数やメソッドの内部へ潜り込む(ステップイン) |
| `continue` | `c` | 次のブレークポイントまで一気に実行を再開する |
| `print 変数名` | `p` | 変数の値を評価して表示する(単に変数名だけでもOK) |
| `whatis 変数名` | – | 変数の型やクラス情報を表示する |
| `pp 変数名` | – | 辞書やリストなどの大きなオブジェクトを綺麗に整形して表示する |
| `quit` | `q` | デバッグを強制終了し、プログラムをアボートさせる |
—
5. 先輩エンジニアからの実践アドバイス
最後に、実務の現場で私が後輩によく伝えている、pdb/ipdb を使いこなすためのマインドセットをシェアします。
1. コードに `ipdb.set_trace()` を仕込んだままコミットしない
うっかりそのまま Git にコミットしてしまい、CI/CDパイプライン(自動テスト)が途中で永遠に止まってしまうという事故が、世界中の開発現場で毎日起きています。「あ、デバッグ中だな」と気づけるように、自分なりのコメント(`# TODO: remove ipdb` など)を添えるか、コミット前のフックで検知できるようにしておきましょう。
2. PyTorchの自動微分(autograd)の勾配も覗ける
逆伝播(Backward)の途中で勾配が `NaN` になる現象(勾配爆発)に悩んだら、loss.backward() の前後にブレークポイントを仕掛け、パラメータの `.grad` 属性(例: `model.fc1.weight.grad`)を覗いてみてください。「あ、ここで完全に値が吹っ飛んでる!」というのが一発で特定できます。
まとめ
- pdb / IPdb を使えば、AIモデルの計算グラフの途中で処理を止め、テンソルの `shape` や `device` をリアルタイムに監視できる。
- `print` デバッグの無限地獄から解放され、エラーの原因箇所をピンポイントで特定できるようになる。
- 条件分岐(`if`)と組み合わせることで、「怪しい瞬間だけ」を効率よくキャッチできる。
これをマスターすれば、どれほど複雑な最新の深層学習アーキテクチャが相手でも、恐れるものは何もなくなります。ぜひ、あなたの手元の開発環境でも試してみてください。毎日のコーディングが、きっと劇的に楽しく、スピーディーになりますよ!