From 0056b408e904f0733f3cfd2c2a7e57c3d74fcd62 Mon Sep 17 00:00:00 2001 From: yaneurao Date: Thu, 28 Nov 2024 15:07:15 +0900 Subject: [PATCH] =?UTF-8?q?-=20search()=E3=81=AEUSE=5FLAZY=5FEVALUATE?= =?UTF-8?q?=E3=81=A7ttHit=E3=81=8B=E3=81=A4PvNode=E3=81=AE=E3=81=A8?= =?UTF-8?q?=E3=81=8D=E3=81=ABevaluate()=E5=91=BC=E3=81=B3=E5=87=BA?= =?UTF-8?q?=E3=81=99=E3=81=AE=E3=82=84=E3=82=81=E3=82=8B=E3=80=82=20-=20US?= =?UTF-8?q?E=5FDIFF=5FEVAL=E5=B0=8E=E5=85=A5=E3=80=82MATERIAL=E8=A9=95?= =?UTF-8?q?=E4=BE=A1=E9=96=A2=E6=95=B0=E3=81=A7=E3=81=AF=E3=80=81=E6=AC=A1?= =?UTF-8?q?node=E3=81=AB=E8=A1=8C=E3=81=8F=E5=A0=B4=E5=90=88=E3=82=82?= =?UTF-8?q?=E3=80=81evaluate()=E5=91=BC=E3=81=B3=E5=87=BA=E3=81=97?= =?UTF-8?q?=E3=82=92=E5=BC=B7=E5=88=B6=E3=81=97=E3=81=AA=E3=81=84=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/config.h | 8 ++ .../yaneuraou-engine/yaneuraou-search.cpp | 101 ++++++++++-------- source/thread.cpp | 8 +- 3 files changed, 68 insertions(+), 49 deletions(-) diff --git a/source/config.h b/source/config.h index f5e0f17e5..ce09af017 100644 --- a/source/config.h +++ b/source/config.h @@ -394,6 +394,9 @@ // 検討目的なら、これをオンにするのは好ましくない。) // #define ENABLE_QUICK_DRAW +// 差分計算型評価関数であるか?(次のnodeに行く前に必ずevaluate()が必要である場合にdefineする) +// #define USE_DIFF_EVAL + // =============================================================== // ここ以降では、↑↑↑で設定した内容に基づき必要なdefineを行う。 // =============================================================== @@ -437,6 +440,11 @@ constexpr int MAX_PLY_NUM = 246; #define USE_SHARED_MEMORY_IN_EVAL #endif + #if defined(YANEURAOU_ENGINE_KPPT) || defined(YANEURAOU_ENGINE_KPP_KKPT) || defined(YANEURAOU_ENGINE_NNUE) + // 差分計算型評価関数である場合、次のnodeに行く前に必ずevaluate()が必要である。 + #define USE_DIFF_EVAL + #endif + // 学習機能を有効にするオプション。 // 教師局面の生成、定跡コマンド(makebook thinkなど)を用いる時には、これを // 有効化してコンパイルしなければならない。 diff --git a/source/engine/yaneuraou-engine/yaneuraou-search.cpp b/source/engine/yaneuraou-engine/yaneuraou-search.cpp index 85e2ebfd2..984360ae1 100644 --- a/source/engine/yaneuraou-engine/yaneuraou-search.cpp +++ b/source/engine/yaneuraou-engine/yaneuraou-search.cpp @@ -203,28 +203,6 @@ Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorseni return futilityMult * d - improvingDeduction - worseningDeduction; } -// 【計測資料 30.】 Reductionのコード、Stockfish 9と10での比較 - -// Reductions lookup table initialized at startup -// 探索深さを減らすためのReductionテーブル。起動時に初期化する。 -std::array reductions; // [depth or moveNumber] - -// 残り探索深さをこの深さだけ減らす。 -// 注意 : Stockfish 17(2024.11)で、1024倍して返すことになった。 -// -// 引数の意味 -// d : depth -// mn : move_count -// i : improving , 評価値が2手前から上がっているかのフラグ。 -// 上がっていないなら悪化していく局面なので深く読んでも仕方ないからreduction量を心もち増やす。 -// delta, rootDelta : staticEvalとchildのeval(value)の差が一貫して低い時にreduction量を増やしたいので、 -// そのためのフラグ。(これがtrueだとreduction量が1増える) -Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { - int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + PARAM_REDUCTION_ALPHA - delta * PARAM_REDUCTION_GAMMA / rootDelta) - + (!i && reductionScale > PARAM_REDUCTION_BETA) * 1135; - // PARAM_REDUCTION_BETAの値、将棋ではもう少し小さくして、reductionの適用範囲を広げた方がいいかも? -} // 【計測資料 29.】 Move CountベースのFutiliy Pruning、Stockfish 9と10での比較 @@ -287,6 +265,28 @@ int stat_bonus(Depth d) { return std::min(168 * d - 100, 1718); } // TODO : あとで int stat_malus(Depth d) { return std::min(768 * d - 257, 2351); } +// 【計測資料 30.】 Reductionのコード、Stockfish 9と10での比較 + +// Reductions lookup table initialized at startup +// 探索深さを減らすためのReductionテーブル。起動時に初期化する。 +std::array reductions; // [depth or moveNumber] + +// 残り探索深さをこの深さだけ減らす。 +// 注意 : Stockfish 17(2024.11)で、1024倍して返すことになった。 +// +// 引数の意味 +// d : depth +// mn : move_count +// i : improving , 評価値が2手前から上がっているかのフラグ。 +// 上がっていないなら悪化していく局面なので深く読んでも仕方ないからreduction量を心もち増やす。 +// delta, rootDelta : staticEvalとchildのeval(value)の差が一貫して低い時にreduction量を増やしたいので、 +// そのためのフラグ。(これがtrueだとreduction量が1増える) +Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { + int reductionScale = reductions[d] * reductions[mn]; + return (reductionScale + PARAM_REDUCTION_ALPHA - delta * PARAM_REDUCTION_GAMMA / rootDelta) + + (!i && reductionScale > PARAM_REDUCTION_BETA) * 1135; + // PARAM_REDUCTION_BETAの値、将棋ではもう少し小さくして、reductionの適用範囲を広げた方がいいかも? +} #if 0 // チェスでは、引き分けが0.5勝扱いなので引き分け回避のための工夫がしてあって、 @@ -902,9 +902,9 @@ void search_thread_init(Thread* th, Stack* ss , Move pv[]) // counterMovesをnullptrに初期化するのではなくNO_PIECEのときの値を番兵として用いる。 for (int i = 7; i > 0; --i) { - (ss - i)->continuationHistory = &th->continuationHistory[0][0](NO_PIECE, SQ_ZERO); // Use as a sentinel + (ss - i)->continuationHistory = &th->continuationHistory[0][0](NO_PIECE, SQ_ZERO); // Use as a sentinel // (ss - i)->continuationCorrectionHistory = &this->continuationCorrectionHistory[NO_PIECE][0]; - (ss - i)->staticEval = VALUE_NONE; + (ss - i)->staticEval = VALUE_NONE; } // Stack(探索用の構造体)上のply(手数)は事前に初期化しておけば探索時に代入する必要がない。 @@ -2023,25 +2023,30 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } // ----------------------- - // Step 6. Static evaluation of the position - // Step 6. 局面の静的な評価 + // Lazy Evaluator + // 評価関数の遅延評価子(やねうら王独自拡張) // ----------------------- - Value unadjustedStaticEval = VALUE_NONE; - // この局面で評価関数を呼び出したのか。(do_move()までには呼ばないと駄目) - // ※ やねうら王開発版にて独自追加 bool evaluated = false; auto evaluate = [&](Position& pos) { evaluated = true; return ::evaluate(pos); }; auto lazy_evaluate = [&](Position& pos) { -#if defined(USE_LAZY_EVALUATE) +#if defined(USE_LAZY_EVALUATE) && defined(USE_DIFF_EVAL) + // まだこのnodeでevaluate()が呼び出されていなかったのであれば呼び出す。 if (!evaluated) { evaluated = true; evaluate_with_no_return(pos); } #endif - }; + }; + + // ----------------------- + // Step 6. Static evaluation of the position + // Step 6. 局面の静的な評価 + // ----------------------- + + Value unadjustedStaticEval = VALUE_NONE; if (ss->inCheck) { @@ -2107,19 +2112,17 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Stockfish 17のコード if (unadjustedStaticEval == VALUE_NONE) // TT raceで破壊されてるっぽい? - unadjustedStaticEval = - // evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); - evaluate(pos); + + // unadjustedStaticEval = evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(pos); // 置換表にhitしたなら、評価値が記録されているはずだから、それを取り出しておく。 // あとで置換表に書き込むときにこの値を使えるし、各種枝刈りはこの評価値をベースに行なうから。 - else if (PvNode) + else if (PvNode) { // Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); // → TODO : hint_common_parent_position()実装するか検討する。 - unadjustedStaticEval = evaluate(pos); - // ないほうがいいか? ⇨ V836dev_oとV836dev_pの比較 - // ⇨ スレッド数が多くてTT raceが起きやすいと違うかもな…。 + } #else unadjustedStaticEval = evaluate(pos); #endif @@ -2293,7 +2296,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // ⇨ evalがbetaを超えているので1手パスしてもbetaは超えそう。だからnull moveを試す && ss->staticEval >= beta - PARAM_NULL_MOVE_MARGIN1 * depth + PARAM_NULL_MOVE_MARGIN2 && !excludedMove - // && pos.non_pawn_material(us) // これ終盤かどうかを意味する。将棋でもこれに相当する条件が必要かも。 + // && pos.non_pawn_material(us) // 盤上にpawn以外の駒がある ≒ pawnだけの終盤ではない。将棋でもこれに相当する条件が必要かも。 && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY // 同じ手番側に連続してnull moveを適用しない @@ -2713,7 +2716,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ss->staticEval + (bestValue < ss->staticEval - 45 ? 140 : 43) + 141 * lmrDepth; // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < PARAM_FUTILITY_AT_PARENT_NODE_DEPTH/*12*/ && futilityValue <= alpha) + if (!ss->inCheck && lmrDepth < PARAM_FUTILITY_AT_PARENT_NODE_DEPTH && futilityValue <= alpha) { if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY @@ -3730,16 +3733,16 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) return ttData.value; // ----------------------- - // Step 4. Static evaluation of the position - // Step 4. この局面の静止評価 + // Lazy Evaluator + // 評価関数の遅延評価子(やねうら王独自拡張) // ----------------------- - // 置換表に書いてあったevalの値をなるべく壊さずに保存するための変数 - Value unadjustedStaticEval = VALUE_NONE; + // この局面で評価関数を呼び出したのか。(do_move()までには呼ばないと駄目) bool evaluated = false; auto evaluate = [&](Position& pos) { evaluated = true; return ::evaluate(pos); }; auto lazy_evaluate = [&](Position& pos) { -#if defined(USE_LAZY_EVALUATE) +#if defined(USE_LAZY_EVALUATE) && defined(USE_DIFF_EVAL) + // まだこのnodeでevaluate()が呼び出されていなかったのであれば呼び出す。 if (!evaluated) { evaluated = true; @@ -3748,6 +3751,14 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) #endif }; + // ----------------------- + // Step 4. Static evaluation of the position + // Step 4. この局面の静止評価 + // ----------------------- + + // 置換表に書いてあったevalの値をなるべく壊さずに保存するための変数 + Value unadjustedStaticEval = VALUE_NONE; + if (ss->inCheck) { diff --git a/source/thread.cpp b/source/thread.cpp index 619d4b644..ada38db9e 100644 --- a/source/thread.cpp +++ b/source/thread.cpp @@ -45,9 +45,9 @@ void Thread::clear() { #if defined(USE_MOVE_PICKER) mainHistory.fill(0); - captureHistory.fill(0); + captureHistory.fill(-758); #if defined(ENABLE_PAWN_HISTORY) - pawnHistory.fill(0); + pawnHistory.fill(-1158); pawnCorrectionHistory.fill(0); materialCorrectionHistory.fill(0); majorPieceCorrectionHistory.fill(0); @@ -73,11 +73,11 @@ void Thread::clear() for (StatsType c : { NoCaptures, Captures }) //for (auto& to : continuationHistory[inCheck][c]) // for (auto& h : to) - // h->fill(-678); + // h->fill(-675); // ↑この初期化コードは、ContinuationHistory::fill()に移動させた。 - continuationHistory[inCheck][c].fill(-678); + continuationHistory[inCheck][c].fill(-645); #endif }