@@ -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+
7685def 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