TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

nnue-pytorchを試す

Stockfishで実装されたNNUEモデルをPyTorchで学習できるnnue-pytorchが、nodchip氏によって将棋のNNUE向けにポーティングされた。
GitHub - nodchip/nnue-pytorch at shogi.2022-05-23

dlshogiの知識蒸留を試していることもあり、dlshogiのモデルを教師としてNNUEを学習したらどうなるか興味がでてきたので試すことにする。

まずは、WindowsLinux上で学習を実行できることまで確認を行った。

qsearchについて

通常のNNUEの学習におけるqsearch

通常NNUEの学習では、訓練データの局面に対してqsearch(静止探索:駒の取り合いが完了した静かな局面まで進める)を行い、qsearchの末端局面を実際の学習対象局面とする。
訓練データには、深い探索を行った際の評価値が記録されており、qsearchの局面の評価値との相互情報量を損失とする。

qsearchの末端局面から深い探索を行っているわけではないため、これで正しく学習できる理由ははっきりしない。

また、損失には、勝敗項があり、2値交差エントロピー損失としている。
勝敗を付けているのはqsearchの末端局面からではなく、訓練データの局面の方であるため、これで学習できる理由は謎である。

nodchip氏に質問したところ、NineDayFeverのころから歴史的にそうなっているということだった。
qsearchの末端局面を学習するのは、対局中に評価する局面がqsearchの末端局面になるというのが理由と考えらえる。
nodchip氏の検証では、qsearchを行わないと弱くなることが確かめられている。

Stockfishのnnue-pytorch

Stockfishのコミュニティでも議論があり、qsearch()は不要という結論になったそうだ。
その変わり、--smart-fen-skippingというオプションがあり、指し手が駒を取る局面と、王手の局面を除くようになっている。

nodchip氏が検証したところ、将棋で--smart-fen-skipping相当を行っても、qsearchの末端局面を学習した方がよかったということである。

この点、自分は経験則よりもあるべき学習をした方が良いと考えるので、--smart-fen-skippingの条件をqsearchと完全に一致させて、qsearchで動く局面をすべて除いた上で学習を試したいと考えている。

やねうら王のバージョンとqsearch

qsearchの動作は、やねうら王のバージョンによって異なるらしく、nodchip氏の検証では学習にはV5.33を使うのがよいらしい。
そこで、nnue-pytorchに取り込むやねうら王のソースは、V5.33とした。

qsearchで動く局面を除く処理は、未実装だが、nodchip版nnue-pytorchをフォークしてV5.33を取り込んだ。
GitHub - TadaoYamaoka/nnue-pytorch at develop

Windowsで試す

環境構築

Anacondaを使用して構築した。

conda create -n nnue-pytorch python=3.9
conda activate nnue-pytorch

PyTorchをインストール

conda install pytorch torchvision torchaudio cudatoolkit=11.3 -c pytorch

PyTorch Lightingをインストール

pip install pytorch-lightning

必要ライブラリをインストール

conda install matplotlib
pip install python-chess==0.31.4
ビルド

スタートメニューからx64 Native Tools Command Prompt for VS 2022を起動し、

cd /d <nnue-pytorchをチェックアウトしたフォルダ>
compile_data_loader.bat

nnue-pytorchをチェックアウトしたフォルダに、training_data_loader.dllができる。

訓練実行

テストのためにfloodgateの棋譜から作成したPackedSfenValueファイルを使用して学習する。
拡張子は、.binにする必要がある。
それ以外だとエラーになるので注意が必要である。
はじめエラー原因がわからなかったが、ソースを調べて判明した。

python train.py floodgate_2019-2021_r3500_nomate.psv.bin floodgate_test_2017-2018_r3500_eval5000.psv.bin --gpus 1 --max_epochs 2

※特徴量のデフォルトはHalfKP^になっていたが、標準NNUE(HalfKP)の方がよいとのことなので、ソースを修正してデフォルト値を変更している。

訓練が完了すると、logs\lightning_logs\version_X\checkpoints\last.ckptにチェックポイントができる。

シリアライズ

チェックポイントに格納されたネットワークの重みを量子化して、やねうら王で読める形にする必要がある。
そのためのスクリプトserialize.pyが用意されている。

python serialize.py logs\lightning_logs\version_X\checkpoints\last.ckpt nn.nnue

出力ファイルの拡張子は.nnueである必要がある。
やねうら王のフォルダにコピーする際は、nn.binにファイル名を変更する。

動作確認

やねうら王のevalフォルダに、nn.binをコピーして、コンソールからUSIコマンドを手入力して動作確認する。

setoption name EvalDir value eval
isready
go byoyomi 10000
info depth 29 seldepth 31 score cp 10 nodes 34101765 nps 3839424 hashfull 1000 time 8882 pv 2g2f 8c8d 2f2e 9c9d 2e2d 2c2d 1g1f 8d8e 7g7f 8e8f 8g8f 8b8f 2h2d P*8g 8h5e 8f7f 5i5h 7f7e 5e7g 7e7g+ 8i7g 3c3d 2d2b+ 3a2b R*1b 1a1b 7g8e B*9c 8e9c+
bestmove 2g2f ponder 8c8d

初手2六歩となったので、正しく学習できていそうである。

Linuxで試す

ソース修正

nodchip版は、nnue_training_data_stream.hでWindows用の並列処理ライブラリを使用しているため、そのままではLinuxで動かない。
psvを読み込んで、局面をデコードする部分を並列化している。
PyTorch Lightningはデータローダーのワーカーを並列化しているので、psv読み込みを並列化しなくても影響は小さいと思われるので、Linuxの場合並列化なしにした。

#ifdef _MSC_VER
#include <ppl.h>
#endif

#ifdef _MSC_VER
                    concurrency::parallel_for(size_t(0), n, [&vec, &packedSfenValues](size_t i)
                        {
                            vec[i] = packedSfenValueToTrainingDataEntry(packedSfenValues[i]);
                        });
#else
                    for (size_t i = 0; i < n; i++)
                    {
                        vec[i] = packedSfenValueToTrainingDataEntry(packedSfenValues[i]);
                    }
#endif
CMakeLists.txt修正

Linuxのg++でビルドできるようにCMakeLists.txtを修正した。

set(CMAKE_CXX_FLAGS "-DYANEURAOU_ENGINE_NNUE -DEVAL_LEARN -DUNICODE -msse4.1 -mbmi -mbmi2 -mavx2")
環境構築

NVIDAのPyTorchのDockerイメージを使用した。

docker run --gpus all --shm-size=8g --network host -it -v ~/:/work -w /work --name nnue-pytorch nvcr.io/nvidia/pytorch:22.04-py3

PyTorch Lightingをインストール

pip install pytorch-lightning

必要ライブラリをインストール

pip install python-chess==0.31.4
ビルド
sh compile_data_loader.bat

カレントディレクトリにlibtraining_data_loader.soができる。

訓練実行

Windowsと同様に訓練を実行する。

python train.py floodgate_2019-2021_r3500_nomate.psv.bin floodgate_test_2017-2018_r3500_eval5000.psv.bin --gpus 1 --max_epochs 2

※データローダを並列処理する場合は、--num-workersを2以上にする。

RuntimeError: ffi_prep_cif_var failed

というエラーが起きた。

動的リンクライブラリの呼びだし部分で発生している。
調べたところ、libffiをダウングレードすると解決するらしい。
ffi_prep_cif_var failed · Issue #5711 · biolab/orange3 · GitHub

conda install -c conda-forge libffi=3.3.*

ダウングレードすると、エラーがでなくなり、正常に訓練が実行できた。

シリアライズと動作確認は、Widnowsと同様である。

まとめ

Stockfishからポーティングされたnnue-pytorchをWindowsLinuxで動作確認した。
また、qsearchで動く局面を除く実験をするため、V5.33ベースのやねうら王のソースを取り込んだ。

次回はqsearchで動く局面を除く実験を行いたい。