711 lines
24 KiB
C++
711 lines
24 KiB
C++
/*
|
||
This file is part of Leela Zero.
|
||
Copyright (C) 2017 Gian-Carlo Pascutto
|
||
|
||
Leela Zero is free software: you can redistribute it and/or modify
|
||
it under the terms of the GNU General Public License as published by
|
||
the Free Software Foundation, either version 3 of the License, or
|
||
(at your option) any later version.
|
||
|
||
Leela Zero is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with Leela Zero. If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include "config.h"
|
||
#include <algorithm>
|
||
#include <cassert>
|
||
#include <iostream>
|
||
#include <fstream>
|
||
#include <iterator>
|
||
#include <string>
|
||
#include <memory>
|
||
#include <cmath>
|
||
#include <array>
|
||
#include <thread>
|
||
#include <boost/utility.hpp>
|
||
#include <boost/format.hpp>
|
||
//#include <unistd.h>
|
||
#include <string.h>
|
||
|
||
#include "Im2Col.h"
|
||
|
||
#ifdef __APPLE__
|
||
#include <Accelerate/Accelerate.h>
|
||
#endif
|
||
#ifdef USE_MKL
|
||
#include <mkl.h>
|
||
#endif
|
||
#ifdef USE_OPENBLAS
|
||
|
||
#include <cblas.h>
|
||
|
||
#endif
|
||
#ifdef USE_OPENCL
|
||
|
||
#include "OpenCL.h"
|
||
#include "UCTNode.h"
|
||
|
||
#endif
|
||
|
||
#include "SGFTree.h"
|
||
#include "SGFParser.h"
|
||
#include "Utils.h"
|
||
#include "FastBoard.h"
|
||
#include "Random.h"
|
||
#include "Network.h"
|
||
#include "GTP.h"
|
||
#include "Utils.h"
|
||
|
||
using namespace Utils;
|
||
|
||
// Input + residual block tower
|
||
std::vector<std::vector<float>> conv_weights;
|
||
std::vector<std::vector<float>> conv_biases;
|
||
std::vector<std::vector<float>> batchnorm_means;
|
||
std::vector<std::vector<float>> batchnorm_variances;
|
||
|
||
// Policy head
|
||
std::vector<float> conv_pol_w;
|
||
std::vector<float> conv_pol_b;
|
||
std::array<float, 2> bn_pol_w1;
|
||
std::array<float, 2> bn_pol_w2;
|
||
|
||
std::array<float, 261364> ip_pol_w;
|
||
std::array<float, 362> ip_pol_b;
|
||
|
||
// Value head
|
||
std::vector<float> conv_val_w;
|
||
std::vector<float> conv_val_b;
|
||
std::array<float, 1> bn_val_w1;
|
||
std::array<float, 1> bn_val_w2;
|
||
|
||
std::array<float, 92416> ip1_val_w;
|
||
std::array<float, 256> ip1_val_b;
|
||
|
||
std::array<float, 256> ip2_val_w;
|
||
std::array<float, 1> ip2_val_b;
|
||
|
||
void Network::benchmark(GameState *state) {
|
||
{
|
||
int BENCH_AMOUNT = 1600;
|
||
int cpus = cfg_num_threads;
|
||
int iters_per_thread = (BENCH_AMOUNT + (cpus - 1)) / cpus;
|
||
|
||
Time start;
|
||
|
||
ThreadGroup tg(thread_pool);
|
||
for (int i = 0; i < cpus; i++) {
|
||
tg.add_task([iters_per_thread, state]() {
|
||
GameState mystate = *state;
|
||
for (int loop = 0; loop < iters_per_thread; loop++) {
|
||
auto vec = get_scored_moves(&mystate, Ensemble::RANDOM_ROTATION);
|
||
}
|
||
});
|
||
};
|
||
tg.wait_all();
|
||
|
||
Time end;
|
||
|
||
myprintf("%5d evaluations in %5.2f seconds -> %d n/s\n",
|
||
BENCH_AMOUNT,
|
||
(float) Time::timediff(start, end) / 100.0,
|
||
(int) ((float) BENCH_AMOUNT / ((float) Time::timediff(start, end) / 100.0)));
|
||
}
|
||
}
|
||
|
||
void Network::initialize(void) {
|
||
#ifdef USE_OPENCL
|
||
myprintf("Initializing OpenCL\n");
|
||
opencl.initialize();
|
||
|
||
// Count size of the network
|
||
myprintf("Detecting residual layers...");
|
||
std::ifstream wtfile(cfg_weightsfile);
|
||
if (wtfile.fail()) {
|
||
myprintf("Could not open weights file: %s\n", cfg_weightsfile.c_str());
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
std::string line;
|
||
auto linecount = size_t{0};
|
||
while (std::getline(wtfile, line)) {
|
||
// Second line of parameters are the convolution layer biases,
|
||
// so this tells us the amount of channels in the residual layers.
|
||
// (Provided they're all equally large - that's not actually required!)
|
||
if (linecount == 1) {
|
||
std::stringstream ss(line);
|
||
auto count = std::distance(std::istream_iterator<std::string>(ss),
|
||
std::istream_iterator<std::string>());
|
||
myprintf("%d channels...", count);
|
||
}
|
||
linecount++;
|
||
}
|
||
// 1 input layer (4 x weights), 14 ending weights, the rest are residuals
|
||
// every residual has 8 x weight lines
|
||
auto residual_layers = linecount - (4 + 14);
|
||
if (residual_layers % 8 != 0) {
|
||
myprintf("\nInconsistent number of weights in the file.\n");
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
residual_layers /= 8;
|
||
myprintf("%d layers\nTransferring weights to GPU...", residual_layers);
|
||
|
||
// Re-read file and process
|
||
wtfile.clear();
|
||
wtfile.seekg(0, std::ios::beg);
|
||
|
||
auto plain_conv_layers = 1 + (residual_layers * 2);
|
||
auto plain_conv_wts = plain_conv_layers * 4;
|
||
linecount = 0;
|
||
while (std::getline(wtfile, line)) {
|
||
std::vector<float> weights;
|
||
float weight;
|
||
std::istringstream iss(line);
|
||
while (iss >> weight) {
|
||
weights.emplace_back(weight);
|
||
}
|
||
if (linecount < plain_conv_wts) {
|
||
if (linecount % 4 == 0) {
|
||
conv_weights.emplace_back(weights);
|
||
} else if (linecount % 4 == 1) {
|
||
conv_biases.emplace_back(weights);
|
||
} else if (linecount % 4 == 2) {
|
||
batchnorm_means.emplace_back(weights);
|
||
} else if (linecount % 4 == 3) {
|
||
batchnorm_variances.emplace_back(weights);
|
||
}
|
||
} else if (linecount == plain_conv_wts) {
|
||
conv_pol_w = std::move(weights);
|
||
} else if (linecount == plain_conv_wts + 1) {
|
||
conv_pol_b = std::move(weights);
|
||
} else if (linecount == plain_conv_wts + 2) {
|
||
std::copy(begin(weights), end(weights), begin(bn_pol_w1));
|
||
} else if (linecount == plain_conv_wts + 3) {
|
||
std::copy(begin(weights), end(weights), begin(bn_pol_w2));
|
||
} else if (linecount == plain_conv_wts + 4) {
|
||
std::copy(begin(weights), end(weights), begin(ip_pol_w));
|
||
} else if (linecount == plain_conv_wts + 5) {
|
||
std::copy(begin(weights), end(weights), begin(ip_pol_b));
|
||
} else if (linecount == plain_conv_wts + 6) {
|
||
conv_val_w = std::move(weights);
|
||
} else if (linecount == plain_conv_wts + 7) {
|
||
conv_val_b = std::move(weights);
|
||
} else if (linecount == plain_conv_wts + 8) {
|
||
std::copy(begin(weights), end(weights), begin(bn_val_w1));
|
||
} else if (linecount == plain_conv_wts + 9) {
|
||
std::copy(begin(weights), end(weights), begin(bn_val_w2));
|
||
} else if (linecount == plain_conv_wts + 10) {
|
||
std::copy(begin(weights), end(weights), begin(ip1_val_w));
|
||
} else if (linecount == plain_conv_wts + 11) {
|
||
std::copy(begin(weights), end(weights), begin(ip1_val_b));
|
||
} else if (linecount == plain_conv_wts + 12) {
|
||
std::copy(begin(weights), end(weights), begin(ip2_val_w));
|
||
} else if (linecount == plain_conv_wts + 13) {
|
||
std::copy(begin(weights), end(weights), begin(ip2_val_b));
|
||
}
|
||
linecount++;
|
||
}
|
||
wtfile.close();
|
||
|
||
// input
|
||
size_t weight_index = 0;
|
||
opencl_net.push_convolve(3, conv_weights[weight_index],
|
||
conv_biases[weight_index]);
|
||
opencl_net.push_batchnorm(361, batchnorm_means[weight_index],
|
||
batchnorm_variances[weight_index]);
|
||
weight_index++;
|
||
|
||
// residual blocks
|
||
for (auto i = size_t{0}; i < residual_layers; i++) {
|
||
opencl_net.push_residual(3, conv_weights[weight_index],
|
||
conv_biases[weight_index],
|
||
batchnorm_means[weight_index],
|
||
batchnorm_variances[weight_index],
|
||
conv_weights[weight_index + 1],
|
||
conv_biases[weight_index + 1],
|
||
batchnorm_means[weight_index + 1],
|
||
batchnorm_variances[weight_index + 1]);
|
||
weight_index += 2;
|
||
}
|
||
myprintf("done\n");
|
||
#endif
|
||
#ifdef USE_BLAS
|
||
#ifndef __APPLE__
|
||
#ifdef USE_OPENBLAS
|
||
openblas_set_num_threads(1);
|
||
myprintf("BLAS Core: %s\n", openblas_get_corename());
|
||
#endif
|
||
#ifdef USE_MKL
|
||
//mkl_set_threading_layer(MKL_THREADING_SEQUENTIAL);
|
||
mkl_set_num_threads(1);
|
||
MKLVersion Version;
|
||
mkl_get_version(&Version);
|
||
myprintf("BLAS core: MKL %s\n", Version.Processor);
|
||
#endif
|
||
#endif
|
||
#endif
|
||
}
|
||
|
||
#ifdef USE_BLAS
|
||
|
||
template<unsigned int filter_size,
|
||
unsigned int outputs>
|
||
void convolve(const std::vector<float> &input,
|
||
const std::vector<float> &weights,
|
||
const std::vector<float> &biases,
|
||
std::vector<float> &output) {
|
||
// fixed for 19x19
|
||
constexpr unsigned int width = 19;
|
||
constexpr unsigned int height = 19;
|
||
constexpr unsigned int spatial_out = width * height;
|
||
constexpr unsigned int filter_len = filter_size * filter_size;
|
||
|
||
auto channels = int(weights.size() / (biases.size() * filter_len));
|
||
unsigned int filter_dim = filter_len * channels;
|
||
|
||
std::vector<float> col(filter_dim * width * height);
|
||
im2col<filter_size>(channels, input, col);
|
||
|
||
// Weight shape (output, input, filter_size, filter_size)
|
||
// 96 22 5 5
|
||
// outputs[96,19x19] = weights[96,22x9] x col[22x9,19x19]
|
||
// C←αAB + βC
|
||
// M Number of rows in matrices A and C.
|
||
// N Number of columns in matrices B and C.
|
||
// K Number of columns in matrix A; number of rows in matrix B.
|
||
// lda The size of the first dimention of matrix A; if you are
|
||
// passing a matrix A[m][n], the value should be m.
|
||
// cblas_sgemm(CblasRowMajor, TransA, TransB, M, N, K, alpha, A, lda, B,
|
||
// ldb, beta, C, N);
|
||
|
||
cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,
|
||
// M N K
|
||
outputs, spatial_out, filter_dim,
|
||
1.0f, &weights[0], filter_dim,
|
||
&col[0], spatial_out,
|
||
0.0f, &output[0], spatial_out);
|
||
|
||
for (unsigned int o = 0; o < outputs; o++) {
|
||
for (unsigned int b = 0; b < spatial_out; b++) {
|
||
output[(o * spatial_out) + b] =
|
||
biases[o] + output[(o * spatial_out) + b];
|
||
}
|
||
}
|
||
}
|
||
|
||
template<unsigned int inputs,
|
||
unsigned int outputs,
|
||
size_t W, size_t B>
|
||
void innerproduct(const std::vector<float> &input,
|
||
const std::array<float, W> &weights,
|
||
const std::array<float, B> &biases,
|
||
std::vector<float> &output) {
|
||
assert(B == outputs);
|
||
|
||
cblas_sgemv(CblasRowMajor, CblasNoTrans,
|
||
// M K
|
||
outputs, inputs,
|
||
1.0f, &weights[0], inputs,
|
||
&input[0], 1,
|
||
0.0f, &output[0], 1);
|
||
|
||
auto lambda_ReLU = [](float val) {
|
||
return (val > 0.0f) ?
|
||
val : 0.0f;
|
||
};
|
||
|
||
for (unsigned int o = 0; o < outputs; o++) {
|
||
float val = biases[o] + output[o];
|
||
if (outputs == 256) {
|
||
val = lambda_ReLU(val);
|
||
}
|
||
output[o] = val;
|
||
}
|
||
}
|
||
|
||
template<unsigned int channels,
|
||
unsigned int spatial_size>
|
||
void batchnorm(const std::vector<float> &input,
|
||
const std::array<float, channels> &means,
|
||
const std::array<float, channels> &variances,
|
||
std::vector<float> &output) {
|
||
constexpr float epsilon = 1e-5f;
|
||
|
||
auto lambda_ReLU = [](float val) {
|
||
return (val > 0.0f) ?
|
||
val : 0.0f;
|
||
};
|
||
|
||
for (unsigned int c = 0; c < channels; ++c) {
|
||
float mean = means[c];
|
||
float variance = variances[c] + epsilon;
|
||
float scale_stddiv = 1.0f / std::sqrt(variance);
|
||
|
||
float *out = &output[c * spatial_size];
|
||
float const *in = &input[c * spatial_size];
|
||
for (unsigned int b = 0; b < spatial_size; b++) {
|
||
out[b] = lambda_ReLU(scale_stddiv * (in[b] - mean));
|
||
}
|
||
}
|
||
}
|
||
|
||
#endif
|
||
|
||
void Network::softmax(const std::vector<float> &input,
|
||
std::vector<float> &output,
|
||
float temperature) {
|
||
assert(&input != &output);
|
||
|
||
float alpha = *std::max_element(input.begin(),
|
||
input.begin() + output.size());
|
||
alpha /= temperature;
|
||
|
||
float denom = 0.0f;
|
||
std::vector<float> helper(output.size());
|
||
for (size_t i = 0; i < output.size(); i++) {
|
||
float val = std::exp((input[i] / temperature) - alpha);
|
||
helper[i] = val;
|
||
denom += val;
|
||
}
|
||
for (size_t i = 0; i < output.size(); i++) {
|
||
output[i] = helper[i] / denom;
|
||
}
|
||
}
|
||
|
||
/* magic code, only execute once, what is the mechanism?
|
||
void function() {
|
||
static const auto runOnce = [] (auto content) { std::cout << content << std::endl; return true;};
|
||
}
|
||
*/
|
||
|
||
std::string exec(const char* cmd) {
|
||
std::array<char, 128> buffer;
|
||
std::string result;
|
||
std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose);
|
||
if (!pipe) throw std::runtime_error("popen() failed!");
|
||
while (!feof(pipe.get())) {
|
||
if (fgets(buffer.data(), 128, pipe.get()) != nullptr)
|
||
result += buffer.data();
|
||
}
|
||
return result;
|
||
}
|
||
|
||
Network::Netresult Network::get_scored_moves(
|
||
GameState *state, Ensemble ensemble, int rotation) {
|
||
Netresult result;
|
||
if (state->board.get_boardsize() != 19) {
|
||
return result;
|
||
}
|
||
|
||
NNPlanes planes;
|
||
gather_features(state, planes);
|
||
|
||
/*
|
||
* tianshou_code
|
||
* writing the board information to local file
|
||
*/
|
||
//static std::once_flag plane_size_flag;
|
||
//std::call_once ( plane_size_flag, [&]{ printf("Network::get_scored_moves planses size : %d\n", planes.size());} );
|
||
//std::call_once ( plane_content_flag, [&]{
|
||
/*
|
||
for (int i = 0; i < board_size; ++i) {
|
||
for (int j = 0; j < board_size; ++j) {
|
||
std::cout << planes[k][i * board_size + j] << " ";
|
||
}
|
||
printf("\n");
|
||
}
|
||
printf("======================================\n");
|
||
*/
|
||
// } );
|
||
static int call_number = 0;
|
||
call_number++;
|
||
std::ofstream mctsnn_file;
|
||
mctsnn_file.open("/home/yama/rl/tianshou/leela-zero/src/mcts_nn_files/board_" + std::to_string(call_number));
|
||
const int total_count = board_size * board_size;
|
||
for (int k = 0; k < planes.size(); ++k) {
|
||
for (int i = 0; i < total_count; ++i) {
|
||
mctsnn_file << planes[k][i];
|
||
}
|
||
mctsnn_file << '\n';
|
||
}
|
||
mctsnn_file.close();
|
||
|
||
const int extended_board_size = board_size + 2;
|
||
Network::Netresult nn_res;
|
||
std::string cmd = "python /home/yama/rl/tianshou/AlphaGo/Network.py " + std::to_string(call_number);
|
||
std::string res = exec(cmd.c_str());
|
||
//std::cout << res << std::endl;
|
||
std::string buf; // Have a buffer string
|
||
std::stringstream ss(res); // Insert the string into a stream
|
||
const int policy_size = board_size * board_size + 1;
|
||
int idx = 0;
|
||
for (int i = 0; i < extended_board_size; ++i) {
|
||
for (int j = 0; j < extended_board_size; ++j) {
|
||
if ((0 < i) && (i < board_size + 1) && (0 < j) && (j < board_size + 1)) {
|
||
ss >> buf;
|
||
nn_res.first.emplace_back(std::make_pair(std::stod(buf), idx));
|
||
//std::cout << std::fixed << "[" << std::stod(buf) << "," << idx << "]\n";
|
||
}
|
||
idx++;
|
||
}
|
||
}
|
||
// probability of pass
|
||
ss >> buf;
|
||
nn_res.first.emplace_back(std::make_pair(std::stod(buf), -1));
|
||
std::cout << "tianshou nn output : \t\n";
|
||
auto max_iterator = std::max_element(nn_res.first.begin(), nn_res.first.end());
|
||
int argmax = std::distance(nn_res.first.begin(), max_iterator);
|
||
int line = (argmax / board_size) + 1, column = (argmax % board_size) + 1;
|
||
std::cout << "\tmove : " << argmax << " [" << line << "," << column << "]" << std::endl;
|
||
// evaluation of state value
|
||
ss >> buf;
|
||
nn_res.second = std::stod(buf);
|
||
std::cout << "\tvalue : " << nn_res.second << std::endl;
|
||
|
||
if (ensemble == DIRECT) {
|
||
assert(rotation >= 0 && rotation <= 7);
|
||
result = get_scored_moves_internal(state, planes, rotation);
|
||
} else {
|
||
assert(ensemble == RANDOM_ROTATION);
|
||
assert(rotation == -1);
|
||
int rand_rot = Random::get_Rng()->randfix<8>();
|
||
std::cout << "rotation : " << rand_rot << std::endl;
|
||
result = get_scored_moves_internal(state, planes, rand_rot);
|
||
}
|
||
|
||
/*
|
||
static std::once_flag of;
|
||
std::call_once(of, [&] { } );
|
||
for (auto ele: result.first) {
|
||
std::cout << std::fixed << "[" << ele.first << "," << ele.second << "]\n";
|
||
}
|
||
*/
|
||
std::cout << "leela nn output : \t\n";
|
||
max_iterator = std::max_element(result.first.begin(), result.first.end());
|
||
|
||
argmax = std::distance(result.first.begin(), max_iterator);
|
||
line = (argmax / board_size) + 1, column = (argmax % board_size) + 1;
|
||
std::cout << "\tmove : " << argmax << " [" << line << "," << column << "]" << std::endl;
|
||
std::cout << "\tvalue : " << result.second << std::endl;
|
||
|
||
return nn_res;
|
||
//return result;
|
||
}
|
||
|
||
Network::Netresult Network::get_scored_moves_internal(
|
||
GameState *state, NNPlanes &planes, int rotation) {
|
||
assert(rotation >= 0 && rotation <= 7);
|
||
constexpr int channels = INPUT_CHANNELS;
|
||
assert(channels == planes.size());
|
||
constexpr int width = 19;
|
||
constexpr int height = 19;
|
||
constexpr int max_channels = MAX_CHANNELS;
|
||
std::vector<float> input_data(max_channels * width * height);
|
||
std::vector<float> output_data(max_channels * width * height);
|
||
std::vector<float> policy_data_1(2 * width * height);
|
||
std::vector<float> policy_data_2(2 * width * height);
|
||
std::vector<float> value_data_1(1 * width * height);
|
||
std::vector<float> value_data_2(1 * width * height);
|
||
std::vector<float> policy_out((width * height) + 1);
|
||
std::vector<float> softmax_data((width * height) + 1);
|
||
std::vector<float> winrate_data(256);
|
||
std::vector<float> winrate_out(1);
|
||
for (int c = 0; c < channels; ++c) {
|
||
for (int h = 0; h < height; ++h) {
|
||
for (int w = 0; w < width; ++w) {
|
||
int vtx = rotate_nn_idx(h * 19 + w, rotation);
|
||
input_data[(c * height + h) * width + w] =
|
||
(float) planes[c][vtx];
|
||
}
|
||
}
|
||
}
|
||
#ifdef USE_OPENCL
|
||
opencl_net.forward(input_data, output_data);
|
||
// Get the moves
|
||
convolve<1, 2>(output_data, conv_pol_w, conv_pol_b, policy_data_1);
|
||
batchnorm<2, 361>(policy_data_1, bn_pol_w1, bn_pol_w2, policy_data_2);
|
||
innerproduct<2 * 361, 362>(policy_data_2, ip_pol_w, ip_pol_b, policy_out);
|
||
softmax(policy_out, softmax_data, cfg_softmax_temp);
|
||
std::vector<float> &outputs = softmax_data;
|
||
|
||
// Now get the score
|
||
convolve<1, 1>(output_data, conv_val_w, conv_val_b, value_data_1);
|
||
batchnorm<1, 361>(value_data_1, bn_val_w1, bn_val_w2, value_data_2);
|
||
innerproduct<361, 256>(value_data_2, ip1_val_w, ip1_val_b, winrate_data);
|
||
innerproduct<256, 1>(winrate_data, ip2_val_w, ip2_val_b, winrate_out);
|
||
|
||
// Sigmoid
|
||
float winrate_sig = (1.0f + std::tanh(winrate_out[0])) / 2.0f;
|
||
#elif defined(USE_BLAS) && !defined(USE_OPENCL)
|
||
#error "Not implemented"
|
||
// Not implemented yet - not very useful unless you have some
|
||
// sort of Xeon Phi
|
||
softmax(output_data, softmax_data, cfg_softmax_temp);
|
||
// Move scores
|
||
std::vector<float>& outputs = softmax_data;
|
||
#endif
|
||
std::vector<scored_node> result;
|
||
for (size_t idx = 0; idx < outputs.size(); idx++) {
|
||
if (idx < 19 * 19) {
|
||
auto rot_idx = rev_rotate_nn_idx(idx, rotation);
|
||
auto val = outputs[rot_idx];
|
||
int x = idx % 19;
|
||
int y = idx / 19;
|
||
int vtx = state->board.get_vertex(x, y);
|
||
if (state->board.get_square(vtx) == FastBoard::EMPTY) {
|
||
result.emplace_back(val, vtx);
|
||
}
|
||
} else {
|
||
result.emplace_back(outputs[idx], FastBoard::PASS);
|
||
}
|
||
}
|
||
|
||
return std::make_pair(result, winrate_sig);
|
||
}
|
||
|
||
void Network::show_heatmap(FastState *state, Netresult &result, bool topmoves) {
|
||
auto moves = result.first;
|
||
std::vector<std::string> display_map;
|
||
std::string line;
|
||
|
||
for (unsigned int y = 0; y < 19; y++) {
|
||
for (unsigned int x = 0; x < 19; x++) {
|
||
int vtx = state->board.get_vertex(x, y);
|
||
|
||
auto item = std::find_if(moves.cbegin(), moves.cend(),
|
||
[&vtx](scored_node const &item) {
|
||
return item.second == vtx;
|
||
});
|
||
|
||
float score = 0.0f;
|
||
// Non-empty squares won't be scored
|
||
if (item != moves.end()) {
|
||
score = item->first;
|
||
assert(vtx == item->second);
|
||
}
|
||
|
||
line += boost::str(boost::format("%3d ") % int(score * 1000));
|
||
if (x == 18) {
|
||
display_map.push_back(line);
|
||
line.clear();
|
||
}
|
||
}
|
||
}
|
||
|
||
for (int i = display_map.size() - 1; i >= 0; --i) {
|
||
myprintf("%s\n", display_map[i].c_str());
|
||
}
|
||
assert(result.first.back().second == FastBoard::PASS);
|
||
int pass_score = int(result.first.back().first * 1000);
|
||
myprintf("pass: %d\n", pass_score);
|
||
myprintf("winrate: %f\n", result.second);
|
||
|
||
if (topmoves) {
|
||
std::stable_sort(moves.rbegin(), moves.rend());
|
||
|
||
float cum = 0.0f;
|
||
size_t tried = 0;
|
||
while (cum < 0.85f && tried < moves.size()) {
|
||
if (moves[tried].first < 0.01f) break;
|
||
myprintf("%1.3f (%s)\n",
|
||
moves[tried].first,
|
||
state->board.move_to_text(moves[tried].second).c_str());
|
||
cum += moves[tried].first;
|
||
tried++;
|
||
}
|
||
}
|
||
}
|
||
|
||
void Network::gather_features(GameState *state, NNPlanes &planes) {
|
||
planes.resize(18);
|
||
const size_t our_offset = 0;
|
||
const size_t their_offset = 8;
|
||
BoardPlane &black_to_move = planes[16];
|
||
BoardPlane &white_to_move = planes[17];
|
||
|
||
bool whites_move = state->get_to_move() == FastBoard::WHITE;
|
||
// tianshou_code
|
||
//std::cout << "whites_move : " << whites_move << std::endl;
|
||
if (whites_move) {
|
||
white_to_move.set();
|
||
} else {
|
||
black_to_move.set();
|
||
}
|
||
|
||
// Go back in time, fill history boards
|
||
size_t backtracks = 0;
|
||
for (int h = 0; h < 8; h++) {
|
||
int tomove = state->get_to_move();
|
||
// collect white, black occupation planes
|
||
for (int j = 0; j < 19; j++) {
|
||
for (int i = 0; i < 19; i++) {
|
||
int vtx = state->board.get_vertex(i, j);
|
||
FastBoard::square_t color =
|
||
state->board.get_square(vtx);
|
||
int idx = j * 19 + i;
|
||
if (color != FastBoard::EMPTY) {
|
||
if (color == tomove) {
|
||
planes[our_offset + h][idx] = true;
|
||
} else {
|
||
planes[their_offset + h][idx] = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!state->undo_move()) {
|
||
break;
|
||
} else {
|
||
backtracks++;
|
||
}
|
||
}
|
||
|
||
// Now go back to present day
|
||
for (size_t h = 0; h < backtracks; h++) {
|
||
state->forward_move();
|
||
}
|
||
}
|
||
|
||
int Network::rev_rotate_nn_idx(const int vertex, int symmetry) {
|
||
static const int invert[] = {0, 1, 2, 3, 4, 6, 5, 7};
|
||
assert(rotate_nn_idx(rotate_nn_idx(vertex, symmetry), invert[symmetry])
|
||
== vertex);
|
||
return rotate_nn_idx(vertex, invert[symmetry]);
|
||
}
|
||
|
||
int Network::rotate_nn_idx(const int vertex, int symmetry) {
|
||
assert(vertex >= 0 && vertex < 19 * 19);
|
||
assert(symmetry >= 0 && symmetry < 8);
|
||
int x = vertex % 19;
|
||
int y = vertex / 19;
|
||
int newx;
|
||
int newy;
|
||
|
||
if (symmetry >= 4) {
|
||
std::swap(x, y);
|
||
symmetry -= 4;
|
||
}
|
||
|
||
if (symmetry == 0) {
|
||
newx = x;
|
||
newy = y;
|
||
} else if (symmetry == 1) {
|
||
newx = x;
|
||
newy = 19 - y - 1;
|
||
} else if (symmetry == 2) {
|
||
newx = 19 - x - 1;
|
||
newy = y;
|
||
} else {
|
||
assert(symmetry == 3);
|
||
newx = 19 - x - 1;
|
||
newy = 19 - y - 1;
|
||
}
|
||
|
||
int newvtx = (newy * 19) + newx;
|
||
assert(newvtx >= 0 && newvtx < 19 * 19);
|
||
return newvtx;
|
||
}
|