TadaoYamaokaの日記

山岡忠夫 Home で公開しているプログラムの開発ネタを中心に書いていきます。

ChainerをC++から使う

ディープラーニングを使った将棋プログラムを試しているが、将棋プログラムは速度が必要なため、開発言語は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);

のようにして4次元Tensorを作成して、Pythonの関数の引数に渡し、結果をndarrayで受け取れる。

計測

上記の実装方法で、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