diff --git a/src/Makefile b/src/Makefile index 9db13e44..ca851dba 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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 \ diff --git a/src/learn/convert.cpp b/src/learn/convert.cpp index b84dc2f8..9bd9548d 100644 --- a/src/learn/convert.cpp +++ b/src/learn/convert.cpp @@ -25,20 +25,12 @@ #include #include #include +#include #if defined (_OPENMP) #include #endif -#if defined(_MSC_VER) -// The C++ filesystem cannot be used unless it is C++17 or later or MSVC. -// I tried to use windows.h, but with g++ of msys2 I can not get the files in the folder well. -// Use dirent.h because there is no help for it. -#include -#elif defined(__GNUC__) -#include -#endif - using namespace std; namespace Learner diff --git a/src/learn/gensfen.cpp b/src/learn/gensfen.cpp index 4214233b..b049192e 100644 --- a/src/learn/gensfen.cpp +++ b/src/learn/gensfen.cpp @@ -28,18 +28,12 @@ #include #include #include +#include #if defined (_OPENMP) #include #endif -#if defined(_MSC_VER) -// std::filesystem doesn't work on GCC even though it claims to support C++17. -#include -#elif defined(__GNUC__) -#include -#endif - #if defined(EVAL_NNUE) #include "../nnue/evaluate_nnue_learner.h" #include diff --git a/src/learn/gensfen2019.cpp b/src/learn/gensfen2019.cpp deleted file mode 100644 index 01293b9c..00000000 --- a/src/learn/gensfen2019.cpp +++ /dev/null @@ -1 +0,0 @@ -// just a place holder diff --git a/src/learn/learn.h b/src/learn/learn.h index e29ed74a..1bc39cf9 100644 --- a/src/learn/learn.h +++ b/src/learn/learn.h @@ -27,30 +27,6 @@ // SGD looking only at the sign of the gradient. It requires less memory, but the accuracy is... // #define SGD_UPDATE -// ---------------------- -// Settings for learning -// ---------------------- - -// mini-batch size. -// Calculate the gradient by combining this number of phases. -// If you make it smaller, the number of update_weights() will increase and the convergence will be faster. The gradient is incorrect. -// If you increase it, the number of update_weights() decreases, so the convergence will be slow. The slope will come out accurately. -// I don't think you need to change this value in most cases. - -#define LEARN_MINI_BATCH_SIZE (1000 * 1000 * 1) - -// The number of phases to read from the file at one time. After reading this much, shuffle. -// It is better to have a certain size, but this number x 40 bytes x 3 times as much memory is consumed. 400MB*3 is consumed in the 10M phase. -// Must be a multiple of THREAD_BUFFER_SIZE(=10000). - -#define LEARN_SFEN_READ_SIZE (1000 * 1000 * 10) - -// Saving interval of evaluation function at learning. Save each time you learn this number of phases. -// Needless to say, the longer the saving interval, the shorter the learning time. -// Folder name is incremented for each save like 0/, 1/, 2/... -// By default, once every 1 billion phases. -#define LEARN_EVAL_SAVE_INTERVAL (1000000000ULL) - // ---------------------- // Select the objective function @@ -79,10 +55,6 @@ // debug settings for learning // ---------------------- -// Reduce the output of rmse during learning to 1 for this number of times. -// rmse calculation is done in one thread, so it takes some time, so reducing the output is effective. -#define LEARN_RMSE_OUTPUT_INTERVAL 1 - // ---------------------- // learning from zero vector @@ -205,6 +177,34 @@ typedef float LearnFloatType; namespace Learner { + // ---------------------- + // Settings for learning + // ---------------------- + + // mini-batch size. + // Calculate the gradient by combining this number of phases. + // If you make it smaller, the number of update_weights() will increase and the convergence will be faster. The gradient is incorrect. + // If you increase it, the number of update_weights() decreases, so the convergence will be slow. The slope will come out accurately. + // I don't think you need to change this value in most cases. + + constexpr std::size_t LEARN_MINI_BATCH_SIZE = 1000 * 1000 * 1; + + // The number of phases to read from the file at one time. After reading this much, shuffle. + // It is better to have a certain size, but this number x 40 bytes x 3 times as much memory is consumed. 400MB*3 is consumed in the 10M phase. + // Must be a multiple of THREAD_BUFFER_SIZE(=10000). + + constexpr std::size_t LEARN_SFEN_READ_SIZE = 1000 * 1000 * 10; + + // Saving interval of evaluation function at learning. Save each time you learn this number of phases. + // Needless to say, the longer the saving interval, the shorter the learning time. + // Folder name is incremented for each save like 0/, 1/, 2/... + // By default, once every 1 billion phases. + constexpr std::size_t LEARN_EVAL_SAVE_INTERVAL = 1000000000ULL; + + // Reduce the output of rmse during learning to 1 for this number of times. + // rmse calculation is done in one thread, so it takes some time, so reducing the output is effective. + constexpr std::size_t LEARN_RMSE_OUTPUT_INTERVAL = 1; + //Structure in which PackedSfen and evaluation value are integrated // If you write different contents for each option, it will be a problem when reusing the teacher game // For the time being, write all the following members regardless of the options. diff --git a/src/learn/learner.cpp b/src/learn/learner.cpp index 98c8e32e..ddfaff5a 100644 --- a/src/learn/learner.cpp +++ b/src/learn/learner.cpp @@ -45,15 +45,6 @@ #include #endif -#if defined(_MSC_VER) -// The C++ filesystem cannot be used unless it is C++17 or later or MSVC. -// I tried to use windows.h, but with g++ of msys2 I can not get the files in the folder well. -// Use dirent.h because there is no help for it. -#include -#elif defined(__GNUC__) -#include -#endif - #if defined(EVAL_NNUE) #include "../nnue/evaluate_nnue_learner.h" #include @@ -62,8 +53,11 @@ using namespace std; -//// This is defined in the search section. -//extern Book::BookMoveSelector book; + +#if defined(USE_BOOK) +// This is defined in the search section. +extern Book::BookMoveSelector book; +#endif template T operator +=(std::atomic& x, const T rhs) @@ -128,9 +122,9 @@ namespace Learner constexpr double wdl_total = 1000.0; constexpr double draw_score = 0.5; - double wdl_w = UCI::win_rate_model_double(value, ply); - double wdl_l = UCI::win_rate_model_double(-value, ply); - double wdl_d = wdl_total - wdl_w - wdl_l; + const double wdl_w = UCI::win_rate_model_double(value, ply); + const double wdl_l = UCI::win_rate_model_double(-value, ply); + const double wdl_d = wdl_total - wdl_w - wdl_l; return (wdl_w + wdl_d * draw_score) / wdl_total; } @@ -150,16 +144,17 @@ namespace Learner double calc_cross_entropy_of_winning_percentage(double deep_win_rate, double shallow_eval, int ply) { - double p = deep_win_rate; - double q = winning_percentage(shallow_eval, ply); + const double p = deep_win_rate; + const double q = winning_percentage(shallow_eval, ply); return -p * std::log(q) - (1.0 - p) * std::log(1.0 - q); } double calc_d_cross_entropy_of_winning_percentage(double deep_win_rate, double shallow_eval, int ply) { constexpr double epsilon = 0.000001; - double y1 = calc_cross_entropy_of_winning_percentage(deep_win_rate, shallow_eval, ply); - double y2 = calc_cross_entropy_of_winning_percentage(deep_win_rate, shallow_eval + epsilon, ply); + + const double y1 = calc_cross_entropy_of_winning_percentage(deep_win_rate, shallow_eval, ply); + const double y2 = calc_cross_entropy_of_winning_percentage(deep_win_rate, shallow_eval + epsilon, ply); // Divide by the winning_probability_coefficient to match scale with the sigmoidal win rate return ((y2 - y1) / epsilon) / winning_probability_coefficient; @@ -190,8 +185,8 @@ namespace Learner // Also, the coefficient of 1/m is unnecessary if you use the update formula that has the automatic gradient adjustment function like Adam and AdaGrad. // Therefore, it is not necessary to save it in memory. - double p = winning_percentage(deep); - double q = winning_percentage(shallow); + const double p = winning_percentage(deep, psv.gamePly); + const double q = winning_percentage(shallow, psv.gamePly); return (q - p) * Math::dsigmoid(double(shallow) / 600.0); } #endif @@ -216,8 +211,8 @@ namespace Learner // = ... // = q-p. - double p = winning_percentage(deep); - double q = winning_percentage(shallow); + const double p = winning_percentage(deep, psv.gamePly); + const double q = winning_percentage(shallow, psv.gamePly); return q - p; } @@ -270,8 +265,10 @@ namespace Learner double p = scaled_teacher_signal; if (convert_teacher_signal_to_winning_probability) { - p = winning_percentage(scaled_teacher_signal); + p = winning_percentage(scaled_teacher_signal, ply); } + + return p; } double calculate_lambda(double teacher_signal) @@ -534,7 +531,7 @@ namespace Learner fs.close(); // no more - if (filenames.size() == 0) + if (filenames.empty()) return false; // Get the next file name. @@ -543,6 +540,7 @@ namespace Learner fs.open(filename, ios::in | ios::binary); cout << "open filename = " << filename << endl; + assert(fs); return true; @@ -569,16 +567,12 @@ namespace Learner { sfens.push_back(p); } - else + else if(!open_next_file()) { - // read failure - if (!open_next_file()) - { - // There was no next file. Abon. - cout << "..end of files." << endl; - end_of_files = true; - return; - } + // There was no next file. Abon. + cout << "..end of files." << endl; + end_of_files = true; + return; } } @@ -702,6 +696,7 @@ namespace Learner learn_sum_entropy_win = 0.0; learn_sum_entropy = 0.0; #endif + #if defined(EVAL_NNUE) newbob_scale = 1.0; newbob_decay = 1.0; @@ -1213,7 +1208,7 @@ namespace Learner // cout << pos << value << endl; // Evaluation value of shallow search (qsearch) - const auto [shallow_value, pv] = qsearch(pos); + const auto [_, pv] = qsearch(pos); // Evaluation value of deep search const auto deep_value = (Value)ps.score; @@ -1408,9 +1403,11 @@ namespace Learner if (--trials > 0 && !is_final) { - cout << "reducing learning rate scale from " << newbob_scale + cout + << "reducing learning rate scale from " << newbob_scale << " to " << (newbob_scale * newbob_decay) << " (" << trials << " more trials)" << endl; + newbob_scale *= newbob_decay; Eval::NNUE::SetGlobalLearningRateScale(newbob_scale); } @@ -1432,10 +1429,10 @@ namespace Learner // prng: random number // afs: fstream of each teacher phase file // a_count: The number of teacher positions inherent in each file. - void shuffle_write(const string& output_file_name, PRNG& prng, vector& afs, vector& a_count) + void shuffle_write(const string& output_file_name, PRNG& prng, vector& sfen_file_streams, vector& sfen_count_in_file) { uint64_t total_sfen_count = 0; - for (auto c : a_count) + for (auto c : sfen_count_in_file) total_sfen_count += c; // number of exported phases @@ -1459,39 +1456,39 @@ namespace Learner fstream fs(output_file_name, ios::out | ios::binary); // total teacher positions - uint64_t sum = 0; - for (auto c : a_count) - sum += c; + uint64_t sfen_count_left = total_sfen_count; - while (sum != 0) + while (sfen_count_left != 0) { - auto r = prng.rand(sum); + auto r = prng.rand(sfen_count_left); // Aspects stored in fs[0] file ... Aspects stored in fs[1] file ... //Think of it as a series like, and determine in which file r is pointing. // The contents of the file are shuffled, so you can take the next element from that file. // Each file has a_count[x] phases, so this process can be written as follows. - uint64_t n = 0; - while (a_count[n] <= r) - r -= a_count[n++]; + uint64_t i = 0; + while (sfen_count_in_file[i] <= r) + r -= sfen_count_in_file[i++]; // This confirms n. Before you forget it, reduce the remaining number. - --a_count[n]; - --sum; + --sfen_count_in_file[i]; + --sfen_count_left; PackedSfenValue psv; // It's better to read and write all at once until the performance is not so good... - if (afs[n].read((char*)&psv, sizeof(PackedSfenValue))) + if (sfen_file_streams[i].read((char*)&psv, sizeof(PackedSfenValue))) { fs.write((char*)&psv, sizeof(PackedSfenValue)); ++write_sfen_count; print_status(); } } + print_status(); fs.close(); + cout << "done!" << endl; } @@ -1509,8 +1506,8 @@ namespace Learner // There should have been a limit of 512 per process on Windows, so you can open here as 500, // The current setting is 500 files x 20M = 10G = 10 billion phases. - PSVector buf; - buf.resize(buffer_size); + PSVector buf(buffer_size); + // ↑ buffer, a marker that indicates how much you have used uint64_t buf_write_marker = 0; @@ -1537,7 +1534,7 @@ namespace Learner // write to a file fstream fs; fs.open(make_filename(write_file_count++), ios::out | ios::binary); - fs.write((char*)&buf[0], size * sizeof(PackedSfenValue)); + fs.write(reinterpret_cast(buf.data()), size * sizeof(PackedSfenValue)); fs.close(); a_count.push_back(size); @@ -1552,14 +1549,13 @@ namespace Learner { fstream fs(filename, ios::in | ios::binary); cout << endl << "open file = " << filename; - while (fs.read((char*)&buf[buf_write_marker], sizeof(PackedSfenValue))) + while (fs.read(reinterpret_cast(&buf[buf_write_marker]), sizeof(PackedSfenValue))) if (++buf_write_marker == buffer_size) write_buffer(buffer_size); // Read in units of sizeof(PackedSfenValue), // Ignore the last remaining fraction. (Fails in fs.read, so exit while) // (The remaining fraction seems to be half-finished data that was created because it was stopped halfway during teacher generation.) - } if (buf_write_marker != 0) @@ -1599,20 +1595,20 @@ namespace Learner size_t file_count = filenames.size(); // Number of teacher positions stored in each file in filenames - vector a_count(file_count); + vector sfen_count_in_file(file_count); // Count the number of teacher aspects in each file. - vector afs(file_count); + vector sfen_file_streams(file_count); for (size_t i = 0; i < file_count; ++i) { auto filename = filenames[i]; - auto& fs = afs[i]; + auto& fs = sfen_file_streams[i]; fs.open(filename, ios::in | ios::binary); const uint64_t file_size = get_file_size(fs); const uint64_t sfen_count = file_size / sizeof(PackedSfenValue); - a_count[i] = sfen_count; + sfen_count_in_file[i] = sfen_count; // Output the number of sfen stored in each file. cout << filename << " = " << sfen_count << " sfens." << endl; @@ -1624,7 +1620,7 @@ namespace Learner // Now you have shuffled. // Throw to the subcontract function and end. - shuffle_write(output_file_name, prng, afs, a_count); + shuffle_write(output_file_name, prng, sfen_file_streams, sfen_count_in_file); } // Subcontracting the teacher shuffle "learn shufflem" command. @@ -1656,7 +1652,10 @@ namespace Learner std::cout << "write : " << output_file_name << endl; // If the file to be written exceeds 2GB, it cannot be written in one shot with fstream::write, so use wrapper. - write_memory_to_file(output_file_name, (void*)&buf[0], (uint64_t)sizeof(PackedSfenValue) * (uint64_t)buf.size()); + write_memory_to_file( + output_file_name, + (void*)&buf[0], + sizeof(PackedSfenValue) * buf.size()); std::cout << "..shuffle_on_memory done." << std::endl; } @@ -1664,7 +1663,7 @@ namespace Learner // Learning from the generated game record void learn(Position&, istringstream& is) { - auto thread_num = (int)Options["Threads"]; + const auto thread_num = (int)Options["Threads"]; SfenReader sr(thread_num); LearnerThink learn_think(sr); @@ -1889,13 +1888,6 @@ namespace Learner { string kif_base_dir = Path::Combine(base_dir, target_dir); - // Remove this folder. Keep it relative to base_dir. -#if defined(_MSC_VER) - // If you use std::tr2, warning C4996 will appear, so suppress it. - // * std::tr2 issued a deprecation warning by default under std:c++14, and was deleted by default in /std:c++17. -#pragma warning(push) -#pragma warning(disable:4996) - namespace sys = std::filesystem; sys::path p(kif_base_dir); // Origin of enumeration std::for_each(sys::directory_iterator(p), sys::directory_iterator(), @@ -1903,36 +1895,6 @@ namespace Learner if (sys::is_regular_file(p)) filenames.push_back(Path::Combine(target_dir, p.filename().generic_string())); }); -#pragma warning(pop) - -#elif defined(__GNUC__) - - auto ends_with = [](std::string const& value, std::string const& ending) - { - if (ending.size() > value.size()) return false; - return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); - }; - - // It can't be helped, so read it using dirent.h. - DIR* dp; // pointer to directory - dirent* entry; // entry point returned by readdir() - - dp = opendir(kif_base_dir.c_str()); - if (dp != NULL) - { - do { - entry = readdir(dp); - // Only list files ending with ".bin" - // →I hate this restriction when generating files with serial numbers... - if (entry != NULL && ends_with(entry->d_name, ".bin")) - { - //cout << entry->d_name << endl; - filenames.push_back(Path::Combine(target_dir, entry->d_name)); - } - } while (entry != NULL); - closedir(dp); - } -#endif } cout << "learn from "; @@ -1990,6 +1952,7 @@ namespace Learner dest_score_max_value, check_invalid_fen, check_illegal_move); + return; } @@ -1997,7 +1960,12 @@ namespace Learner { Eval::init_NNUE(); cout << "convert_bin_from_pgn-extract.." << endl; - convert_bin_from_pgn_extract(filenames, output_file_name, pgn_eval_side_to_move, convert_no_eval_fens_as_score_zero); + convert_bin_from_pgn_extract( + filenames, + output_file_name, + pgn_eval_side_to_move, + convert_no_eval_fens_as_score_zero); + return; } @@ -2154,12 +2122,6 @@ namespace Learner #endif } - } // namespace Learner -#if defined(GENSFEN2019) -#include "gensfen2019.cpp" -#endif - - #endif // EVAL_LEARN