関数の戻り値にハマる
関数の戻り値が合わないのである。半日ほど無駄にした。
どういうことかというと、こういう↓コードで(1)と(2)の所で数値が合わないのである。
torch::Tensor pbox()
{
torch::Tensor boxes;
//tensorを作る処理
std::cout << boxes << std::endl; //(1)
return boxes;
}
int main()
{
torch::Tensor _ts;
_ts = pbox();
cout << _ts << endl; //(2)
return 0;
}
(1)の出力
0.7000 0.8750 1.0000 0.5657
0.7000 0.8750 0.5657 1.0000
…
0.9000 0.8750 1.0000 0.5657
0.9000 0.8750 0.5657 1.0000
[ CPUDoubleType{5100,4} ]
(2)の出力
-1.4568e+144 -1.4568e+144 -1.4568e+144 -1.4568e+144
-1.4568e+144 -1.4568e+144 -1.4568e+144 -1.4568e+144
…
-1.4568e+144 -1.4568e+144 -1.4568e+144 -1.4568e+144
-1.4568e+144 -1.4568e+144 -1.4568e+144 -1.4568e+144
[ CPUDoubleType{5100,4} ]
昭和のおじさんはベテランなので、torch::Tensorのようなデカい構造体の値渡しのバグってことは、まあたぶんコンパイラの最適化関連のエラーだろうなぁ、と当りは付いた。けど使い慣れないコンパイラオプションいじくるのは嫌だなあと、参照渡しにしたり、ポインタ渡しにしたり、グローバル変数にしたり、飯を食ったりしているうちに時間がかかってしまった。
まあこういう不可解な動作をするのがCやC++の楽しいところなのだが、pythonみたいなスクリプトから入った人には嫌われる所だろう。とにかく、参照もポインタもグローバル変数もダメである。
torch::Tensorもやたら複雑な構造体でデバッガで値をウォッチするのも大変。ラッパークラスとテンプレートの嵐である。所詮配列操作のライブラリやないか。そんなにpythonライクにしたいのかなあ。エラーリカバリはデバッガに任せればいいのに。デバッガが使えるのもC,C++のいいところなのでもっとシンプルな体系にしてもらいたいものである。ランタイムでエラーが出ると時間がかかるのだ。
仕方なくコンパイラオプションをいじることにした。グローバル変数の値渡しもダメってことは、おそらくDLL関連だろうとチコッと変えたら一発で治った。一発である。さすが昭和のおじさんである。
コンパイラオプション
おそらくこのオプションにすると標準ライブラリやLibtorchのソースまでデバック追跡しなくなるのだろう。そんな所まで別に見たくないのでこれでOK。
ちなみにReleaseの方は初めから/MDオプションだった。
追記
偉そうに書いたが上の方法ではランタイムエラーが出る場合もあった。デバッグ情報がなんたらかんたらと例外が発生する。デバッグ情報のミスマッチでコケるようだ。自動生成のプロジェクトのRelease構成にデバッグ情報を付加した方が良いみたい。↓こういう設定にした。
原因が特定できないのは現場猫みたいですっきりせんなあ。Libtorchもリビルドすればいいのかもしれない。
追記2
tensorを作る処理に問題があるのかもしれない、という気がしてきた。
ここでvector<T>を使った配列で数値配列を生成してTensorに格納しているのだが、格納方法に問題があるのかもしれない。関数が終わるとデータが未定義になるので。継続調査中。
torch::Tensor pbox()
{
torch::Tensor boxes;
//tensorを作る処理 <<ここに問題が
std::cout << boxes << std::endl; //(1)
return boxes;
}
int main()
{
torch::Tensor _ts;
_ts = pbox();
cout << _ts << endl; //(2)
return 0;
}
原因特定?
結論から書くとtensorの生成の時にfrom_blobという関数を使ったのが良くなかったらしい。
#include <torch/torch.h>
#include <iostream>
torch::Tensor test()
{
torch::Tensor _ret;
double _buf[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10,11,12,13,14,15,16,17,18,19,
20,21,22,23,24,25,26,27,28,29,
30,31,32,33,34,35,36,37,38,39
};
_ret = torch::from_blob(_buf, { 10 , 4 }, torch::TensorOptions().dtype(torch::kDouble));
cout << _ret << endl;
return _ret;
}
int main()
{
torch::Tensor _ts;
_ts = test();
cout << _ts << endl;
return 0;
}
//実行結果
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
16 17 18 19
20 21 22 23
24 25 26 27
28 29 30 31
32 33 34 35
36 37 38 39
[ CPUDoubleType{10,4} ]
-9.2560e+61 -9.2560e+61 -9.2560e+61 -9.2560e+61
-9.2560e+61 -9.2560e+61 -9.2560e+61 -9.2560e+61
-9.2560e+61 -9.2560e+61 -9.2560e+61 -9.2560e+61
-9.2560e+61 -9.2560e+61 -9.2560e+61 -9.2560e+61
-9.2560e+61 -9.2560e+61 -9.2560e+61 -9.2560e+61
-9.2560e+61 -9.2560e+61 -9.2560e+61 -9.2560e+61
-9.2560e+61 -9.2560e+61 -9.2560e+61 -9.2560e+61
-9.2560e+61 -9.2560e+61 1.6786e-313 1.6786e-313
1.6786e-313 1.6786e-313 1.6786e-313 -9.2560e+61
-9.2560e+61 -9.2560e+61 -9.2560e+61 -9.2560e+61
[ CPUDoubleType{10,4} ]
#include <torch/torch.h>
#include <iostream>
torch::Tensor test()
{
torch::Tensor _ret;
double _buf[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10,11,12,13,14,15,16,17,18,19,
20,21,22,23,24,25,26,27,28,29,
30,31,32,33,34,35,36,37,38,39
};
_ret = torch::tensor(torch::ArrayRef<double>(_buf)).reshape({ 10, 4 });
cout << _ret << endl;
return _ret;
}
int main()
{
torch::Tensor _ts;
_ts = test();
cout << _ts << endl;
return 0;
}
//実行結果
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
16 17 18 19
20 21 22 23
24 25 26 27
28 29 30 31
32 33 34 35
36 37 38 39
[ CPUFloatType{10,4} ]
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
16 17 18 19
20 21 22 23
24 25 26 27
28 29 30 31
32 33 34 35
36 37 38 39
[ CPUFloatType{10,4} ]
torch::from_blob
公式にはこう書いてある。
所有権を取得せずにtensorを生成するとある。元の配列は関数の中にあるインスタンスなので、関数が終了すると消滅してしまう。戻り値のtensor自体は配列のラッパークラスなので空になった配列を持つことに。それがどういう仕組みかエラーも出さず未定義の値を吐き出す、ということか。コンパイラオプションでランタイムエラーが出たり出なかったりするのは、関数のメモリをdeleteするかしないかの違いがデバッグ関連のオプションにあるからだろう。
ポインタをコピーするのはoperand=
の機能と思うがオーバーライドが激しいので何しているのか追うのが難しい。pythonライクにしたいのか分からないが、重要な演算子が何しているのか分からんのは困る。ポインタは->
でいいじゃないか。
torch::ArrayRef
一方、torch::ArrayRefの方はクラスになっていて、公式ではこんな風な記述がある。これだけで使い方を把握するのは困難だがconstructするとあるので新しくインスタンスを構成してくれるのだろう。このArrayRefはいろんなメソッドの引数になっているので、Libtorchの体系ではAPIとして重要なクラスということらしい。
これで最終解決なのか分からんけど「動いているからヨシ」。
(なんだかpythonみたいで嫌だなあ)
つづく。