extra analysis

This commit is contained in:
Sander Land committed 2020-04-11 21:05:04 +02:00
1 parent 0a331cf294
commit 6fc6332f9d
6 files changed
+114 -23

No files matched your search

+27 -6
View File
@@ -1,6 +1,7 @@
import os
import random
from datetime import datetime
import copy
class IllegalMoveException(Exception):
@@ -13,7 +14,7 @@ class Move:
SGF_COORD = [chr(i) for i in range(97, 123)]
_move_id_counter = -1
def __init__(self, player, coords=None, gtpcoords=None, sgfcoords=None, robot=False):
def __init__(self, player=0, coords=None, gtpcoords=None, sgfcoords=None, robot=False):
Move._move_id_counter += 1
self.id = Move._move_id_counter
self.player = player
@@ -108,9 +109,12 @@ class Move:
elif not self.is_pass and self.parent.analysis[0]["move"] != self.gtp():
if sgf: # shown in stats anyway
text += f"Evaluation: {self.evaluation:.1%} efficient\n"
outdated_evaluation = self.outdated_evaluation
outdated_evaluation, outdated_details = self.outdated_evaluation
if outdated_evaluation and outdated_evaluation > self.evaluation and outdated_evaluation > self.evaluation + 0.05:
text += f"(Was considered last move as {outdated_evaluation:.0%})\n"
if outdated_evaluation > self.evaluation + 0.15:
with open("outdated_log.txt", "a") as f:
f.write(f"logs.append({repr(outdated_details)})\n")
points_lost = self.player_sign * (prev_best_score - score)
if points_lost > 0.5:
text += f"Estimated point loss: {points_lost:.1f}\n"
@@ -158,24 +162,26 @@ class Move:
prev_analysis_current_move = [d for d in self.parent.analysis if d["move"] == self.gtp()]
if prev_analysis_current_move:
best_score, worst_score, prev_temp = self.parent.temperature_stats
best_score = outdated_score(self.parent.analysis[0])
worst_score = self.parent.pass_analysis[0]["scoreLead"]
prev_temp = max(self.player_sign * (best_score - worst_score), 0)
score = outdated_score(prev_analysis_current_move[0])
return self.player_sign * (score - worst_score) / prev_temp if prev_temp > 0 else None
return (self.player_sign * (score - worst_score) / prev_temp if prev_temp > 0 else None), prev_analysis_current_move
else:
return None, None
@property
def ai_moves(self):
if not self.analysis_ready:
return []
_, worst_score, temperature = self.temperature_stats
for d in self.analysis:
analysis = copy.copy(self.analysis) # not deep, so eval is saved, but avoids race conditions
for d in analysis:
if temperature > 0.5:
d["evaluation"] = -self.player_sign * (d["scoreLead"] - worst_score) / temperature
else:
d["evaluation"] = int(-self.player_sign * d["scoreLead"] >= -self.player_sign * self.analysis[0]["scoreLead"])
return self.analysis
return analysis
# various output and conversion functions
@staticmethod
@@ -335,6 +341,21 @@ class Board:
return 1 - self.current_move.player
def store_analysis(self, json):
if json["id"].startswith("AA:"): # board sweep analyze all
_, move_id, gtpcoords = json["id"].split(":")
move = self.all_moves.get(int(move_id))
if not move.analysis:
return # should have been prevented, but better not to crash
cur_analysis = [d for d in move.analysis if d["move"] == gtpcoords]
move_analysis = {k: v for k, v in json["moveInfos"][0].items() if k not in {"move", "pv"}}
move_analysis["visits"] = sum(d["visits"] for d in json["moveInfos"]) # TODO: ??
if cur_analysis:
if cur_analysis[0]["visits"] < move_analysis["visits"]:
cur_analysis[0].update(move_analysis)
else:
move.analysis.append({"move": gtpcoords, **move_analysis})
return
if json["id"].startswith("PASS_"):
move_id = int(json["id"].lstrip("PASS_"))
is_pass = True
+3 -1
View File
@@ -3,7 +3,9 @@
"pass_visits": 100,
"pass_visits_fast": 25,
"visits": 2000,
"visits_fast": 500
"visits_fast": 500,
"analyze_all_visits": 500,
"analyze_all_visits_fast": 100
},
"board": {
"size": 19,
+36 -13
View File
@@ -37,11 +37,15 @@ class EngineControls(GridLayout):
self.command = shlex.split(self.command)
analysis_settings = Config.get("analysis")
self.visits = [[analysis_settings["pass_visits"], analysis_settings["visits"]], [analysis_settings["pass_visits_fast"], analysis_settings["visits_fast"]]]
self.visits = [
[analysis_settings["pass_visits"], analysis_settings["visits"], analysis_settings["analyze_all_visits"]],
[analysis_settings["pass_visits_fast"], analysis_settings["visits_fast"], analysis_settings["analyze_all_visits_fast"]],
]
self.train_settings = Config.get("trainer")
self.debug = Config.get("debug")["level"]
self.board_size = Config.get("board")["size"]
self.ready = False
self.ai_thinking = False
self.message_queue = None
self.board = Board(self.board_size)
self.komi = 6.5 # loaded from config in init
@@ -162,7 +166,9 @@ class EngineControls(GridLayout):
ts = self.train_settings
while not self.board.current_move.analysis_ready:
self.info.text = "Thinking..."
self.ai_thinking = True
time.sleep(0.05)
self.ai_thinking = False
# select move
current_move = self.board.current_move
pos_moves = [
@@ -233,6 +239,32 @@ class EngineControls(GridLayout):
self.show_error(f"could not decode file contents of {file}")
return ""
def _do_analyze_extra(self, sweep):
stones = {s.coords for s in self.board.stones}
current_move = self.board.current_move
if not current_move.analysis:
self.info.text = "Wait for initial analysis to complete before doing a board-sweep or refinement"
return
played_moves = self.board.moves
if sweep:
analyze_moves = [Move(coords=(x, y)).gtp() for x in range(self.board_size) for y in range(self.board_size) if (x, y) not in stones]
visits = self.visits[self.ai_fast.active][2]
self.info.text = f"Refining analysis of entire board to {visits} visits"
else:
analyze_moves = [a["move"] for a in current_move.analysis]
visits = current_move.analysis[0]["visits"] + self.visits[1][2]
self.info.text = f"Refining analysis of candidate moves to {visits} visits"
for gtpcoords in analyze_moves:
self._send_analysis_query(
{
"id": f"AA:{current_move.id}:{gtpcoords}",
"moves": [[m.bw_player(), m.gtp()] for m in played_moves] + [[current_move.bw_player(True), gtpcoords]],
"includeOwnership": False,
"maxVisits": visits,
}
)
def _do_analyze_sgf(self, sgf, faster=False, rewind=False):
sgfprops = {k: v.strip("[]").split("][") if k in ["AB", "AW"] else v.strip("[]") for k, v in re.findall(r"\b(\w+)((?:\[.*?\])+)", sgf)}
size = int(sgfprops.get("SZ", self.board_size))
@@ -292,6 +324,7 @@ class EngineControls(GridLayout):
if self.debug:
print(f"[{time.time()-self.query_time.get(analysis['id'],0):.1f}] kata analysis received:", line[:80], "...")
if "error" in analysis:
if "AA" not in analysis["id"]: # silently drop illegal moves from analysis all
print(analysis)
self.show_error(f"ERROR IN KATA ANALYSIS: {analysis['error']}")
else:
@@ -300,6 +333,7 @@ class EngineControls(GridLayout):
def _send_analysis_query(self, query):
self.query_time[query["id"]] = time.time()
query = {"rules": "japanese", "komi": self.komi, "boardXSize": self.board_size, "boardYSize": self.board_size, "analyzeTurns": [len(query["moves"])], **query}
if self.kata:
self.kata.stdin.write((json.dumps(query) + "\n").encode())
self.kata.stdin.flush()
@@ -311,23 +345,12 @@ class EngineControls(GridLayout):
move_id = move.id
moves = self.board.moves
fast = self.ai_fast.active
query = {
"id": str(move_id),
"moves": [[m.bw_player(), m.gtp()] for m in moves],
"rules": "japanese",
"komi": self.komi,
"boardXSize": self.board_size,
"boardYSize": self.board_size,
"analyzeTurns": [len(moves)],
"includeOwnership": True,
"maxVisits": self.visits[fast][1] // faster_fac,
}
query = {"id": str(move_id), "moves": [[m.bw_player(), m.gtp()] for m in moves], "includeOwnership": True, "maxVisits": self.visits[fast][1] // faster_fac}
if self.debug:
print(f"sending query for move {move_id}: {str(query)[:80]}")
self._send_analysis_query(query)
query.update({"id": f"PASS_{move_id}", "maxVisits": self.visits[fast][0] // faster_fac, "includeOwnership": False})
query["moves"] += [[move.bw_player(next_move=True), "pass"]]
query["analyzeTurns"][0] += 1
self._send_analysis_query(query)
def output_sgf(self):
+4
View File
@@ -38,6 +38,7 @@
<CheckBoxHint>
orientation: 'vertical'
checkbox: checkbox
label: label
text: ''
default_active: False
position: (0,0)
@@ -46,6 +47,7 @@
font_size: root.height * 0.2 if root.orientation=='vertical' else root.height
text: root.text
on_press: if not checkbox.disabled: checkbox._do_press()
id: label
CheckBox:
size_hint: 1,0.55 if root.orientation=='vertical' else (0.2,1)
id: checkbox
@@ -55,11 +57,13 @@
<BWCheckBoxHint>
black: black
white: white
label: label
orientation: 'vertical'
text: ''
default_active: False
CheckBoxHintLabel:
size_hint: 1,0.2
id: label
text: root.text
font_size: self.height
on_press: if not white.disabled: white._do_press(); black._do_press()
+31 -3
View File
@@ -169,11 +169,18 @@ class BadukPanWidget(Widget):
# hints
if self.engine.hints.active(current_player):
for i, d in enumerate(last_move.ai_moves):
move = Move(gtpcoords=d["move"], player=0)
hint_moves = last_move.ai_moves
for i, d in enumerate(hint_moves):
move = Move(gtpcoords=d["move"])
c = [*self._eval_spectrum(d["evaluation"]), 0.5]
if move.coords[0] is not None and move.coords not in undo_coords:
self.draw_stone(move.coords[0], move.coords[1], c, scale=1.0 if i == 0 else 0.8)
if i == 0:
scale = 1.0
elif d["visits"] < 0.1 * hint_moves[0]["visits"]: # TODO: config?
scale = 0.6 # TODO: config?
else:
scale = 0.85
self.draw_stone(move.coords[0], move.coords[1], c, scale=scale)
# hover next move ghost stone
if self.ghost_stone:
@@ -208,6 +215,27 @@ class KaTrainGui(BoxLayout):
self.controls.action("redo-branch", 1)
elif keycode[1] == "left":
self.controls.action("redo-branch", -1)
elif keycode[1] == "s":
self.controls.action("analyze-extra", True)
elif keycode[1] == "r":
self.controls.action("analyze-extra", False)
elif keycode[1] == "a":
if not self.controls.ai_thinking:
self.controls.ai_move.trigger_action(duration=0)
elif keycode[1] == "p":# TODO: clean repetitive shortcuts
self.controls.play.trigger_action(duration=0)
elif keycode[1] == "f":
self.controls.ai_fast.label.trigger_action(duration=0)
elif keycode[1] == "h":
self.controls.hints.label.trigger_action(duration=0)
elif keycode[1] == "e":
self.controls.eval.label.trigger_action(duration=0)
elif keycode[1] == "u":
self.controls.auto_undo.label.trigger_action(duration=0)
elif keycode[1] == "b":
self.controls.ai_balance.label.trigger_action(duration=0)
elif keycode[1] == "o":
self.controls.ownership.label.trigger_action(duration=0)
return True
+13
View File
@@ -0,0 +1,13 @@
logs.append([{'lcb': 0.9866929576961049, 'move': 'Q13', 'order': 2, 'prior': 0.11910770833492279, 'pv': ['Q13', 'R13', 'S12', 'J1', 'M1', 'G16'], 'scoreLead': 124.47271696460673, 'scoreMean': 126.27482461531041, 'scoreSelfplay': 122.06720914951835, 'scoreStdev': 27.614507025114275, 'utility': 1.1346133945628056, 'utilityLcb': 1.098224634752818, 'visits': 68, 'winrate': 0.9996889433425291, 'outdatedScoreLead': 127.91250052175701, 'evaluation': 0.6566815927216013}])
logs.append([{'lcb': 0.23000714901044894, 'move': 'A7', 'order': 2, 'prior': 0.0939372107386589, 'pv': ['A7', 'B4', 'B3', 'B7', 'G12', 'G10', 'H10', 'G9'], 'scoreLead': -9.121776796888566, 'scoreMean': -4.895108362277802, 'scoreSelfplay': -5.2100667634473705, 'scoreStdev': 21.56650224292897, 'utility': -0.4353121358045132, 'utilityLcb': -0.6303358573690578, 'visits': 97, 'winrate': 0.2996584781406435, 'evaluation': 0.9730087446544682, 'outdatedScoreLead': -4.895108362277802}])
logs.append([{'lcb': 0.48324174996112224, 'move': 'F10', 'order': 2, 'prior': 0.12889033555984497, 'pv': ['F10', 'B9', 'A10', 'A9', 'B9', 'B7', 'H17'], 'scoreLead': 0.7020370412502724, 'scoreMean': 3.1918735507436278, 'scoreSelfplay': 3.8505667580252867, 'scoreStdev': 22.209495140940156, 'utility': 0.18945409233268506, 'utilityLcb': -0.12664315194471099, 'visits': 33, 'winrate': 0.5961336229173351, 'evaluation': 0.9857626433435042, 'outdatedScoreLead': 3.1918735507436278}])
logs.append([{'lcb': 1.2961580404924993, 'move': 'B8', 'order': 4, 'prior': 0.18784967064857483, 'pv': ['B8', 'D10', 'D11', 'G12', 'C14', 'B12'], 'scoreLead': 39.980447187304534, 'scoreMean': 16.300339300165696, 'scoreSelfplay': 16.75147660635949, 'scoreStdev': 27.816062826637406, 'utility': 0.6706339164931426, 'utilityLcb': 2.134771010104406, 'visits': 11, 'winrate': 0.7732519356313338, 'evaluation': 0.465537391117901, 'outdatedScoreLead': 16.300339300165696}])
logs.append([{'lcb': -0.9128409039094549, 'move': 'F6', 'order': 7, 'prior': 0.006048424169421196, 'pv': ['F6', 'E6'], 'scoreLead': -16.519556878825906, 'scoreMean': -14.270362377166748, 'scoreSelfplay': -14.119140625, 'scoreStdev': 19.22452345556336, 'utility': -0.8780453309766154, 'utilityLcb': -2.8000000000000003, 'visits': 2, 'winrate': 0.08715909609054506, 'evaluation': 1.0506796226282822, 'outdatedScoreLead': -14.270362377166748}])
logs.append([{'lcb': -0.11179767394897855, 'move': 'C8', 'order': 4, 'prior': 0.029647205024957657, 'pv': ['C8', 'A9', 'A10', 'A8', 'A7', 'B7', 'B9'], 'scoreLead': -27.91700043151085, 'scoreMean': -21.906844603223075, 'scoreSelfplay': -21.44171085687105, 'scoreStdev': 22.73403649360621, 'utility': -0.9781789774390067, 'utilityLcb': -1.4388028521708551, 'visits': 10, 'winrate': 0.05271085274096721, 'evaluation': 0.925279362535498, 'outdatedScoreLead': -21.906844603223075}])
logs.append([{'lcb': 0.004716111156360503, 'move': 'A8', 'order': 1, 'prior': 0.26070424914360046, 'pv': ['A8', 'A10', 'A6', 'B9', 'A9', 'C8', 'B9', 'C14'], 'scoreLead': -29.377870307329598, 'scoreMean': -25.069371462858335, 'scoreSelfplay': -24.13833752712488, 'scoreStdev': 19.46618456676441, 'utility': -1.0407429104348054, 'utilityLcb': -1.140719563219286, 'visits': 112, 'winrate': 0.0404220585793893, 'evaluation': 0.6556051615755801, 'outdatedScoreLead': -25.069371462858335}])
logs.append([{'lcb': 0.14960351964179275, 'move': 'C8', 'order': 2, 'prior': 0.29037389159202576, 'pv': ['C8', 'B9', 'A10', 'A3', 'A6', 'A4', 'A11', 'A9'], 'scoreLead': -13.234832497594427, 'scoreMean': -19.963710264718976, 'scoreSelfplay': -18.960616476623688, 'scoreStdev': 20.125619040929173, 'utility': -0.8869855532624162, 'utilityLcb': -0.710554899867088, 'visits': 64, 'winrate': 0.0865925720006041, 'evaluation': 0.7800065883824968, 'outdatedScoreLead': -19.963710264718976}])
logs.append([{'lcb': 0.2780396932369631, 'move': 'C14', 'order': 2, 'prior': 0.11985918134450912, 'pv': ['C14', 'A3', 'A6', 'A4', 'A10', 'G8', 'B12', 'F5', 'A11'], 'scoreLead': -6.718795563515677, 'scoreMean': -15.893474225710637, 'scoreSelfplay': -15.203650210809267, 'scoreStdev': 22.044457012561093, 'utility': -0.6851691288815772, 'utilityLcb': -0.422485576723899, 'visits': 122, 'winrate': 0.18422413889493516, 'evaluation': 1.0768651808168639, 'outdatedScoreLead': -15.893474225710637}])
logs.append([{'lcb': -0.04419652907096561, 'move': 'A6', 'order': 1, 'prior': 0.25112441182136536, 'pv': ['A6', 'B2', 'C2', 'A3', 'B1', 'A2', 'C1', 'D10'], 'scoreLead': -30.647512138007034, 'scoreMean': -22.862077390041346, 'scoreSelfplay': -21.523760459148246, 'scoreStdev': 22.10661200254798, 'utility': -0.8707647010387976, 'utilityLcb': -1.2786073140053726, 'visits': 35, 'winrate': 0.10146154698852561, 'evaluation': 0.5179365909169477, 'outdatedScoreLead': -22.862077390041346}])
logs.append([{'lcb': -0.04419652907096561, 'move': 'A6', 'order': 1, 'prior': 0.25112441182136536, 'pv': ['A6', 'B2', 'C2', 'A3', 'B1', 'A2', 'C1', 'D10'], 'scoreLead': -30.647512138007034, 'scoreMean': -22.862077390041346, 'scoreSelfplay': -21.523760459148246, 'scoreStdev': 22.10661200254798, 'utility': -0.8707647010387976, 'utilityLcb': -1.2786073140053726, 'visits': 35, 'winrate': 0.10146154698852561, 'evaluation': 0.2362162847661815, 'outdatedScoreLead': -22.862077390041346}])
logs.append([{'lcb': 1.054309982706412, 'move': 'A11', 'order': 7, 'prior': 0.2366078794002533, 'pv': ['A11', 'A4', 'R9', 'D5', 'Q10'], 'scoreLead': 6.910397638811527, 'scoreMean': 4.639930709907449, 'scoreSelfplay': 5.535607733520525, 'scoreStdev': 15.874905556161806, 'utility': 0.6591456525404961, 'utilityLcb': 1.379769560744805, 'visits': 12, 'winrate': 0.7969443012048731, 'evaluation': 0.28741264032782, 'outdatedScoreLead': 4.639930709907449}])
logs.append([{'lcb': 1.006487464714684, 'move': 'H11', 'order': 2, 'prior': 0.2041485607624054, 'pv': ['H11', 'G10', 'H7', 'H1', 'G2', 'K2'], 'scoreLead': 52.47195084132946, 'scoreMean': 46.81754482750254, 'scoreSelfplay': 46.92620595873263, 'scoreStdev': 27.67551678483527, 'utility': 1.067884055424014, 'utilityLcb': 1.1097375772381621, 'visits': 24, 'winrate': 0.9915397783524882, 'outdatedScoreLead': 45.208157412185365}])