summaryrefslogtreecommitdiff
path: root/artifacts/spectral_frontier_probe/eval_impure_anchors.py
blob: f6b4397d0fac915dc47dbb9f5f492a1cdebfcc9b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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}")