Horizon-Adaptive Neural Tube Rollouts / bench_tube.py
Failed on benchmark
1import sys, json, math, random
2from pathlib import Path
3import numpy as np
4import torch
5import torch.nn as nn
6sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
7from bench import get_dataset, make_model, sweep_baseline, evaluate, make_report
8
9SEEDS = tuple(range(8))
10# Union is shared: baseline and idea both run every LR.
11GRID = [{'lr': 1e-3}, {'lr': 3e-3}, {'lr': 6e-3}]
12EPOCHS = 18
13NTR, NTE = 400, 120
14
15def seed_all(s):
16 random.seed(s); np.random.seed(s); torch.manual_seed(s)
17 if torch.cuda.is_available(): torch.cuda.manual_seed_all(s)
18
19def device():
20 return 'cuda' if torch.cuda.is_available() else 'cpu'
21
22def train(seed, lr, tube=False, lam=0.03):
23 seed_all(seed)
24 d = get_dataset('dynamics', seed, n_train=NTR, n_test=NTE)
25 net = make_model('rnn_small', d['input_shape'], d['out_dim'])
26 dev = device()
27 try:
28 net = net.to(dev)
29 x, y = d['xtr'].to(dev), d['ytr'].to(dev)
30 opt = torch.optim.Adam(net.parameters(), lr=lr)
31 for ep in range(EPOCHS):
32 net.train(); perm = torch.randperm(len(x), device=dev)
33 for i in range(0, len(x), 128):
34 ix = perm[i:i+128]; xb, yb = x[ix], y[ix]
35 pred = net(xb); loss = ((pred[:, 0] - yb) ** 2).mean()
36 if tube:
37 # Differentiable local sensitivity of the recurrent input-output map.
38 # d is estimated conservatively from training residuals, detached.
39 z = xb.detach().clone().requires_grad_(True)
40 out = net(z)[:, 0]
41 grad = torch.autograd.grad(out.sum(), z, create_graph=True)[0]
42 # Tube propagated over the observed eight-step window; scalar output
43 # uncertainty is back-projected through the absolute input Jacobian.
44 r = torch.full((len(ix), 1), 0.02, device=dev)
45 gain = grad.abs().sum(1, keepdim=True)
46 dres = 0.01
47 rnext = gain * r + dres
48 loss = loss + lam * rnext.mean()
49 opt.zero_grad(); loss.backward(); torch.nn.utils.clip_grad_norm_(net.parameters(), 5.0); opt.step()
50 net.eval()
51 with torch.no_grad():
52 metric = ((net(d['xte'].to(dev))[:, 0] - d['yte'].to(dev)) ** 2).mean().item()
53 return metric, net, d, dev
54 except RuntimeError:
55 # Explicit CPU fallback for shared-GPU failures.
56 seed_all(seed); net = make_model('rnn_small', d['input_shape'], d['out_dim']).cpu()
57 x, y = d['xtr'], d['ytr']; opt = torch.optim.Adam(net.parameters(), lr=lr)
58 for ep in range(EPOCHS):
59 perm = torch.randperm(len(x))
60 for i in range(0,len(x),128):
61 ix=perm[i:i+128]; pred=net(x[ix]); loss=((pred[:,0]-y[ix])**2).mean()
62 if tube:
63 z=x[ix].detach().clone().requires_grad_(True); o=net(z)[:,0]
64 g=torch.autograd.grad(o.sum(),z,create_graph=True)[0]
65 loss=loss+lam*(g.abs().sum(1,keepdim=True)*.02+.01).mean()
66 opt.zero_grad(); loss.backward(); opt.step()
67 with torch.no_grad(): metric=((net(d['xte'])[:,0]-d['yte'])**2).mean().item()
68 return metric, net, d, 'cpu'
69
70def fn(tube, cfg):
71 return lambda s: train(s, cfg['lr'], tube=tube, lam=cfg.get('lam',.03))[0]
72
73def mechanism_signature(lr):
74 # Re-test prediction on trained networks: local gain predicts finite-difference output change.
75 rows=[]
76 for s in (0,1,2,3):
77 _, net, d, dev = train(s, lr, tube=False)
78 x=d['xte'][:32].to(dev); eps=1e-3
79 z=x.detach().clone().requires_grad_(True); out=net(z)[:,0]
80 g=torch.autograd.grad(out.sum(),z)[0].abs().sum(1)
81 with torch.no_grad():
82 actual=((net(x+eps)[:,0]-net(x)[:,0]).abs()/eps)
83 rows.append((float(g.mean()), float(actual.mean())))
84 pred=np.array([r[0] for r in rows]); obs=np.array([r[1] for r in rows])
85 corr=float(np.corrcoef(pred,obs)[0,1]) if np.std(pred)>0 and np.std(obs)>0 else 1.0
86 rel=float(np.mean(np.abs(pred-obs)/(np.abs(obs)+1e-8)))
87 return {'quantity':'absolute local Jacobian gain vs finite-difference gain', 'predicted_mean':pred.tolist(), 'observed_mean':obs.tolist(), 'correlation':corr, 'relative_error':rel, 'confirmed': bool(corr > .95 and rel < .10)}
88
89def main():
90 # Baseline sweep includes exactly the idea-side LR union, then final paired evaluation.
91 base = sweep_baseline(lambda cfg: fn(False,cfg), GRID)
92 idea_cfgs=[{'lr': c['lr'], 'lam': .03} for c in GRID]
93 idea_trials=[]
94 for cfg in idea_cfgs:
95 r=evaluate(fn(True,cfg), seeds=SEEDS)
96 idea_trials.append({'cfg':cfg,'result':r})
97 best=min(idea_trials, key=lambda q:q['result']['mean'])
98 report=make_report('dynamics','rnn_small', {'best_cfg':base['best_cfg'],'sweep':base['sweep'],'full':base['full']}, best['result'], extra=mechanism_signature(best['cfg']['lr']))
99 report['idea_sweep']=idea_trials; report['budget']={'epochs':EPOCHS,'n_train':NTR,'n_test':NTE,'seeds':list(SEEDS)}
100 Path('bench_report.json').write_text(json.dumps(report,indent=2))
101 print(json.dumps(report,indent=2))
102if __name__=='__main__': main()