def sgf_properties(self, save_comments_player=None, save_comments_class=None, eval_thresholds=None, save_analysis=False): properties = copy.copy(super().sgf_properties()) note = self.note.strip() if save_analysis and self.analysis_complete: try: properties["KT"] = analysis_dumps(self.analysis) except Exception as e: print(f"Error in saving analysis: {e}") if self.points_lost and save_comments_class is not None and eval_thresholds is not None: show_class = save_comments_class[evaluation_class( self.points_lost, eval_thresholds)] else: show_class = False comments = properties.get("C", []) if (self.parent and self.parent.analysis_exists and self.analysis_exists and (note or ((save_comments_player or {}).get(self.player, False) and show_class))): candidate_moves = self.parent.candidate_moves top_x = Move.from_gtp(candidate_moves[0]["move"]).sgf( self.board_size) best_sq = [ Move.from_gtp(d["move"]).sgf(self.board_size) for d in candidate_moves[1:] if d["pointsLost"] <= 0.5 ] if best_sq and "SQ" not in properties: properties["SQ"] = best_sq if top_x and "MA" not in properties: properties["MA"] = [top_x] comments.append( self.comment(sgf=True, interactive=False) + SGF_INTERNAL_COMMENTS_MARKER) if self.is_root: comments = [ i18n._("SGF start message") + SGF_INTERNAL_COMMENTS_MARKER + "\n", *comments, f"\nSGF generated by {PROGRAM_NAME} {VERSION}{SGF_INTERNAL_COMMENTS_MARKER}\n", ] properties["CA"] = ["UTF-8"] properties["AP"] = [f"{PROGRAM_NAME}:{VERSION}"] if self.shortcut_from: properties["KTSF"] = [id(self.shortcut_from)] elif "KTSF" in properties: del properties["KTSF"] if self.shortcuts_to: properties["KTSID"] = [id(self)] elif "KTSID" in properties: del properties["KTSID"] if note: comments.append(f"{self.note}") if comments: properties["C"] = [SGF_SEPARATOR_MARKER.join(comments)] elif "C" in properties: del properties["C"] return properties
def eval_color( self, points_lost, show_dots_for_class: List[bool] = None) -> Optional[List[float]]: i = evaluation_class(points_lost, self.trainer_config["eval_thresholds"]) if show_dots_for_class is None or show_dots_for_class[i]: return EVAL_COLORS[i]
def sgf_properties(self, save_comments_player=None, save_comments_class=None, eval_thresholds=None): properties = copy.copy(super().sgf_properties()) note = self.note.strip() if self.points_lost and save_comments_class is not None and eval_thresholds is not None: show_class = save_comments_class[evaluation_class( self.points_lost, eval_thresholds)] else: show_class = False if (self.parent and self.parent.analysis_ready and self.analysis_ready and (note or ((save_comments_player or {}).get(self.player, False) and show_class))): candidate_moves = self.parent.candidate_moves top_x = Move.from_gtp(candidate_moves[0]["move"]).sgf( self.board_size) best_sq = [ Move.from_gtp(d["move"]).sgf(self.board_size) for d in candidate_moves[1:] if d["pointsLost"] <= 0.5 ] if best_sq and "SQ" not in properties: properties["SQ"] = best_sq if top_x and "MA" not in properties: properties["MA"] = [top_x] comment = self.comment(sgf=True, interactive=False) if comment: properties["C"] = [ "\n".join(properties.get("C", "")) + comment ] if self.is_root: properties["C"] = [ i18n._("SGF start message") + "\n" + "\n".join(properties.get("C", "")) + "\nSGF with review generated by KaTrain." ] if note: properties["C"] = [ "\n".join(properties.get("C", "")) + f"\nNote: {self.note}" ] return properties
def game_report(game, thresholds, depth_filter=None): cn = game.current_node nodes = cn.nodes_from_root while cn.children: # main branch cn = cn.children[0] nodes.append(cn) x, y = game.board_size depth_filter = [ math.ceil(board_frac * x * y) for board_frac in depth_filter or (0, 1e9) ] nodes = [ n for n in nodes if n.move and not n.is_root and depth_filter[0] <= n.depth < depth_filter[1] ] histogram = [{"B": 0, "W": 0} for _ in thresholds] ai_top_move_count = {"B": 0, "W": 0} ai_approved_move_count = {"B": 0, "W": 0} player_ptloss = {"B": [], "W": []} weights = {"B": [], "W": []} for n in nodes: points_lost = n.points_lost if n.points_lost is None: continue else: points_lost = max(0, points_lost) bucket = len(thresholds) - 1 - evaluation_class( points_lost, thresholds) player_ptloss[n.player].append(points_lost) histogram[bucket][n.player] += 1 cands = n.parent.candidate_moves filtered_cands = [ d for d in cands if d["order"] < ADDITIONAL_MOVE_ORDER and "prior" in d ] weight = min( 1.0, sum([max(d["pointsLost"], 0) * d["prior"] for d in filtered_cands]) / (sum(d["prior"] for d in filtered_cands) or 1e-6), ) # complexity capped at 1 # adj_weight between 0.05 - 1, dependent on difficulty and points lost adj_weight = max(0.05, min(1.0, max(weight, points_lost / 4))) weights[n.player].append((weight, adj_weight)) if n.parent.analysis_complete: ai_top_move_count[n.player] += int( cands[0]["move"] == n.move.gtp()) ai_approved_move_count[n.player] += int(n.move.gtp() in [ d["move"] for d in filtered_cands if d["order"] == 0 or ( d["pointsLost"] < 0.5 and d["order"] < 5) ]) wt_loss = { bw: sum(s * aw for s, (w, aw) in zip(player_ptloss[bw], weights[bw])) / (sum(aw for _, aw in weights[bw]) or 1e-6) for bw in "BW" } sum_stats = { bw: { "accuracy": 100 * 0.75**wt_loss[bw], "complexity": sum(w for w, aw in weights[bw]) / len(player_ptloss[bw]), "mean_ptloss": sum(player_ptloss[bw]) / len(player_ptloss[bw]), "weighted_ptloss": wt_loss[bw], "ai_top_move": ai_top_move_count[bw] / len(player_ptloss[bw]), "ai_top5_move": ai_approved_move_count[bw] / len(player_ptloss[bw]), } if len(player_ptloss[bw]) > 0 else {} for bw in "BW" } return sum_stats, histogram, player_ptloss