This commit is contained in:
noobpwnftw
2020-09-08 09:35:53 +08:00
parent e5f05fa2b9
commit d21424c8d3
15 changed files with 61 additions and 238 deletions
+2 -3
View File
@@ -17,12 +17,10 @@ setoption name Threads value x
setoption name Hash value y
setoption name SyzygyPath value path
isready
gensfen depth a loop b use_draw_in_training_data_generation 1 eval_limit 32000 use_raw_nnue_eval 0
gensfen depth a loop b use_draw_in_training_data_generation 1 eval_limit 32000
```
Specify how many threads and how much memory you would like to use with the x and y values. The option SyzygyPath is not necessary, but if you would like to use it, you must first have Syzygy endgame tablebases on your computer, which you can find [here](http://oics.olympuschess.com/tracker/index.php). You will need to have a torrent client to download these tablebases, as that is probably the fastest way to obtain them. The path is the path to the folder containing those tablebases. It does not have to be surrounded in quotes.
use_raw_nnue_eval controls if the training data generator or trainer uses raw NNUE eval values. Don't forget to set use_raw_nnue_eval 0 when initial training data are generated. Otherwise, the gensfen command will crash.
This will save a file named "generated_kifu.bin" in the same folder as the binary. Once generation is done, rename the file to something like "1billiondepth12.bin" to remember the depth and quantity of the positions and move it to a folder named "trainingdata" in the same directory as the binaries.
#### Generation Parameters
- Depth is the searched depth per move, or how far the engine looks forward. This value is an integer.
@@ -34,6 +32,7 @@ Use the "learn" binary. Create an empty folder named "evalsave" in the same dire
```
uci
setoption name SkipLoadingEval value true
setoption name Training value true
setoption name Use NNUE value true
setoption name Threads value x
isready
+1 -2
View File
@@ -56,7 +56,6 @@ SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp
nnue/features/enpassant.cpp \
nnue/nnue_test_command.cpp \
extra/sfen_packer.cpp \
learn/gensfen2019.cpp \
learn/learner.cpp \
learn/gensfen.cpp \
learn/convert.cpp \
@@ -908,7 +907,7 @@ learn: config-sanity
EXTRALDFLAGS=' $(BLASLDFLAGS) -fopenmp ' \
all
profile-learn: net config-sanity objclean profileclean
profile-learn: config-sanity objclean profileclean
@echo ""
@echo "Step 1/4. Building instrumented executable ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make) \
+25 -27
View File
@@ -32,13 +32,6 @@
#include "thread.h"
#include "uci.h"
#ifdef EVAL_LEARN
namespace Learner
{
extern bool use_raw_nnue_eval;
}
#endif
namespace Eval {
bool useNNUE;
@@ -947,27 +940,32 @@ make_v:
/// evaluation of the position from the point of view of the side to move.
Value Eval::evaluate(const Position& pos) {
#ifdef EVAL_LEARN
if (Learner::use_raw_nnue_eval) {
return NNUE::evaluate(pos);
if (Options["Training"]) {
Value v = NNUE::evaluate(pos);
// Damp down the evaluation linearly when shuffling
v = v * (100 - pos.rule50_count()) / 100;
// Guarantee evaluation does not hit the tablebase range
v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
return v;
} else {
bool classical = !Eval::useNNUE
|| abs(eg_value(pos.psq_score())) * 16 > NNUEThreshold1 * (16 + pos.rule50_count());
Value v = classical ? Evaluation<NO_TRACE>(pos).value()
: NNUE::evaluate(pos) * 5 / 4 + Tempo;
if (classical && Eval::useNNUE && abs(v) * 16 < NNUEThreshold2 * (16 + pos.rule50_count()))
v = NNUE::evaluate(pos) * 5 / 4 + Tempo;
// Damp down the evaluation linearly when shuffling
v = v * (100 - pos.rule50_count()) / 100;
// Guarantee evaluation does not hit the tablebase range
v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
return v;
}
#endif
bool classical = !Eval::useNNUE
|| abs(eg_value(pos.psq_score())) * 16 > NNUEThreshold1 * (16 + pos.rule50_count());
Value v = classical ? Evaluation<NO_TRACE>(pos).value()
: NNUE::evaluate(pos) * 5 / 4 + Tempo;
if (classical && Eval::useNNUE && abs(v) * 16 < NNUEThreshold2 * (16 + pos.rule50_count()))
v = NNUE::evaluate(pos) * 5 / 4 + Tempo;
// Damp down the evaluation linearly when shuffling
v = v * (100 - pos.rule50_count()) / 100;
// Guarantee evaluation does not hit the tablebase range
v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
return v;
}
/// trace() is like evaluate(), but instead of returning a value, it returns
+18 -152
View File
@@ -11,10 +11,6 @@
#include "../uci.h"
#include "../syzygy/tbprobe.h"
#if defined(USE_BOOK)
#include "../extra/book/book.h"
#endif
#include <chrono>
#include <random>
#include <regex>
@@ -54,11 +50,7 @@ namespace Learner
static bool detect_draw_by_consecutive_low_score = false;
static bool detect_draw_by_insufficient_mating_material = false;
// Use raw NNUE eval value in the Eval::evaluate().
// If hybrid eval is enabled, training data
// generation and training don't work well.
// https://discordapp.com/channels/435943710472011776/733545871911813221/748524079761326192
static bool use_raw_nnue_eval = true;
static std::vector<std::string> bookStart;
// Helper class for exporting Sfen
struct SfenWriter
@@ -313,13 +305,6 @@ namespace Learner
int ply,
int& random_move_c);
Value evaluate_leaf(
Position& pos,
std::vector<StateInfo, AlignedAllocator<StateInfo>>& states,
int ply,
int depth,
vector<Move>& pv);
// Min and max depths for search during gensfen
int search_depth_min;
int search_depth_max;
@@ -674,69 +659,6 @@ namespace Learner
return random_move_flag;
}
Value MultiThinkGenSfen::evaluate_leaf(
Position& pos,
std::vector<StateInfo, AlignedAllocator<StateInfo>>& states,
int ply,
int depth,
vector<Move>& pv)
{
auto rootColor = pos.side_to_move();
for (auto m : pv)
{
#if 1
// There should be no illegal move. This is as a debugging precaution.
if (!pos.pseudo_legal(m) || !pos.legal(m))
{
cout << "Error! : " << pos.fen() << m << endl;
}
#endif
pos.do_move(m, states[ply++]);
// Because the difference calculation of evaluate() cannot be
// performed unless each node evaluate() is called!
// If the depth is 8 or more, it seems
// faster not to calculate this difference.
#if defined(EVAL_NNUE)
if (depth < 8)
{
Eval::NNUE::update_eval(pos);
}
#endif // defined(EVAL_NNUE)
}
// Reach leaf
Value v;
if (pos.checkers())
{
// Sometime a king is checked. An example is a case that a checkmate is
// found in the search. If Eval::evaluate() is called whne a king is
// checked, classic eval crashes by an assertion. To avoid crashes, return
// VALUE_NONE and let the caller assign a value to the position.
v = VALUE_NONE;
}
else
{
v = Eval::evaluate(pos);
// evaluate() returns the evaluation value on the turn side, so
// If it's a turn different from root_color, you must invert v and return it.
if (rootColor != pos.side_to_move())
{
v = -v;
}
}
// Rewind the pv moves.
for (auto it = pv.rbegin(); it != pv.rend(); ++it)
{
pos.undo_move(*it);
}
return v;
}
// thread_id = 0..Threads.size()-1
void MultiThinkGenSfen::thread_worker(size_t thread_id)
{
@@ -760,12 +682,7 @@ namespace Learner
auto th = Threads[thread_id];
auto& pos = th->rootPos;
pos.set(StartFEN, false, &si, th);
#if defined(USE_BOOK)
// Refer to the members of BookMoveSelector defined in the search section.
auto& book = ::book;
#endif
pos.set(bookStart[prng.rand(bookStart.size())], false, &si, th);
// Vector for holding the sfens in the current simulated game.
PSVector a_psv;
@@ -800,35 +717,6 @@ namespace Learner
flush_psv(result.value());
break;
}
#if defined(USE_BOOK)
if ((next_move = book.probe(pos)) != MOVE_NONE)
{
// Hit the constant track.
// The move was stored in next_move.
// Do not use the fixed phase for learning.
sfens.clear();
if (random_move_minply != -1)
{
// Random move is performed with a certain
// probability even in the constant phase.
goto RANDOM_MOVE;
}
else
{
// When -1 is specified as random_move_minply,
// it points according to the standard until
// it goes out of the standard.
// Prepare an innumerable number of situations
// that have left the constant as
// ConsiderationBookMoveCount true using a huge constant
// Used for purposes such as performing
// a random move 5 times from there.
goto DO_MOVE;
}
}
#endif
{
auto [search_value, search_pv] = search(pos, depth, 1, nodes);
@@ -916,18 +804,7 @@ namespace Learner
// Get the value of evaluate() as seen from the
// root color on the leaf node of the PV line.
// I don't know the goodness and badness of using the
// return value of search() as it is.
// TODO: Consider using search value instead of evaluate_leaf.
// Maybe give it as an option.
// Use PV moves to reach the leaf node and use the value
// that evaluated() is called on that leaf node.
const auto leaf_value = evaluate_leaf(pos, states, ply, depth, search_pv);
// If for some reason the leaf node couldn't yield an eval
// we fallback to search value.
psv.score = leaf_value == VALUE_NONE ? search_value : leaf_value;
psv.score = search_value;
psv.gamePly = ply;
@@ -948,9 +825,6 @@ namespace Learner
// Update the next move according to best search result.
next_move = search_pv[0];
}
RANDOM_MOVE:;
auto random_move = choose_random_move(pos, random_move_flag, ply, actual_random_move_count);
if (random_move.has_value())
{
@@ -962,13 +836,7 @@ namespace Learner
{
break;
}
// Clear the sfens that were written before the random move.
// (???) why?
a_psv.clear();
}
DO_MOVE:;
pos.do_move(next_move, states[ply]);
// Call node evaluate() for each difference calculation.
@@ -1095,18 +963,10 @@ namespace Learner
is >> detect_draw_by_consecutive_low_score;
else if (token == "detect_draw_by_insufficient_mating_material")
is >> detect_draw_by_insufficient_mating_material;
else if (token == "use_raw_nnue_eval")
is >> use_raw_nnue_eval;
else
cout << "Error! : Illegal token " << token << endl;
}
#if defined(USE_GLOBAL_OPTIONS)
// Save it for later restore.
auto oldGlobalOptions = GlobalOptions;
GlobalOptions.use_eval_hash = use_eval_hash;
#endif
// If search depth2 is not set, leave it the same as search depth.
if (search_depth_max == INT_MIN)
search_depth_max = search_depth_min;
@@ -1130,15 +990,26 @@ namespace Learner
output_file_name = output_file_name + "_" + to_hex(r.rand<uint64_t>()) + to_hex(r.rand<uint64_t>());
}
bookStart.clear();
{
std::string line;
std::ifstream myfile ("3moves_v2.epd");
if (myfile.is_open())
{
while (getline(myfile,line))
{
bookStart.push_back(line);
}
myfile.close();
}
}
std::cout << "gensfen : " << endl
<< " search_depth_min = " << search_depth_min << " to " << search_depth_max << endl
<< " nodes = " << nodes << endl
<< " loop_max = " << loop_max << endl
<< " eval_limit = " << eval_limit << endl
<< " thread_num (set by USI setoption) = " << thread_num << endl
#if defined(USE_BOOK)
<< " book_moves (set by USI setoption) = " << Options["BookMoves"] << endl
#endif
<< " thread_num = " << thread_num << endl
<< " bookStart = " << bookStart.size() << endl
<< " random_move_minply = " << random_move_minply << endl
<< " random_move_maxply = " << random_move_maxply << endl
<< " random_move_count = " << random_move_count << endl
@@ -1188,11 +1059,6 @@ namespace Learner
std::cout << "gensfen finished." << endl;
#if defined(USE_GLOBAL_OPTIONS)
// Restore Global Options.
GlobalOptions = oldGlobalOptions;
#endif
}
}
#endif
-1
View File
@@ -1 +0,0 @@
// just a place holder
-25
View File
@@ -98,12 +98,6 @@ namespace Learner
// data directly. In those cases, we set false to this variable.
static bool convert_teacher_signal_to_winning_probability = true;
// Use raw NNUE eval value in the Eval::evaluate(). If hybrid eval is enabled, training data
// generation and training don't work well.
// https://discordapp.com/channels/435943710472011776/733545871911813221/748524079761326192
// This CANNOT be static since it's used elsewhere.
bool use_raw_nnue_eval = true;
// Using WDL with win rate model instead of sigmoid
static bool use_wdl = false;
@@ -1616,15 +1610,6 @@ namespace Learner
uint64_t eta1_epoch = 0; // eta2 is not applied by default
uint64_t eta2_epoch = 0; // eta3 is not applied by default
#if defined(USE_GLOBAL_OPTIONS)
// Save it for later restore.
auto oldGlobalOptions = GlobalOptions;
// If you hit the eval hash, you can not calculate rmse etc. so turn it off.
GlobalOptions.use_eval_hash = false;
// If you hit the replacement table, pruning may occur at the previous evaluation value, so turn it off.
GlobalOptions.use_hash_probe = false;
#endif
// --- Function that only shuffles the teacher aspect
// normal shuffle
@@ -1796,7 +1781,6 @@ namespace Learner
else if (option == "dest_score_min_value") is >> dest_score_min_value;
else if (option == "dest_score_max_value") is >> dest_score_max_value;
else if (option == "convert_teacher_signal_to_winning_probability") is >> convert_teacher_signal_to_winning_probability;
else if (option == "use_raw_nnue_eval") is >> use_raw_nnue_eval;
// Otherwise, it's a filename.
else
@@ -2076,18 +2060,9 @@ namespace Learner
// Save once at the end.
learn_think.save(true);
#if defined(USE_GLOBAL_OPTIONS)
// Restore Global Options.
GlobalOptions = oldGlobalOptions;
#endif
}
} // namespace Learner
#if defined(GENSFEN2019)
#include "gensfen2019.cpp"
#endif
#endif // EVAL_LEARN
+1 -1
View File
@@ -23,7 +23,7 @@ namespace Eval {
}
if (perspective == BLACK) {
epSquare = rotate180(epSquare);
epSquare = flip_rank(epSquare);
}
auto file = file_of(epSquare);
+2 -2
View File
@@ -23,9 +23,9 @@
namespace Eval::NNUE::Features {
// Orient a square according to perspective (rotates by 180 for black)
// Orient a square according to perspective (flip rank for black)
inline Square orient(Color perspective, Square s) {
return Square(int(s) ^ (bool(perspective) * 63));
return Square(int(s) ^ (bool(perspective) * SQ_A8));
}
// Find the index of the feature quantity from the king position and PieceSquare
+2 -2
View File
@@ -11,9 +11,9 @@ namespace NNUE {
namespace Features {
// Orient a square according to perspective (rotates by 180 for black)
// Orient a square according to perspective (flip rank for black)
inline Square orient(Color perspective, Square s) {
return Square(int(s) ^ (bool(perspective) * 63));
return Square(int(s) ^ (bool(perspective) * SQ_A8));
}
// Find the index of the feature quantity from the ball position and PieceSquare
+2 -2
View File
@@ -11,9 +11,9 @@ namespace NNUE {
namespace Features {
// Orient a square according to perspective (rotates by 180 for black)
// Orient a square according to perspective (flip rank for black)
inline Square orient(Color perspective, Square s) {
return Square(int(s) ^ (bool(perspective) * 63));
return Square(int(s) ^ (bool(perspective) * SQ_A8));
}
// Index of a feature for a given king position.
+2 -2
View File
@@ -11,9 +11,9 @@ namespace NNUE {
namespace Features {
// Orient a square according to perspective (rotates by 180 for black)
// Orient a square according to perspective (flip rank for black)
inline Square orient(Color perspective, Square s) {
return Square(int(s) ^ (bool(perspective) * 63));
return Square(int(s) ^ (bool(perspective) * SQ_A8));
}
// Find the index of the feature quantity from the king position and PieceSquare
+1 -1
View File
@@ -69,7 +69,7 @@
namespace Eval::NNUE {
// Version of the evaluation file
constexpr std::uint32_t kVersion = 0x7AF32F16u;
constexpr std::uint32_t kVersion = 0x7AF32F17u;
// Constant used in evaluation value calculation
constexpr int FV_SCALE = 16;
+1 -16
View File
@@ -68,8 +68,6 @@ namespace {
return Value(223 * (d - improving));
}
bool training;
// Reductions lookup table, initialized at startup
int Reductions[MAX_MOVES]; // [depth or moveNumber]
@@ -195,8 +193,6 @@ void Search::init() {
for (int i = 1; i < MAX_MOVES; ++i)
Reductions[i] = int((22.0 + std::log(Threads.size())) * std::log(i));
training = Options["Training"];
}
@@ -1011,7 +1007,7 @@ moves_loop: // When in check, search starts from here
// Step 12. Pruning at shallow depth (~200 Elo)
if ( !rootNode
&& !(training && PvNode)
&& !(Options["Training"] && PvNode)
&& pos.non_pawn_material(us)
&& bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
{
@@ -2070,17 +2066,6 @@ namespace Learner
rootMoves.push_back(Search::RootMove(m));
assert(!rootMoves.empty());
//#if defined(USE_GLOBAL_OPTIONS)
// Since the generation of the substitution table for each search thread should be managed,
// Increase the generation of the substitution table for this thread because it is a new search.
//TT.new_search(th->thread_id());
// ª If you call new_search here, it may be a loss because you can't use the previous search result.
// Do not do this here, but caller should do TT.new_search(th->thread_id()) for each station ...
// ¨Because we want to avoid reaching the same final diagram, use the substitution table commonly for all threads when generating teachers.
//#endif
}
}
+3 -1
View File
@@ -115,7 +115,9 @@ void TranspositionTable::clear() {
/// TTEntry t2 if its replace value is greater than that of t2.
TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
if (Options["Training"]) {
return found = false, first_entry(0);
}
TTEntry* const tte = first_entry(key);
const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster
+1 -1
View File
@@ -82,7 +82,7 @@ void init(OptionsMap& o) {
o["Use NNUE"] << Option(true, on_use_NNUE);
// The default must follow the format nn-[SHA256 first 12 digits].nnue
// for the build process (profile-build and fishtest) to work.
o["EvalFile"] << Option("nn-82215d0fd0df.nnue", on_eval_file);
o["EvalFile"] << Option("nn.bin", on_eval_file);
#ifdef EVAL_NNUE
// When the evaluation function is loaded at the ucinewgame timing, it is necessary to convert the new evaluation function.
// I want to hit the test eval convert command, but there is no new evaluation function