次世代の将棋思考エンジン、NNUE関数を学ぼう(その1.ネットワーク構造編)

update 18/06/03 12:00

NNUE評価関数とは将棋ソフトの思考エンジン(もとい、局面の良し悪しを評価するための関数)です。NNUE関数は今流行りのニューラルネットディープラーニングの小さいやつ)を盤面評価に取り入れており第28回コンピュータ将棋選手権で多くの既存評価関数ユーザをぶちのめしてきた新機軸評価関数です。

2018年6月時点で多くの開発者、有志がNNUE関数をイジって遊んでいます。公開されてから未だ日が浅いため、もしNNUE関数の学習で大当たりを引けば、次の電王の座を手に入れられるかも知れません。

というわけで、このゴールデンカムイに乗っかろうとする皆様のために、NNUE関数の成り立ちについて解説していきます。今回はネットワークの構造と、学習のルーチンの仕様について紹介していきます。

開発者による解説文書(正直結構難しい)
tnk-のgithub (現在NNUE評価関数はやねうら王にマージされており、ファイル名がtnk-とは異なるなどの違いがあるようです。本稿ではファイル名は暫定的にtnk-を基準としています)
やねうら王のgithub (NNUEが既にマージされています。)

【1ページで解るNNUE関数】
NNUEのネットワーク構造は以下のような浅いニューラルネットワークになっています。ニューラルネットディープラーニングについては此方の本などを参照してください。

f:id:qhapaq:20180603123332p:plain

この辺の実装はeval/nn/architectures/halfkp_256x2-32-32.h などに書かれています。
NNUEの強さの秘訣は点線で囲んだ最初の層の計算をCPUベースで高速に行なえるようにした(SIMD計算と差分計算を使えるようにした)ことです。また、NNUEのコードはテンプレートを上手く使いこなし、高速且つ拡張性が高い形になっています。

【NNUE関数を自前で作る】
NNUE関数はCPUベースで動くように作られているため、基本的にやねうら王の学習方法を使い回すことが出来ます。例えば以下のようなコマンドで学習が出来ます。
(NNUE開発チームの一人であるnodchipさんの呟きからインプットの例を復元しました)

# 学習前の評価関数を決める
setoption name EvalDir value nn_20180519
# 学習させる
learn teacherdata.bin lambda 0.5 eta 0.1 newbob_decay 0.5 batchsize 1000000 nn_batch_size 1000 eval_save_interval 100000000 validation_set_file_name varidationfile.bin

これをやると例えば以下のような出力が得られます。
各インプットの意味は#以下に書き記しておきます(実際の出力ファイルにはでてきません)

# gensfenで作った教師データ
learn from teacherdata.bin,
# 過学習回避のためのバリデーションデータ
# 各iteration毎にvalidationの中身は全て読むので巨大なファイルにしないこと!
validation set  :  varidationfile.bin
# 学習ファイルを何らかのフォルダにまとめて入れる場合
base dir        :
target dir      :
# 学習のループ回数
loop              : 1
# 学習データとして用いる評価値の上限
eval_limit        : 32000
# 保存を一度だけするか
save_only_once    : false
# 学習時に棋譜のシャッフルを飛ばすか
# 教師データをデフォでシャッフルしてるなら多分意味はない
no_shuffle        : false
# 使う目的関数。elmo型を使う
Loss Function     : ELMO_METHOD(WCSC27)
# lossを表示し評価関数を更新するinterval
# 実効的にこいつはミニバッチサイズではないことに注意
mini-batch size   : 1000000
# 真のミニバッチサイズに相当。
nn_batch_size     : 1000
# ニューラルネットに特殊なオプションを送る
nn_options        :
# 学習率
learning rate     : 0.1 , 0 , 0
# 学習率をiteration毎に変える場合の係数。詳しくはやねうら王のドキュメント参照
eta_epoch         : 0 , 0
# lossを見張り学習率を動的に動かすためのパラメタ newbob = 1.0で無効化される
scheduling        : newbob with decay = 0.5, 2 trials
discount rate     : 0
reduction_gameply : 1
# elmo学習のlambda
LAMBDA            : 0.5
LAMBDA2           : 0.33
LAMBDA_LIMIT      : 32000
# 盤面の鏡像化の頻度
mirror_percentage : 0
# 保存する頻度を指定
eval_save_interval  :  100000000 sfens
# mini-batch-sizeと同値
loss_output_interval: 1000000 sfens

【重要な注意点】
NNUE型を回す際に特に以下の点に注意する必要があります。

・validation_set_file_name をちゃんと作る。かつ大きくし過ぎない
validationファイルは過学習をしていないか確かめるためのデータで、教師データとは異なる(教師データにはない局面を使う)必要があります。
また、validationファイルは各iteration毎に全て読み込まれるようなので、これを大きくし過ぎると学習時間が伸びます。

・実効的なミニバッチサイズはnn_batch_sizeである
パラメタにbatch_sizeとnn_batch_sizeがありますが、実効的なbatchsizeはnn_batch_sizeです。
どういう原理で動いているかを時系列で表すと

1.読ませた局面数がbatch_sizeになったら評価関数の更新を行う
2.評価関数の更新ではbatch_size個の教師をnn_batch_size個毎の教師グループ
(即ち、グループ数はbatch_size / nn_batch_size個になる)に分ける
3.教師グループ0を用いて、評価関数の勾配を求める
4.勾配を用いて評価関数を更新する
5.教師グループ1,2,について同様の処理を行う

という設計になっています。(eval/nn/evaluate_nn_learner.cpp、および、eval/nn/trainer/trainer_affine_transform.h を参照)
本家やねうら王のbatch_sizeがbatch_sizeとして機能していないことに注意しましょう。

KPPTと比べるとかなりbatchsizeが小さいですが、開発者曰く、ニューラルネットワーク的にはnn_batch_sizeの1000は大きめの値だそうです。そして、大きくしすぎると学習が上手く行かなくなるそうです。

なお、NNUEでは学習時はblasを使い複数の局面の評価値を並列的に求めて高速化しているため、nn_batch_sizeは大きくしたほうが学習が早くなるようです。ただし、nn_batch_sizeを大きくした分だけメモリも消費することにはご注意ください。


・学習率の調整オプションがKPPTと異なる

NNUEには現時点ではadagradなどのadaptiveな学習ルーチンは実装されていません。学習が進むに連れてネットワークの変化量が少なくさせるためにnewbob_decayというパラメタを使います。
これは評価関数を保存する(eval_save_interval)毎にvalidation用のデータに対するlossを観察し、lossが大きくなった場合は学習率を下げる(newbob_decay倍する)と同時に、評価関数を最もlossが少なかったものに戻す機能を持っています。

ただ、newbob_decayの効果がどのぐらいあるかは不明です。tttakさんが作ったNNUE関数ではoffにされているようです。
(リストアを一時的にoffにするというのがコレに相当すると予想。本人に聞いたわけではないので定かではありません)

newbob_decayではvalidation用のデータのlossを参照するので、validation用のデータはちゃんと用意する必要があります。
データの数を大きくしすぎると評価に時間がかかりますが、小さくしすぎても学習に悪影響をもたらします。
newbob_decayは1.0にすることで無効化出来ます。

ただし、newbob_decayを1にしてetaを一定とした場合、ネットワークの変化量が少なくなるということはありません。
newbob_decayを無効化し、かつ動的に学習率を下げたい場合は、iteration毎に学習率を変える必要があります。
これは、やねうら王に実装されている動的学習率変化を用いて実現可能です。

・nn_optionsという魔境
learn nn_options hoge とすることで、各ニューロンに命令を送ることが出来ます。
ただし、nn_optionsの用法については謎が多く(今公開されている評価関数群で使われた形跡がない)、
その効果も定かではないので現在調査中です。

# 高度な構文解析機能を用いて様々な命令を遅れるようにしてるみたいですが、c++の文字列制御コード読むの怠いんよ

【ここまでの纏め】
NNUEは良くも悪くも新しい関数であり、その学習のさせ方についても不明な点が多いです。
本稿ではネットワークの構造と新しく増えたパラメタ、その使い方や注意点について解説しました。

具体的にどの程度の深さの棋譜がどのぐらい必要か、エントロピーの減少を見る以外に
学習の進度を見る方法があるか、学習パラメタは何を選べばいいのか、などは今後検討していかねばならない
内容であり、私自身は最終的には数の力で全部試すことになるのだろうと思っています。

ただ、各パラメタの意味を知っておくことで、同じガチャでも無駄な引きを避けられるのではと期待しています。

次回はNNUEの内部構造と改造に仕方について紹介する予定です。
コレ長編になるぞ..... orz