TadaoYamaokaの日記

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

将棋でディープラーニングする その25(C++でミニバッチ作成)

先日の日記で、elmoの教師データを使用してバリューネットワークの学習を行ったところ、elmoの教師データはハフマン符号で圧縮されているため、デコードする処理に時間がかかるという問題があることがわかった。

そこで、デコード部分をC++で実装することで高速化できないか試した。
PythonからC++のモジュールを呼び出すとオーバーヘッドがあるため、できるだけまとめて処理できた方が良い。
そのため、1回の呼び出しでミニバッチデータの作成までをまとめて行うようにした。

Python側実装

Python側では、elmoの教師データのデータ構造であるHuffmanCodedPosAndEvalをNumpyの構造体として定義する。

HuffmanCodedPosAndEval = np.dtype([
    ('hcp', np.uint8, 32),
    ('eval', np.int16),
    ('bestMove16', np.uint16),
    ('gameResult', np.uint8),
    ('dummy', np.uint8),
    ])

ファイルから読み込む際は、

hcpevec = np.fromfile(args.file, dtype=HuffmanCodedPosAndEval)

のようにすることで一度で全件読み込める。

C++側の実装

Boost.Pythonを使うことで、Pythonのモジュールが実装できる。
また、Boost.Numpyを使うことで、Python側からnumpyのオブジェクトを受け取ることができる。
(詳細は、先日の日記参照)

入力は、HuffmanCodedPosAndEval型のnumpyの配列を受け取るようにする。
出力は、引数で受け取ったnumpyのオブジェクトに設定して返すようにする。
出力のnumpyのオブジェクトは、Python側でnp.emptyで事前に領域を確保しておく。

C++側では、ndarray.get_data()でメモリ領域にポインタでアクセスできる。
numpyのオブジェクトは、shapeによらずメモリ上では連続した領域になっているので、C++側ではshapeに合わせてポインタ操作を行う。

elmoの教師データのデコードは、elmoのソースコード(および派生元のApery)をそのまま流用した。
王手を入力特徴に加えているので、王手のチェックを高速で行えるように盤面管理にelmoのPositionクラスを利用した。
探索部分は不要なためコメントアウトしている。

座標系が今まで使用していたpython-shogiとは異なるが、今後はelmoのソースを流用していこうと思うので、座標系の変換は行わずそのままとした。
そのため、いままで学習したモデルは使用できなくなる。

座標系の違いについては以前の日記を参照。
駒の順番もelmoに合わせた。
python-shogiは、歩(PAWN)、香(LANCE)、桂(KNIGHT)、銀(SILVER)、金(GOLD)、角(BISHOP)、飛(ROOK)、玉(KING)の順だが、
elmo(Apery)では、歩(PAWN)、香(LANCE)、桂(KNIGHT)、銀(SILVER)、角(BISHOP)、飛(ROOK)、金(GOLD)、玉(KING)の順になる。

Python側から利用

C++で作成したソースは、DLLとしてビルドする。
拡張子を、.pydにすることで、Pythonからimportして、Pythonのモジュールと同等に使用できる。

import hcp_decoder

hcpevec = np.fromfile(args.file, dtype=HuffmanCodedPosAndEval)

features1 = np.empty((len(hcpevec), 2, 14, 81), dtype=np.float32)
features2 = np.empty((len(hcpevec), 2 * MAX_PIECES_IN_HAND_SUM + 1, 81), dtype=np.float32)

result = np.empty(len(hcpevec), dtype=np.float32)
move = np.empty(len(hcpevec), dtype=np.int32)
value = np.empty(len(hcpevec), dtype=np.float32)

hcp_decoder.decode_with_value(hcpevec, features1, features2, value, move, result)

デバッグについて

C++を使うとデバッグが難しくなるが、Visual StudioPython Tools for Visual Studioを使い、デバッグの設定で、Enable native code debuggingをチェックしていると、PythonのコードとC++のコードをシームレスにデバッグできる。

処理時間測定

elmoで生成した教師データ1万局面を使い、Pythonでデコードした場合と、C++でデコードしてミニバッチデータの作成まで行った場合で処理時間の比較を行った。
結果は以下の通りとなった。

Python 7.186秒
C++ 0.111秒

約64.7倍速くなった。
Pythonではデコードのみでミニバッチデータの作成までは行っていないので、実際はこれ以上に差がある。

C++にすることで圧倒的に速くなることが確認できたので、python-shogiからelmoから流用したソースに置き換える予定。


C++側の教師データの読み込み処理のソースをGitHubに公開しました。
github.com

なお、C++側のソースのライセンスはelmoのソースを流用しているのでGPLが適用されるようになります。