Skip to content

Commit 195fb29

Browse files
andre15silvaclaude
andcommitted
feat(figures): rework lookahead horizon plot aesthetics
- Property-based titles (Semantic/Syntactic Correctness, etc.) - k=0 on left; x-axis label "Horizon k (turns)" with single n count - Despined, no grid (seaborn-white), sequential blue palette per layer - Per-layer marker shapes for print/colorblind accessibility - Direct line labels; "random" baseline labeled below the dashed line - MaxNLocator for clean x-ticks (no crowding at max_k=50) - Auto-wider figure for max_k=50 - filename_suffix param to distinguish max15/max50 outputs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ed14345 commit 195fb29

2 files changed

Lines changed: 74 additions & 48 deletions

File tree

run_figures.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ def main():
2020
parser.add_argument("--lookahead-probes", nargs="+", default=None,
2121
help="Subset of --probe to generate lookahead figures for. "
2222
"Defaults to all probes. Exclude static-label probes (e.g. will_resolve).")
23+
parser.add_argument("--lookahead-filename-suffix", default="",
24+
help="Suffix appended to the output filename, e.g. 'max50' → probe_lookahead_max50.png")
2325
args = parser.parse_args()
2426

2527
model_cfg = load_config(args.model_config, ModelConfig)
@@ -46,6 +48,7 @@ def main():
4648
probe_layers=model_cfg.probe_layers,
4749
results_dir=args.results_dir,
4850
figures_dir=args.figures_dir,
51+
filename_suffix=args.lookahead_filename_suffix,
4952
)
5053

5154

src/figures.py

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,15 @@ def plot_probe_heatmap(
7373
plt.close()
7474

7575

76+
_PROBE_DISPLAY_NAME = {
77+
"currently_compiles": "Syntactic Correctness",
78+
"currently_correct": "Semantic Correctness",
79+
"currently_reduces_failing": "Reduced Failing Tests",
80+
"currently_has_regressions": "Introduced Regressions",
81+
"will_resolve": "Will Resolve",
82+
}
83+
84+
7685
def plot_lookahead_horizon(
7786
base_run_id: str,
7887
shift_run_ids: list[str],
@@ -81,15 +90,9 @@ def plot_lookahead_horizon(
8190
probe_layers: list[int],
8291
results_dir: str = "results",
8392
figures_dir: str = "figures",
93+
filename_suffix: str = "",
8494
) -> None:
85-
"""Plot probe lift and AUC vs lookahead horizon k (in assistant turns).
86-
87-
X-axis is inverted: k=0 (at the label flip) is on the right; larger k
88-
(earlier prediction) is on the left — matching trajectory time direction.
89-
"""
90-
colours = plt.rcParams["axes.prop_cycle"].by_key()["color"]
91-
92-
# Collect per-k, per-layer metrics
95+
"""Plot AUC vs lookahead horizon k (in assistant turns), k=0 on left."""
9396
all_k = list(k_values)
9497
all_run_ids = list(shift_run_ids)
9598

@@ -107,72 +110,92 @@ def plot_lookahead_horizon(
107110
results = all_results.get(layer_idx, [])
108111
if not results:
109112
continue
110-
# Aggregate across bins weighted by n_test
111-
# majority baseline = weighted average of per-bin majority baselines
112113
total_n, total_correct, total_majority_w, total_auc_w = 0, 0, 0, 0.0
113114
for r in results:
114115
n = r.n_test if hasattr(r, "n_test") else r["n_test"]
115116
acc = r.test_acc if hasattr(r, "test_acc") else r["test_acc"]
116117
auc = r.test_auc if hasattr(r, "test_auc") else r["test_auc"]
117118
n_pos = r.n_pos_test if hasattr(r, "n_pos_test") else (r["n_pos_test"] if isinstance(r, dict) and "n_pos_test" in r else n // 2)
118119
total_correct += acc * n
119-
total_majority_w += max(n_pos, n - n_pos) # per-bin majority
120+
total_majority_w += max(n_pos, n - n_pos)
120121
total_auc_w += auc * n
121122
total_n += n
122123
if total_n == 0:
123124
continue
124125
agg_acc = total_correct / total_n
125126
agg_auc = total_auc_w / total_n
126-
majority = total_majority_w / total_n # weighted avg per-bin majority baseline
127+
majority = total_majority_w / total_n
127128
lift = agg_acc - majority
128129
data[layer_idx].append((k, lift, agg_auc, total_n))
129130

130131
out_dir = Path(figures_dir) / base_run_id
131132
out_dir.mkdir(parents=True, exist_ok=True)
132133

133-
# Collect per-k n_test (same across layers; use first available layer)
134134
k_to_n: dict[int, int] = {}
135135
for layer_idx in probe_layers:
136136
for k, _lift, _auc, n in data[layer_idx]:
137137
if k not in k_to_n:
138138
k_to_n[k] = n
139139

140-
fig, ax_auc = plt.subplots(1, 1, figsize=(8, 4))
140+
ks_present = sorted(k_to_n.keys())
141+
n_k = len(ks_present)
142+
fig_width = max(9, 9 + (n_k - 16) * 0.12) # wider for max50
141143

142-
for li, layer_idx in enumerate(probe_layers):
143-
pts = sorted(data[layer_idx], key=lambda x: x[0])
144-
if not pts:
145-
continue
146-
ks = [p[0] for p in pts]
147-
aucs = [p[2] for p in pts]
148-
col = colours[li % len(colours)]
149-
ax_auc.plot(ks, aucs, marker="o", color=col, label=f"Layer {layer_idx}")
150-
151-
ax_auc.axhline(0.5, linestyle="--", color="#aaa", linewidth=1)
152-
ax_auc.set_ylabel("AUC (random = 0.5)")
153-
ax_auc.set_ylim(bottom=0.48)
154-
ax_auc.set_xlabel("Turns ahead (k) ←earlier prediction at flip→")
155-
ax_auc.set_title(f"{probe_name} — lookahead horizon ({base_run_id})")
156-
ax_auc.legend(fontsize=8, loc="upper left")
157-
ax_auc.invert_xaxis()
144+
_MARKERS = ["o", "s", "^", "D"]
145+
# Sequential blues: sample 4 points from dark→light so layer ordering is visible
146+
_cmap = plt.get_cmap("Blues")
147+
_seq_colours = [_cmap(v) for v in [0.85, 0.65, 0.45, 0.30]]
158148

159-
ks_present = sorted(k_to_n.keys())
160-
ax_auc.set_xticks(ks_present)
161-
ax_auc.set_xticklabels([]) # replaced by staggered annotations below
162-
163-
# Staggered tick labels: alternate between two vertical offsets to avoid overlap
164-
for i, k in enumerate(ks_present):
165-
pad = 18 if i % 2 == 0 else 34
166-
ax_auc.annotate(
167-
f"{k}\n(n={k_to_n[k]:,})",
168-
xy=(k, ax_auc.get_ylim()[0]),
169-
xytext=(0, -pad),
170-
textcoords="offset points",
171-
ha="center", va="top", fontsize=7,
172-
annotation_clip=False,
149+
with plt.style.context("seaborn-v0_8-white"):
150+
fig, ax_auc = plt.subplots(1, 1, figsize=(fig_width, 4.5))
151+
152+
for li, layer_idx in enumerate(probe_layers):
153+
pts = sorted(data[layer_idx], key=lambda x: x[0])
154+
if not pts:
155+
continue
156+
ks = [p[0] for p in pts]
157+
aucs = [p[2] for p in pts]
158+
col = _seq_colours[li % len(_seq_colours)]
159+
marker = _MARKERS[li % len(_MARKERS)]
160+
ax_auc.plot(ks, aucs, marker=marker, markersize=5, linewidth=2,
161+
color=col, label=f"Layer {layer_idx}", zorder=3)
162+
ax_auc.annotate(
163+
f"L{layer_idx}",
164+
xy=(ks[-1], aucs[-1]),
165+
xytext=(5, 0),
166+
textcoords="offset points",
167+
ha="left", va="center", fontsize=8, color=col,
168+
)
169+
170+
# Baseline with direct label
171+
ax_auc.axhline(0.5, linestyle="--", color="#bbb", linewidth=1, zorder=1)
172+
ax_auc.text(
173+
ks_present[-1], 0.5, " random",
174+
va="top", ha="left", fontsize=7, color="#999",
175+
transform=ax_auc.transData,
173176
)
174177

175-
plt.tight_layout()
176-
plt.subplots_adjust(bottom=0.18)
177-
plt.savefig(out_dir / f"{probe_name}_lookahead.png", dpi=150)
178-
plt.close()
178+
ax_auc.set_ylabel("AUC-ROC", fontsize=11)
179+
ax_auc.set_ylim(bottom=0.48)
180+
181+
n_vals = [k_to_n[k] for k in ks_present if k in k_to_n]
182+
n_str = f" (n = {n_vals[0]:,})" if n_vals else ""
183+
ax_auc.set_xlabel(f"Horizon k (turns){n_str}", fontsize=10)
184+
185+
title = _PROBE_DISPLAY_NAME.get(probe_name, probe_name)
186+
ax_auc.set_title(title, fontsize=13, fontweight="bold")
187+
188+
# Auto-select ~10 clean tick positions
189+
from matplotlib.ticker import MaxNLocator
190+
ax_auc.xaxis.set_major_locator(MaxNLocator(nbins=10, integer=True))
191+
ax_auc.tick_params(axis="both", labelsize=9)
192+
ax_auc.margins(x=0.06)
193+
194+
# Despine
195+
ax_auc.spines["top"].set_visible(False)
196+
ax_auc.spines["right"].set_visible(False)
197+
198+
plt.tight_layout()
199+
suffix = f"_{filename_suffix}" if filename_suffix else ""
200+
plt.savefig(out_dir / f"{probe_name}_lookahead{suffix}.png", dpi=150, bbox_inches="tight")
201+
plt.close()

0 commit comments

Comments
 (0)