ディープラーニングを使った将棋プログラムを試しているが、将棋プログラムは速度が必要なため、開発言語はC++が適している。
しかし、使用しているディープラーニングフレームワークのChainerはPythonにしか対応していない。
CaffeやCNTK、TensorFlowなどC++で実行可能なフレームワークもあるが、個人的にはChainerが使いやすいので、できればC++からChainerを使用したい。
以前に、ChainerのモデルをC++で読み込んでcuDNNで実行することを試したが、今回はBatchNormalizationやDropoutも使用しているので、スクラッチで作成するには少々無理がある。
ということで、C++からPythonランタイムを呼び出して、PythonでChainerを使うことを考える。
Pythonの呼び出しにはオーバーヘッドがあるので、その影響も考慮する。
実装方法
C++からPythonの呼び出しは、Boost.Pythonを使うことで比較的簡単に実装できる。
(Boost.Pythonのセットアップ方法は以前の日記参照)
#include <boost/python.hpp> namespace py = boost::python; py::object module_ns = py::import("module").attr("__dict__"); py::object module_func = module_ns["func"]; module_func();
のようにして、Pythonに定義した関数を実行できる。
Chainerを使用する場合、データの入出力にNumpyを使用する。
NumpyもBoost.Numpyを使うとC++側から簡単に利用できる。
float features[BATCHSIZE][FEATURENUM][H][W]; np::ndarray ndfeatures = np::from_data( features, np::dtype::get_builtin<float>(), py::make_tuple(BATCHSIZE, FEATURENUM, H, W), py::make_tuple(sizeof(float)*FEATURENUM*H*W, sizeof(float)*H*W, sizeof(float)*W, sizeof(float)), py::object()); auto result_object = predict(features); np::ndarray result = py::extract<np::ndarray>(result_object);
計測
上記の実装方法で、Chainerで実装した将棋の指し手を予測する方策ネットワークを呼び出すプログラムを作成し、処理時間を計測してみた。
predict call 1000 times 8480[msec] 8.48[msec per each call] 117.925[nps]
昨日Pythonで計測したときは、140npsだったので、スループット(nps)が落ちている。
C++からPython呼び出しのオーバーヘッドが発生している。
オーバーヘッド計測
オーバーヘッドを計測するため中身のない処理をC++から呼び出した場合の、処理時間を計測した。
dummy call 100000 times 7[msec] 7e-05[msec per each call]
無視できる程度の時間しかかかっていない。
引数、戻り値が一切ない処理の場合は、ほとんどオーバーヘッドはないようだ。
次に、引数と戻り値を方策ネットワークと同じにして、処理をなくしたもので計測してみた。
dummy2 call 1000 times 15[msec] 0.015[msec per each call]
今度は、1000回の呼び出しで15msec、1回あたり0.015msecかかっている。
言語間の変数のバインディングにオーバーヘッドがあるようだ。
ただし、方策ネットワークの実行時間が1回あたり8.48msecなので、それほど気にする必要はなさそうだ。
処理時間の計測には、以下のコードを使用した。
github.com