Free-Loss Jacobian Spectral Target / experiment.py
Failed on benchmark
1import json, math, random
2import numpy as np
3import torch
4from torch import nn
5
6SEED = 447
7np.random.seed(SEED); random.seed(SEED); torch.manual_seed(SEED)
8torch.set_num_threads(min(12, torch.get_num_threads()))
9
10
11def free_moments(tau, K=3):
12 """m_p = 1/p [w^(p-1)] (1+w)^p exp(-p*tau/(1+w))."""
13 out = []
14 for p in range(1, K + 1):
15 n, a = p - 1, p * tau
16 # exp(-a/(1+w)) = exp(-a) exp(sum_j>=1 a*(-1)^(j+1) w^j)
17 q = np.zeros(n + 1); q[0] = 1.0
18 b = np.zeros(n + 1)
19 for j in range(1, n + 1):
20 b[j] = a * ((-1) ** (j + 1))
21 for k in range(1, n + 1):
22 q[k] = sum(j * b[j] * q[k-j] for j in range(1, k + 1)) / k
23 coeff = 0.0
24 for j in range(n + 1):
25 coeff += math.comb(p, j) * q[n-j]
26 out.append(math.exp(-a) * coeff / p)
27 return np.array(out)
28
29
30def haar_unitary(n):
31 z = (np.random.randn(n, n) + 1j*np.random.randn(n, n)) / np.sqrt(2)
32 q, r = np.linalg.qr(z)
33 d = np.diag(r)
34 return q * (d / np.abs(d)).conj()
35
36
37def product_check(n=48, taus=(.25, .5, 1., 2.), reps=25):
38 rows = []
39 for tau in taus:
40 L = max(1, round(tau * n))
41 vals, atom = [], []
42 P = np.diag([1.0] * (n-1) + [0.0])
43 for _ in range(reps):
44 B = np.eye(n, dtype=complex)
45 for _ in range(L):
46 B = P @ haar_unitary(n) @ B
47 eig = np.linalg.eigvalsh(B.conj().T @ B).real
48 vals.append([np.mean(eig), np.mean(eig**2), np.mean(eig**3)])
49 atom.append(np.mean(eig > 1 - 1e-9))
50 emp = np.mean(vals, axis=0); target = free_moments(tau, 3)
51 rows.append(dict(tau=tau, L=L, target=target.tolist(), empirical=emp.tolist(),
52 abs_err=np.abs(emp-target).tolist(), atom_emp=float(np.mean(atom)),
53 atom_target=max(0., 1.-tau)))
54 return rows
55
56
57class MLP(nn.Module):
58 def __init__(self, d=16, width=32, depth=4, out=4):
59 super().__init__()
60 layers = []
61 for i in range(depth):
62 layers += [nn.Linear(d if i == 0 else width, width), nn.Tanh()]
63 layers.append(nn.Linear(width, out))
64 self.net = nn.Sequential(*layers)
65 def forward(self, x): return self.net(x)
66
67
68def jacobian_moments(model, x, K=2):
69 """Exact per-example J^T J moments, with scalar output probes replaced by full J."""
70 x = x.detach().requires_grad_(True)
71 y = model(x)
72 estimates = []
73 for b in range(x.shape[0]):
74 Jrows = []
75 for o in range(y.shape[1]):
76 g = torch.autograd.grad(y[b, o], x, retain_graph=True, create_graph=True)[0][b]
77 Jrows.append(g)
78 J = torch.stack(Jrows)
79 G = J.T @ J
80 pows = G
81 estimates.append([torch.trace(pows) / x.shape[1]])
82 for p in range(2, K + 1):
83 pows = pows @ G
84 estimates[-1].append(torch.trace(pows) / x.shape[1])
85 return torch.stack([torch.stack(a) for a in estimates]).mean(0)
86
87
88def train_variant(use_spec, steps=120):
89 torch.manual_seed(SEED + (1 if use_spec else 0))
90 d, out, width, depth = 16, 4, 32, 4
91 model = MLP(d, width, depth, out)
92 opt = torch.optim.Adam(model.parameters(), lr=3e-3)
93 x = torch.randn(64, d)
94 true_w = torch.randn(d, out)
95 labels = x @ true_w + .15 * torch.randn(64, out)
96 tau = depth / width
97 target = torch.tensor(free_moments(tau, 2), dtype=x.dtype)
98 history = []
99 for step in range(steps):
100 pred = model(x)
101 task = ((pred-labels)**2).mean()
102 spec = torch.tensor(0.)
103 if use_spec and step < steps // 3 and step % 8 == 0:
104 moms = jacobian_moments(model, x[:8], 2)
105 spec = ((torch.log(moms + 1e-5) - torch.log(target + 1e-5))**2).sum()
106 loss = task + (0.03 * spec.clamp(max=10.0) if use_spec else 0.)
107 opt.zero_grad(); loss.backward(); opt.step()
108 if step in (0, 19, 39, 79, steps-1):
109 hist_m = jacobian_moments(model, x[:8], 2).detach().numpy()
110 history.append(dict(step=step, task=float(task), spec=float(spec), moments=hist_m.tolist()))
111 final_m = jacobian_moments(model, x[:8], 2).detach().numpy()
112 return dict(final_task=float(((model(x)-labels)**2).mean()), target=target.numpy().tolist(),
113 final_moments=final_m.tolist(), history=history)
114
115
116def main():
117 result = dict(seed=SEED, product_check=product_check(),
118 baseline=train_variant(False), spectral=train_variant(True))
119 with open('results.json', 'w') as f: json.dump(result, f, indent=2)
120 print(json.dumps(result, indent=2))
121
122if __name__ == '__main__':
123 main()