Skip to content

Commit

Permalink
Better cnn (#48)
Browse files Browse the repository at this point in the history
* feat: implementing alpha-beta prunning

* feat: better cnn model
  • Loading branch information
Magulak authored Jun 6, 2024
1 parent 1e657fc commit c4987a0
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 73 deletions.
7 changes: 6 additions & 1 deletion ai/ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ def material_balance(self, board):

def fen_to_onehot(self, fen):
# print(f"FEN TO BE ONEHOTED:{fen}")

# temp_str = fen.strip() + " " # clean useless symbols
# temp_str = temp_str.strip() + "[" # clean useless symbols
# temp_str = temp_str.strip() + "]" # clean useless symbols
# fen = temp_str
# Initialize an empty 8x8x12 array
board = np.zeros((8, 8, 12))

Expand Down Expand Up @@ -152,6 +155,8 @@ def csv_stockfish_into_input_data2(self, csv_stockfish_path, dataset_path):
with open(csv_stockfish_path, "r") as file:
for line in file:
temp_str = line.strip() + " " # clean useless symbols
temp_str = line.strip() + "[" # clean useless symbols
temp_str = line.strip() + "]" # clean useless symbols
temp_str = temp_str.split(
" "
) # split fen string from additional castling/pawn move data.
Expand Down
157 changes: 103 additions & 54 deletions ai/model.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,53 @@
import keras.models
import numpy as np
import tensorflow as tf

# print("Num GPUs Available: ", len(tf.config.list_physical_devices('GPU')))
DATA_PATH = "D:\\ML WSB Chess\\ML-Chess\\datasets\\dataset.csv"
CHUNKSIZE = 769


chunksize = 1000
X, y = [], []
class TrainingModel:
def __init__(self, data_path, chunksize,activation_fun,l1,l2,p,l4):
self.l1 = l1
self.l2 = l2
self.p = p
self.l4 = l4

#
# conda create --prefix D:\ML WSB Chess\ML-Chess\ai\conda python=3.10
# Open the CSV file
with open("D:\\ML WSB Chess\\ML-Chess\\datasets\\dataset.csv", "r") as f:
# Loop through each chunk of data
for i in range(0, int(13e9 / chunksize)):
# Read a chunk of data from the CSV file
data = np.loadtxt(f, dtype=np.float64, delimiter=" ", max_rows=chunksize)

self.data_path = data_path
self.chunksize = chunksize
self.activation_fun = activation_fun

def training(self):
data = []

# Load data in chunks and append to data list
for i in range(0, 76800, self.chunksize):
chunk = np.loadtxt(self.data_path, dtype=np.float64, delimiter=" ",
skiprows=i, max_rows=self.chunksize)
data.append(chunk)

data = np.concatenate(data)
print("Data loading finished")
print(data[:5])
print(data.shape)

X, y = [], []

if data.size == 0:
continue
print("ERROR")
return

# Extract input features and output label
X_chunk = data[:, :-1]
y_chunk = data[:, -1]

print(f"Shape of y_chunk: {np.shape(y_chunk)}")

# Normalize the input data to have mean 0 and standard deviation 1
X_mean = X_chunk.mean(axis=0, keepdims=True)
X_std = X_chunk.std(axis=0, keepdims=True)
X_std[X_std == 0] = 1
X_chunk = (X_chunk - X_mean) / X_std

X_std = X_chunk.std(axis=0, keepdims=True) + 1e-8

# Normalize the reward values to have mean 0 and standard deviation 1
y_mean = y_chunk.mean()
y_std = y_chunk.std()
Expand All @@ -41,41 +57,74 @@
X.append(X_chunk)
y.extend(y_chunk)

# Concatenate the list of input features into a single array
X = np.concatenate(X)

# Add a small constant to avoid division by zero
X += 1e-8

# Reshape the input data to match the expected input shape of the model
X = X.reshape((-1, 768, 1))


# Define the architecture of the CNN model
model = tf.keras.Sequential()
model.add(tf.keras.layers.Reshape((768, 1), input_shape=(1, 768)))
model.add(tf.keras.layers.Conv1D(32, 3, activation="relu"))
model.add(tf.keras.layers.MaxPooling1D(2))
model.add(tf.keras.layers.Conv1D(64, 3, activation="relu"))
model.add(tf.keras.layers.MaxPooling1D(2))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(64, activation="relu"))
model.add(tf.keras.layers.Dense(1))

# Compile the model
model.compile(optimizer="adam", loss="mean_squared_error")

# Print the shapes of X and y
for i, (x, y_val) in enumerate(zip(X, y)):
print(f"Shape of X[{i}]: {np.shape(x)}")
print(f"Shape of y[{i}]: {np.shape(y_val)}")

if len(X) > 0:
model.fit(X, y, epochs=10, batch_size=32)
else:
print("Error: X is an empty array")

# Save the model to a file
tf.saved_model.save(model, "first_model_on_13gb_database")
np.save("X_mean.npy", X_mean)
np.save("X_std.npy", X_std)
# Concatenate the list of input features into a single array
X = np.concatenate(X)
y = np.array(y)
from sklearn.model_selection import train_test_split


X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print("Shapes after concatenation")
print(f"Shape of X: {X.shape}")
print(f"Shape of y: {y.shape}")

# Add a small constant to avoid division by zero
X += 1e-8

# Reshape the input data to match the expected input shape of the model
X = X.reshape((-1, 768, 1))
print("reshape done")

# Define the architecture of the CNN model
model = tf.keras.Sequential([
tf.keras.layers.Reshape((self.l1, 1), input_shape=(768, 1)),
tf.keras.layers.Conv1D(self.l2, 3, activation=self.activation_fun),
tf.keras.layers.MaxPooling1D(self.p),
tf.keras.layers.Conv1D(self.l4, 3, activation=self.activation_fun),
tf.keras.layers.MaxPooling1D(self.p),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(64, activation=self.activation_fun),
tf.keras.layers.Dense(1)
])

# Compile the model with multiple metrics
model.compile(optimizer="adam", loss="mean_squared_error", metrics=["accuracy", "precision", "recall"])
mse = model.evaluate(X_test, y_test)
# Train the model if X is not empty
if X.size > 0:
model.fit(X, y, epochs=20, batch_size=32)
else:
print("Error: X is an empty array")

loss, accuracy, precision, recall = model.evaluate(X_test, y_test)

# Print the loss and metrics
print(f"Loss: {loss}")
print(f"Accuracy: {accuracy}")
print(f"Precision: {precision}")
print(f"Recall: {recall}")
print(f"Mean squared error: {mse}")



# model_name = f"\models\CNN loss{loss},acc{accuracy},prec{precision},mse:{mse},l1{self.l1},l2{self.l2},p{self.p},l4{self.l4}"
model_name = f"model"
# Save the model to a file
tf.saved_model.save(model,f"{model_name}.keras")
np.save(f"{model_name}X_mean.npy", X_mean)
np.save(f"{model_name}X_std.npy", X_std)
model.save(f"{model_name}.h5")


nn = TrainingModel(DATA_PATH, CHUNKSIZE,"relu",768,128,2,64)
nn.training()
# nn = TrainingModel(DATA_PATH, CHUNKSIZE,"relu",768,32,2,64)
# nn.training()
#
# nn = TrainingModel(DATA_PATH, CHUNKSIZE,"relu",768,64,2,64)
# nn.training()
# nn = TrainingModel(DATA_PATH, CHUNKSIZE,"relu",768,64,2,128)
# nn.training()
#
# nn = TrainingModel(DATA_PATH, CHUNKSIZE,"relu",768,128,2,64)
# nn.training()
62 changes: 44 additions & 18 deletions ai/use_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

PATH = "./ai/" # change this to dynamic path


class neural_network():

def moves_to_fen(self,moves, board):
def moves_to_fen(self, moves, board):
"""
Convert a list of chess moves to a list of FEN strings.
"""
Expand All @@ -22,41 +23,66 @@ def moves_to_fen(self,moves, board):

return merged_fen_list


def give_me_predictions(self, list_of_moves_to_eval, current_board_state):
list_to_be_evaluated = self.moves_to_fen(list_of_moves_to_eval,current_board_state)
list_to_be_evaluated = self.moves_to_fen(list_of_moves_to_eval, current_board_state)
from ai import AI
ai = AI(1)
ai = AI(1, 0, 0) # Needs fixing, we shoudn't call class in such place ( the class that we call is calling us)
highest_prediction = -float('inf')
best_move = None

for one_move in list_to_be_evaluated:
prepared_data_to_eval = ai.fen_matrix_into_one_row(ai.fen_to_onehot(one_move[1])) # fen to one hot smiga
prepared_data_to_eval = ai.fen_matrix_into_one_row(ai.fen_to_onehot(one_move[1])) # fen to one hot smiga
prediction = self.nn_evaluate_move(prepared_data_to_eval)
if prediction > highest_prediction:
highest_prediction = prediction
best_move = one_move[0]
return best_move
def give_me_predictions_test(self):

"""
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1, 0.24
rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1, 0.59
rnbqkbnr/pppppp1p/6p1/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2, 0.27
r1bq1rk1/pp1nppbp/3p1np1/2p5/2PPP3/2N1BN2/PP3PPP/2RQKB1R w K - 0 8, -1.84
r1bq1rk1/pp1nppbp/3p1np1/2p1P3/2PP4/2N1BN2/PP3PPP/2RQKB1R b K - 0 8, -1.34
r1bq1rk1/pp1nppbp/5np1/2p1p3/2PP4/2N1BN2/PP3PPP/2RQKB1R w K - 0 9, -0.95
3rr1k1/pp1n1ppp/2p5/2bp4/B7/8/PPP2PPP/R1BR2K1 w - - 2 18, -1.97
3rr1k1/pp1n1ppp/2p5/2bp2B1/B7/8/PPP2PPP/R2R2K1 b - - 3 18, -1.92
3rr1k1/pp1n2pp/2p2p2/2bp2B1/B7/8/PPP2PPP/R2R2K1 w - - 0 19, -5.08
"""
fens_to_eval = [
["rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq"], # 0.24
["r1bq1rk1/pp1nppbp/3p1np1/2p5/2PPP3/2N1BN2/PP3PPP/2RQKB1R"], # -1.84
["3rr1k1/pp1n1ppp/2p5/2bp4/B7/8/PPP2PPP/R1BR2K1"], # -1.97
["3rr1k1/pp1n2pp/2p2p2/2bp2B1/B7/8/PPP2PPP/R2R2K1 w - - 0 19"], # -5.08
["4r1k1/3R2pp/5p2/2p1n3/1b1p4/1P4BP/2P2PP1/3R2K1 b - - 0 29"], # -6.52
["8/8/1r3kp1/3b1p2/8/4P1P1/R2NKP2/8 w"] # 0.41
]
fens_to_eval_resul = [[0.24],[-1.84],[-1.97],[-5.08],[-6.52],[0.41]]
from ai import AI
ai = AI(1, 0, 0) # Needs fixing, we shoudn't call class in such place ( the class that we call is calling us)
highest_prediction = -float('inf')
best_move = None

for i, one_move in enumerate(fens_to_eval):
print(fens_to_eval_resul[i])
prepared_data_to_eval = ai.fen_matrix_into_one_row(ai.fen_to_onehot(str(one_move))) # fen to one hot smiga
prediction = self.nn_evaluate_move(prepared_data_to_eval)
print(f"predicted:{prediction}, true_result:{fens_to_eval[i]}")

def nn_evaluate_move(self, input_data):
# Wczytaj dane z załączonych plików

X_MEAN, X_STD = np.load(PATH + "X_mean.npy"), np.load(PATH + "X_std.npy")
# Wczytaj oraz znormalizuj dane
# input_data = "tutaj wkładamy Jednowymiarową tablicę z 768 wartościami ( Mam algorytm do przerabiania fen na taką spłaszczoną tablicę)"
input_data = (input_data - X_MEAN) / X_STD

# Jak mam dane równe 0 to tensorflow narzeka na dzielenie przez 0, więc dodajemy wszędzie malutką wartosc,
input_data = (input_data - X_MEAN) / X_STD
input_data += 1e-8

# Przekształć dane aby pasowały pod model 10 - ilość 'batchów danych' 768 - ilość wartości w podanej zmiennej
# ilość wymiarów naszej listy
input_data = input_data.reshape((1, 768, 1))

model = tf.keras.models.load_model(PATH + "ML_chessCNN1.h5")

# Przewiduj to jak dobry jest ten ruch.. Im wyższa wartość tym lepiej
predictions = model.predict(input_data)
return predictions

# Import the AI class here

# nn = neural_network()
# nn.give_me_predictions_test()

0 comments on commit c4987a0

Please sign in to comment.