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
|
import sys, numpy as np, ot, torch
sys.path.insert(0,'/home/yurenh2/emm')
from scipy.optimize import linear_sum_assignment
from worldalign.synth_fast_gate import ClosedFormEnergy, fast_pair_descent
from worldalign.synth_triangle_gate import standardized
DEV=torch.device("cuda:1")
def standardise(m):
mask=~np.eye(len(m),dtype=bool); v=m[mask]
o=(m-v.mean())/v.std(); np.fill_diagonal(o,0.0); return o
def tlb(V,S):
n=len(V); a=np.sort(V,1); b=np.sort(S,1)
return (a*a).sum(1)[:,None]/n+(b*b).sum(1)[None,:]/n-2*(a@b.T)/n
st=torch.load("/home/yurenh2/emm/artifacts/synth_v1/omit_size.pt",map_location='cpu',weights_only=False)
V=standardise(st["visual_field"].double().numpy()); T=standardise(st["text_field"].double().numpy())
n=len(V); w=ot.unif(n)
rng=np.random.default_rng(0); hid=rng.permutation(n); S=T[np.ix_(hid,hid)]
Vg=standardized(torch.from_numpy(V).to(DEV)).double(); Sg=standardized(torch.from_numpy(S).to(DEV)).double()
en=ClosedFormEnergy(Sg,Vg,1.0,0.0,64)
M=tlb(V,S); M=M/M.max()
G=np.outer(w,w)
for a in (0.0,0.2):
G=ot.gromov.fused_gromov_wasserstein(M,V,S,w,w,"square_loss",alpha=a,G0=G,max_iter=200,tol_rel=1e-9)
_,cols=linear_sum_assignment(-G)
fin=fast_pair_descent(Sg,Vg,torch.from_numpy(np.ascontiguousarray(cols)).to(DEV),2000).cpu().numpy()
truth=np.argsort(hid)
Ef=float(en.energy(torch.from_numpy(np.ascontiguousarray(fin)).to(DEV)[None])[0])
Et=float(en.energy(torch.from_numpy(np.ascontiguousarray(truth)).to(DEV)[None])[0])
acc=float((hid[fin]==np.arange(n)).mean())
print("accuracy=%.4f E_found=%.10f E_truth=%.10f diff=%.3e"%(acc,Ef,Et,Ef-Et))
wrong=np.where(hid[fin]!=np.arange(n))[0]
print("n_wrong=%d"%len(wrong))
# for each wrong scene i: correlation between S-row of assigned node and S-row of true node
Sal=S # in shuffled index space
corr=[]
vcorr=[]
for i in wrong:
a_idx=fin[i]; t_idx=truth[i]
x=np.delete(Sal[a_idx],[a_idx,t_idx]); y=np.delete(Sal[t_idx],[a_idx,t_idx])
corr.append(np.corrcoef(x,y)[0,1])
u=np.delete(V[i],[i]);
# visual side: is the visually-assigned scene similar to i? compare V rows of i and of the scene truly at a_idx
j=hid[a_idx]
v1=np.delete(V[i],[i,j]); v2=np.delete(V[j],[i,j])
vcorr.append(np.corrcoef(v1,v2)[0,1])
rng2=np.random.default_rng(7); pairs=rng2.integers(0,n,(len(wrong),2))
base=[np.corrcoef(np.delete(Sal[p],[p,q]),np.delete(Sal[q],[p,q]))[0,1] for p,q in pairs if p!=q]
print("text-row corr of confused partners: median=%.3f mean=%.3f | random pair baseline median=%.3f"%(np.median(corr),np.mean(corr),np.median(base)))
print("visual-row corr of confused partners: median=%.3f"%np.median(vcorr))
# cycle structure of the error
perm=hid[fin]
seen=set(); cyc=[]
for i in range(n):
if i in seen or perm[i]==i: continue
c=0; j=i
while j not in seen:
seen.add(j); j=perm[j]; c+=1
cyc.append(c)
import collections
print("error cycle lengths:", dict(collections.Counter(cyc)))
|