From fcface1079b1c44c7ccca3d117c698347c978f20 Mon Sep 17 00:00:00 2001 From: tslil clingman Date: Fri, 29 Jan 2021 14:05:06 -0500 Subject: Syntax errors, small tweak to training data generation --- resources/extract.sh | 2 +- src/cnn_train.py | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/pptdb.c | 11 +-- src/train5.py | 189 --------------------------------------------------- 4 files changed, 196 insertions(+), 195 deletions(-) create mode 100644 src/cnn_train.py delete mode 100644 src/train5.py diff --git a/resources/extract.sh b/resources/extract.sh index fb6f267..7fdda28 100755 --- a/resources/extract.sh +++ b/resources/extract.sh @@ -62,7 +62,7 @@ fi make pptdb -for size in 5; do +for size in 5 6; do echo extract $size notation,result process $size 400000 training diff --git a/src/cnn_train.py b/src/cnn_train.py new file mode 100644 index 0000000..0580b03 --- /dev/null +++ b/src/cnn_train.py @@ -0,0 +1,189 @@ +# train5.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 + + +# We need something that's close to logistic, but cheaper to compute. +# (12.0+x+50.0*x/(x*x+10.0))/24.0, clipped between 0 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(divide(val, k(24.0)), 0.0, 1.0) + + +# A cheaper version of tanh, x/6+25*x/(6*(2*x*x+5)), +# clipped between -1 and 1. +def clipped_pade_tanh(x): + val = add(divide(x, k(6.0)), + multiply(k(25.0), + divide(x, multiply(k(6.0), add(k(5.0), + multiply(k(2.0), square(x))))))) + return clip_by_value(val, -1.0, 1.0) + + +def make_model(size, magic=[12, 9, 9]): + # 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', + # loss='mean_absolute_error', + # loss='mean_absolute_percentage_error', + metrics=['accuracy']) + model.summary() + return model + + +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) #.head(100000) + 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)] + + +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) + v_loss, v_accuracy = model.evaluate(val_input, val_outcome, + verbose=False, batch_size=16) + t_loss, t_accuracy = model.evaluate(training_input, training_outcome, + verbose=False, batch_size=32) + results += [((v_loss, t_loss), (v_accuracy, t_accuracy))] + print("\nScores") + for i in range(len(results)): + print("Iteration {0}: {1}".format(i+1, results[i])) + return results + + +def model_size(model): + return sum([np.prod(get_value(w).shape) for w in model.trainable_weights]) + + +def magic_search(data): + results = [] + for width in range(9, 18): + for dense1 in range(9, 16): + for dense2 in range(9, max(32, dense1*2)): + m = make_model(5, [width, dense1, dense2]) + if model_size(m) < 1990: + results += [([width, dense1, dense2], + train(5, m, data, iterations=1, epochs=20))] + print("\nSummary") + for r in results: + print("Parameters {0}: {1}".format(r[0], r[1][0])) + return results + + +def write_weights(model): + 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.txt", "w") + 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=5, epochs=10) +write_weights(model) + +# results = magic_search(data) diff --git a/src/pptdb.c b/src/pptdb.c index b5bdfa4..ecf5caa 100644 --- a/src/pptdb.c +++ b/src/pptdb.c @@ -133,12 +133,13 @@ parse_line(const char *pt, const ssize_t read) { } } } - // Generate training data, not too early in the game, random - // orientation + // Generate training data, not too early in the game, all + // orientations if (generate && ply + 4 >= total_plies) { - int dx = (rand()&1)?-1:+1; - int dy = (rand()&1)?-1:+1; - write_input(dx,dy); + write_input(-1,-1); + write_input(-1,+1); + write_input(+1,-1); + write_input(+1,+1); } // Parse next action while (idx. - - -# 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 - - -# We need something that's close to logistic, but cheaper to compute. -# (12.0+x+50.0*x/(x*x+10.0))/24.0, clipped between 0 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(divide(val, k(24.0)), 0.0, 1.0) - - -# A cheaper version of tanh, x/6+25*x/(6*(2*x*x+5)), -# clipped between -1 and 1. -def clipped_pade_tanh(x): - val = add(divide(x, k(6.0)), - multiply(k(25.0), - divide(x, multiply(k(6.0), add(k(5.0), - multiply(k(2.0), square(x))))))) - return clip_by_value(val, -1.0, 1.0) - - -def make_model(size, magic=[12, 9, 9]): - # 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', - # loss='mean_absolute_error', - # loss='mean_absolute_percentage_error', - metrics=['accuracy']) - model.summary() - return model - - -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) #.head(100000) - 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)] - - -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) - v_loss, v_accuracy = model.evaluate(val_input, val_outcome, - verbose=False, batch_size=16) - t_loss, t_accuracy = model.evaluate(training_input, training_outcome, - verbose=False, batch_size=32) - results += [((v_loss, t_loss), (v_accuracy, t_accuracy))] - print("\nScores") - for i in range(len(results)): - print("Iteration {0}: {1}".format(i+1, results[i])) - return results - - -def model_size(model): - return sum([np.prod(get_value(w).shape) for w in model.trainable_weights]) - - -def magic_search(data): - results = [] - for width in range(9, 18): - for dense1 in range(9, 16): - for dense2 in range(9, max(32, dense1*2)): - m = make_model(5, [width, dense1, dense2]) - if model_size(m) < 1990: - results += [([width, dense1, dense2], - train(5, m, data, iterations=1, epochs=20))] - print("\nSummary") - for r in results: - print("Parameters {0}: {1}".format(r[0], r[1][0])) - return results - - -def write_weights(model): - 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.txt", "w") - 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=5, epochs=10) -write_weights(model) - -# results = magic_search(data) -- cgit v1.2.3