Merge branch 'master' into trainer

This commit is contained in:
noobpwnftw
2020-09-09 20:19:13 +08:00
17 changed files with 553 additions and 836 deletions
-12
View File
@@ -25,19 +25,7 @@
#include <chrono>
#include <random>
#include <regex>
#if defined (_OPENMP)
#include <omp.h>
#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 <filesystem>
#elif defined(__GNUC__)
#include <dirent.h>
#endif
using namespace std;
+20 -59
View File
@@ -1,46 +1,32 @@
#if defined(EVAL_LEARN)
#include "../eval/evaluate_common.h"
#include "learn.h"
#include "multi_think.h"
#include "../misc.h"
#include "../thread.h"
#include "../nnue/evaluate_nnue_learner.h"
#include "../position.h"
#include "../syzygy/tbprobe.h"
#include "../thread.h"
#include "../tt.h"
#include "../uci.h"
#include "../syzygy/tbprobe.h"
#include "learn.h"
#include "multi_think.h"
#include <chrono>
#include <random>
#include <regex>
#include <sstream>
#include <fstream>
#include <unordered_set>
#include <iomanip>
#include <list>
#include <climits>
#include <cmath>
#include <cstring>
#include <memory>
#include <limits>
#include <optional>
#if defined (_OPENMP)
#include <omp.h>
#endif
#if defined(_MSC_VER)
// std::filesystem doesn't work on GCC even though it claims to support C++17.
#include <filesystem>
#elif defined(__GNUC__)
#include <dirent.h>
#endif
#if defined(EVAL_LEARN)
#include "../nnue/evaluate_nnue_learner.h"
#include <climits>
#include <fstream>
#include <iomanip>
#include <limits>
#include <list>
#include <memory>
#include <optional>
#include <random>
#include <regex>
#include <shared_mutex>
#endif
#include <sstream>
#include <unordered_set>
using namespace std;
@@ -80,7 +66,7 @@ namespace Learner
file_worker_thread.join();
output_file_stream.close();
#if !defined(DNDEBUG)
#if defined(_DEBUG)
{
// All buffers should be empty since file_worker_thread
// should have written everything before exiting.
@@ -176,7 +162,7 @@ namespace Learner
output_file_stream.write(reinterpret_cast<const char*>(buf->data()), sizeof(PackedSfenValue) * buf->size());
sfen_write_count += buf->size();
#if 1
// Add the processed number here, and if it exceeds save_every,
// change the file name and reset this counter.
sfen_write_count_current_file += buf->size();
@@ -196,7 +182,7 @@ namespace Learner
output_file_stream.open(new_filename, ios::out | ios::binary | ios::app);
cout << endl << "output sfen file = " << new_filename << endl;
}
#endif
// Output '.' every time when writing a game record.
std::cout << ".";
@@ -522,10 +508,6 @@ namespace Learner
{
// Write out one sfen.
sfen_writer.write(thread_id, *it);
#if 0
pos.set_from_packed_sfen(it->sfen);
cout << pos << "Win : " << it->is_win << " , " << it->score << endl;
#endif
}
return quit;
@@ -717,6 +699,7 @@ namespace Learner
flush_psv(result.value());
break;
}
{
auto [search_value, search_pv] = search(pos, depth, 1, nodes);
@@ -744,19 +727,6 @@ namespace Learner
// Save the move score for adjudication.
move_hist_scores.push_back(search_value);
#if 0
dbg_hit_on(search_value == leaf_value);
// gensfen depth 3 eval_limit 32000
// Total 217749 Hits 203579 hit rate (%) 93.490
// gensfen depth 6 eval_limit 32000
// Total 78407 Hits 69190 hit rate (%) 88.245
// gensfen depth 6 eval_limit 3000
// Total 53879 Hits 43713 hit rate (%) 81.132
// Problems such as pruning with moves in the substitution table.
// This is a little uncomfortable as a teacher...
#endif
// If depth 0, pv is not obtained, so search again at depth 2.
if (search_depth_min <= 0)
{
@@ -892,12 +862,6 @@ namespace Learner
string token;
// When hit to eval hash, as a evaluation value near the initial stage, if a hash collision occurs and a large value is written
// When eval_limit is set small, eval_limit will be exceeded every time in the initial phase, and phase generation will not proceed.
// Therefore, eval hash needs to be disabled.
// After that, when the hash of the eval hash collides, the evaluation value of a strange value is used, and it may be unpleasant to use it for the teacher.
bool use_eval_hash = false;
// Save to file in this unit.
// File names are serialized like file_1.bin, file_2.bin.
uint64_t save_every = UINT64_MAX;
@@ -946,8 +910,6 @@ namespace Learner
is >> write_minply;
else if (token == "write_maxply")
is >> write_maxply;
else if (token == "use_eval_hash")
is >> use_eval_hash;
else if (token == "save_every")
is >> save_every;
else if (token == "random_file_name")
@@ -1017,7 +979,6 @@ namespace Learner
<< " write_minply = " << write_minply << endl
<< " write_maxply = " << write_maxply << endl
<< " output_file_name = " << output_file_name << endl
<< " use_eval_hash = " << use_eval_hash << endl
<< " save_every = " << save_every << endl
<< " random_file_name = " << random_file_name << endl
<< " write_out_draw_game_in_training_data_generation = " << write_out_draw_game_in_training_data_generation << endl
+28 -99
View File
@@ -5,84 +5,6 @@
#include <vector>
// =====================
// Settings for learning
// =====================
// If you select one of the following, the details after that will be automatically selected.
// If you don't select any of them, you need to set the subsequent details one by one.
// Learning setting by elmo method. This is the default setting.
// To make a standard squeeze diaphragm, specify "lambda 1" with the learn command.
#define LEARN_ELMO_METHOD
// ----------------------
// update formula
// ----------------------
// Ada Grad. Recommended because it is stable.
// #define ADA_GRAD_UPDATE
// 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
// ----------------------
// The objective function is the sum of squares of the difference in winning percentage
// See learner.cpp for more information.
// #define LOSS_FUNCTION_IS_WINNING_PERCENTAGE
// Objective function is cross entropy
// See learner.cpp for more information.
// So-called ordinary "rag cloth squeezer"
// #define LOSS_FUNCTION_IS_CROSS_ENTOROPY
// A version in which the objective function is cross entropy, but the win rate function is not passed
// #define LOSS_FUNCTION_IS_CROSS_ENTOROPY_FOR_VALUE
// elmo (WCSC27) method
// #define LOSS_FUNCTION_IS_ELMO_METHOD
// ※ Other things may be added.
// ----------------------
// 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
// ----------------------
// Floating point for learning
// ----------------------
@@ -109,28 +31,7 @@ typedef float LearnFloatType;
// Learning with the method of elmo (WCSC27)
// ----------------------
#if defined( LEARN_ELMO_METHOD )
#define LOSS_FUNCTION_IS_ELMO_METHOD
#define ADA_GRAD_UPDATE
#endif
// Character string according to update formula. (Output for debugging.)
// Implemented various update expressions, but concluded that AdaGrad is the best in terms of speed and memory.
#if defined(ADA_GRAD_UPDATE)
#define LEARN_UPDATE "AdaGrad"
#elif defined(SGD_UPDATE)
#define LEARN_UPDATE "SGD"
#endif
#if defined(LOSS_FUNCTION_IS_WINNING_PERCENTAGE)
#define LOSS_FUNCTION "WINNING_PERCENTAGE"
#elif defined(LOSS_FUNCTION_IS_CROSS_ENTOROPY)
#define LOSS_FUNCTION "CROSS_ENTOROPY"
#elif defined(LOSS_FUNCTION_IS_CROSS_ENTOROPY_FOR_VALUE)
#define LOSS_FUNCTION "CROSS_ENTOROPY_FOR_VALUE"
#elif defined(LOSS_FUNCTION_IS_ELMO_METHOD)
#define LOSS_FUNCTION "ELMO_METHOD(WCSC27)"
#endif
// ----------------------
// Definition of struct used in Learner
@@ -139,6 +40,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.
+432 -477
View File
File diff suppressed because it is too large Load Diff
-3
View File
@@ -2,9 +2,6 @@
#if defined (EVAL_LEARN)
#if defined(_OPENMP)
#include <omp.h>
#endif
#include "../misc.h"
using namespace Eval;
+4 -98
View File
@@ -4,7 +4,11 @@
// A set of machine learning tools related to the weight array used for machine learning of evaluation functions
#include "learn.h"
#if defined (EVAL_LEARN)
#include "../misc.h" // PRNG , my_insertion_sort
#include <array>
#include <cmath> // std::sqrt()
@@ -24,14 +28,6 @@ namespace EvalLearningTools
// cumulative value of one mini-batch gradient
LearnFloatType g = LearnFloatType(0);
// When ADA_GRAD_UPDATE. LearnFloatType == float,
// total 4*2 + 4*2 + 1*2 = 18 bytes
// It suffices to secure a Weight array that is 4.5 times the size of the evaluation function parameter of 1GB.
// However, sizeof(Weight)==20 code is generated if the structure alignment is in 4-byte units, so
// Specify pragma pack(2).
// For SGD_UPDATE, this structure is reduced by 10 bytes to 8 bytes.
// Learning rate η(eta) such as AdaGrad.
// It is assumed that eta1,2,3,eta1_epoch,eta2_epoch have been set by the time updateFV() is called.
// The epoch of update_weights() gradually changes from eta1 to eta2 until eta1_epoch.
@@ -71,96 +67,6 @@ namespace EvalLearningTools
template <typename T> void updateFV(T& v) { updateFV(v, 1.0); }
#if defined (ADA_GRAD_UPDATE)
// Since the maximum value that can be accurately calculated with float is INT16_MAX*256-1
// Keep the small value as a marker.
const LearnFloatType V0_NOT_INIT = (INT16_MAX * 128);
// What holds v internally. The previous implementation kept a fixed decimal with only a fractional part to save memory,
// Since it is doubtful in accuracy and the visibility is bad, it was abolished.
LearnFloatType v0 = LearnFloatType(V0_NOT_INIT);
// AdaGrad g2
LearnFloatType g2 = LearnFloatType(0);
// update with AdaGrad
// When executing this function, the value of g and the member do not change
// Guaranteed by the caller. It does not have to be an atomic operation.
// k is a coefficient for eta. 1.0 is usually sufficient. If you want to lower eta for your turn item, set this to 1/8.0 etc.
template <typename T>
void updateFV(T& v,double k)
{
// AdaGrad update formula
// Gradient vector is g, vector to be updated is v, η(eta) is a constant,
// g2 = g2 + g^2
// v = v - ηg/sqrt(g2)
constexpr double epsilon = 0.000001;
if (g == LearnFloatType(0))
return;
g2 += g * g;
// If v0 is V0_NOT_INIT, it means that the value is not initialized with the value of KK/KKP/KPP array,
// In this case, read the value of v from the one passed in the argument.
double V = (v0 == V0_NOT_INIT) ? v : v0;
V -= k * eta * (double)g / sqrt((double)g2 + epsilon);
// Limit the value of V to be within the range of types.
// By the way, windows.h defines the min and max macros, so to avoid it,
// Here, it is enclosed in parentheses so that it is not treated as a function-like macro.
V = (std::min)((double)(std::numeric_limits<T>::max)() , V);
V = (std::max)((double)(std::numeric_limits<T>::min)() , V);
v0 = (LearnFloatType)V;
v = (T)round(V);
// Clear g because one update of mini-batch for this element is over
// g[i] = 0;
// → There is a problem of dimension reduction, so this will be done by the caller.
}
#elif defined(SGD_UPDATE)
// See only the sign of the gradient Update with SGD
// When executing this function, the value of g and the member do not change
// Guaranteed by the caller. It does not have to be an atomic operation.
template <typename T>
void updateFV(T & v , double k)
{
if (g == 0)
return;
// See only the sign of g and update.
// If g <0, add v a little.
// If g> 0, subtract v slightly.
// Since we only add integers, no decimal part is required.
// It's a good idea to move around 0-5.
// It is better to have a Gaussian distribution, so generate a 5-bit random number (each bit has a 1/2 probability of 1),
// Pop_count() it. At this time, it has a binomial distribution.
//int16_t diff = (int16_t)POPCNT32((u32)prng.rand(31));
// → If I do this with 80 threads, this AsyncPRNG::rand() locks, so I slowed down. This implementation is not good.
int16_t diff = 1;
double V = v;
if (g > 0.0)
V-= diff;
else
V+= diff;
V = (std::min)((double)(std::numeric_limits<T>::max)(), V);
V = (std::max)((double)(std::numeric_limits<T>::min)(), V);
v = (T)V;
}
#endif
// grad setting
template <typename T> void set_grad(const T& g_) { g = g_; }