Zonotope Active Exposure for Sensor-Fusion Networks / experiment.py

Mechanism failed

Raw ⬇ ZIP
 1import json, random
 2import numpy as np
 3import torch
 4
 5SEED = 7
 6np.random.seed(SEED); random.seed(SEED); torch.manual_seed(SEED)
 7
 8def support(q, H):
 9    return float(np.sum(np.abs(q @ H)))
10
11def margin(q, c0, H0, cj, Hj):
12    return float(q @ (c0 - cj) - support(q, H0) - support(q, Hj))
13
14def math_check():
15    # Exact numerical checks of Minkowski addition and linear-map identities.
16    c1=np.array([.3,-.2]); H1=np.array([[.4,.1],[.0,.25]])
17    c2=np.array([-.1,.5]); H2=np.array([[.2],[.3]])
18    K=np.array([[1.2,-.4],[.5,.8]])
19    z1=np.random.uniform(-1,1,(20000,2)); z2=np.random.uniform(-1,1,(20000,1))
20    lhs=(c1+z1@H1.T)+(c2+z2@H2.T)
21    rhs=(c1+c2)+np.concatenate([z1,z2],1)@np.concatenate([H1,H2],1).T
22    mink_err=float(np.max(np.abs(lhs-rhs)))
23    lhs2=(c1+z1@H1.T)@K.T
24    rhs2=(K@c1)+z1@(K@H1).T
25    map_err=float(np.max(np.abs(lhs2-rhs2)))
26
27    # Choose q toward the trusted center, so the certificate is positive.
28    c0=np.array([0.,0.]); H0=np.diag([.1,.08])
29    cj=np.array([.55,0.]); Hj=np.diag([.08,.06]); q=np.array([-1.,0.])
30    m=margin(q,c0,H0,cj,Hj)
31    trusted_right=q@c0-support(q,H0)
32    attack_left=q@cj+support(q,Hj)
33    return dict(minkowski_max_error=mink_err, linear_map_max_error=map_err,
34                positive_certificate_margin=m,
35                sampled_projection_gap=float(trusted_right-attack_left))
36
37class Fusion(torch.nn.Module):
38    def __init__(self):
39        super().__init__()
40        self.net=torch.nn.Sequential(torch.nn.Linear(2,12),torch.nn.Tanh(),torch.nn.Linear(12,1))
41    def forward(self,x): return self.net(x)
42
43def jacobian(model, x):
44    x=x.detach().requires_grad_(True); y=model(x); rows=[]
45    for k in range(y.shape[1]):
46        rows.append(torch.autograd.grad(y[:,k].sum(),x,retain_graph=True)[0])
47    return torch.stack(rows,1)  # B,out,in
48
49def probe_test():
50    # Two-sensor nonlinear fusion regression; sensor 0 has a fixed attack shift.
51    torch.manual_seed(SEED)
52    n=256; x=torch.rand(n,2)*2-1
53    y=x[:,0:1]+.6*x[:,1:2]+.2*torch.sin(3*x[:,0:1])
54    model=Fusion(); opt=torch.optim.Adam(model.parameters(),lr=.03)
55    for _ in range(250):
56        opt.zero_grad(); loss=((model(x)-y)**2).mean(); loss.backward(); opt.step()
57
58    x0=x[:1].clone(); J=jacobian(model,x0)[0,0].detach().numpy()[None,:]
59    He=np.diag([.08,.08]); attack=np.array([[.45],[0.]])
60    cbase=float(model(x0)); c0=np.array([cbase]); cj=np.array([cbase+float((J@attack).item())])
61    H0=J@He; Hj=J@He; eps=.25
62    ds=np.linspace(-eps,eps,101)
63    qs=np.array([[-1.],[1.]])
64    local_margins=[]
65    exact_sep=[]
66    for d in ds:
67        # Under the supplied local model, both hypotheses receive the same J*d.
68        local_margins.append(max(margin(q,c0+J[:,0]*d,H0,cj+J[:,0]*d,Hj) for q in qs))
69        with torch.no_grad():
70            a=model(x0+torch.tensor([[d,0.]],dtype=x0.dtype)).item()
71            b=model(x0+torch.tensor([[d+.45,0.]],dtype=x0.dtype)).item()
72        exact_sep.append(abs(b-a))
73    local_margins=np.asarray(local_margins); exact_sep=np.asarray(exact_sep)
74    zero_i=len(ds)//2; best_i=int(np.argmax(exact_sep))
75    # Monte Carlo confirms the support bound for the trusted linearized zonotope.
76    z=np.random.uniform(-1,1,(10000,2)); proj=(c0+(z@(H0.T))).ravel()
77    bound=float(abs(c0[0])+support(np.array([1.]),H0))
78    return dict(train_mse=float(((model(x)-y)**2).mean()), jacobian=J.tolist(),
79        local_margin_at_zero=float(local_margins[zero_i]),
80        local_margin_best=float(local_margins.max()),
81        local_margin_range=float(local_margins.max()-local_margins.min()),
82        exact_separation_at_zero=float(exact_sep[zero_i]),
83        exact_separation_best=float(exact_sep.max()),
84        exact_separation_gain=float(exact_sep[best_i]-exact_sep[zero_i]),
85        best_probe=float(ds[best_i]),
86        trusted_projection_max_abs=float(np.max(np.abs(proj))),
87        trusted_support_bound=bound)
88
89def main():
90    out={'seed':SEED,'math_check':math_check(),'probe_test':probe_test()}
91    with open('results.json','w') as f: json.dump(out,f,indent=2)
92    print(json.dumps(out,indent=2))
93
94if __name__=='__main__': main()