aboutsummaryrefslogtreecommitdiff
path: root/src/selfplay.c
diff options
context:
space:
mode:
authortslil <tslil@posteo.de>2026-09-10 14:12:41 +0100
committertslil <tslil@posteo.de>2026-09-10 21:04:40 +0100
commit220a4b79e4b6d742a9ca7c278a1962b870500fbc (patch)
treeba527c7ec1feac91c5d59772c05fe9d32af6bdb8 /src/selfplay.c
parent710d5a5225edb44d171ca9ac6345e9a61aa4f125 (diff)
credit Mitchell Riley: we can drop 65 parameters for about the same strength; also train for longer
The idea is that the current model is M_theta(board, ply) = "score for player 0", but we can instead create a different model M'_theta(board) = "score for player 0 assuming that it's player 0's move" and for a certain pair of functions sigma, normalise, recover M_theta(board, ply) = sigma(ply) M'_theta(normalise(board, ply)). This pair of functions is sigma(ply) = +/- 1 (parity), and normalise being a conditional interchange of pieces on the board depending on the parity of the ply. The only things stopping this from being completely straightforward is that nn_train.py evaluates M'_theta itself so we need to pass sigma(ply) to the training loop. 6x6 is still untrained.
Diffstat (limited to 'src/selfplay.c')
-rw-r--r--src/selfplay.c13
1 files changed, 7 insertions, 6 deletions
diff --git a/src/selfplay.c b/src/selfplay.c
index 5ffa6f6..42d82d2 100644
--- a/src/selfplay.c
+++ b/src/selfplay.c
@@ -38,10 +38,11 @@
static tak_state_p state;
-static float row_head[MAX_PLY][3];
+static float row_head[MAX_PLY][2];
static uint8_t row_cells[MAX_PLY][MAX_CELLS];
static uint8_t row_term[MAX_PLY];
static float row_terminal_value[MAX_PLY];
+static float row_parity_sign[MAX_PLY];
static uint32_t row_game_id[MAX_PLY];
static uint32_t n_rows;
@@ -90,6 +91,7 @@ static int play_game(const uint8_t min_ply) {
memcpy(row_cells[n_rows], leaf->cells, sizeof(row_cells[n_rows]));
row_term[n_rows] = leaf->terminal;
row_terminal_value[n_rows] = leaf->terminal_value;
+ row_parity_sign[n_rows] = leaf->parity_sign;
row_game_id[n_rows] = state->current_colour == C_BLACK ? game_id_black : game_id_white;
n_rows++;
}
@@ -114,12 +116,11 @@ static int play_game(const uint8_t min_ply) {
const float R = win_value(state->won);
for (uint32_t k = 0; k < n_rows; k++) {
- fprintf(stdout, "%.8f,%.8f,%.8f,", row_head[k][0], row_head[k][1],
- row_head[k][2]);
+ fprintf(stdout, "%.8f,%.8f,", row_head[k][0], row_head[k][1]);
for (uint8_t c = 0; c < n_cells; c++)
fprintf(stdout, "%u,", (unsigned)row_cells[k][c]);
- fprintf(stdout, "%u,%.0f,%u,%.0f\n", row_game_id[k], R,
- (unsigned)row_term[k], row_terminal_value[k]);
+ fprintf(stdout, "%u,%.0f,%u,%.0f,%.0f\n", row_game_id[k], R,
+ (unsigned)row_term[k], row_terminal_value[k], row_parity_sign[k]);
}
return (int)n_rows;
@@ -129,7 +130,7 @@ const char *usage =
"selfplay, one game of asymmetric self-play training data.\n\n\
Usage: selfplay <size> <depth> <min_ply>\n\n\
Plays a single game of <size>x<size> self-play, with <min_ply> moves made randomly. Only the deterministic plies are written to stdout, one CSV row per recorded search, from ply <min_ply> onwards:\n\n\
-\tparity,white_flats,black_flats,<cell ids...>,game_id,R,terminal,leaf_R\n\n\
+\tmover_flats,opponent_flats,<cell ids...>,game_id,R,terminal,leaf_R,parity_sign\n\n\
nn_train.py runs many of these in parallel to generate training data.\n\n\
Copyright (C) 2026, tslil clingman\n\n\
This program 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.";