aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorInigoGutierrez <inigogf.95@gmail.com>2022-06-25 01:47:38 +0200
committerInigoGutierrez <inigogf.95@gmail.com>2022-06-25 01:47:38 +0200
commitf57799a2b45cf7ad76ca9cff32cb4d1d86c2f2c4 (patch)
tree00401e687f07fc620c7bbbb97971d7429d7e7501
parent259a6e8a1ff030060ca624d07f37f3ffd0c2295d (diff)
downloadimago-f57799a2b45cf7ad76ca9cff32cb4d1d86c2f2c4.tar.gz
imago-f57799a2b45cf7ad76ca9cff32cb4d1d86c2f2c4.zip
Working convolutional neural network.
-rw-r--r--imago/engine/keras/convNeuralNetwork.py35
-rw-r--r--imago/engine/keras/keras.py13
-rw-r--r--imago/engine/keras/neuralNetwork.py133
3 files changed, 117 insertions, 64 deletions
diff --git a/imago/engine/keras/convNeuralNetwork.py b/imago/engine/keras/convNeuralNetwork.py
index 2534db6..9d97586 100644
--- a/imago/engine/keras/convNeuralNetwork.py
+++ b/imago/engine/keras/convNeuralNetwork.py
@@ -1,43 +1,46 @@
"""Convolutional neural network."""
from tensorflow.keras.models import Sequential
-from tensorflow.keras.layers import Conv2D, Conv2DTranspose, Reshape, Dense
+from tensorflow.keras.layers import Conv2D, MaxPool2D, Flatten, Dense
from tensorflow.keras.optimizers import Adam
from imago.engine.keras.neuralNetwork import NeuralNetwork
-defaultModelFile = 'models/imagoConvKerasModel.h5'
-
class ConvNeuralNetwork(NeuralNetwork):
+ NETWORK_ID = "convNeuralNetwork"
+ DEFAULT_MODEL_FILE = 'models/imagoConvKerasModel.h5'
+
def _initModel(self, boardSize=NeuralNetwork.DEF_BOARD_SIZE):
model = Sequential([
Conv2D(
filters=32,
- kernel_size=(3),
+ kernel_size=(3,3),
activation='relu',
padding='same',
- input_shape=(boardSize,boardSize,1)
+ input_shape=(boardSize,boardSize,2)
+ ),
+ MaxPool2D(
+ pool_size=(2,2),
+ strides=2,
+ padding='valid'
),
Conv2D(
filters=64,
- kernel_size=(3),
+ kernel_size=(3,3),
activation='relu',
padding='same'
),
- #Conv2DTranspose(
- # filters=64,
- # kernel_size=(3),
- # activation='relu',
- # padding='same'
- #),
+ MaxPool2D(
+ pool_size=(2,2),
+ strides=2,
+ padding='valid'
+ ),
+ Flatten(),
Dense(
- units=1,
+ units=81,
activation='softmax'
),
- Reshape(
- (boardSize,boardSize)
- ),
])
model.summary()
diff --git a/imago/engine/keras/keras.py b/imago/engine/keras/keras.py
index 80b6647..4f39818 100644
--- a/imago/engine/keras/keras.py
+++ b/imago/engine/keras/keras.py
@@ -3,18 +3,19 @@
from imago.gameLogic.gameMove import GameMove
from imago.gameLogic.gameBoard import GameBoard
from imago.engine.decisionAlgorithm import DecisionAlgorithm
-from imago.engine.keras.denseNeuralNetwork import DenseNeuralNetwork
-from imago.engine.keras.convNeuralNetwork import ConvNeuralNetwork
+from imago.engine.keras.neuralNetwork import NeuralNetwork
-MODEL_FILE = "" # Use network's default model file
+MODEL_FILE = "/home/taamas/IIS/TFG/imago/models/imagoConvKerasModel.h5"
class Keras(DecisionAlgorithm):
def __init__(self, move):
self.currentMove = move
self.boardSize = move.board.getBoardHeight()
- #self.nn = NeuralNetwork(MODEL_FILE, self.boardSize)
- self.nn = ConvNeuralNetwork(MODEL_FILE, self.boardSize)
+ self.nn = NeuralNetwork(
+ MODEL_FILE,
+ self.boardSize
+ )
def forceNextMove(self, coords):
"""Selects given move as next move."""
@@ -22,7 +23,7 @@ class Keras(DecisionAlgorithm):
def pickMove(self):
"""Returns a move to play."""
- return self.nn.pickMove(self.currentMove)
+ return self.nn.pickMove(self.currentMove, self.currentMove.getNextPlayer())
def clearBoard(self):
"""Empties move history."""
diff --git a/imago/engine/keras/neuralNetwork.py b/imago/engine/keras/neuralNetwork.py
index a52a3af..d0eb4ae 100644
--- a/imago/engine/keras/neuralNetwork.py
+++ b/imago/engine/keras/neuralNetwork.py
@@ -1,32 +1,34 @@
"""Keras neural network."""
import sys
+import os
import os.path
import numpy
from matplotlib import pyplot
from tensorflow.keras.models import load_model
+from tensorflow.keras.utils import plot_model
from imago.data.enums import Player
-defaultModelFile = 'models/imagoKerasModel.h5'
-
class NeuralNetwork:
DEF_BOARD_SIZE = 9
- PLAYER_ID = 1
- OPPONENT_ID = -1
+
+ NETWORK_ID = "neuralNetwork"
+ DEFAULT_MODEL_FILE = "models/imagoKerasModel.h5"
def __init__(self, modelPath="", boardSize=DEF_BOARD_SIZE):
self.boardSize = boardSize
- self.path = defaultModelFile
+ self.path = self.DEFAULT_MODEL_FILE
if modelPath != "":
self.path = modelPath
try:
- self.model = self._loadModel(modelPath)
+ self.model = self._loadModel(self.path)
except FileNotFoundError:
self.model = self._initModel(boardSize)
+ self.saveModelPlot()
def _initModel(self, boardSize=DEF_BOARD_SIZE):
raise NotImplementedError("Tried to directly use NeuralNetwork class. Use one of the subclasses instead.")
@@ -60,6 +62,7 @@ class NeuralNetwork:
% modelPath)
def saveModel(self, modelPath=""):
+ """Saves the neural network model at the given path."""
if modelPath != "":
self.model.save(modelPath)
else:
@@ -68,80 +71,126 @@ class NeuralNetwork:
def _movesToTrainMoves(self, moves):
trainMoves = []
for move in moves:
- trainMove = self._moveToTrainMove(move)
+ if len(move.nextMoves) == 0:
+ continue
+ player = move.nextMoves[0].getPlayer()
+ board = move.board.board
+ trainMove = self._boardToPlayerContext(board, player)
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."""
+ """Converts the board to a 3D matrix with two representations of the board, one
+ marking the player's stones and the oter marking the opponent's stones."""
boardRows = len(board)
boardCols = len(board[0])
- contextBoard = numpy.zeros((boardRows, boardCols), dtype = int)
+ contextBoard = numpy.zeros((boardRows, boardCols, 2), dtype = float)
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
+ contextBoard[row][col][0] = 1
else:
- contextBoard[row][col] = OPPONENT_ID
- return contextBoard.tolist()
+ contextBoard[row][col][1] = 1
+ return contextBoard
def _movesToTargets(self, moves):
+ """Converts the moves to 2D matrices with values zero except for a one on the
+ played vertex."""
targets = []
for move in moves:
- target = numpy.zeros(
- (move.board.getBoardHeight(),
- move.board.getBoardWidth()),
- dtype = float)
- target[move.getRow()][move.getCol()] = 1
+ if len(move.nextMoves) == 0:
+ continue
+ target = numpy.zeros(self.boardSize * self.boardSize, dtype = float)
+ target[move.nextMoves[0].getRow() * self.boardSize + move.nextMoves[0].getCol()] = 1
targets.append(target.tolist())
return targets
- def pickMove(self, gameMove):
- prediction = self._predict(gameMove)[0]
- #self.showHeatmap(prediction)
+ def pickMove(self, gameMove, player):
+ """Uses the model's predict function to pick the highest valued vertex to play."""
+
+ predictionVector = self._predict(gameMove, player)[0]
+ prediction = numpy.zeros((self.boardSize, self.boardSize))
+ for row in range(self.boardSize):
+ for col in range(self.boardSize):
+ prediction[row][col] = predictionVector[row * self.boardSize + col]
+ self.saveHeatmap(prediction)
+
+ # Search the highest valued vertex which is also playable
playableVertices = gameMove.getPlayableVertices()
highest = -sys.float_info.max
hRow = -1
hCol = -1
for row in range(self.boardSize):
for col in range(self.boardSize):
- #print("[%d, %d] %s" % (row, col, (row, col) in playableVertices))
if prediction[row][col] > highest and (row, col) in playableVertices:
hRow = row
hCol = col
highest = prediction[row][col]
+
return [hRow, hCol]
- def _predict(self, gameMove):
+ def _predict(self, gameMove, player):
board = gameMove.board.board
- player = gameMove.getPlayer()
- sampleBoard = self._boardToPlayerContext(board, player)
- sampleBoard = numpy.array([sampleBoard])
+ sampleBoards = self._boardToPlayerContext(board, player)
+ sampleBoards = numpy.array([sampleBoards])
return self.model.predict(
- x = sampleBoard,
+ x = sampleBoards,
batch_size = 1,
verbose = 2)
- def showHeatmap(self, data):
+ def saveHeatmap(self, data):
+ rows = len(data)
+ cols = len(data[0])
+
fig, ax = pyplot.subplots()
- im = ax.imshow(data)
+ im = ax.imshow(data, cmap="YlGn")
# Show all ticks and label them with the respective list entries
- ax.set_xticks(numpy.arange(9))
- ax.set_yticks(numpy.arange(9))
+ ax.set_xticks(numpy.arange(cols))
+ ax.set_xticklabels(self._getLetterLabels(cols))
+ ax.set_yticks(numpy.arange(rows))
+ ax.set_yticklabels(numpy.arange(rows, 0, -1))
# Loop over data dimensions and create text annotations.
- for i in range(9):
- for j in range(9):
- text = ax.text(j, i, data[i, j],
- ha="center", va="center", color="w")
-
- ax.set_title("Harvest of local farmers (in tons/year)")
+ textColorThreshold = 0.35
+ for row in range(rows):
+ for col in range(cols):
+ textColor = ("k" if data[row, col] < textColorThreshold else "w")
+ ax.text(col, row, "%.2f"%(data[row, col]),
+ ha="center", va="center", color=textColor)
+
+ ax.set_title("Heat map of move likelihood")
fig.tight_layout()
- pyplot.show()
+ pyplot.savefig("heatmaps/heatmap_%s_%s_%d.png" %
+ (
+ self.NETWORK_ID,
+ self.path.replace('/','-'),
+ len([file for file in os.listdir("heatmaps")])
+ )
+ )
+
+ def _getLetterLabels(self, count):
+ labels = []
+ letter = 'A'
+ for _ in range(count):
+ labels.append(letter)
+ letter = chr(ord(letter) + 1)
+ # Skip I
+ if letter == 'I':
+ letter = 'J'
+ return labels
+
+ def saveModelPlot(self):
+ plot_model(
+ self.model,
+ to_file="model.png",
+ show_shapes=True,
+ show_dtype=True,
+ show_layer_names=True,
+ rankdir="TB",
+ expand_nested=True,
+ dpi=96,
+ layer_range=None,
+ show_layer_activations=True,
+ )