I wrote two codes, one of which generates random moves, evaluates them with Stockfish, and then trains them to create a model. Other code for using this model and playing with it.
import chess
import chess.engine
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from sklearn.model_selection import train_test_split
import tensorflow as tf
from tqdm import tqdm
import random
STOCKFISH_PATH = "/Users/berkegulacar/Downloads/stockfish"
ENGINE_TIME_LIMIT = 1.0
NUM_GAMES = 500
def evaluate_board(engine, board):
with engine.analysis(board, limit=chess.engine.Limit(time=ENGINE_TIME_LIMIT)) as analysis:
for info in analysis:
if info.get("score") is not None:
return info.get("score").white().score(mate_score=10000)
return
def board_to_input(board):
piece_to_index = {
'P': 0, 'N': 1, 'B': 2, 'R': 3, 'Q': 4, 'K': 5,
'p': 6, 'n': 7, 'b': 8, 'r': 9, 'q': 10, 'k': 11
}
board_planes = np.zeros((64, 12), dtype=np.float32)
for square in chess.SQUARES:
piece = board.piece_at(square)
if piece:
index = piece_to_index[piece.symbol()]
board_planes[square, index] = 1
board_planes = board_planes.reshape((8, 8, 12))
extra_features = np.zeros((8, 8, 5), dtype=np.float32)
extra_features[:, :, 0] = int(board.has_kingside_castling_rights(chess.WHITE))
extra_features[:, :, 1] = int(board.has_queenside_castling_rights(chess.WHITE))
extra_features[:, :, 2] = int(board.has_kingside_castling_rights(chess.BLACK))
extra_features[:, :, 3] = int(board.has_queenside_castling_rights(chess.BLACK))
if board.ep_square:
ep_square = np.unravel_index(board.ep_square, (8, 8))
extra_features[ep_square[0], ep_square[1], 4] = 1
turn_channel = np.full((8, 8, 1), int(board.turn == chess.BLACK), dtype=np.float32)
board_input = np.concatenate((board_planes, extra_features, turn_channel), axis=-1)
return board_input
def play_game(engine):
board = chess.Board()
game_data = []
while not board.is_game_over(claim_draw=True):
move = random.choice(list(board.legal_moves))
board.push(move)
score = evaluate_board(engine, board)
game_data.append((board_to_input(board), score))
return game_data
def play_and_collect_data(num_games, engine_path):
engine = chess.engine.SimpleEngine.popen_uci(engine_path)
games_data = []
for _ in tqdm(range(num_games), desc="Playing games and collecting data"):
games_data.extend(play_game(engine))
engine.quit()
return games_data
def preprocess_data(games_data):
X, y = zip(*games_data)
X = np.array(X, dtype=np.float32)
y = np.array(y, dtype=np.float32) / 10000
return train_test_split(X, y, test_size=0.1, random_state=42)
def create_model(input_shape):
model = Sequential([
Dense(512, activation='relu', input_shape=input_shape),
Dropout(0.2),
Dense(512, activation='relu'),
Dropout(0.2),
Dense(1, activation='tanh')
])
model.compile(optimizer='adam', loss='mean_squared_error')
return model
def train_model(model, X_train, y_train, X_test, y_test):
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=50)
return model
if __name__ == '__main__':
games_data = play_and_collect_data(NUM_GAMES, STOCKFISH_PATH)
X_train, X_test, y_train, y_test = preprocess_data(games_data)
model = create_model(X_train[0].shape)
model = train_model(model, X_train, y_train, X_test, y_test)
model.save('model.h5')
**And code for playing with model: **
import chess
import chess.engine
import numpy as np
import tensorflow as tf
MODEL_PATH = '/Users/berkegulacar/Downloads/model.h5'
STOCKFISH_PATH = '/Users/berkegulacar/Downloads/stockfish'
model = tf.keras.models.load_model(MODEL_PATH)
engine = chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH)
def board_to_input(board):
piece_to_index = {
'P': 0, 'N': 1, 'B': 2, 'R': 3, 'Q': 4, 'K': 5,
'p': 6, 'n': 7, 'b': 8, 'r': 9, 'q': 10, 'k': 11
}
board_planes = np.zeros((64, 12), dtype=np.float32)
for square in chess.SQUARES:
piece = board.piece_at(square)
if piece:
index = piece_to_index[piece.symbol()]
board_planes[square, index] = 1
board_planes = board_planes.reshape((8, 8, 12))
extra_features = np.zeros((8, 8, 5), dtype=np.float32)
extra_features[:, :, 0] = int(board.has_kingside_castling_rights(chess.WHITE))
extra_features[:, :, 1] = int(board.has_queenside_castling_rights(chess.WHITE))
extra_features[:, :, 2] = int(board.has_kingside_castling_rights(chess.BLACK))
extra_features[:, :, 3] = int(board.has_queenside_castling_rights(chess.BLACK))
if board.ep_square:
ep_square = np.unravel_index(board.ep_square, (8, 8))
extra_features[ep_square[0], ep_square[1], 4] = 1
turn_channel = np.full((8, 8, 1), int(board.turn == chess.BLACK), dtype=np.float32)
board_input = np.concatenate((board_planes, extra_features, turn_channel), axis=-1)
return board_input
def select_best_move(model, board, engine):
legal_moves = list(board.legal_moves)
best_move = None
best_move_score = -np.inf
for move in legal_moves:
board.push(move)
processed_board = board_to_input(board).reshape(1, 8, 8, 18)
board_score = model.predict(processed_board)
board.pop()
current_move_score = board_score.flatten()[0]
if current_move_score > best_move_score:
best_move_score = current_move_score
best_move = move
if best_move is None:
result = engine.play(board, chess.engine.Limit(time=0.1))
best_move = result.move
print("Stockfish's move: {best_move}")
else:
print(f"Model's move: {best_move}")
return best_move
def play_game(model, engine):
board = chess.Board()
while not board.is_game_over(claim_draw=True):
print(board)
user_move = input("Your move (e.g., e2e4): ")
try:
move = chess.Move.from_uci(user_move)
if move in board.legal_moves:
board.push(move)
else:
print("Invalid move, try again.")
continue
except ValueError:
print("Invalid move format, try again.")
continue
if board.is_game_over(claim_draw=True):
break
best_move = select_best_move(model, board, engine)
board.push(best_move)
print("Game result:", board.result())
if __name__ == '__main__':
play_game(model, engine)
engine.quit()
The problem is that I think there is a scoring error in the trained model, so no matter what I play, it always repeats the same and illogical g8h6 followed by h8g8 and g8h8 and is checkmated. I need help for this. I'm waiting for your help.
I tried fen to numeric and one-hot encoding but the result is the same.