This commit is contained in:
Sander Land committed 2020-04-21 23:28:36 +02:00
1 parent 8171a6d5fd
commit 39556ad690
9 files changed
+64 -50

No files matched your search

+2 -2
View File
@@ -1,7 +1,7 @@
# additions # additions
KataGoData KataGoData
KataGo/KataGoData KataGo/KataGoData
old experiments
.idea .idea
gtp.log gtp.log
*.zip *.zip
@@ -14,7 +14,7 @@ my
outdated_log.txt outdated_log.txt
# too big # too big
models/b20*.gz models/*.bin.gz
# standard python ignore # standard python ignore
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
+9 -5
View File
@@ -1,14 +1,18 @@
[x] Player button to black/white with AI style spinner [x] Player button to black/white with AI style spinner
[] Multi engine (detect all req. analysis in analysis[engine] fn) [x Non square boards
[] Non square boards
[] Polish graph
[x] remove fast -> in settings? [x] remove fast -> in settings?
[x] Polish graph
[] Multi engine (detect all req. analysis in analysis[engine] fn)
[] Graph is awkward when reviewing. Should we store nodes rather than values? & populate on new game.
[] Score instead of game end
[] Self-play tournaments?
[] List edit settings/object edit settings? [] List edit settings/object edit settings?
[] README [] README
[] Release notes [] Release notes
[] remove button disable code?
[] BoxLayout padding/spacing use instead of fiddling?
+2 -1
View File
@@ -15,7 +15,8 @@
"init_size": 19, "init_size": 19,
"komi_19": 6.5, "komi_19": 6.5,
"komi_13": 6.5, "komi_13": 6.5,
"komi_9": 6.5 "komi_9": 6.5,
"sweep_visits_frac": 0.2
}, },
"trainer": { "trainer": {
"num_undo_prompts": [1,1,1,0.5,0], "num_undo_prompts": [1,1,1,0.5,0],
+11 -9
View File
@@ -78,18 +78,24 @@ class KataGoEngine:
else: else:
self.katrain.log(f"Query result {analysis['id']} discarded -- recent new game?", OUTPUT_DEBUG) self.katrain.log(f"Query result {analysis['id']} discarded -- recent new game?", OUTPUT_DEBUG)
def request_analysis(self, analysis_node: GameNode, callback: Callable, visits: int = None, priority: int = 0, ownership: Optional[bool] = None, next_move=None): def send_query(self, query, callback):
query_id = f"QUERY:{str(self.query_counter)}"
self.query_counter += 1 self.query_counter += 1
if "id" not in query:
query["id"] = f"QUERY:{str(self.query_counter)}"
self.queries[query["id"]] = (callback, time.time())
if self.katago_process:
self.katrain.log(f"Sending query {query['id']}: {str(query)}", OUTPUT_DEBUG)
self.katago_process.stdin.write((json.dumps(query) + "\n").encode())
self.katago_process.stdin.flush()
def request_analysis(self, analysis_node: GameNode, callback: Callable, visits: int = None, priority: int = 0, ownership: Optional[bool] = None, next_move=None):
moves = [m for node in analysis_node.nodes_from_root for m in node.move_with_placements] moves = [m for node in analysis_node.nodes_from_root for m in node.move_with_placements]
if next_move: if next_move:
moves.append(next_move) moves.append(next_move)
if ownership is None: if ownership is None:
ownership = self.config["enable_ownership"] and not next_move ownership = self.config["enable_ownership"] and not next_move
size_x, size_y = analysis_node.board_size size_x, size_y = analysis_node.board_size
query = { query = {
"id": query_id,
"rules": self.get_rules(analysis_node), "rules": self.get_rules(analysis_node),
"priority": self.base_priority + priority, "priority": self.base_priority + priority,
"analyzeTurns": [len(moves)], "analyzeTurns": [len(moves)],
@@ -101,8 +107,4 @@ class KataGoEngine:
"includePolicy": not next_move, "includePolicy": not next_move,
"moves": [[m.player, m.gtp()] for m in moves], "moves": [[m.player, m.gtp()] for m in moves],
} }
self.queries[query_id] = (callback, time.time()) self.send_query(query, callback)
if self.katago_process:
self.katrain.log(f"Sending query {query_id}: {str(query)}", OUTPUT_DEBUG)
self.katago_process.stdin.write((json.dumps(query) + "\n").encode())
self.katago_process.stdin.flush()
+3 -3
View File
@@ -281,13 +281,13 @@ class Game:
elif mode == "sweep": elif mode == "sweep":
board_size_x, board_size_y = self.board_size board_size_x, board_size_y = self.board_size
analyze_moves = [Move(coords=(x, y), player=cn.next_player) for x in range(board_size_x) for y in range(board_size_y) if (x, y) not in stones] analyze_moves = [Move(coords=(x, y), player=cn.next_player) for x in range(board_size_x) for y in range(board_size_y) if (x, y) not in stones]
visits = self.engine.config["visits_fast"] visits = self.engine.config["visits"] * self.config["sweep_visits_frac"]
self.katrain.controls.set_status(f"Refining analysis of entire board to {visits} visits") self.katrain.controls.set_status(f"Refining analysis of entire board to {visits} visits")
priority = -1_000_000_000 priority = -1_000_000_000
else: # mode=='refine': else: # mode=='refine':
analyze_moves = [Move.from_gtp(gtp, player=cn.next_player) for gtp, _ in cn.analysis["moves"].items()] analyze_moves = [Move.from_gtp(gtp, player=cn.next_player) for gtp, _ in cn.analysis["moves"].items()]
visits = max(d["visits"] for d in cn.analysis["moves"].values()) + self.engine.config["visits_fast"] visits = max(d["visits"] for d in cn.analysis["moves"].values())
self.katrain.controls.set_status(f"Refining analysis of candidate moves to {visits} visits") self.katrain.controls.set_status(f"Equalizing analysis of candidate moves to {visits} visits")
priority = -1_000 priority = -1_000
for move in analyze_moves: for move in analyze_moves:
cn.analyze(self.engine, priority, visits=visits, refine_move=move) cn.analyze(self.engine, priority, visits=visits, refine_move=move)
+7 -2
View File
@@ -75,12 +75,17 @@ class GameNode(SGFNode):
if self.parent and self.parent.analysis_ready: if self.parent and self.parent.analysis_ready:
previous_top_move = self.parent.candidate_moves[0] previous_top_move = self.parent.candidate_moves[0]
if sgf or hints and previous_top_move["move"] != single_move.gtp(): # TODO: when to include? if sgf or hints and previous_top_move["move"] != single_move.gtp(): # TODO: when to include?
text += f"Top move was {previous_top_move['move']} ({self.format_score(previous_top_move['scoreLead'])})\n" text += f"Predicted top move was {previous_top_move['move']} ({self.format_score(previous_top_move['scoreLead'])})\n"
points_lost = self.points_lost points_lost = self.points_lost
if sgf and points_lost > 0.5: if sgf and points_lost > 0.5:
text += f"Estimated point loss: {points_lost:.1f}\n" text += f"Estimated point loss: {points_lost:.1f}\n"
if sgf or hints: if sgf or hints:
text += f"Top policy move was {self.parent.policy_ranking[0][0].gtp()}\n" policy_ranking = self.parent.policy_ranking
policy_ix = [ix+1 for (m,p),ix in zip(policy_ranking,range(len(policy_ranking))) if m==single_move]
if not policy_ix or policy_ix[0]!=1:
text += f"Top policy move was {policy_ranking[0][0].gtp()}\n"
if policy_ix:
text += f"Your move was #{policy_ix} according to NN policy\n"
if self.auto_undo: if self.auto_undo:
text += "Move was automatically undone." text += "Move was automatically undone."
else: else:
+1 -1
View File
@@ -69,6 +69,6 @@ class Controls(BoxLayout):
self.show_evaluation_stats(current_node) self.show_evaluation_stats(current_node)
if current_node.score: if current_node.score:
self.graph.update_value(current_node.depth, current_node.score) self.graph.update_value(current_node)
self.info.text = info self.info.text = info
+15 -19
View File
@@ -19,17 +19,6 @@ from kivy.uix.spinner import Spinner
from kivy.uix.textinput import TextInput from kivy.uix.textinput import TextInput
class CheckBoxHint(BoxLayout):
__events__ = ("on_active",)
@property
def active(self):
return self.checkbox.active
def on_active(self, *args):
pass
class DarkLabel(Label): class DarkLabel(Label):
pass pass
@@ -173,7 +162,7 @@ class CensorableLabel(BoxLayout):
class ScoreGraph(Label): class ScoreGraph(Label):
values = ListProperty([]) nodes = ListProperty([])
line_points = ListProperty([]) line_points = ListProperty([])
dot_pos = ListProperty([0, 0]) dot_pos = ListProperty([0, 0])
highlighted_index = NumericProperty(None) highlighted_index = NumericProperty(None)
@@ -182,12 +171,18 @@ class ScoreGraph(Label):
super().__init__(**kwargs) super().__init__(**kwargs)
Clock.schedule_once(self.on_size, 0) Clock.schedule_once(self.on_size, 0)
def clear(self): def initialize_from_game(self, root):
self.values = [] self.nodes = [root]
node = root
while node.children:
node = node.children[0]
self.nodes.append(node)
self.highlighted_index = 0
def on_size(self, *args): def on_size(self, *args):
values = self.values nodes = self.nodes
if values: if nodes:
values = [n.score if n and n.score else 0 for n in nodes]
val_range = min(values or [0]), max(values or [0]) val_range = min(values or [0]), max(values or [0])
scale = math.ceil(max(3, max(-val_range[0], val_range[1]) * 1.05)) scale = math.ceil(max(3, max(-val_range[0], val_range[1]) * 1.05))
@@ -202,9 +197,10 @@ class ScoreGraph(Label):
self.highlighted_index = min(self.highlighted_index, len(values) - 1) self.highlighted_index = min(self.highlighted_index, len(values) - 1)
self.dot_pos = [c - self.highlight_size / 2 for c in line_points[self.highlighted_index]] self.dot_pos = [c - self.highlight_size / 2 for c in line_points[self.highlighted_index]]
def update_value(self, index, value): def update_value(self, node):
self.values.extend([0] * max(0, index - (len(self.values) - 1))) index = node.depth
self.values[index] = value or 0 self.nodes.extend([None] * max(0, index - (len(self.nodes) - 1)))
self.nodes[index] = node
self.highlighted_index = index self.highlighted_index = index
self.on_size() self.on_size()
+14 -8
View File
@@ -19,6 +19,7 @@ from engine import KataGoEngine
from game import Game, IllegalMoveException, KaTrainSGF, Move from game import Game, IllegalMoveException, KaTrainSGF, Move
from gui import * from gui import *
from gui.popups import ConfigPopup, NewGamePopup from gui.popups import ConfigPopup, NewGamePopup
from sgf_parser import ParseError
class KaTrainGui(BoxLayout): class KaTrainGui(BoxLayout):
@@ -50,7 +51,7 @@ class KaTrainGui(BoxLayout):
config_file = sys.argv[1] if len(sys.argv) > 1 else os.path.join(base_path, "config.json") config_file = sys.argv[1] if len(sys.argv) > 1 else os.path.join(base_path, "config.json")
try: try:
self.log(f"Using config file {config_file}", OUTPUT_INFO) self.log(f"Using config file {config_file}", OUTPUT_INFO)
self._config_store = JsonStore(config_file) self._config_store = JsonStore(config_file,indent=4)
self._config = dict(self._config_store) self._config = dict(self._config_store)
except Exception as e: except Exception as e:
self.log(f"Failed to load config {config_file}: {e}", OUTPUT_ERROR) self.log(f"Failed to load config {config_file}: {e}", OUTPUT_ERROR)
@@ -125,7 +126,7 @@ class KaTrainGui(BoxLayout):
def _do_new_game(self, move_tree=None): def _do_new_game(self, move_tree=None):
self.game = Game(self, self.engine, self.config("game"), move_tree=move_tree) self.game = Game(self, self.engine, self.config("game"), move_tree=move_tree)
self.controls.select_mode("analyze" if move_tree and len(move_tree.nodes_in_tree) > 1 else "play") self.controls.select_mode("analyze" if move_tree and len(move_tree.nodes_in_tree) > 1 else "play")
self.controls.graph.clear() self.controls.graph.initialize_from_game(self.game.root)
self.update_state(redraw_board=True) # TODO: just board here/redraw is in all anyway? self.update_state(redraw_board=True) # TODO: just board here/redraw is in all anyway?
def _do_ai_move(self, node=None): def _do_ai_move(self, node=None):
@@ -163,7 +164,12 @@ class KaTrainGui(BoxLayout):
def readfile(files, _mouse): def readfile(files, _mouse):
fileselect_popup.dismiss() fileselect_popup.dismiss()
self._do_new_game(self, move_tree=KaTrainSGF.parse_file(files[0])) try:
move_tree = KaTrainSGF.parse_file(files[0])
except ParseError as e:
self.log(f"Failed to load SGF. Parse Error: {e}", OUTPUT_ERROR)
return
self._do_new_game(move_tree=move_tree)
popup_contents.filesel.on_submit = readfile popup_contents.filesel.on_submit = readfile
fileselect_popup.open() fileselect_popup.open()
@@ -184,7 +190,7 @@ class KaTrainGui(BoxLayout):
for pl in Move.PLAYERS: for pl in Move.PLAYERS:
if not self.game.root.get_first(f"P{pl}"): if not self.game.root.get_first(f"P{pl}"):
_, model_file = os.path.split(self.engine.config["model"]) _, model_file = os.path.split(self.engine.config["model"])
self.game.root.properties[f"P{pl}"] = [f"KaTrain (KataGo {model_file})" if self.controls.ai_auto.active(pl) else "Player"] self.game.root.properties[f"P{pl}"] = [f"KaTrain (KataGo {model_file})" if 'ai' in self.controls.player_mode(pl) else "Player"]
msg = self.game.write_sgf(self.config("files/sgf_save")) msg = self.game.write_sgf(self.config("files/sgf_save"))
self.log(msg, OUTPUT_INFO) self.log(msg, OUTPUT_INFO)
self.controls.set_status(msg) self.controls.set_status(msg)
@@ -194,10 +200,10 @@ class KaTrainGui(BoxLayout):
return # if in new game or load, don't allow keyboard shortcuts return # if in new game or load, don't allow keyboard shortcuts
shortcuts = { shortcuts = {
"u": self.controls.eval.checkbox, "u": self.controls.eval,
"i": self.controls.hints.checkbox, "i": self.controls.hints,
"p": self.controls.policy.checkbox, "p": self.controls.policy,
"o": self.controls.ownership.checkbox, "o": self.controls.ownership,
"a": ("ai-move",), "a": ("ai-move",),
"right": ("switch-branch", 1), "right": ("switch-branch", 1),
"left": ("switch-branch", -1), "left": ("switch-branch", -1),