diff options
author | InigoGutierrez <inigogf.95@gmail.com> | 2022-06-18 14:44:38 +0200 |
---|---|---|
committer | InigoGutierrez <inigogf.95@gmail.com> | 2022-06-18 14:44:38 +0200 |
commit | e9af1d809f25f6499a9aeb43264ce809118e63e8 (patch) | |
tree | df849383ea7bcd31ac18ff08f9f9dd7586ea37c6 | |
parent | faea06ac8d0dc92edb9ca0f2b6aacf79f640ace7 (diff) | |
download | imago-e9af1d809f25f6499a9aeb43264ce809118e63e8.tar.gz imago-e9af1d809f25f6499a9aeb43264ce809118e63e8.zip |
Testing working neural network.
-rw-r--r-- | imago/engine/decisionAlgorithm.py | 4 | ||||
-rw-r--r-- | imago/engine/keras/__init__.py | 0 | ||||
-rw-r--r-- | imago/engine/keras/neuralNetwork.py | 83 | ||||
-rw-r--r-- | imago/gameLogic/gameBoard.py | 2 | ||||
-rw-r--r-- | imago/gameLogic/gameMove.py | 8 | ||||
-rwxr-xr-x | testKeras.py | 24 | ||||
-rwxr-xr-x | testSGF.py | 5 |
7 files changed, 108 insertions, 18 deletions
diff --git a/imago/engine/decisionAlgorithm.py b/imago/engine/decisionAlgorithm.py index 67fc3ef..e3f4b15 100644 --- a/imago/engine/decisionAlgorithm.py +++ b/imago/engine/decisionAlgorithm.py @@ -7,8 +7,8 @@ class DecisionAlgorithm: def forceNextMove(self, coords): """Selects given move as next move.""" - pass + raise NotImplementedError("Method forceNextMove not implemented on type %s" % type(self)) def pickMove(self): """Returns a move to play.""" - pass + raise NotImplementedError("Method pickMove not implemented on type %s" % type(self)) diff --git a/imago/engine/keras/__init__.py b/imago/engine/keras/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/imago/engine/keras/__init__.py diff --git a/imago/engine/keras/neuralNetwork.py b/imago/engine/keras/neuralNetwork.py index d0b7798..26cb19e 100644 --- a/imago/engine/keras/neuralNetwork.py +++ b/imago/engine/keras/neuralNetwork.py @@ -1,50 +1,67 @@ """Keras neural network.""" import os.path +import numpy +from copy import deepcopy import tensorflow as tf from tensorflow import keras from tensorflow.keras.models import Sequential, load_model -from tensorflow.keras.layers import Activation, Dense +from tensorflow.keras.layers import Dense, Flatten from tensorflow.keras.optimizers import Adam from tensorflow.keras.metrics import categorical_crossentropy -modelFile = 'models/imagoKerasModel.h5' +from imago.data.enums import Player + +defaultModelFile = 'models/imagoKerasModel.h5' DEF_BOARD_SIZE = 9 +PLAYER_ID = 1 +OPPONENT_ID = -1 class NeuralNetwork: def __init__(self, modelPath="", boardSize=DEF_BOARD_SIZE): - self.path = modelFile + self.path = defaultModelFile if modelPath != "": + self.path = modelPath + try: self.model = self._loadModel(modelPath) - else: + except FileNotFoundError: self.model = self._initModel(boardSize) def _initModel(self, boardSize=DEF_BOARD_SIZE): model = Sequential([ Dense(units=16, activation='relu', input_shape=(boardSize,boardSize)), Dense(units=32, activation='relu'), - Dense(units=2, activation='softmax') + Dense(units=boardSize, activation='softmax') ]) model.summary() model.compile( optimizer=Adam(learning_rate=0.0001), - loss='sparse_categorical_crossentropy', + loss='categorical_crossentropy', metrics=['accuracy'] ) return model - def _trainModel(self, boardSize=DEF_BOARD_SIZE): + def trainModel(self, games): + trainMoves = [] + targets = [] + for game in games: + for move in self._movesToTrainMoves(game): + trainMoves.append(move) + for target in self._movesToTargets(game): + targets.append(target) + trainMoves = numpy.array(trainMoves) + targets = numpy.array(targets) self.model.fit( - x=scaled_train_samples, - y=train_labels, + x=trainMoves, + y=targets, validation_split=0.1, batch_size=10, epochs=30, - shuffle=True, + shuffle=False, verbose=2 ) @@ -53,7 +70,47 @@ class NeuralNetwork: if os.path.isfile(modelPath): return load_model(modelPath) else: - raise Exception("Keras neural network file not found at %s" % modelPath) + raise FileNotFoundError("Keras neural network file not found at %s" % modelPath) + + def saveModel(self, modelPath=""): + if modelPath != "": + self.model.save(modelPath) + else: + self.model.save(self.path) + + def _movesToTrainMoves(self, moves): + trainMoves = [] + for move in moves: + trainMove = self._moveToTrainMove(move) + trainMoves.append(trainMove) + return trainMoves + + def _moveToTrainMove(self, move): + player = move.getPlayer() + return self._boardToPlayerContext(move.board.board, player) + + def _boardToPlayerContext(self, board, player): + """Converts to a board where PLAYER_ID marks the stones who made the move and + OPPONENT_ID marks the stones of the other player.""" + boardRows = len(board) + boardCols = len(board[0]) + contextBoard = numpy.zeros( (boardRows, boardCols), dtype = int) + for row in range(boardRows): + for col in range(boardCols): + if board[row][col] != Player.EMPTY: + if board[row][col] == player: + contextBoard[row][col] = PLAYER_ID + else: + contextBoard[row][col] = OPPONENT_ID + return contextBoard.tolist() - def _saveModel(self, modelPath): - self.model.save(modelPath) + def _movesToTargets(self, moves): + targets = [] + for move in moves: + target = numpy.zeros( + (move.board.getBoardHeight(), + move.board.getBoardWidth()), + dtype = float) + target[move.getRow()][move.getCol()] = 1 + targets.append(target.tolist()) + return targets diff --git a/imago/gameLogic/gameBoard.py b/imago/gameLogic/gameBoard.py index d6114b9..9054a18 100644 --- a/imago/gameLogic/gameBoard.py +++ b/imago/gameLogic/gameBoard.py @@ -112,7 +112,7 @@ class GameBoard: if (row < 0 or row >= self.getBoardHeight() or col < 0 or col >= self.getBoardWidth()): - print("[ERROR] Move and capture: out of bounds (%d, %d)" % (row, col)) + raise RuntimeError("[ERROR] Move and capture: out of bounds (%d, %d)" % (row, col)) self.board[row][col] = player diff --git a/imago/gameLogic/gameMove.py b/imago/gameLogic/gameMove.py index 6d2d464..cdd2fd6 100644 --- a/imago/gameLogic/gameMove.py +++ b/imago/gameLogic/gameMove.py @@ -124,6 +124,14 @@ class GameMove: self.nextMoves.append(newMove) return newMove + def getMainLineOfPlay(self, previousMoves=[]): + """Returns, recursively, this move and its first children, so a list of moves is + obtained. This typically represents the played sequence of a match.""" + previousMoves.append(self) + if len(self.nextMoves) == 0: + return previousMoves + return self.nextMoves[0].getMainLineOfPlay(previousMoves) + def toString(self): """Returns the coordinates of the move as a string.""" if self.isPass: diff --git a/testKeras.py b/testKeras.py new file mode 100755 index 0000000..3b0b3f5 --- /dev/null +++ b/testKeras.py @@ -0,0 +1,24 @@ +#!/usr/bin/python + +"""Starts training a keras neural network.""" + +import sys + +from imago.sgfParser.sgf import loadGameTree +from imago.engine.keras.neuralNetwork import NeuralNetwork + +def main(): + games = [] + for file in sys.argv[1:]: + print(file) + games.append(loadGameTree(file)) + + matches = [game.getMainLineOfPlay() for game in games] + + modelFile = "models/testModel.h5" + nn = NeuralNetwork(modelFile, 9) + nn.trainModel(matches) + nn.saveModel() + +if __name__ == '__main__': + main() @@ -1,9 +1,10 @@ #!/usr/bin/python +"""Gets a game from an SGF file.""" + import sys -from imago.sgfParser.sgf import loadGameTree -"""Gets a game from an SGF file.""" +from imago.sgfParser.sgf import loadGameTree def main(): filename = sys.argv[1] |