初めに
最近、C++とLibtorchを用いた機械学習(強化学習)のプログラムを書いています。そこで起きた事件を丸一日使って解決したので、教訓としてここに書きます。
- OS: Windows 11
- 言語: C++
- ライブラリ: Libtorch (最新版)
事件発生
コンパイルは通るのに 「実行するとエラーメッセージもなくプログラムが落ちる」 という現象に遭遇しました。
調査
どこで落ちているのかわからないので、怪しい箇所を try-catch で囲み、無理やりエラーを出力させました。
torch::Tensor forward(torch::Tensor x) {
try {
x = torch::relu(conv1(x));
x = torch::relu(conv2(x));
x = torch::relu(conv3(x));
x = x.view({x.size(0), -1});
x = torch::relu(fc1(x));
x = fc2(x);
} catch(const std::exception& e){
std::cerr << "Error details: " << e.what() << std::endl;
throw;
}
return x;
}
その結果、このようなログが出力されました。
Error details: Given groups=1, weight of size [64, 64, 3, 3], expected input[1, 32, 24, 24] to have 64 channels, but got 32 channels instead
原因特定
ログには「入力64を期待していたのに32しかありませんでした」と書いてありますね。
では、原因となったコードを見てみましょう。
// ...省略
struct QNetworkImpl : torch::nn::Module {
torch::nn::Conv2d conv1{nullptr}, conv2{nullptr}, conv3{nullptr};
QNetworkImpl() {
// ...
conv1 = register_module("conv1", torch::nn::Conv2d(torch::nn::Conv2dOptions(3, 32, 3).padding(1)));
conv2 = register_module("conv2", torch::nn::Conv2d(torch::nn::Conv2dOptions(32, 64, 3).padding(1)));
conv2 = register_module("conv3", torch::nn::Conv2d(torch::nn::Conv2dOptions(64, 64, 3).padding(1)));
// ...
}
};
// ...
どこがおかしいのか、わかりましたか?
それでは答え合わせです。
何が起きていたか
conv1 を通過したデータはチャンネル数が 32 になっています。
本来の conv2 は「入力32 -> 出力64」の設定ですが、コピペミスにより変数 conv2 には 「入力64 -> 出力64」 の層(本来 conv3 に入るはずだったもの)が上書き代入されてしまいました。
その結果、 forward() 内で conv2(x) が実行された瞬間、 「32チャンネルのデータが渡されたが、この層は64チャンネルの入力を期待している」 という不整合が発生し、プログラムがクラッシュしてしまいました。
教訓
-
register_moduleを連続して書くときは、代入先の変数名が正しいかを確認する - エラー文が出ずに落ちる場合、
nullptrへのアクセスだけでなく、 行列演算の次元不一致 も疑う必要がある