先日の日記で、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 StudioのPython 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