aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/diagrams/DecisionAlgorithm.pumlc8
-rw-r--r--doc/diagrams/Keras.pumlc9
-rw-r--r--doc/diagrams/NeuralNetwork.pumlc8
-rw-r--r--doc/diagrams/analysisClasses.puml20
-rw-r--r--doc/diagrams/keras.puml13
-rw-r--r--doc/diagrams/skinparams.puml3
-rw-r--r--imago/engine/core.py17
-rw-r--r--imago/engine/decisionAlgorithm.py14
-rw-r--r--imago/engine/decisionAlgorithmFactory.py23
-rw-r--r--imago/engine/keras.py16
-rw-r--r--imago/engine/keras/neuralNetwork.py52
-rw-r--r--imago/engine/monteCarlo.py11
-rw-r--r--imago/scripts/__init__.py0
-rw-r--r--imago/scripts/monteCarloSimulation.py25
-rwxr-xr-ximago/scripts/monteCarloSimulation.sh24
-rw-r--r--results.txt41
-rwxr-xr-xsimulationMatchesPyplot.py27
17 files changed, 290 insertions, 21 deletions
diff --git a/doc/diagrams/DecisionAlgorithm.pumlc b/doc/diagrams/DecisionAlgorithm.pumlc
new file mode 100644
index 0000000..aada4f0
--- /dev/null
+++ b/doc/diagrams/DecisionAlgorithm.pumlc
@@ -0,0 +1,8 @@
+@startuml
+
+interface DecisionAlgorithm {
+ {abstract} forceNextMove(self, coords)
+ {abstract} pickMove(self)
+}
+
+@enduml
diff --git a/doc/diagrams/Keras.pumlc b/doc/diagrams/Keras.pumlc
new file mode 100644
index 0000000..daca149
--- /dev/null
+++ b/doc/diagrams/Keras.pumlc
@@ -0,0 +1,9 @@
+@startuml
+
+class Keras {
+ forceNextMove(self, coords)
+ pickMove(self)
+ loadNetwork(self)
+}
+
+@enduml
diff --git a/doc/diagrams/NeuralNetwork.pumlc b/doc/diagrams/NeuralNetwork.pumlc
new file mode 100644
index 0000000..30f783e
--- /dev/null
+++ b/doc/diagrams/NeuralNetwork.pumlc
@@ -0,0 +1,8 @@
+@startuml
+
+class NeuralNetwork {
+ load(self, path)
+ save(self, path)
+}
+
+@enduml
diff --git a/doc/diagrams/analysisClasses.puml b/doc/diagrams/analysisClasses.puml
index 4a286f7..d051cf9 100644
--- a/doc/diagrams/analysisClasses.puml
+++ b/doc/diagrams/analysisClasses.puml
@@ -7,24 +7,24 @@ package GameModule {
class GameState
class GameBoard
class GameMove
-}
-GameIO -> GameState
-GameState -> GameMove
-GameMove -> GameBoard
+ GameIO -> GameState
+ GameState -> GameMove
+ GameMove -> GameBoard
+}
package EngineModule {
class EngineIO
class EngineLogic
- interface DecisionAlgorithm
+ !include DecisionAlgorithm.pumlc
class MonteCarloTreeSearch
class OtherDecisionAlgorithm
-}
-EngineIO --> EngineLogic
-EngineLogic -> DecisionAlgorithm
-DecisionAlgorithm <|.. MonteCarloTreeSearch
-DecisionAlgorithm <|.. OtherDecisionAlgorithm
+ EngineIO --> EngineLogic
+ EngineLogic -> DecisionAlgorithm
+ DecisionAlgorithm <|.. MonteCarloTreeSearch
+ DecisionAlgorithm <|.. OtherDecisionAlgorithm
+}
MonteCarloTreeSearch --> GameMove
OtherDecisionAlgorithm --> GameMove
diff --git a/doc/diagrams/keras.puml b/doc/diagrams/keras.puml
new file mode 100644
index 0000000..98776d1
--- /dev/null
+++ b/doc/diagrams/keras.puml
@@ -0,0 +1,13 @@
+@startuml
+
+!include skinparams.puml
+
+!include DecisionAlgorithm.pumlc
+!include Keras.pumlc
+!include NeuralNetwork.pumlc
+
+DecisionAlgorithm <|.. Keras
+
+Keras -> NeuralNetwork
+
+@enduml
diff --git a/doc/diagrams/skinparams.puml b/doc/diagrams/skinparams.puml
index cde3da7..2a9e58e 100644
--- a/doc/diagrams/skinparams.puml
+++ b/doc/diagrams/skinparams.puml
@@ -1,5 +1,8 @@
@startuml
+'Old style
+skin rose
+
skinparam {
monochrome true
shadowing false
diff --git a/imago/engine/core.py b/imago/engine/core.py
index 44da3b8..09ee51d 100644
--- a/imago/engine/core.py
+++ b/imago/engine/core.py
@@ -1,18 +1,21 @@
"""Imago GTP engine"""
-from imago.engine.monteCarlo import MCTS
+from imago.engine.decisionAlgorithmFactory import DecisionAlgorithms, DecisionAlgorithmFactory
from imago.gameLogic.gameState import GameState
DEF_SIZE = 7
DEF_KOMI = 5.5
+DEF_ALGORITHM = DecisionAlgorithms.MONTECARLO
class GameEngine:
"""Plays the game of Go."""
- def __init__(self):
+ def __init__(self, decisionAlgorithmId = DEF_ALGORITHM):
self.komi = DEF_KOMI
self.gameState = GameState(DEF_SIZE)
- self.mcts = MCTS(self.gameState.lastMove)
+ self.daId = decisionAlgorithmId
+ self.daFactory = DecisionAlgorithmFactory()
+ self.da = self.daFactory.create(self.daId, self.gameState.lastMove)
def setBoardsize(self, newSize):
"""Changes the size of the board.
@@ -20,14 +23,14 @@ class GameEngine:
It is wise to call clear_board after this command.
"""
self.gameState = GameState(newSize)
- self.mcts = MCTS(self.gameState.lastMove)
+ self.da = self.da.__init__(self.gameState.lastMove)
def clearBoard(self):
"""The board is cleared, the number of captured stones reset to zero and the move
history reset to empty.
"""
self.gameState.clearBoard()
- self.mcts.clearBoard()
+ self.da.clearBoard()
def setKomi(self, komi):
"""Sets a new value of komi."""
@@ -48,11 +51,11 @@ class GameEngine:
row = vertex[0]
col = vertex[1]
self.gameState.playMoveForPlayer(row, col, color)
- self.mcts.forceNextMove(vertex)
+ self.da.forceNextMove(vertex)
def genmove(self, color):
"""Returns a list representing coordinates of the board in the form (row, col)."""
- coords = self.mcts.pickMove().coords
+ coords = self.da.pickMove().coords
#TODO: The move should NOT be played in its generation. This method is just for
#suggesting a move.
#self.gameState.playMoveForPlayer(coords[0], coords[1], color)
diff --git a/imago/engine/decisionAlgorithm.py b/imago/engine/decisionAlgorithm.py
new file mode 100644
index 0000000..67fc3ef
--- /dev/null
+++ b/imago/engine/decisionAlgorithm.py
@@ -0,0 +1,14 @@
+"""Decision algorithm interface."""
+
+class DecisionAlgorithm:
+
+ def __init__(self, move):
+ pass
+
+ def forceNextMove(self, coords):
+ """Selects given move as next move."""
+ pass
+
+ def pickMove(self):
+ """Returns a move to play."""
+ pass
diff --git a/imago/engine/decisionAlgorithmFactory.py b/imago/engine/decisionAlgorithmFactory.py
new file mode 100644
index 0000000..c40c687
--- /dev/null
+++ b/imago/engine/decisionAlgorithmFactory.py
@@ -0,0 +1,23 @@
+"""Decision algorithm factory."""
+
+from enum import Enum, auto as enumAuto
+from imago.engine.monteCarlo import MCTS
+
+class DecisionAlgorithms(Enum):
+ RANDOM = enumAuto()
+ MONTECARLO = enumAuto()
+ KERAS = enumAuto()
+
+class DecisionAlgorithmFactory:
+
+ def create(self, algorithm, move):
+ """Creates an instance of the requested algorithm."""
+
+# if algorithm == DecisionAlgorithms.RANDOM:
+# --
+
+ if algorithm == DecisionAlgorithms.MONTECARLO:
+ return MCTS(move)
+
+# if algorithm == DecisionAlgorithms.KERAS:
+# --
diff --git a/imago/engine/keras.py b/imago/engine/keras.py
new file mode 100644
index 0000000..5445d92
--- /dev/null
+++ b/imago/engine/keras.py
@@ -0,0 +1,16 @@
+"""Keras neural network module."""
+
+from imago.engine.decisionAlgorithm import DecisionAlgorithm
+
+class Keras(DecisionAlgorithm):
+
+ def __init__(self, move):
+ pass
+
+ def forceNextMove(self, coords):
+ """Selects given move as next move."""
+ pass
+
+ def pickMove(self):
+ """Returns a move to play."""
+ pass
diff --git a/imago/engine/keras/neuralNetwork.py b/imago/engine/keras/neuralNetwork.py
new file mode 100644
index 0000000..7a712f2
--- /dev/null
+++ b/imago/engine/keras/neuralNetwork.py
@@ -0,0 +1,52 @@
+"""Keras neural network."""
+
+import os.path
+
+import tensorflow as tf
+from tensorflow import keras
+from tensorflow.keras.models import Sequential
+from tensorflow.keras.layers import Activation, Dense
+from tensorflow.keras.optimizers import Adam
+from tensorflow.keras.metrics import categorical_crossentropy
+
+modelFile = 'models/medical_trial_model.h5'
+
+class NeuralNetwork:
+ def __init__(self, boardSize):
+ self.path = modelFile
+
+ def _initModel(self, modelPath, boardSize=9):
+ # Save full model
+ model = Sequential([
+ Dense(units=16, activation='relu', input_shape=(boardSize,boardSize)),
+ Dense(units=32, activation='relu'),
+ Dense(units=2, activation='softmax')
+ ])
+
+ model.summary()
+
+ model.compile(
+ optimizer=Adam(learning_rate=0.0001),
+ loss='sparse_categorical_crossentropy',
+ metrics=['accuracy']
+ )
+
+ model.fit(
+ x=scaled_train_samples,
+ y=train_labels,
+ validation_split=0.1,
+ batch_size=10,
+ epochs=30,
+ shuffle=True,
+ verbose=2
+ )
+
+ model.save(modelPath)
+
+ def _loadModel(self, modelPath):
+ # Load model
+ if os.path.isfile(modelPath):
+ from tensorflow.keras.models import load_model
+ model = load_model(modelPath)
+ else:
+ raise Exception("Keras neural network file not found at %s" % modelPath)
diff --git a/imago/engine/monteCarlo.py b/imago/engine/monteCarlo.py
index 5c916b0..3f28d8d 100644
--- a/imago/engine/monteCarlo.py
+++ b/imago/engine/monteCarlo.py
@@ -4,8 +4,9 @@ import sys
import random
from imago.data.enums import Player
+from imago.engine.decisionAlgorithm import DecisionAlgorithm
-class MCTS:
+class MCTS(DecisionAlgorithm):
"""Monte Carlo tree."""
def __init__(self, move):
@@ -15,7 +16,7 @@ class MCTS:
"""Selects given move as next move."""
for node in self.root.children:
if (node.move.getRow() == coords[0]
- and node.move.getCol() == coords[1]):
+ and node.move.getCol() == coords[1]):
self.root = node
return
self.root = self.root.expansionForCoords(coords)
@@ -108,13 +109,15 @@ class MCTSNode:
# Check if node has unexplored children and better UCB than previously explored
if len(self.unexploredVertices) > 0:
- if self.ucbForSpecificPlayer(player) > bestNode.ucbForSpecificPlayer(player):
+ if self.ucbForSpecificPlayer(
+ player) > bestNode.ucbForSpecificPlayer(player):
bestNode = self
# Recursively search children for better UCB
for child in self.children:
bestChildNode = child.selectionRec(bestNode)
- if bestChildNode.ucbForSpecificPlayer(player) > bestNode.ucbForSpecificPlayer(player):
+ if bestChildNode.ucbForSpecificPlayer(
+ player) > bestNode.ucbForSpecificPlayer(player):
bestNode = bestChildNode
return bestNode
diff --git a/imago/scripts/__init__.py b/imago/scripts/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/imago/scripts/__init__.py
diff --git a/imago/scripts/monteCarloSimulation.py b/imago/scripts/monteCarloSimulation.py
new file mode 100644
index 0000000..395a87e
--- /dev/null
+++ b/imago/scripts/monteCarloSimulation.py
@@ -0,0 +1,25 @@
+#!/usr/bin/python
+
+"""Runs MonteCarlo simulations."""
+
+import sys
+
+from imago.gameLogic.gameBoard import GameBoard
+from imago.gameLogic.gameMove import GameMove
+from imago.engine.monteCarlo import MCTSNode
+
+if __name__ == "__main__":
+
+ if len(sys.argv) != 4:
+ print("[ERROR] Usage: monteCarloSimulation.py <size> <scoreDiffHeur> <nMatches>")
+ sys.exit(1)
+
+ size = int(sys.argv[1])
+ scoreDiffHeur = int(sys.argv[2])
+ nMatches = int(sys.argv[3])
+
+ board = GameBoard(size, size)
+ move = GameMove(board)
+ node = MCTSNode(move, None)
+ node.simulation(nMatches, scoreDiffHeur)
+ print("Score: %d" % node.score)
diff --git a/imago/scripts/monteCarloSimulation.sh b/imago/scripts/monteCarloSimulation.sh
new file mode 100755
index 0000000..8aa9e68
--- /dev/null
+++ b/imago/scripts/monteCarloSimulation.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# Stores statistical data about MonteCarlo simulations
+
+outputDir="out"
+
+sampleSize=50
+nMatches=10
+
+mkdir -p "$outputDir"
+
+for size in 9 13 19; do
+ for scoreDiffHeur in 10 30 60; do
+ filename="size${size}_scoreDiffHeur${scoreDiffHeur}.dat"
+ logFile="size${size}_scoreDiffHeur${scoreDiffHeur}_fullOutput.log"
+ rm "$logFile"
+ for i in $(seq "$sampleSize"); do
+ python ./monteCarloSimulation.py "$size" "$scoreDiffHeur" "$nMatches" |
+ tee -a "${outputDir}/${logFile}" |
+ grep -E '^Score:' |
+ cut -d' ' -f2
+ done >"${outputDir}/${filename}"
+ done
+done
diff --git a/results.txt b/results.txt
new file mode 100644
index 0000000..aa1a8a3
--- /dev/null
+++ b/results.txt
@@ -0,0 +1,41 @@
+Visits: 10
+Score: 2
+Visits: 10
+Score: -4
+Visits: 10
+Score: 2
+Visits: 10
+Score: -2
+Visits: 10
+Score: -4
+Visits: 10
+Score: 0
+Visits: 10
+Score: 4
+Visits: 10
+Score: -2
+Visits: 10
+Score: 0
+Visits: 10
+Score: -4
+----------
+Visits: 50
+Score: -4
+Visits: 50
+Score: 0
+Visits: 50
+Score: -6
+Visits: 50
+Score: 0
+Visits: 50
+Score: -12
+Visits: 50
+Score: -6
+Visits: 50
+Score: 10
+Visits: 50
+Score: 0
+Visits: 50
+Score: 8
+Visits: 50
+Score: -2
diff --git a/simulationMatchesPyplot.py b/simulationMatchesPyplot.py
new file mode 100755
index 0000000..809e1a1
--- /dev/null
+++ b/simulationMatchesPyplot.py
@@ -0,0 +1,27 @@
+#!/usr/bin/python
+
+"""Uses pyplot to create graphs of the results of MonteCarlo simulations"""
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+data = np.loadtxt("out/size9_scoreDiffHeur10.dat")
+
+dic = {}
+for value in data:
+ if value in dic.keys():
+ dic[value] += 1
+ else:
+ dic[value] = 1
+
+names = list(dic.keys())
+values = list(dic.values())
+
+fig, axs = plt.subplots(1, 3, figsize=(9, 3), sharey=True)
+axs[0].bar(names, values)
+axs[1].scatter(names, values)
+axs[2].plot(names, values)
+axs[2].axis([-10, 10, 0, max(values)+2])
+fig.suptitle('Categorical Plotting')
+
+plt.show()