diff options
| author | YurenHao0426 <Blackhao0426@gmail.com> | 2026-08-01 21:37:55 -0500 |
|---|---|---|
| committer | YurenHao0426 <Blackhao0426@gmail.com> | 2026-08-01 21:37:55 -0500 |
| commit | 22acd2899958def0d103f11da49c2c4a499be773 (patch) | |
| tree | 892b422c1e04cd9854420eda0c965e21df81c8db /artifacts/spectral_frontier_probe/eval_impure_anchors.py | |
| parent | ef104fe4f07713bf11f266d2954b7446f176f8ae (diff) | |
The missing term was unary: omit-size solved at its ceiling in 1.4s
Adversarial review of the artifacts found three of my numbers to be
artifacts of my own code. All three reproduced here before acceptance:
- anchor_bound presented probe rows in the same index order on both
sides, so exact twins had their tie broken onto the diagonal. 0.997 ->
0.920 on omit-size. Fixed by scrambling the T-side presentation.
- The truth is not a strict local minimum: 51 transpositions have
exactly zero energy delta. fast_pair_descent only looked stationary
because its break test treats zero as no-improvement.
- scipy's FAQ takes no n_init, so it was swallowed into unknown_options
and 'FAQ x30 restarts' computed bit-identically to plain FAQ. Replaced
with a real restart loop over P0='randomized'.
The blind ceiling for omit-size is 0.836, not 1.0: the text field has 51
exact transposition automorphisms, so T[s,s] is bitwise identical to T
and no objective f(V, P T P^T) can separate an orbit at any order. Every
synthetic accuracy was being divided by the wrong denominator.
The fifteen failed solvers share one property -- all purely quadratic or
purely spectral, none with a node-level term. Eight moments of each
node's own field row, blended with the quadratic term through
Frank-Wolfe, reach 0.837 with an energy gap of exactly zero. The term
must stay in the loop: as a seed for pure-quadratic descent it scores
0.21, pinned through the iterations it scores 0.84 -- which is also why
amplification plateaued, being itself pure-quadratic.
Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'artifacts/spectral_frontier_probe/eval_impure_anchors.py')
| -rw-r--r-- | artifacts/spectral_frontier_probe/eval_impure_anchors.py | 65 |
1 files changed, 65 insertions, 0 deletions
diff --git a/artifacts/spectral_frontier_probe/eval_impure_anchors.py b/artifacts/spectral_frontier_probe/eval_impure_anchors.py new file mode 100644 index 0000000..f6b4397 --- /dev/null +++ b/artifacts/spectral_frontier_probe/eval_impure_anchors.py @@ -0,0 +1,65 @@ +import numpy as np, torch +from scipy.optimize import linear_sum_assignment + +def standardise(M): + M = np.asarray(M, dtype=np.float64); mask = ~np.eye(len(M), dtype=bool); v = M[mask] + out = (M - v.mean()) / v.std(); np.fill_diagonal(out, 0.0); return out + +d = torch.load('/home/yurenh2/emm/artifacts/synth_v1/omit_size.pt', map_location='cpu', weights_only=False) +V = standardise(d['visual_field']); T = standardise(d['text_field']); N = len(V) +truth = np.arange(N) + +def expand(anchorL, anchorR): + """anchorL: vision indices; anchorR: claimed text partner. Returns full accuracy.""" + probe = np.setdiff1d(np.arange(N), anchorL) + # probe scenes on the text side are whatever is not claimed + probeR = np.setdiff1d(np.arange(N), anchorR) + L = V[np.ix_(probe, anchorL)]; R = T[np.ix_(probeR, anchorR)] + L = (L - L.mean(1, keepdims=True)) / L.std(1, keepdims=True).clip(1e-9) + R = (R - R.mean(1, keepdims=True)) / R.std(1, keepdims=True).clip(1e-9) + S = L @ R.T / L.shape[1] + _, cols = linear_sum_assignment(-S) + resolved = probeR[cols] + n_ok = int((resolved == probe).sum()) + int((anchorR == anchorL).sum()) + return n_ok / N + +rng = np.random.default_rng(0) +REP = 20 +print("=== (a) PURE random anchors: reproduce the published curve ===") +for K in (12, 25, 40, 50, 64, 128): + a = [expand(*(lambda s: (s, s))(np.sort(rng.choice(N, K, replace=False)))) for _ in range(REP)] + print(f" K={K:4d} pure accuracy {np.mean(a):.3f} +- {np.std(a):.3f}") + +print("\n=== (b) IMPURE random anchors: c correct, K-c wrong (wrong = derangement among selected) ===") +for K in (12, 25, 40, 50, 100): + for prec in (1.0, 0.92, 0.88, 0.84, 0.80, 0.72, 0.60): + nb = int(round(K * (1 - prec))) + if nb == 1: nb = 2 # a single wrong pair is impossible inside a bijection + accs = [] + for _ in range(REP): + sel = np.sort(rng.choice(N, K, replace=False)) + right = sel.copy() + if nb >= 2: + bad = rng.choice(K, nb, replace=False) + sh = right[bad].copy() + while True: + perm = rng.permutation(nb) + if not (perm == np.arange(nb)).any(): break + right[bad] = sh[perm] + accs.append(expand(sel, right)) + print(f" K={K:4d} prec={prec:.2f} ({K-nb}/{K} right) accuracy {np.mean(accs):.3f} +- {np.std(accs):.3f}") + print() + +print("=== (c) IMPURE with wrong partners drawn from OUTSIDE the anchor set ===") +for K in (25, 50): + for prec in (1.0, 0.88, 0.84, 0.80, 0.72): + nb = int(round(K * (1 - prec))); accs = [] + for _ in range(REP): + sel = np.sort(rng.choice(N, K, replace=False)) + right = sel.copy() + if nb: + bad = rng.choice(K, nb, replace=False) + outside = np.setdiff1d(np.arange(N), sel) + right[bad] = rng.choice(outside, nb, replace=False) + accs.append(expand(sel, right)) + print(f" K={K:4d} prec={prec:.2f} accuracy {np.mean(accs):.3f} +- {np.std(accs):.3f}") |
