From ee216c008a188a9436fedb85c70ee5d1719733b1 Mon Sep 17 00:00:00 2001 From: tslil clingman Date: Sun, 15 Jan 2023 16:03:37 +0100 Subject: new neural network arch (faster + better) & minor changes + fixes Gone is the convolutional neural network, for it turns out not only is it more difficult to train, but all of the extra information about board layers didn't make much of a difference at this size. So cnn1986 has been replaced by nn1986, a standard, two-layer, dense nn configured as a binary classifier and (mis)used in that capacity. Note: total number of parameters is unchanged. HARK: this new nn exposes a bug somewhere in ctak. Run ctlm with self-play to see the completely borked board state at the end. --- src/cnn_train.py | 156 ------------------------------------------------------- 1 file changed, 156 deletions(-) delete mode 100644 src/cnn_train.py (limited to 'src/cnn_train.py') diff --git a/src/cnn_train.py b/src/cnn_train.py deleted file mode 100644 index 4ba0a0b..0000000 --- a/src/cnn_train.py +++ /dev/null @@ -1,156 +0,0 @@ -# cnn_train.py, train a small CNN to recognise winning Tak positions -# -# Copyright (C) 2021, tslil clingman -# -# 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. -# -# This program 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 this program. If not, see . - - -# Loading data -import pandas as pd -import numpy as np - -# Output of weights -from tensorflow import transpose - -# Custom activation function -from tensorflow import constant as k -from tensorflow import clip_by_value -from tensorflow.math import add, divide, multiply, square - -# Computing size -from tensorflow.keras.backend import get_value - -# Building models -from tensorflow.keras.models import Model -from tensorflow.keras.layers import Concatenate -from tensorflow.keras.layers import Input -from tensorflow.keras.layers import Dense -from tensorflow.keras.layers import Convolution2D -from tensorflow.keras.layers import Flatten - - -def load_data(size): - shape = (-1, size, size, 6) - # Load training data - tr_fn = "training-"+str(size)+".csv" - training_csv = pd.read_csv(tr_fn) - training_data = training_csv - training_stack_input = np.array(training_data.iloc[:, 2:-1]).reshape(shape, order='F') - training_flats_input = np.array(training_data.iloc[:, 0:2]) - training_input = [training_stack_input, training_flats_input] - training_outcome = training_data.iloc[:, -1:] - # Load validation data - val_fn = "validation-"+str(size)+".csv" - val_data = pd.read_csv(val_fn).tail(20000) - val_stack_input = np.array(val_data.iloc[:, 2:-1]).reshape(shape, order='F') - val_flats_input = np.array(val_data.iloc[:, 0:2]) - val_input = [val_stack_input, val_flats_input] - val_outcome = val_data.iloc[:, -1:] - 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 = [] - for i in range(0, iterations): - print("Iteration {0}/{1}".format(i+1, iterations)) - model.fit(training_input, training_outcome, epochs=epochs, - validation_data=(val_input, val_outcome), - 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, iteration, performance): - def fix(val): - string = str(np.array(val).tolist()) - string = string.replace("[", "{").replace("]", "}") - return string - # Prepare everything in a sane memory order This isn't exactly in - # the correct order that tensorflow uses, because memory access - # out of order is an eyesore. Compared to tensorflow, the C - # implementation has the board reflected about the diagonal. - conv2d_weights = transpose(model.trainable_variables[0], perm=[3, 0, 1, 2]) - conv2d_biases = model.trainable_variables[1] - dense1_weights = transpose(model.trainable_variables[2], perm=[1, 0]) - dense1_biases = model.trainable_variables[3] - dense2_weights = transpose(model.trainable_variables[4], perm=[1, 0]) - dense2_biases = model.trainable_variables[5] - output_weights = transpose(model.trainable_variables[6], perm=[1, 0])[0] - output_bias = model.trainable_variables[7][0] - # Prepare formatting - names = ["conv2d_weights[KERN_NUM][KERN_SIZE][KERN_SIZE][KERN_CHAN]", - "conv2d_biases[KERN_NUM]", - "dense1_weights[DENSE1_NUM][CONV_NUM+2]", - "dense1_biases[DENSE1_NUM]", - "dense2_weights[DENSE2_NUM][DENSE1_NUM]", - "dense2_biases[DENSE2_NUM]", - "output_weights[DENSE2_NUM]", - "output_bias"] - variables = [conv2d_weights, conv2d_biases, - dense1_weights, dense1_biases, - dense2_weights, dense2_biases, - output_weights, output_bias] - # Write to file - 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") - f.close() - - -data = load_data(5) -model = make_model(5, [12, 11, 8]) -results = train(5, model, data, iterations=20, epochs=10) -- cgit v1.3.1