|
17 | 17 |
|
18 | 18 | One strong test per non-trivial metric: each checks the result against an independent |
19 | 19 | reference (a definitional oracle or a hand-computed value) rather than a loose sanity bound. |
20 | | -The trivial standardize helper is exercised transitively (decode_eval test); split_indices |
21 | | -folds into the buffer roundtrip. |
| 20 | +standardize has a direct test for its zero-variance floor (the dead-latent safety the probe |
| 21 | +relies on); split_indices folds into the buffer roundtrip. |
22 | 22 | """ |
23 | 23 |
|
24 | 24 | import numpy as np |
|
32 | 32 | decode_eval, |
33 | 33 | domain_f1, |
34 | 34 | split_indices, |
| 35 | + standardize, |
35 | 36 | ) |
36 | 37 |
|
37 | 38 |
|
@@ -196,3 +197,22 @@ def test_buffer_roundtrip_without_dense_or_instances(tmp_path): |
196 | 197 | lo = ActivationBuffer.load(path) |
197 | 198 | assert lo.dense is None and lo.instances is None |
198 | 199 | assert np.array_equal(lo.codes, codes) and lo.label_names == ["a", "b"] |
| 200 | + |
| 201 | + |
| 202 | +def test_standardize_floors_zero_variance_and_uses_train_rows(): |
| 203 | + """standardize floors std at 1e-6 (constant/dead latents don't divide to NaN) and uses only tr rows. |
| 204 | +
|
| 205 | + The probe's linear/codon paths z-score SAE codes, where ~20% of latents are dead (constant 0); |
| 206 | + without the floor those columns would produce inf/NaN and poison every logreg fit. |
| 207 | + """ |
| 208 | + X = torch.zeros(6, 3) |
| 209 | + X[:, 0] = 5.0 # constant feature -> zero variance (dead-latent-like) |
| 210 | + X[:, 1] = torch.tensor([1.0, 2.0, 3.0, 9.0, 9.0, 9.0]) # varies within the train rows |
| 211 | + tr = torch.tensor([0, 1, 2]) # train split = first three rows |
| 212 | + |
| 213 | + mu, sd = standardize(X, tr) |
| 214 | + assert torch.all(sd >= 1e-6) # constant column floored, never exactly 0 |
| 215 | + assert sd[1] > 1e-6 # a varying feature keeps its real std |
| 216 | + assert torch.isfinite((X - mu) / sd).all() # so the z-score is finite everywhere |
| 217 | + # stats are computed over the train rows only (no test-set leakage) |
| 218 | + assert torch.allclose(mu, X[tr].mean(0)) and torch.allclose(sd, X[tr].std(0) + 1e-6) |
0 commit comments