From 11956b2e940f5e1839efab187d898092e819a766 Mon Sep 17 00:00:00 2001 From: tslil clingman Date: Mon, 1 Feb 2021 21:46:24 -0500 Subject: 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. --- include/negamax.c | 35 ++++++++++++++++++------- include/negamax.h | 1 + resources/extract.sh | 4 +-- src/cnn_train.py | 74 +++++++++++++++++++++++++++------------------------- src/ctaklm.c | 17 ++++++++---- src/pptdb.c | 13 ++++----- 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