|
| 1 | +import argparse |
| 2 | +import json |
| 3 | +from collections import defaultdict |
| 4 | +from pathlib import Path |
| 5 | + |
| 6 | +import matplotlib.colors as mcolors |
| 7 | +import matplotlib.pyplot as plt |
| 8 | +import numpy as np |
| 9 | +from tqdm import tqdm |
| 10 | + |
| 11 | +from codeclash.constants import LOCAL_LOG_DIR |
| 12 | + |
| 13 | + |
| 14 | +def main(log_dir: Path): |
| 15 | + print(f"Creating win rate heatmap from logs in {log_dir}...") |
| 16 | + |
| 17 | + # Track round-level wins: (model1, model2) -> [wins, total_rounds] |
| 18 | + results = defaultdict(lambda: [0, 0]) |
| 19 | + |
| 20 | + for metadata_file in tqdm(list(log_dir.rglob("metadata.json"))): |
| 21 | + try: |
| 22 | + metadata = json.load(open(metadata_file)) |
| 23 | + p2m = {x["name"]: x["config"]["model"]["model_name"].strip("@") for x in metadata["config"]["players"]} |
| 24 | + |
| 25 | + if len(p2m) != 2: |
| 26 | + continue |
| 27 | + |
| 28 | + # Process each round (skip round 0) |
| 29 | + for round_id, round_data in metadata["round_stats"].items(): |
| 30 | + if round_id == "0": |
| 31 | + continue |
| 32 | + |
| 33 | + winner = round_data.get("winner") |
| 34 | + if winner in p2m: |
| 35 | + winner_model = p2m[winner] |
| 36 | + loser_model = next(m for m in p2m.values() if m != winner_model) |
| 37 | + |
| 38 | + results[(winner_model, loser_model)][0] += 1 # win |
| 39 | + results[(winner_model, loser_model)][1] += 1 # total |
| 40 | + results[(loser_model, winner_model)][1] += 1 # total for loser |
| 41 | + except: |
| 42 | + continue |
| 43 | + |
| 44 | + # Build matrix |
| 45 | + models = sorted({m for pair in results.keys() for m in pair}) |
| 46 | + clean_names = [m.split("/")[-1] for m in models] |
| 47 | + n = len(models) |
| 48 | + |
| 49 | + matrix = np.full((n, n), np.nan) |
| 50 | + for i, m1 in enumerate(models): |
| 51 | + for j, m2 in enumerate(models): |
| 52 | + if i != j and results[(m1, m2)][1] > 0: |
| 53 | + matrix[i, j] = results[(m1, m2)][0] / results[(m1, m2)][1] |
| 54 | + |
| 55 | + # Plot |
| 56 | + fig, ax = plt.subplots(figsize=(10, 8)) |
| 57 | + cmap = mcolors.LinearSegmentedColormap.from_list("br", ["#3498db", "#ffffff", "#e74c3c"]) |
| 58 | + |
| 59 | + masked = np.ma.masked_where(np.isnan(matrix), matrix) |
| 60 | + im = ax.imshow(masked, cmap=cmap, vmin=0, vmax=1) |
| 61 | + |
| 62 | + # Add percentages |
| 63 | + for i in range(n): |
| 64 | + for j in range(n): |
| 65 | + if not np.isnan(matrix[i, j]): |
| 66 | + color = "white" if abs(matrix[i, j] - 0.5) > 0.3 else "black" |
| 67 | + ax.text(j, i, f"{matrix[i, j]:.0%}", ha="center", va="center", color=color, fontweight="bold") |
| 68 | + |
| 69 | + ax.set_xticks(range(n)) |
| 70 | + ax.set_yticks(range(n)) |
| 71 | + ax.set_xticklabels(clean_names, rotation=45, ha="right") |
| 72 | + ax.set_yticklabels(clean_names) |
| 73 | + ax.set_title("Model Win Rate Heatmap (Round Level)\n(Row beats Column)", fontsize=14, fontweight="bold") |
| 74 | + |
| 75 | + plt.colorbar(im, label="Win Rate") |
| 76 | + plt.tight_layout() |
| 77 | + plt.savefig("heatmap_win_rates.png", dpi=300, bbox_inches="tight") |
| 78 | + print("Heatmap saved to heatmap_win_rates.png") |
| 79 | + |
| 80 | + |
| 81 | +if __name__ == "__main__": |
| 82 | + parser = argparse.ArgumentParser(description="Create model win rate heatmap") |
| 83 | + parser.add_argument("-d", "--log_dir", type=Path, default=LOCAL_LOG_DIR, help="Path to logs") |
| 84 | + args = parser.parse_args() |
| 85 | + main(args.log_dir) |
0 commit comments