Merge branch 'v1.0' of github.com:sanderland/katrain into v1.0

This commit is contained in:
Sander Land committed 2020-04-26 23:40:04 +02:00
commit be05459a48
13 files changed
+52 -46

No files matched your search

+1
View File
@@ -9,6 +9,7 @@ log.txt
*.sgf *.sgf
sgfout sgfout
sgf_selfplay sgf_selfplay
sgf_ogs
log* log*
tmp.pickle tmp.pickle
my my
+6 -5
View File
@@ -56,8 +56,9 @@ def ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, GameNode
ai_thoughts += f"Top policy move has weight > {ai_settings['pick_override']:.1%}, so overriding other strategies." ai_thoughts += f"Top policy move has weight > {ai_settings['pick_override']:.1%}, so overriding other strategies."
elif top_5_pass or "weighted" in ai_mode: elif top_5_pass or "weighted" in ai_mode:
weighted_coords = [(policy_grid[y][x], policy_grid[y][x], x, y) for x in range(size[0]) for y in range(size[1]) if policy_grid[y][x] > 0] weighted_coords = [(policy_grid[y][x], policy_grid[y][x], x, y) for x in range(size[0]) for y in range(size[1]) if policy_grid[y][x] > 0]
aimove = Move(weighted_selection_without_replacement(weighted_coords, 1)[0][2:], player=cn.next_player) # just take a random move by policy w/o noise best = weighted_selection_without_replacement(weighted_coords, 1)[0]
ai_thoughts += f"Playing policy-weighted random move {aimove.gtp()}" + (" because one of them is pass." if top_5_pass else " because strategy is weighted.") aimove = Move(best[2:], player=cn.next_player) # just take a random move by policy w/o noise
ai_thoughts += f"Playing policy-weighted random move {aimove.gtp()} ({best[0]:.1%})" + (" because one of them is pass." if top_5_pass else " because strategy is weighted.")
elif "noise" in ai_mode: elif "noise" in ai_mode:
noise_str = ai_settings["noise_strength"] noise_str = ai_settings["noise_strength"]
d_noise = dirichlet_noise(len(legal_policy_moves)) d_noise = dirichlet_noise(len(legal_policy_moves))
@@ -69,9 +70,9 @@ def ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, GameNode
n_moves = int(ai_settings["pick_frac"] * len(legal_policy_moves) + ai_settings["pick_n"]) n_moves = int(ai_settings["pick_frac"] * len(legal_policy_moves) + ai_settings["pick_n"])
if "influence" in ai_mode or "territory" in ai_mode: if "influence" in ai_mode or "territory" in ai_mode:
if "influence" in ai_mode: if "influence" in ai_mode:
weight = lambda x, y: (1/ai_settings["line_weight"]) ** max(0, 3 - min(size[0] - 1 - x, x, y, size[1] - 1 - y)) weight = lambda x, y: (1 / ai_settings["line_weight"]) ** max(0, 3 - min(size[0] - 1 - x, x, y, size[1] - 1 - y))
else: else:
weight = lambda x, y:(1/ai_settings["line_weight"]) ** max(0, min(size[0] - 1 - x, x, y, size[1] - 1 - y) - 2) weight = lambda x, y: (1 / ai_settings["line_weight"]) ** max(0, min(size[0] - 1 - x, x, y, size[1] - 1 - y) - 2)
weighted_coords = [(policy_grid[y][x] * weight(x, y), weight(x, y), x, y) for x in range(size[0]) for y in range(size[1]) if policy_grid[y][x] > 0] weighted_coords = [(policy_grid[y][x] * weight(x, y), weight(x, y), x, y) for x in range(size[0]) for y in range(size[1]) if policy_grid[y][x] > 0]
ai_thoughts += f"Generated weights for {ai_mode} according to weight factor {ai_settings['line_weight']} and distance from 4th line. " ai_thoughts += f"Generated weights for {ai_mode} according to weight factor {ai_settings['line_weight']} and distance from 4th line. "
elif "local" in ai_mode or "tenuki" in ai_mode: elif "local" in ai_mode or "tenuki" in ai_mode:
@@ -120,7 +121,7 @@ def ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, GameNode
ai_thoughts += f"Balance strategy selected moves {sel_moves} based on target score and max points lost, and randomly chose {aimove.gtp()}." ai_thoughts += f"Balance strategy selected moves {sel_moves} based on target score and max points lost, and randomly chose {aimove.gtp()}."
elif "jigo" in ai_mode and candidate_ai_moves[0]["move"] != "pass": elif "jigo" in ai_mode and candidate_ai_moves[0]["move"] != "pass":
sign = cn.player_sign(cn.next_player) # TODO check sign = cn.player_sign(cn.next_player) # TODO check
jigo_move = min(candidate_ai_moves, key=lambda move: abs(sign * move["scoreLead"] - ai_settings['target_score'])) jigo_move = min(candidate_ai_moves, key=lambda move: abs(sign * move["scoreLead"] - ai_settings["target_score"]))
aimove = Move.from_gtp(jigo_move["move"], player=cn.next_player) aimove = Move.from_gtp(jigo_move["move"], player=cn.next_player)
ai_thoughts += f"Jigo strategy found candidate moves {candidate_ai_moves} moves and chose {aimove.gtp()} as closest to 0.5 point win" ai_thoughts += f"Jigo strategy found candidate moves {candidate_ai_moves} moves and chose {aimove.gtp()} as closest to 0.5 point win"
else: else:
+2 -9
View File
@@ -27,14 +27,7 @@ ENGINE_SETTINGS = {
"enable_ownership": False, "enable_ownership": False,
"threads": 1, "threads": 1,
} }
ai_settings = { ai_settings = {"noise_strength": 0.8, "pick_n": 10, "pick_frac": 0.2, "stddev": 10, "line_weight": 10, "pick_override": 0.95}
"noise_strength": 0.8,
"pick_n": 10,
"pick_frac": 0.2,
"stddev": 10,
"line_weight": 10,
"pick_override": 0.95,
}
engine = KataGoEngine(logger, ENGINE_SETTINGS) engine = KataGoEngine(logger, ENGINE_SETTINGS)
@@ -53,7 +46,7 @@ ai_settings["pick_n"] = 5 # dropping below 7k at 5/0.33
ai_strategy = "P+Weighted" ai_strategy = "P+Weighted"
ai_settings = {"pick_override": 0.95} ai_settings = {"pick_override": 0.95}
ai_strategy = "P+Local" ai_strategy = "P+Local"
ai_settings["pick_frac"] = 0.0 ai_settings["pick_frac"] = 0.0
Binary file not shown.
+1 -1
View File
@@ -80,7 +80,7 @@ class KataGoEngine:
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)
continue continue
if "error" in analysis: if "error" in analysis:
if not (next_move is None and "Illegal move" in analysis["error"]): # sweep if not (next_move and "Illegal move" in analysis["error"]): # sweep
self.katrain.log(f"{analysis} received from KataGo", OUTPUT_ERROR) self.katrain.log(f"{analysis} received from KataGo", OUTPUT_ERROR)
continue continue
else: else:
+8 -1
View File
@@ -5,6 +5,7 @@ from datetime import datetime
from typing import List, Union, Dict from typing import List, Union, Dict
import threading import threading
from common import var_to_grid
from engine import KataGoEngine from engine import KataGoEngine
from game_node import GameNode from game_node import GameNode
from sgf_parser import SGF, Move from sgf_parser import SGF, Move
@@ -224,7 +225,13 @@ class Game:
return return
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] policy_grid = var_to_grid(self.current_node.policy, size=(board_size_x, board_size_y)) if self.current_node.policy else None
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 (policy_grid is None and (x, y) not in stones) or policy_grid[y][x] >= 0
]
visits = int(engine.config["max_visits"] * self.config["sweep_visits_frac"] + 0.5) visits = int(engine.config["max_visits"] * self.config["sweep_visits_frac"] + 0.5)
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
+3 -2
View File
@@ -43,7 +43,8 @@ class GameNode(SGFNode):
def set_analysis(self, analysis_json, refine_move): def set_analysis(self, analysis_json, refine_move):
if refine_move: if refine_move:
self.update_move_analysis(analysis_json["rootInfo"], refine_move.gtp()) pvtail = analysis_json["moveInfos"][0]["pv"] if analysis_json["moveInfos"] else []
self.update_move_analysis({"pv": [refine_move.gtp()] + pvtail, **analysis_json["rootInfo"]}, refine_move.gtp())
else: else:
for move_analysis in analysis_json["moveInfos"]: for move_analysis in analysis_json["moveInfos"]:
self.update_move_analysis(move_analysis, move_analysis["move"]) self.update_move_analysis(move_analysis, move_analysis["move"])
@@ -134,7 +135,7 @@ class GameNode(SGFNode):
def policy_ranking(self) -> Optional[List[Tuple[float, Move]]]: # return moves from highest policy value to lowest def policy_ranking(self) -> Optional[List[Tuple[float, Move]]]: # return moves from highest policy value to lowest
if self.policy: if self.policy:
szx, szy = self.board_size szx, szy = self.board_size
policy_grid = var_to_grid(self.policy, size=[szx, szy]) policy_grid = var_to_grid(self.policy, size=(szx, szy))
moves = [(policy_grid[y][x], Move((x, y), player=self.next_player)) for x in range(szx) for y in range(szy)] moves = [(policy_grid[y][x], Move((x, y), player=self.next_player)) for x in range(szx) for y in range(szy)]
moves.append((self.policy[-1], Move(None, player=self.next_player))) moves.append((self.policy[-1], Move(None, player=self.next_player)))
return sorted(moves, key=lambda mp: -mp[0]) return sorted(moves, key=lambda mp: -mp[0])
+1 -1
View File
@@ -64,7 +64,7 @@ class Controls(BoxLayout):
self.win_rate.text = current_node.format_win_rate() self.win_rate.text = current_node.format_win_rate()
if move and next_player_is_human_or_both_robots: # don't immediately hide this when an ai moves comes in if move and next_player_is_human_or_both_robots: # don't immediately hide this when an ai moves comes in
points_lost = current_node.points_lost points_lost = current_node.points_lost
self.score_change.label = f"Points lost for {move.player}" if points_lost and points_lost > 0 else f"Points gained for {move.player}" self.score_change.label = f"Points lost ({move.player})" if points_lost and points_lost > 0 else f"Points gained ({move.player})"
self.score_change.text = f"{abs(points_lost):.1f}" if points_lost else "..." self.score_change.text = f"{abs(points_lost):.1f}" if points_lost else "..."
elif not current_player_is_ai_playing_human: elif not current_player_is_ai_playing_human:
self.score_change.label = f"Points lost" self.score_change.label = f"Points lost"
+3 -1
View File
@@ -24,9 +24,11 @@ class DarkLabel(Label):
class ScaledLightLabel(DarkLabel): class ScaledLightLabel(DarkLabel):
pass pass
class LightHelpLabel(ScaledLightLabel): class LightHelpLabel(ScaledLightLabel):
pass pass
class StyledButton(Button): class StyledButton(Button):
button_color = ListProperty([]) button_color = ListProperty([])
button_color_down = ListProperty([]) button_color_down = ListProperty([])
@@ -79,7 +81,7 @@ class ToggleButtonContainer(GridLayout):
def _size(self, _dt): def _size(self, _dt):
if self.autosize: if self.autosize:
for tb in self.children: for tb in self.children:
tb.size_hint = (tb.texture_size[0] + 25, 1) tb.size_hint = (tb.texture_size[0] + 10, 1)
@property @property
def value(self): def value(self):
+24 -14
View File
@@ -8,7 +8,17 @@ from kivy.uix.label import Label
from common import OUTPUT_DEBUG, OUTPUT_ERROR from common import OUTPUT_DEBUG, OUTPUT_ERROR
from engine import KataGoEngine from engine import KataGoEngine
from game import Game, GameNode from game import Game, GameNode
from gui.kivyutils import LabelledCheckBox, LabelledFloatInput, LabelledIntInput, LabelledObjectInputArea, LabelledSpinner, LabelledTextInput, ScaledLightLabel, StyledButton, LightHelpLabel from gui.kivyutils import (
LabelledCheckBox,
LabelledFloatInput,
LabelledIntInput,
LabelledObjectInputArea,
LabelledSpinner,
LabelledTextInput,
ScaledLightLabel,
StyledButton,
LightHelpLabel,
)
class InputParseError(Exception): class InputParseError(Exception):
@@ -16,7 +26,7 @@ class InputParseError(Exception):
class QuickConfigGui(BoxLayout): class QuickConfigGui(BoxLayout):
def __init__(self, katrain, popup, initial_values=None,**kwargs): def __init__(self, katrain, popup, initial_values=None, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.katrain = katrain self.katrain = katrain
self.popup = popup self.popup = popup
@@ -65,7 +75,7 @@ class LoadSGFPopup(BoxLayout):
class NewGamePopup(QuickConfigGui): class NewGamePopup(QuickConfigGui):
def __init__(self, katrain, popup, properties, **kwargs): def __init__(self, katrain, popup, properties, **kwargs):
properties["RU"] = KataGoEngine.get_rules(katrain.game.root) properties["RU"] = KataGoEngine.get_rules(katrain.game.root)
super().__init__(katrain, popup, properties,**kwargs) super().__init__(katrain, popup, properties, **kwargs)
self.rules_spinner.values = list(set(self.katrain.engine.RULESETS.values())) self.rules_spinner.values = list(set(self.katrain.engine.RULESETS.values()))
self.rules_spinner.text = properties["RU"] self.rules_spinner.text = properties["RU"]
@@ -82,11 +92,11 @@ class NewGamePopup(QuickConfigGui):
class ConfigPopup(QuickConfigGui): class ConfigPopup(QuickConfigGui):
def __init__(self, katrain, popup, config, ignore_cats,**kwargs): def __init__(self, katrain, popup, config, ignore_cats, **kwargs):
self.config = config self.config = config
self.ignore_cats = ignore_cats self.ignore_cats = ignore_cats
self.orientation = "vertical" self.orientation = "vertical"
super().__init__(katrain, popup,**kwargs) super().__init__(katrain, popup, **kwargs)
Clock.schedule_once(self._build, 0) Clock.schedule_once(self._build, 0)
def _build(self, _): def _build(self, _):
@@ -161,22 +171,22 @@ class ConfigPopup(QuickConfigGui):
class ConfigAIPopup(QuickConfigGui): class ConfigAIPopup(QuickConfigGui):
def __init__(self, katrain, popup, ai_modes, **kwargs): def __init__(self, katrain, popup, ai_modes, **kwargs):
super().__init__(katrain, popup, katrain.ai_settings, **kwargs) super().__init__(katrain, popup, katrain.ai_settings, **kwargs)
self.settings = self.katrain.ai_settings self.settings = self.katrain.ai_settings
self.ai_modes = ai_modes self.ai_modes = ai_modes
Clock.schedule_once(self._build, 0) Clock.schedule_once(self._build, 0)
self.orientation ='vertical' self.orientation = "vertical"
def _build(self,_dt): def _build(self, _dt):
colbox = BoxLayout(spacing=5) colbox = BoxLayout(spacing=5)
for mode in self.ai_modes: for mode in self.ai_modes:
mode_settings = self.settings[mode] mode_settings = self.settings[mode]
num_rows = len(mode_settings) - 2 num_rows = len(mode_settings) - 2
column = GridLayout(cols=2, rows=max(num_rows,4) + 3,spacing=1,padding=3) column = GridLayout(cols=2, rows=max(num_rows, 4) + 3, spacing=1, padding=3)
column.add_widget(ScaledLightLabel(text=f"Settings for AI")) column.add_widget(ScaledLightLabel(text=f"Settings for AI"))
column.add_widget(ScaledLightLabel(text=f"{mode}", bold=True)) column.add_widget(ScaledLightLabel(text=f"{mode}", bold=True))
column.add_widget(LightHelpLabel(size_hint=(1,3),text=mode_settings.get('_help_left',''))) column.add_widget(LightHelpLabel(size_hint=(1, 3), text=mode_settings.get("_help_left", "")))
column.add_widget(LightHelpLabel(size_hint=(1,3),text=mode_settings.get('_help_right', ''))) column.add_widget(LightHelpLabel(size_hint=(1, 3), text=mode_settings.get("_help_right", "")))
for k, v in mode_settings.items(): for k, v in mode_settings.items():
if not k.startswith("_"): if not k.startswith("_"):
column.add_widget(ScaledLightLabel(text=f"{k}")) column.add_widget(ScaledLightLabel(text=f"{k}"))
@@ -185,9 +195,9 @@ class ConfigAIPopup(QuickConfigGui):
column.add_widget(ScaledLightLabel(text=f"")) column.add_widget(ScaledLightLabel(text=f""))
column.add_widget(ScaledLightLabel(text=f"")) column.add_widget(ScaledLightLabel(text=f""))
colbox.add_widget(column) colbox.add_widget(column)
if len(self.ai_modes)==1: if len(self.ai_modes) == 1:
colbox.add_widget(ScaledLightLabel(text=f"")) colbox.add_widget(ScaledLightLabel(text=f""))
bl = BoxLayout(size_hint=(1,0.2),spacing=2) bl = BoxLayout(size_hint=(1, 0.2), spacing=2)
bl.add_widget(StyledButton(text=f"Apply", on_press=lambda _: self.update_config(False))) bl.add_widget(StyledButton(text=f"Apply", on_press=lambda _: self.update_config(False)))
bl.add_widget(StyledButton(text=f"Apply and Save", on_press=lambda _: self.update_config(True))) bl.add_widget(StyledButton(text=f"Apply and Save", on_press=lambda _: self.update_config(True)))
self.add_widget(colbox) self.add_widget(colbox)
@@ -197,7 +207,7 @@ class ConfigAIPopup(QuickConfigGui):
try: try:
for k, v in self.collect_properties(self).items(): for k, v in self.collect_properties(self).items():
k1, k2 = k.split("/") k1, k2 = k.split("/")
print(k1,k2,v,self.settings[k1][k2]) print(k1, k2, v, self.settings[k1][k2])
if self.settings[k1][k2] != v: if self.settings[k1][k2] != v:
self.katrain.log(f"Updating setting {k} = {v}", OUTPUT_DEBUG) self.katrain.log(f"Updating setting {k} = {v}", OUTPUT_DEBUG)
+1 -1
View File
@@ -36,7 +36,7 @@
<StyledSpinner>: <StyledSpinner>:
text: self.values[0] if self.values else '' text: self.values[0] if self.values else ''
font_size: self.size[1] * 0.35 font_size: self.size[1] * 0.33
sync_height: True sync_height: True
background_color: [*[c*255/88 for c in BUTTON_COLOR[:3]], 1] # compensate for texture background_color: [*[c*255/88 for c in BUTTON_COLOR[:3]], 1] # compensate for texture
option_cls: 'StyledSpinnerOption' option_cls: 'StyledSpinnerOption'
+1 -1
View File
@@ -193,7 +193,7 @@ class KaTrainGui(BoxLayout):
def _do_config_popup(self): def _do_config_popup(self):
config_popup = Popup(title="Edit Settings", size_hint=(0.9, 0.9)) config_popup = Popup(title="Edit Settings", size_hint=(0.9, 0.9))
popup_contents = ConfigPopup(self, config_popup, dict(self._config), ignore_cats=("board_ui","ai")) popup_contents = ConfigPopup(self, config_popup, dict(self._config), ignore_cats=("board_ui", "ai"))
config_popup.add_widget(popup_contents) config_popup.add_widget(popup_contents)
config_popup.open() config_popup.open()
+1 -10
View File
@@ -143,18 +143,9 @@ test_ais = [
AI("P+Local", {"local_stddev": 5}), AI("P+Local", {"local_stddev": 5}),
AI("P+Local", {"local_stddev": 1}), AI("P+Local", {"local_stddev": 1}),
AI("P+Local", {"local_stddev": 1, "pick_frac": 0.0, "pick_n": 20}), AI("P+Local", {"local_stddev": 1, "pick_frac": 0.0, "pick_n": 20}),
AI("P+Weighted", {"pick_override": 1.0}),
] ]
test_ais = [
AI("Policy", {}),
AI("P+Weighted", {'pick_override':1.0}),
AI("P+Noise", {"noise_strength": 0.6}),
AI("P+Noise", {"noise_strength": 0.7}),
AI("P+Noise", {"noise_strength": 0.8}),
AI("P+Noise", {"noise_strength": 0.9}),
]
# test_ais = [ # test_ais = [
# AI("Policy", {}), # AI("Policy", {}),