NNUE評価関数の学習方法について

やねうら王でNNUE評価関数の学習をする方法について解説記事がなかったのでざっとまとめておきます。

教師局面の生成

gensfenコマンドを使います。KPPT型のときと同じなので割愛。

学習用の実行ファイルのビルド(MSYS2)

OpenBlasという行列演算などが速くなるライブラリを有効にしてビルドします。(OpenBlasが有効でないと学習に倍以上時間がかかります。)

MSYS2上でビルドするとき、pacmanでOpenBlasをインストールします。(32bit版のときは、OpenBlasを用いないので以下の操作は不要)

64bit版
$ pacman -S mingw-w64-x86_64-clang mingw-w64-x86_64-toolchain mingw-w64-x86_64-openblas

あとはMakefileを使って、そのままビルドできるかと思います。

$ mingw32-make clean YANEURAOU_EDITION=YANEURAOU_2018_TNK_ENGINE
$ mingw32-make -j8 evallearn COMPILER=g++ YANEURAOU_EDITION=YANEURAOU_2018_TNK_ENGINE

学習用の実行ファイルのビルド(Visual Studio)

Visual Studio上でビルドする場合、USE_BLASを定義してやります。これはプロジェクトの設定のほうでやってもいいですし、extra/config.hに

#define USE_BLAS

などと追加しても良いです。次に、”libopenblas.dll.a”というファイルをlibとして追加して、実行ファイルの配置されるフォルダにlibopenblas.dll , libgcc_s_seh-1.dll , libgfortran-3.dll , libquadmath-0.dll を配置します。

このへんの手順の解説は以下の記事に譲ります。

[VC++] OpenBLASを使ってみた : https://qiita.com/t–k/items/69c43a667a1283578012

libgcc_s_seh-1.dll , libgfortran-3.dll , libquadmath-0.dllは、MSYS2のなかにあるようですが、私はOpenBlasのサイトからダウンロードしてきて配置しました。

※ https://sourceforge.net/projects/openblas/files/v0.2.12/mingw64_dll.zip/download

学習時オプション


を参考に。

KPPT型との違い
・eta 1.0 ぐらいにすべき(KPPTのときのように32とかにすると発散する)
・mirror_percentage 50 : これを指定しておくとミラーの次元下げをするのか学習効率が少しよくなる
・batchsize 1000000 : これで最初のうちは問題なさげ。これを変更してもあまり強さは変わらないっぽい
・lambda 0.5 : lambdaの最適値はよくわかっていない 0.5とか1.0とかで正常に学習できることは確認済み。
・nn_batch_size 1000 : とりあえずデフォルトのまま
・eval_limit 32000 : これの最適値もよくわかっていない
・validation_set_file_name (検証データファイルパス) : 学習のときのlossの計算をするために検証用のデータセットは別のものにしたほうが良いということで別の教師ファイルを指定できるようになっている(指定しなくとも学習自体はできる)
・newbob_decay 0.5 : これを指定すると学習のスケジューラーの挙動が変わる。eval_save_interval局面学習させるごとに前回のロスの値と比較して、ロスが下がっていない場合は、一旦前回の結果にロールバックし、内部的なetaの値をnewbob_decay倍したうえで学習を続行する。

あと、SkipLoadingEvalは、初回はtrueにしておかないと評価関数を読み込もうとして失敗するような。2回目以降はEvalDirオプションで評価関数の配置しているフォルダを指定すると良い。

学習コマンドの例)

$ YaneuraOu2018NNUE_LEARN.exe , evaldir eval\zero , evalsavedir yanehome\eval\learn_777 , threads 80 , hash 16 , evalshare false , skiploadingeval true , learn newbob_decay 0.5 mirror_percentage 50 nn_batch_size 1000 loop 1 basedir yanehome batchsize 1000000 eta 1 lambda 0.5 eval_limit 32000 save_only_once no_shuffle targetdir learn_sfen\sfen2018D10 , quit

学習時間・学習効率

40コアのPCで教師局面1億局面につき1時間程度。KPPT型の時の数倍ぐらいかかるようです。

以前公開していた月刊教師局面(depth 8で生成)、2億局面を用いて、ゼロから上の設定で学習させた場合、elmo(WCSC27)よりR200程度弱いものができました。

その他

NNUEの学習に関して、何か質問があればコメント欄にどうぞ。

ちなみに、NNUEは駒割(駒の価値)も全くのゼロから学習しているので、ゼロから棋譜を生成してそこから学習を繰り返すような場合、KPPT型に比べて時間がかかるかも…。

 

89 thoughts on “NNUE評価関数の学習方法について

  1. validation_set_fileとnewbob_decayについては使い方が非常に難しいですね。
    etaの値次第でeval_save_intervalの時に毎回rejectedされて全然学習が進まない事がよくあります。
    newbob_num_trialsがデフォルトでは2なので、2回rejectedされると次で学習が強制終了してしまうので、多めの数字にする必要があります。
    newbob_decayを1.0にしつつ、eta可変絞りを上手く設定してやった方が良いかもしれません。
    その場合はlogの中のlossを見比べて良かった物を選び出して採用する必要はありますね。

        • ほほー!これは実装が簡単でそこそこ期待できそうな手法ですね!
          (lossが下がることが確認できる “1 step” が小さくて済む場合ならば。NNUEでこの条件を満たすかは…わかりません。まあ、EvalSaveIntervalを1000万ぐらいに設定して、newbob_decay 0.5とかにしておけば、こまめにセーブして、そのときにloss下がってなからったらetaを1/2にしていくので、これくらいの挙動でもいいような気はしますけども。)

  2. これってShivorayのコードを弄ってできちゃったりします?その場合どこをどう弄ればいいんですかね

    • Shivorayで出来ないですし、やねうら王にはそのようなコマンドも用意してないです。ニューラルネットは単純には合成できない(合成しても意味がない)ので。
      ニューラルネットの研究を専門にしている人なら何らかアクロバティックな技で合成できるのかな?程度の意味です。

  3. すみません。やねうら王で教師局面を作っていたのですが、まったく進まなくなってしまいました。どうすれば良いでしょうか?
    YaneuraOu2018NNUE_learn_sse42.exe threads 4 , hash 4000 , bookmoves 32 , bookfile no_book , evaldir eval , gensfen depth 8 loop 1000000000 random_move_count 0 write_minply 1 write_maxply 160 eval_limit 3000 output_file_name generated_sfens.bin , quit

    • hashが小さすぎ&開始局面が1手目なので、初期局面に対応する置換表エントリーに詰みのスコアの局面が偶然書かれていて(hash衝突)、それで詰みのスコアなので即座に詰みのスコアが返ってくるのですが、eval_limit 3000になっているため、その情報はスキップされて…ということなのではないでしょうか。(´ω`) 以前は置換表、スレッドごとにわけてたのですが、これやると置換表効率落ちるので、それをやめた影響ですね。

      1秒間、教師情報の生成がなければ、TT.clear()を呼び出すなどすれば回避できます。あるいはply==1でabs(value)>=3000 みたいな値が返ってきたらTT.clear()を呼び出すだとか。

      まあ、しかし定跡なしで教師生成するのはお勧めしません…。

  4. やねうら王のhash 〇〇〇〇の設定についてですが、これを今余っているメモリをすべて使うような時はどのようにすればよいのでしょうか?hash 99999とかに設定すれば良いのでしょうか?

    • その機能はついてないです。
      (2エンジンでの対局ができなくなる、使っているうちに物理メモリが少し足りなくなってえらいことになる等が懸念されるため)

  5. ある程度学習された野良のNNUE評価関数に少数精鋭の局面食べさせようと思うのですが、その影響力を強くするには、学習時にどの値をどのように変えるべきですか?

    • Bonanzaの学習のように教師の指し手の評価値が、その局面の他の指し手の評価値より上回るように調整すれば良いような?Qhapaqさんが学習部にそういう改造されてたはず…。

      • いけました。返信ありがとうございます。
        後、gensfenコマンドで教師局面を生成する際に、教師局面を(save_every Nで保存されたもの)、指定したフォルダの中に保存したいのですが、どうすればよいでしょうか?

        • 後、手始めに、depth6の教師局面1500万程を一度食べさせてみたところ、評価値が、
          1,-1,13,-13しか出なくなったのですが、これは、どうしてでしょうか?

          • これですね、今更ながら特殊な特徴のNNUEでrezeroから振り飛車の教師10億学習させたんですが、※オプションは以下の通り。
            loop 3 lambda 0.5 newbob_decay 0.5 eta 0.1
            なぜか同じ現象が起きてまともに指してくれないんですよね…
            ちなみに教師はdepth8、multiPv 2+10 multiPv 1なんですが…
            初期学習時にSkiploandingeval trueにしないとあかんのかなぁ
            なにか原因思いついたらよろしくお願いします。

          • うおー、、何か壊している可能性があるですか…。
            GitHubにあるcommitからその現象が起きるのか、特定することはできますか?(2分法などで..)

          • TextFileReaderクラスがバグっていたので修正しました。定跡の読み込みに影響しますが、これが原因の可能性はありますか?

          • > 初期学習時にSkiploandingeval trueにしないとあかんのかなぁ

            「rezeroから」というのは、教師生成がrezeroからということですか?評価関数をゼロの状態から学習させるには、Skiploandingeval true が必要ですけども、これつけないと、そもそも評価関数の読み込みでエラーになるのでは…。

          • 最初はetaが30になっていて、評価値が永遠に±2945のどちらかをさしていたので、etaを1.0にしてもう一度ゼロからしてみたのですが、結果は先ほどの通りです・・・。
            etaが問題ではなさそうです・・。コマンドは以下の通りです。
            learn [教師棋譜ファイル名] bat 10 lambda 0.5 newbob_decay 0.5 eta 1.0

  6. なるほど・・・(;。□。;)
    了解です!確かにeta 1.0は大きすぎですね・・・・・。ありがとうございますドモ

  7. 評価関数をゼロから学習するときの、教師局面はすべてのパラメーターがゼロなので、
    depth1でも問題ないような気がするのですがどうでしょうか?

    • 最初はdepth低めで問題ないですけど、depth 1でいいかは、depth 1でやったことがないのでわかりません><
      5年ぐらい前はdepth 3ぐらいからスタートしてましたです..。

      • そうですか、ありがとうございます。
        因みに、リゼロ評価関数を作る時、定跡はどうしましたか?

          • ゼロから学習させる場合は、そうですね。
            SkipLoadingEval true
            とやると、ゼロクリアされた評価関数になるので、実際には評価関数ファイルは用いてませんけども。

          • ありがとうございます(^ω^)
            とても参考になりました!

  8. INFO: largest min activation = 0, smallest max activation = 0
    INFO: largest min activation = 0, smallest max activation = 0
    INFO: largest min activation = 0, smallest max activation = 0
    ↑永遠に此れが続いているのですが、此れは何ですか?
    後、
    learn Teacher.bin Teacher_1.bin bat 10 lambda 1.0 mirror_percentage 50 newbob_decay 0.5 eta 1.0 eval_save_interval 100000
    でしてみると、200万局面程で
    test_cross_entropyが全く減らなくなります。
    どうしてでしょうか?(それとも200万局面程度であーだこーだ言うのが短気なのでしょうか?)

    • すみません、bat 10 の所を
      batchsize 100000 nn_batch_size 1000 に、
      eta 1 の所ををeta 0.1に変えて読んで下さい。
      間違ったものをコピペしてました….

    • 教師がおかしいのでは?ゼロからの学習ですかね?であれば、最初は対局シミュレーションの勝敗に頼るしかないので、lambdaに0を指定する必要があるような…。(elmo式についての記事を参考にしてください)

      • 返信ありがとうございます。
        教師局面の生成は、NNUEでしてみると、何分待っても始まらなかったので、
        KPPTでしました。定跡は使ってません。
        コマンドは
        SkipLoadingEval true
        gensfen depth 3 loop 100000000 output_file_name Teacher_D3 eval_limit 3000 write_minply 1 random_move_minply 1 random_move_count 5 save_every 5000000
        こんな感じです。(Teacher_D3 は教師ファイル名)何処かおかしい所が見られますか?

        lambda 0でもう一度やってみます。

        • lambda 0 でも500万局面で
          loss: 0.69206 >= best (0.691917), rejectedとなりました・・・・。
          やね師匠、「これならまともな物が出来るだろう!」という様なサンプルのコマンドを頂けませんでしょうか?(出来れば一番最初の学習時のものと、2回目以降のものを・・・)

          • 何度も返信ありがとうございます。(๑´ڡ`๑)
            NNUEの育成には根気が必要なのですね(*>ш<*)

    • まあ…、そこまで無理にlossが下がるまで、現状のetaで頑張らずとも、数回やってlossが下がらないならetaを1/2ずつしてくぐらいでいいと私は思いますけども..

  9. SkipLoadingEval trueで作成して教師局面3億をSkipLoadingEval trueで一周学習した評価関数にもう一周同じ教師局面3億を使って学習させれば、loop 2と同等の効果が得られますか?
    又、2回目もlambda 0としなければなりませんか?

    • あと一つ、学習の途中でlearn.exe消してしまったのですが、そこから再開する方法はありますでしょうか

    • 学習コマンドのloopは単に教師データを繰り返し与える回数だった気がします。なので1つ目の質問はYesです。2つ目の質問はいまひとつ意味がわかりませんが、初回がlambda 0を指定しているなら、2回目もlambda 0を指定しないとloop 2と同等ではないです。

      • 即ち、学習の最中に、右上にあるバッテンマークを誤ってクリックしてしまい、今までしていた分が水泡に帰したのです。もう一度最初からするのは億劫なので、バッテンマークを押す時に学習されていた所から再開する方法はあるのか?という意味でございまする。

  10. ①学習の際、何度か同じ学習をし、一番ロスが下がた
     ものを採用するのと、ひたすら、
     教師生成→学習→教師生成→学習…を繰り返すのと、
     どちらのほうが効率がよいのでしょうか?
    ②newbob_decayの試行回数を指定するにはどうすば
     良いですか?

    • > ①学習の際、何度か同じ学習をし

      同じ教師をloopで複数回学習させるかという意味でしたら、まあ、教師局面が少ないならやったほうがいいと思いますけど、あまり回し続けても過学習しますし、ほどほどに…。

      > ②newbob_decayの試行回数

      newbob_num_trialsでできたような?

  11. 例えば、嬉野流評価巻子を作ろうと、嬉野流の定跡(先手後手両方)を入れた状態で生成した教師で学習を進めると、
    ソフトが嬉野流側ではなく対嬉野流側の駒の位置関係のほうが良いと捉えてただ嬉野流をボコす評価関数が出来上がると思うのですが、どうすればソフト的にマイナーな戦法(振り飛車等)を上手く学習させることができるのでしょうか?(一般人に出来る方法で)

    • 嬉野流側が負けた棋譜を間引く(減らす)といいような?たややんさんがなんかそんな手法で振り飛車評価関数を作成されてますよね。
      教師局面の生成のコードのなかで、終局後にその1局分を書き出している箇所で、以下のようにやるなど..
      if (嬉野流側が負け && prng.rand(100) < 20) continue; // 嬉野流側が負けなら20%の確率で書き出さない

      • とても我が儘な話ですが、私の環境だとコードをいじれてもビルド出来ないので、
        それを、何らかの形で、一般に公開している
        やねうら王に標準搭載して頂くという事は可能でしょうか?

        • その機能、検証が大変そうで、私はあまり興味ないのでその検証までやりたくない感じです..すみません。ビルドに関してはAWSでVisual StudioつきのAMI借りれば速攻でビルドできるので、まあ、1時間かかってもスポットインスタンスの小さいマシンなら30円にもならないかと…。


  12. epoch0に、depth3の10億局面を一度、
    batchsize 1000000 nn_batch_size 10000 lambda 0 mirror_percentage 50 eta 0.1 eval_save_interval 5000000
    で一周回したのですが、(これをepoch1とする。)
    epoch1で教師を生成してepoch1を学習する時、
    lambdaはどれぐらいが適正値でしょうか?

    (まだはっきりとは決めていませんが次回は、
    depth4の1億を一周か、depth4の5億を一周か
    のどちらかをしようと思っております。
    lambda以外の値は今回と同じです。)

    KPPTは元(epoch0)がR1800程度あるのに対し、
    NNUEは元(epoch0の状態)が実質R0程度ですよね?
    ならば、しばらくは、やね師匠のepoch0.1にほぼ勝てないのは当たり前、と考えてよいですか?
    学習に失敗してる気しかせず、不安で夜も寝れません。(一手500万ノードで、飛車先交換から飛車を浮いて5筋に回って、9七角と角を覗き、挙句の果てに向かい飛車にするという始末で・・・。)

    長文失礼しました・・・。

    • > lambdaはどれぐらいが適正値でしょうか?

      どうせlossが下がらなければetaは自動的に半分になっていくので、0.1ぐらいからスタートしても良いのでは。

      > しばらくは、やね師匠のepoch0.1にほぼ勝てないのは当たり前、と考えてよいですか?

      とりま、ゼロクリアした評価関数にどれくらい勝ててるか計測したほうが良いような…。

      • 返信ありがとうございます。

        >とりま、ゼロクリアした評価関数にどれくらい勝ててるか計測したほうが良いような…。

        どれぐらいなら良いとかありますか?


        • すみません、改めて棋譜見ると、8億学習した時の評価関数での傾向で、10億学習したもの(epoch1)では見られませんでした。

          ramdom_move_like_aperyは使った方が良いでしょうか?もし使った方が良いのであれば、どれぐらいの値が良いでしょうか?

          • 質問が多く大変申し訳ないのですが、
            ramdom_move_like_aperyは使った方が良いでしょうか?もし使った方が良いのであれば、どれぐらいの値が良いでしょうか?

          • KPPTもNNUEも、玉の位置ごとにテーブルを持ってるような感じですので、ある程度ランダムに玉を動かしたものを教師生成のときの初期局面とすべきだと私は考えてます。なので、使ったほうが良いと私は思うのですけど、比較データは持ってないです。使わなくとも、たぶんR20も変わらないかと…。

  13. 最近tttakさんがネットワークを改造したNNUE型を公開されてますね。今まで似たようなものが公開されてなかったので結構遊んでて面白いです。(https://github.com/tttak/YaneuraOu/releases/tag/V4.89_NNUE-features_20200401)
    これらの評価関数を学習させてみたいと思ったのですが、添付ReadMeによると学習時の次元下げ用のFactorizerが用意されてないとのことで、Factorizerがないと学習はできないのでしょうか。

    • NNUEのことは、よくわかりません>< 一般的に言って、次元下げ自体は、しなくとも評価関数の学習自体は可能ですけども、Factorizerがないと学習が正常に機能しないのかどうかは私にはわかりません。

      • そうでしたか、すみません…(´ཀ` )、学習はまあビルドして動くか試してみます。
        これまでK,P,KP以外の入力特徴をもったNNUE型のサンプルが少なくて(ちなみにそれ以前のサンプルもtttakさんだったり)、今回のバーゲンセールを機に新たなNNUE型が増えれば更にやねうらファミリーが強くなっていくのではと期待してます。(やねさんの入力特徴改造講座お待ちしております|ω・) )

        • 入力する特徴量自体は、まあ、いまぐらいでいいバランスだとは思いますよ。ネットワークの構造は改造の余地が大いにありますけども。

  14. gensfenコマンドで、生成する教師を手数ごとに分ける機能ってあったりします?
    例 1~64がa.bin 64~200がb.bin 200~がc.binなど

  15. やねうら王の学習ログに出てくる
    test_cross_entropy_eval
    test_cross_entropy_win
    learn_cross_entropy_eval
    learn_cross_entropy_win
    とは、それぞれどのような事を表していて、学習時には、それぞれどのように値が推移するのが理想的でしょうか?

    • loss関数の値ですね。下がれば下がるほど良いです(`・ω・´)b
      グラフプロットしたときに滑らかに下がって行くべきではあります。

      “learn_”は学習に使っている教師局面に対して、です。
      “test_”は検証用に使っている教師局面に対して、です。
      “_eval”は教師局面の評価値に対してのlossです。
      “_win”は教師局面の勝率に対してのlossです。

    • 先手から見たqsearch(浅い探索)の評価値の合計ですね。
      いま見たら足すときに絶対値はとってないのであまり意味がない値かも..。

      教師局面の先手勝率が55%だとして、教師局面が平均的に55%に相当する評価値であることが言える状況なら、評価値のスケールの指標にはなるのかな…。

      • 返信ありがとうございます。
        この機にもう一つ聞いておきたい事があるので、もう一つ質問させて頂きます。
        学習に関しての質問で、
        all threads are joinみたいなのが出て学習が終了する時のイタレーションが、ハイパーパラメーター等は全く同じなのに、
        教師数 / mini-batchの時と(教師数 / mini-batch) – 1
        の時があるのでしょうか?

        • 教師局面を指定されたsfen分だけ処理した時点で終了です。ところが、その処理している間にも先読みバッファ分だけは局面を先読みしてシャッフルする作業を読み込み用のスレッドがやり続けるので、”all threads are joined.”と出力されたタイミングでどれだけ教師局面が読み込まているかについては不定です。

ベイン へ返信する コメントをキャンセル

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です