HTTPS SSH

README

This README explains what RuyTune does and documents the steps necessary to get you up and running.

What is RuyTune for?

The vast majority of chess engines use an evaluation function at the leaves of an alpha-beta search tree. An evaluation function is a piece of code that takes a chess position as input and produces a heuristic value indicating which side is ahead and by how much. Traditionally evaluation functions return a positive number if white is ahead and a negative number if black is ahead, with the units being centipawns. An evaluation function has many (hundreds?) parameters that can interact in subtle ways, and tuning is a non-trivial problem.

RuyTune allows you to tune an evaluation function for chess using a powerful optimization algorithm called L-BFGS.

What does it do?

In order for RuyTune to use your evaluation function, it should have several peculiar features:

  • It should be a template,
  • read parameters in a prescribed way, and
  • accept an EPD string as input.

It should be a template, so any numeric type can be used to represent scores. Think of int and double as examples, but RuyTune will actually use the type RT::Variable, which allows it to perform automatic differentiation.

It should read its tunable parameters from a text file using RT::parameter<Score>("my_parameters").

You should provide an interface that takes an EPD string and returns the score.

Here's an example that uses a function that simply counts material difference:

#include "../include/ruy_tune.hpp"

template <typename Score>
Score evaluate(std::string const &epd) {
  static Score pawn_value = RT::parameter<Score>("pawn_value");
  static Score knight_value = RT::parameter<Score>("knight_value");
  static Score bishop_value = RT::parameter<Score>("bishop_value");
  static Score rook_value = RT::parameter<Score>("rook_value");
  static Score queen_value = RT::parameter<Score>("queen_value");

  int count[128] = {0};
  for (size_t i = 0; epd[i] != ' '; ++i)
    ++count[static_cast<int>(epd[i])];

  return (count['P']-count['p']) * pawn_value
    + (count['N']-count['n']) * knight_value
    + (count['B']-count['b']) * bishop_value
    + (count['R']-count['r']) * rook_value
    + (count['Q']-count['q']) * queen_value;
}

int main() {
  RT::train(evaluate<RT::Variable>, "quiescent_positions_with_results", "evaluation_parameters");
}

That is a complete program. I compile it with a command like this:

g++ tune.cpp -o tune -std=c++11 -Wall -Wextra -pedantic -llbfgs -O3 -march=native

It will run for under a minute and produce a parameters file that looks something like this:

bishop_value 338.635
knight_value 318.898
pawn_value 100.789
queen_value 1001.72
rook_value 509.762

How does it work?

There are three pieces to RuyTune: 1. autodiff.hpp implements reverse-mode automatic differentiation so we can define a function that measures how bad our prediction is (a.k.a. "loss function") and then compute its gradient. 2. parameters.hpp provides a convenient singleton to manage the values of the parameters being tuned. This header file can be used for normal operation of the evaluation function, even when you are not tuning it. 3. ruy_tune.hpp includes the other two files and uses L-BFGS to find optimal settings of the parameters. The exact function being minimized is the average value of 0.5(tanh(0.0043score)-result)^2. The tanh(0.0043*score) part converts centipawns into an expected result in [-1,1], and then we are simply minimizing the mean squared error of the prediction.

How do I get set up?

Code

For now you simply add the three .hpp files to your project.

Dependencies

The only dependency is libLBFGS.

Parameter file

Your parameter settings should be in a text file with this very simple format:

bishop_value 340
knight_value 330
pawn_value 100
queen_value 980
rook_value 510

The initial values don't seem to matter much. For this simple example, you can set them all to 0, which is what I did in the repository. The code will read the initial values from a file and write the optimized values to the same file.

Database

The other important thing you need is a database of quiescent positions with associated results. I generated such a database, as explained in this TalkChess.com thread: [http://talkchess.com/forum/viewtopic.php?t=61861]. If you want to use it, it's part of this repository. Otherwise, you'll have to provide your own.

The format is <EPD> <result>:

3r4/4k3/8/5p1R/8/1b2PB2/1P6/4K3 b - - 1-0
3nk2r/rp1b2pp/pR3p2/3P4/5Q2/3B1N2/5PPP/5RK1 b k - 1-0
1R6/7p/4k1pB/p1Ppn3/3K3P/8/r7/8 w - - 0-1
3R4/5B1k/2b4p/5p2/1P6/4q3/P4RPP/6K1 b - - 1/2-1/2
8/5kp1/p4n1p/3pK3/1B6/8/8/8 w - - 0-1
3q3k/1br2pp1/1p6/pP1pR1b1/3P4/P2Q2P1/1B5P/5RK1 b - - 1-0
2b1rbk1/1p1n1pp1/3B3p/6q1/2B1P3/2N2P1P/R2Q2P1/6K1 b - - 1/2-1/2
2q3k1/5pp1/p3p2p/1p6/1Q1P4/5PP1/PP2N2P/3R2K1 b - - 1-0
8/7Q/p2p1pp1/4b1k1/6r1/8/P4PP1/3R1RK1 b - - 1-0
rq3rk1/2p2ppp/p2b4/1p1Rp1BQ/4P3/1P5P/1PP2PP1/3R2K1 b - - 1-0
[...]

Whom do I talk to?

If you have any questions or comments, please send email to Álvaro Begué.