aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorInigoGutierrez <inigogf.95@gmail.com>2022-06-18 14:44:38 +0200
committerInigoGutierrez <inigogf.95@gmail.com>2022-06-18 14:44:38 +0200
commite9af1d809f25f6499a9aeb43264ce809118e63e8 (patch)
treedf849383ea7bcd31ac18ff08f9f9dd7586ea37c6
parentfaea06ac8d0dc92edb9ca0f2b6aacf79f640ace7 (diff)
downloadimago-e9af1d809f25f6499a9aeb43264ce809118e63e8.tar.gz
imago-e9af1d809f25f6499a9aeb43264ce809118e63e8.zip
Testing working neural network.
-rw-r--r--imago/engine/decisionAlgorithm.py4
-rw-r--r--imago/engine/keras/__init__.py0
-rw-r--r--imago/engine/keras/neuralNetwork.py83
-rw-r--r--imago/gameLogic/gameBoard.py2
-rw-r--r--imago/gameLogic/gameMove.py8
-rwxr-xr-xtestKeras.py24
-rwxr-xr-xtestSGF.py5
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()
diff --git a/testSGF.py b/testSGF.py
index 6291033..08a1bee 100755
--- a/testSGF.py
+++ b/testSGF.py
@@ -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]