aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortslil clingman <tslil@posteo.de>2021-02-01 21:46:24 -0500
committertslil <tslil@posteo.de>2026-08-28 19:37:41 +0100
commit11956b2e940f5e1839efab187d898092e819a766 (patch)
treed485a6944d3fedbd62690a96bdcb19e0918104af
parent328c8d1e3094a942d6a2edd933c9cc4ab09daab1 (diff)
Tried some naive iterative deepening. Work on TEI interface next
If TEI is implemented, then i could make use of Morten's racetrack (https://github.com/MortenLohne/racetrack) and develop a quantitative measure of the bot's performance. This is the current priority.
-rw-r--r--include/negamax.c35
-rw-r--r--include/negamax.h1
-rwxr-xr-xresources/extract.sh4
-rw-r--r--src/cnn_train.py74
-rw-r--r--src/ctaklm.c17
-rw-r--r--src/pptdb.c13
6 files changed, 85 insertions, 59 deletions
diff --git a/include/negamax.c b/include/negamax.c
index 44deea9..f5bbf02 100644
--- a/include/negamax.c
+++ b/include/negamax.c
@@ -24,12 +24,14 @@
const float infty = 3.0;
char negamax_ptn[9];
uint8_t negamax_search_depth = 3;
+static uint64_t negamax_best_action;
// ===================================================================
// Helper declarations
// ===================================================================
-static float negamax(const uint8_t cur_depth, float alpha, float beta,
+static float negamax(const uint8_t cur_depth, const uint8_t init_depth,
+ float alpha, float beta,
const float colour, const uint64_t hash);
// ===================================================================
@@ -51,25 +53,31 @@ float negamax_generate(void) {
// We need to start with something outside of [-∞,∞] because those
// values are wins
const float safe_infty = infty + 1;
+ float result = -infty;
+
+ negamax_best_action = -1;
tt_init();
- float result =
- negamax(negamax_search_depth, -safe_infty, safe_infty,
- (ply & 1) ? +1.0 : -1.0, zobrist_compute());
+ for (int d = 1; d <= negamax_search_depth; d++) {
+ result = negamax(d, d, -safe_infty, safe_infty,
+ (ply & 1) ? +1.0 : -1.0, zobrist_compute());
+ }
tt_free();
return result;
}
// ===================================================================
-// α-β negamax using the cnn1986 evaluation function and transposition
-// tables using Zobrist hasing and a chaining hash table
+// α-β negamax with iterative deepening, using the cnn1986 evaluation
+// function and transposition tables using Zobrist hasing and a
+// chaining hash table
// ===================================================================
static enum TT_FLAG flag;
static enum WIN_TYPE w;
-static float negamax(const uint8_t cur_depth, float alpha, float beta,
+static float negamax(const uint8_t cur_depth, const uint8_t init_depth,
+ float alpha, float beta,
const float colour, const uint64_t hash) {
tt_entry_t *entry = tt_seek(hash);
@@ -94,12 +102,16 @@ static float negamax(const uint8_t cur_depth, float alpha, float beta,
action_move_to_front(entry->action, list);
}
+ if (init_depth > 1 && cur_depth == init_depth) {
+ action_move_to_front(negamax_best_action, list);
+ }
+
// TODO: what to do if this is never written to?
action_t best_action = list->head->action;
float best_value = -infty;
for (action_node_t *node=list->head; node!=NULL; node=node->next) {
- negamax_display_progress(cur_depth, list->length);
+ negamax_display_progress(cur_depth, init_depth, list->length);
action_take(node->action);
@@ -115,7 +127,8 @@ static float negamax(const uint8_t cur_depth, float alpha, float beta,
}
} else if (cur_depth > 1) {
// If nobody won, or too early and not leaf, recurse
- node_value = -negamax(cur_depth - 1, -beta, -alpha,
+ node_value = -negamax(cur_depth - 1, init_depth,
+ -beta, -alpha,
-colour, zobrist_compute());
} else {
node_value = colour * cnn1986_evaluate_black_win();
@@ -125,8 +138,10 @@ static float negamax(const uint8_t cur_depth, float alpha, float beta,
if (node_value > best_value) {
best_value = node_value;
best_action = node->action;
- if (cur_depth == negamax_search_depth)
+ if (cur_depth == init_depth) {
action_to_ptn(node->action, negamax_ptn);
+ negamax_best_action = best_action;
+ }
}
if (best_value > alpha) alpha = best_value;
diff --git a/include/negamax.h b/include/negamax.h
index 77461b8..b3dec75 100644
--- a/include/negamax.h
+++ b/include/negamax.h
@@ -33,6 +33,7 @@ extern char negamax_ptn[9];
extern uint8_t negamax_search_depth;
extern void negamax_display_progress(const uint8_t cur_depth,
+ const uint8_t init_depth,
const uint32_t length);
// ===================================================================
diff --git a/resources/extract.sh b/resources/extract.sh
index 0e78505..e9cd62a 100755
--- a/resources/extract.sh
+++ b/resources/extract.sh
@@ -2,7 +2,7 @@
db_file=games_anon.db
-chosen_players="AaaarghBot Tiltak_Bot"
+chosen_players="AaaarghBot Tiltak_Bot Taktician"
query() {
query="(size == $1) and (result != '1-0') and (result != '0-1') and (result != '0-0') and (result != '1/2-1/2')"
@@ -63,5 +63,5 @@ make pptdb
for size in 5; do
echo
extract $size notation,result
- process $size 400000 training
+ process $size 1000000 training
done
diff --git a/src/cnn_train.py b/src/cnn_train.py
index 1264e9f..ddf208b 100644
--- a/src/cnn_train.py
+++ b/src/cnn_train.py
@@ -40,37 +40,6 @@ from tensorflow.keras.layers import Convolution2D
from tensorflow.keras.layers import Flatten
-# We need something that's close to 2*logistic-1, but cheaper to
-# compute: (12+x+50*x/(x*x+10))/12-1, clipped between -1 and 1 as it
-# would otherwise exceed this range at +- 4.6 or so
-def clipped_pade_logistic(x):
- val = add(k(12.0),
- add(x, multiply(k(50.0),
- divide(x, add(square(x), k(10.0))))))
- return clip_by_value(add(k(-1.0), divide(val, k(12.0))), -1.0, +1.0)
-
-
-def make_model(size, magic=[12, 11, 8]):
- # Our model for the stacks, a small CNN
- stack_shape = (size, size, 6)
- stack_input = Input(shape=stack_shape)
- stack_model = Convolution2D(magic[0], kernel_size=(3, 3), strides=(1, 1),
- padding='valid', activation="relu",
- use_bias=True)(stack_input)
- stack_model = Flatten()(stack_model)
- stack_model = Model(inputs=stack_input, outputs=stack_model)
- # The overall model
- flats_input = Input(shape=(2,))
- combn_input = Concatenate()([stack_model.output, flats_input])
- model = Dense(magic[1], activation="relu", use_bias=True)(combn_input)
- model = Dense(magic[2], activation="relu", use_bias=True)(model)
- model = Dense(1, activation=clipped_pade_logistic, use_bias=True)(model)
- model = Model(inputs=[stack_model.input, flats_input], outputs=model)
- model.compile(optimizer='adam', loss='mean_squared_error')
- model.summary()
- return model
-
-
def load_data(size):
shape = (-1, size, size, 6)
# Load training data
@@ -91,6 +60,16 @@ def load_data(size):
return [(training_input, training_outcome), (val_input, val_outcome)]
+# We need something that's close to 2*logistic-1, but cheaper to
+# compute: (12+x+50*x/(x*x+10))/12-1, clipped between -1 and 1 as it
+# would otherwise exceed this range at +- 4.6 or so
+def clipped_pade_logistic(x):
+ val = add(k(12.0),
+ add(x, multiply(k(50.0),
+ divide(x, add(square(x), k(10.0))))))
+ return clip_by_value(add(k(-1.0), divide(val, k(12.0))), -1.0, +1.0)
+
+
def train(size, model, data, iterations=1, epochs=10):
(training_input, training_outcome), (val_input, val_outcome) = data
results = []
@@ -98,19 +77,40 @@ def train(size, model, data, iterations=1, epochs=10):
print("Iteration {0}/{1}".format(i+1, iterations))
model.fit(training_input, training_outcome, epochs=epochs,
validation_data=(val_input, val_outcome),
- verbose=True)
+ verbose=True, batch_size=16)
v_loss = model.evaluate(val_input, val_outcome,
verbose=False, batch_size=16)
t_loss = model.evaluate(training_input, training_outcome,
verbose=False, batch_size=32)
results += [(v_loss, t_loss)]
+ write_weights(model, i+1, str((v_loss, t_loss)))
print("\nScores")
for i in range(len(results)):
print("Iteration {0}: {1}".format(i+1, results[i]))
return results
+def make_model(size, magic=[16, 128, 64, 64, 64]):
+ # Our model for the stacks, a small CNN
+ stack_shape = (size, size, 6)
+ stack_input = Input(shape=stack_shape)
+ stack_model = Convolution2D(magic[0], kernel_size=(3, 3), strides=(1, 1),
+ padding='valid', activation="relu",
+ use_bias=True)(stack_input)
+ stack_model = Flatten()(stack_model)
+ stack_model = Model(inputs=stack_input, outputs=stack_model)
+ # The overall model
+ flats_input = Input(shape=(2,))
+ combn_input = Concatenate()([stack_model.output, flats_input])
+ model = Dense(magic[1], activation="relu", use_bias=True)(combn_input)
+ model = Dense(magic[2], activation="relu", use_bias=True)(model)
+ model = Dense(1, activation=clipped_pade_logistic, use_bias=True)(model)
+ model = Model(inputs=[stack_model.input, flats_input], outputs=model)
+ model.compile(optimizer='adam', loss='mean_squared_error')
+ model.summary()
+ return model
+
-def write_weights(model):
+def write_weights(model, iteration, performance):
def fix(val):
string = str(np.array(val).tolist())
string = string.replace("[", "{").replace("]", "}")
@@ -141,7 +141,10 @@ def write_weights(model):
dense2_weights, dense2_biases,
output_weights, output_bias]
# Write to file
- f = open("weights.txt", "w")
+ f = open("weights-"+str(iteration)+".txt", "w")
+ f.write("/*\n")
+ model.summary(print_fn=lambda l: f.write(" * "+l+"\n"))
+ f.write(" * "+performance+"\n/*\n\n")
f.write("#include \"weights.h\"\n\n")
for (name, val) in zip(names, variables):
f.write("const float "+name+" =\n"+fix(val)+";\n\n")
@@ -150,5 +153,4 @@ def write_weights(model):
data = load_data(5)
model = make_model(5, [12, 11, 8])
-results = train(5, model, data, iterations=5, epochs=10)
-write_weights(model)
+results = train(5, model, data, iterations=20, epochs=10)
diff --git a/src/ctaklm.c b/src/ctaklm.c
index 4d910c4..d790769 100644
--- a/src/ctaklm.c
+++ b/src/ctaklm.c
@@ -320,15 +320,22 @@ load_ptn(const char* fn) {
}
static float num_check, progress;
+static uint8_t old_depth;
// Set up output function for negamax
inline void
-negamax_display_progress(const uint8_t depth, const uint32_t length) {
+negamax_display_progress(const uint8_t cur_depth,
+ const uint8_t init_depth,
+ const uint32_t length) {
num_check += 1;
- if (depth == negamax_search_depth) {
+ if (cur_depth == init_depth) {
+ if (init_depth != old_depth) {
+ old_depth = init_depth;
+ progress = 0;
+ }
progress++;
- printf("\x1B[0GComputing: %.0f/%d",
- progress,length);
+ printf("\x1B[0GComputing: %.0f/%d @ D%d ",
+ progress, length, init_depth);
fflush(stdout);
}
}
@@ -337,7 +344,7 @@ static int
negamax_turn(void) {
if (won == 0xFF) {
// Run the minimax
- num_check = 0; progress = 0;
+ num_check = 0; progress = 0; old_depth = 0;
float minimax = negamax_generate();
putchar('\n');
// Failed to find a move?
diff --git a/src/pptdb.c b/src/pptdb.c
index 557b894..e3fd687 100644
--- a/src/pptdb.c
+++ b/src/pptdb.c
@@ -32,7 +32,7 @@ int generate, outcome_black;
const int max_depth = 6;
static void
-write_input(const int dx, const int dy) {
+write_input(const int dx, const int dy, const uint8_t swap) {
// Two numbers for flats remaining
fprintf(training_fh,"%.8f,%.8f,",
(float)(white_count & 127)/max_flats,
@@ -48,7 +48,8 @@ write_input(const int dx, const int dy) {
col = (dx>0)?-1:board_size;
for (int j = 0; j < board_size; j++) {
col += dx;
- const uint8_t k = THE_COORDS(col, row);
+ const uint8_t k =
+ (swap) ? THE_COORDS(row, col) : THE_COORDS(col, row);
val = 0;
if (COUNT_AT(k)>depth) {
if (depth == 0) {
@@ -136,10 +137,10 @@ parse_line(const char *pt, const ssize_t read) {
// Generate training data, not too early in the game, all
// orientations
if (generate && ply + 4 >= total_plies) {
- write_input(-1,-1);
- write_input(-1,+1);
- write_input(+1,-1);
- write_input(+1,+1);
+ write_input(-1, -1, 0); write_input(-1, -1, 1);
+ write_input(-1, +1, 0); write_input(-1, +1, 1);
+ write_input(+1, -1, 0); write_input(+1, -1, 1);
+ write_input(+1, +1, 0); write_input(+1, +1, 1);
}
// Parse next action
while (idx<read && pt[idx++]!=',');