チェスAIのStockfishでは、SPSAという方法で、探索パラメータのチューニングが行われている。
今年の世界コンピュータ将棋選手権の打ち上げで、Qhapaqチームの澤田さんからKaggleのFIDE & Google Efficient Chess AI Challengeで、SPSAが効果的だったという話を聞いて、試そうと思いつつ時間がたってしまった。
やねうら王でも、最新のStockfishのソースを取り込んだ後、SPSAでチューニングを行う方法が取り入れられている。
SPSA、探索パラメーターの自動調整手法について | やねうら王 公式サイト
電竜戦も近いこともあり、そろそろやらねばと思い、今回ひとまずチューニングのスクリプトの実装を行った。
SPSA
目的関数の形式が不明で、勾配を直接計算することが不可能な場合の最適化アルゴリズムである。
dlshogiは、これまではOptunaを使用して探索パラメータの最適化を行っていたが、安定して更新するには信頼できる勝率を求めるために1更新につき100回近い対局が必要だった。
SPSAは、対局結果のようにノイズの多い状況でも使用可能で、少ない対局回数で更新できる。
アルゴリズム自体はシンプルで、すべてのパラメータを±方向に同じ量だけ摂動して評価した結果から勾配を近似して、パラメータ更新を繰り返すだけである。
摂動の幅は、イテレーションが進むにつれて、減衰する。
詳細は、Wikipediaを参照。
SPSA in Fishtest
Stockfishでは、原論文にあるSPSAの式はそのまま使用しておらず、チェスに特化した式が使用されている。
Stockfish Docs
元になる論文は探しても見つからないため、経験則から導かれた式のようである。
摂動して勾配を近似し、摂動幅を減衰するという基本的な考え方はSPSAと同じである。
FishtestのSPSAは、OpenBenchに実装されている。
今回は、OpenBenchの実装を参考にして実装した。
dlshogi向けの改良
基準ソフト
dlshogiの探索パラメータの更新用に実装するにあたり、OpenBenchでは、摂動したプラスとマイナスを対局させているが、基準となる固定のソフトがある方が安定するため、基準ソフトを加えた3組の対局結果から勾配を求めるようにした。
並列実行
8GPUを使用して並列実行できるように、ワーカーのID(0~7)をUSIパラメータに埋め込みできるようにした。
開始局面
SFEN形式で記述した開始局面集をファイルで与えられるようにした。
3組×先後の6対局のセットでは、同じ開始局面を使用する。
使い方
コマンドラインから以下のように実行する。
python -m dlshogi.utils.spsa_usi_tuner --baseline D:\src\hayabusa\bin\hayabusa.exe --candidate D:\src\DeepLearningShogi2\x64\Release\dlshogi_tensorrt.exe --options-baseline EvalDir:20250416,FV_SCALE:24,ResignValue:3000,NetworkDelay2:0 --options-candidate "DNN_Model:F:\model\model-pre55-012.onnx,UCT_Threads:{2 if id==0 else 0},UCT_Threads{'' if id==0 else id+1}:2" --params C_init:100~200:127,C_base:20000~40000:27126:10:5,C_fpu_reduction:10~40:31,C_init_root:100~200:112,C_base_root:20000~40000:33311:10:5,Softmax_Temperature:100~200:140 --openings R:\openings.sfen「--params」が摂動するUSIパラメータの定義で、以下の形式で入力する。
name:min~max:start[:c_end][:r_end][:type]
minは、最小値、maxは最大値、startは初期値を指定する。
c_endは、最終的な摂動の幅を指定する(省略時は1)。
r_endは、最終的な更新率を指定する(省略時は0.5)。
typeは、intまたはfloatを指定する(USIパラメータなので指定なし(int)でよい)。
実行結果
実行すると以下のように表示される。
Loaded 1 openings from R:\openings.sfen. Parameters: C_init: range=(100.0,200.0), start=127.0, c_end=1.0, r_end=0.5, int=True C_base: range=(20000.0,40000.0), start=27126.0, c_end=10.0, r_end=5.0, int=True C_fpu_reduction: range=(10.0,40.0), start=31.0, c_end=1.0, r_end=0.5, int=True C_init_root: range=(100.0,200.0), start=112.0, c_end=1.0, r_end=0.5, int=True C_base_root: range=(20000.0,40000.0), start=33311.0, c_end=10.0, r_end=5.0, int=True Softmax_Temperature: range=(100.0,200.0), start=140.0, c_end=1.0, r_end=0.5, int=True [Iter 1] sets=1, games=6 theta : C_init=131, C_base=26712, C_fpu_reduction=27, C_init_root=108, C_base_root=33725, Softmax_Temperature=136 theta+ : C_init=125, C_base=27143, C_fpu_reduction=33, C_init_root=114, C_base_root=33294, Softmax_Temperature=142 theta- : C_init=129, C_base=27109, C_fpu_reduction=29, C_init_root=110, C_base_root=33328, Softmax_Temperature=138 (+) vs base : W-L-D = 0-2-0 (S=-2.0) (-) vs base : W-L-D = 1-0-1 (S=1.5) (+) vs (-) : W-L-D = 1-1-0 magnitude : -3.50 updates : C_init=+4.145, C_base=-414.487, C_fpu_reduction=-4.145, C_init_root=-4.145, C_base_root=+414.487, Softmax_Temperature=-4.145 [Iter 2] sets=1, games=6 theta : C_init=132, C_base=26774, C_fpu_reduction=28, C_init_root=107, C_base_root=33787, Softmax_Temperature=135 theta+ : C_init=129, C_base=26696, C_fpu_reduction=25, C_init_root=110, C_base_root=33709, Softmax_Temperature=138 theta- : C_init=133, C_base=26728, C_fpu_reduction=29, C_init_root=106, C_base_root=33741, Softmax_Temperature=134 (+) vs base : W-L-D = 0-1-1 (S=-0.5) (-) vs base : W-L-D = 1-1-0 (S=0.0) (+) vs (-) : W-L-D = 1-1-0 magnitude : -0.50 updates : C_init=+0.621, C_base=+62.080, C_fpu_reduction=+0.621, C_init_root=-0.621, C_base_root=+62.080, Softmax_Temperature=-0.621 [Iter 3] sets=1, games=6 theta : C_init=129, C_base=26458, C_fpu_reduction=25, C_init_root=110, C_base_root=34103, Softmax_Temperature=138 theta+ : C_init=134, C_base=26790, C_fpu_reduction=30, C_init_root=105, C_base_root=33771, Softmax_Temperature=133 theta- : C_init=130, C_base=26758, C_fpu_reduction=26, C_init_root=109, C_base_root=33803, Softmax_Temperature=137 (+) vs base : W-L-D = 0-2-0 (S=-2.0) (-) vs base : W-L-D = 0-1-1 (S=-0.5) (+) vs (-) : W-L-D = 0-1-1 magnitude : -2.50 updates : C_init=-3.164, C_base=-316.371, C_fpu_reduction=-3.164, C_init_root=+3.164, C_base_root=+316.371, Softmax_Temperature=+3.164 [Iter 4] sets=1, games=6 theta : C_init=125, C_base=26012, C_fpu_reduction=29, C_init_root=106, C_base_root=33657, Softmax_Temperature=142 theta+ : C_init=127, C_base=26443, C_fpu_reduction=27, C_init_root=108, C_base_root=34088, Softmax_Temperature=140 theta- : C_init=131, C_base=26473, C_fpu_reduction=23, C_init_root=112, C_base_root=34118, Softmax_Temperature=136 (+) vs base : W-L-D = 0-0-2 (S=1.0) (-) vs base : W-L-D = 0-1-1 (S=-0.5) (+) vs (-) : W-L-D = 2-0-0 magnitude : +3.50 updates : C_init=-4.464, C_base=-446.446, C_fpu_reduction=+4.464, C_init_root=-4.464, C_base_root=-446.446, Softmax_Temperature=+4.464 [Iter 5] sets=1, games=6 theta : C_init=126, C_base=25884, C_fpu_reduction=30, C_init_root=107, C_base_root=33529, Softmax_Temperature=143 theta+ : C_init=124, C_base=26027, C_fpu_reduction=28, C_init_root=105, C_base_root=33672, Softmax_Temperature=141 theta- : C_init=126, C_base=25997, C_fpu_reduction=30, C_init_root=107, C_base_root=33642, Softmax_Temperature=143 (+) vs base : W-L-D = 0-1-1 (S=-0.5) (-) vs base : W-L-D = 0-1-1 (S=-0.5) (+) vs (-) : W-L-D = 0-1-1 magnitude : -1.00 updates : C_init=+1.278, C_base=-127.828, C_fpu_reduction=+1.278, C_init_root=+1.278, C_base_root=-127.828, Softmax_Temperature=+1.278 [Iter 6] sets=1, games=6 theta : C_init=126, C_base=25884, C_fpu_reduction=30, C_init_root=107, C_base_root=33529, Softmax_Temperature=143 theta+ : C_init=125, C_base=25899, C_fpu_reduction=29, C_init_root=106, C_base_root=33544, Softmax_Temperature=144 theta- : C_init=127, C_base=25869, C_fpu_reduction=31, C_init_root=108, C_base_root=33514, Softmax_Temperature=142 (+) vs base : W-L-D = 1-1-0 (S=0.0) (-) vs base : W-L-D = 1-1-0 (S=0.0) (+) vs (-) : W-L-D = 1-1-0 magnitude : +0.00 updates : C_init=-0.000, C_base=+0.000, C_fpu_reduction=-0.000, C_init_root=-0.000, C_base_root=+0.000, Softmax_Temperature=+0.000
まとめ
SPSAによるUSIパラメータのチューニングを行うスクリプトを作成した。
次は、dlshogの最新モデルでチューニングして強くなるか測定したい。