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
|
import numpy as np, torch
from collections import defaultdict
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')
V0=d['visual_field'].double().numpy(); T0=d['text_field'].double().numpy()
T=standardise(T0); V=standardise(V0); N=len(T)
# exact automorphism-by-identical-rows of T (blind: uses T only)
key={}; cls=defaultdict(list)
Toff=T.copy(); np.fill_diagonal(Toff,0.0)
# group scenes whose T rows agree after removing the two swapped coords
groups=[]; used=np.zeros(N,bool)
for i in range(N):
if used[i]: continue
g=[i]; used[i]=True
for j in range(i+1,N):
if used[j]: continue
a=np.delete(Toff[i],[i,j]); b=np.delete(Toff[j],[i,j])
if np.abs(a-b).max()<1e-9 and abs(Toff[i,j]-max(Toff[i,i],0))<1e6:
g.append(j); used[j]=True
groups.append(g)
sizes=np.array([len(g) for g in groups])
print("T exact-twin classes: total",len(groups),"; size histogram",np.bincount(sizes)[1:])
excess=int((sizes-1).sum())
print("scenes in non-trivial classes:",int(sizes[sizes>1].sum()),"; log|Aut| classes:",int((sizes>1).sum()))
ceil=(N-int(sizes[sizes>1].sum())+int((sizes>1).sum()))/N
print(f"blind accuracy ceiling if class is identified but member picked at random: {ceil:.3f}")
# check the objective really is invariant: swap two members of a class
import itertools
p=np.arange(N)
E0=(( (T*T).sum()+(V*V).sum() )-2*(T[np.ix_(p,p)]*V).sum())/(N*(N-1))
bad=0; tested=0
for g in groups:
if len(g)>1:
i,j=g[0],g[1]; q=p.copy(); q[[i,j]]=q[[j,i]]
E1=(((T*T).sum()+(V*V).sum())-2*(T[np.ix_(q,q)]*V).sum())/(N*(N-1))
tested+=1
if abs(E1-E0)>1e-9: bad+=1
print(f"swapping twins changes E in {bad}/{tested} classes (0 means exact symmetry of the objective)")
print(f"E(truth) = {E0:.10f}")
|