feat(SAC_LSTM_Bot): embedded plain-English reading guides in graphs
This commit is contained in:
@@ -29,6 +29,21 @@ BUCKETS = 40 # v1 downsampling for the Run1-vs-Run3 panel
|
||||
COLORS = {"Corners": "#d62728", "Crazy": "#1f77b4", "Target": "#2ca02c"}
|
||||
V1_COLOR = "#999999"
|
||||
W, M_L, M_R, M_T, M_B = 1000, 70, 20, 40, 45 # graph 2 geometry (unchanged)
|
||||
GUIDE_H = 124 # bottom band holding the plain-English "How to read" panel
|
||||
WINRATE_GUIDE = [
|
||||
"One small dot = one test match. 0 means lost all ten fights.",
|
||||
"A thick line shows the recent trend. Up = learning.",
|
||||
"Watch the three colors. Red = Corners. Blue = Crazy. Green = Target.",
|
||||
"Gray line = old run. Red line = new run. Same enemy.",
|
||||
"If red ends higher than gray, the new training worked.",
|
||||
]
|
||||
LOSSES_GUIDE = [
|
||||
"These are internal error numbers. They are not game scores.",
|
||||
"Critic and actor panels: big spikes are normal early.",
|
||||
"Steady growth forever is bad.",
|
||||
"Alpha near 1 = bot still experimenting.",
|
||||
"If alpha falls too fast, the bot freezes its habits.",
|
||||
]
|
||||
|
||||
|
||||
def parse_eval_series(path):
|
||||
@@ -186,6 +201,19 @@ def map_fn(p0, p1, vmin, vmax, log=False):
|
||||
return f
|
||||
|
||||
|
||||
def guide_block(w, h, lines):
|
||||
"""Plain-English "How to read" band at the bottom of the canvas."""
|
||||
y = h - GUIDE_H
|
||||
s = f'<rect x="0" y="{y}" width="{w}" height="{GUIDE_H}" fill="#f2f2f2"/>\n'
|
||||
s += (f'<text x="16" y="{y + 21}" font-size="15" font-weight="bold">'
|
||||
f"How to read</text>\n"
|
||||
f'<text x="{w - 16}" y="{y + 20}" text-anchor="end" font-size="11" '
|
||||
f'fill="#666">Regenerate anytime: python3 tools/plot_progress.py</text>\n')
|
||||
for i, ln in enumerate(lines):
|
||||
s += f'<text x="16" y="{y + 41 + i * 17}" font-size="13">{esc(ln)}</text>\n'
|
||||
return s
|
||||
|
||||
|
||||
# ---------- graph 1: eval win rates ----------
|
||||
|
||||
def graph_eval(series_v2, series_v1, out):
|
||||
@@ -195,7 +223,7 @@ def graph_eval(series_v2, series_v1, out):
|
||||
# panel geometry (local: graph 2 keeps the module-level constants)
|
||||
CW, ML, MR = 1200, 64, 24
|
||||
TOP, PH, GAP, MB = 78, 310, 84, 56
|
||||
H = TOP + PH + GAP + PH + MB # 838 >= 800
|
||||
H = TOP + PH + GAP + PH + MB + GUIDE_H
|
||||
x0, x1 = ML, CW - MR
|
||||
s = svg_open(CW, H, "SAC-LSTM campaign: eval win rate vs opponents (Run 3 live, Run 1 archived)")
|
||||
|
||||
@@ -256,6 +284,7 @@ def graph_eval(series_v2, series_v1, out):
|
||||
items_b.append((COLORS["Corners"], f"Run 3 trend (rolling-{TREND_WINDOW})"))
|
||||
s += legend(items_b, x0 + 12, y1b + 14)
|
||||
|
||||
s += guide_block(CW, H, WINRATE_GUIDE)
|
||||
s += "</svg>\n"
|
||||
return 1 if write_svg(out, s) else 0
|
||||
|
||||
@@ -287,7 +316,7 @@ def graph_losses(rows, out):
|
||||
return 0
|
||||
|
||||
PH, GAP = 210, 55
|
||||
H = M_T + 3 * PH + 2 * GAP + M_B
|
||||
H = M_T + 3 * PH + 2 * GAP + M_B + GUIDE_H
|
||||
x0, x1 = M_L, W - M_R
|
||||
n = len(rows)
|
||||
s = svg_open(W, H, "SAC-LSTM training losses (x = metric line number)")
|
||||
@@ -335,8 +364,10 @@ def graph_losses(rows, out):
|
||||
panel(M_T + PH + GAP, actor, "actor_loss |abs| (log scale)", True, "|actor_loss|")
|
||||
panel(M_T + 2 * (PH + GAP), alpha, "alpha (temperature)", False, "alpha",
|
||||
fixed_range=(0, max(1.0, max(alpha))))
|
||||
s += (f'<text x="{x0 + (x1 - x0) // 2}" y="{H - 8}" text-anchor="middle" '
|
||||
f'font-size="12">metric line number ({n} rows)</text>\n</svg>\n')
|
||||
s += (f'<text x="{x0 + (x1 - x0) // 2}" y="{H - GUIDE_H - 8}" text-anchor="middle" '
|
||||
f'font-size="12">metric line number ({n} rows)</text>\n')
|
||||
s += guide_block(W, H, LOSSES_GUIDE)
|
||||
s += "</svg>\n"
|
||||
return 1 if write_svg(out, s) else 0
|
||||
|
||||
|
||||
@@ -368,8 +399,10 @@ def selftest():
|
||||
ok = graph_losses(rows, td / "g2.svg") and graph_eval(ser, {"Corners": [0, 10]}, td / "g1.svg")
|
||||
assert ok and (td / "g1.svg").stat().st_size > 500
|
||||
g1 = (td / "g1.svg").read_text()
|
||||
g2 = (td / "g2.svg").read_text()
|
||||
ET.fromstring(g1) # whole doc must parse -> closing tag present
|
||||
ET.fromstring((td / "g2.svg").read_text())
|
||||
ET.fromstring(g2)
|
||||
assert "How to read" in g1 and "How to read" in g2, "reading guide missing"
|
||||
assert 'width="1200"' in g1, "canvas must be >=1200 wide"
|
||||
assert g1.count("<circle") == sum(len(v) for v in ser.values()) # one dot per raw eval
|
||||
n_trend = sum(1 for v in ser.values() if len(v) >= 2) + 2 # trends + v1 buckets + v3 trend
|
||||
|
||||
Reference in New Issue
Block a user