Hilbert-Schmidt-scale KSD loss / stage2_bench.py
Mechanism confirmed, baseline not beaten
1import sys, json, 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 make_model, get_dataset, evaluate, sweep_baseline, make_report
8
9SEEDS = tuple(range(8))
10SWEEP_SEEDS = tuple(range(4))
11GRID = [{"lr": 1e-3}, {"lr": 3e-3}, {"lr": 1e-2}]
12EPOCHS, BATCH, GAMMA = 25, 64, 0.5
13
14def seed_all(s):
15 random.seed(s); np.random.seed(s); torch.manual_seed(s)
16 if torch.cuda.is_available(): torch.cuda.manual_seed_all(s)
17
18def stein_h(x, gamma=GAMMA):
19 # Target is N(0,1), score s(x)=-x, and RBF exp(-gamma*(x-y)^2).
20 dif=x[:,None,:]-x[None,:,:]
21 r2=(dif*dif).sum(-1)
22 dot=x@x.T
23 d=1
24 return torch.exp(-gamma*r2)*(dot + 2*gamma*d - (2*gamma+4*gamma*gamma)*r2)
25
26def objective(out, kind):
27 h=stein_h(out)
28 n=out.shape[0]
29 if kind == 'v':
30 return torch.sqrt(torch.relu(h.mean()) + 1e-8)
31 u=(h.sum()-torch.diagonal(h).sum())/(n*(n-1))
32 return torch.sqrt(torch.relu(u) + 1e-8)
33
34def train_system(seed, cfg, kind, return_model=False):
35 seed_all(seed)
36 ds=get_dataset('gaussian_score_matching_1d', seed, 400, 400)
37 net=make_model('mlp_tiny', (1,), 1)
38 # train_model cannot express the intervention, so this local loop changes
39 # only the loss while retaining Adam, batch size, epochs, and architecture.
40 use_cuda=torch.cuda.is_available()
41 device=torch.device('cuda' if use_cuda else 'cpu')
42 try:
43 net.to(device)
44 x= torch.as_tensor(ds['xtr'], dtype=torch.float32, device=device)
45 opt=torch.optim.Adam(net.parameters(), lr=cfg['lr'])
46 for _ in range(EPOCHS):
47 perm=torch.randperm(len(x), device=device)
48 for i in range(0,len(x),BATCH):
49 o=net(x[perm[i:i+BATCH]])
50 loss=objective(o,kind)
51 opt.zero_grad(); loss.backward(); opt.step()
52 with torch.no_grad():
53 pred=net(torch.as_tensor(ds['xte'],dtype=torch.float32,device=device))
54 metric=float(((pred-torch.as_tensor(ds['yte'],dtype=torch.float32,device=device))**2).mean().cpu())
55 return (metric, net.cpu(), ds) if return_model else metric
56 except Exception:
57 # Explicit CPU fallback for CUDA/runtime failures.
58 seed_all(seed); device=torch.device('cpu'); net=make_model('mlp_tiny',(1,),1).to(device)
59 x=torch.as_tensor(ds['xtr'],dtype=torch.float32)
60 opt=torch.optim.Adam(net.parameters(),lr=cfg['lr'])
61 for _ in range(EPOCHS):
62 perm=torch.randperm(len(x))
63 for i in range(0,len(x),BATCH):
64 loss=objective(net(x[perm[i:i+BATCH]]),kind)
65 opt.zero_grad(); loss.backward(); opt.step()
66 with torch.no_grad():
67 p=net(torch.as_tensor(ds['xte'],dtype=torch.float32))
68 metric=float(((p-torch.as_tensor(ds['yte'],dtype=torch.float32))**2).mean())
69 return (metric,net,ds) if return_model else metric
70
71def train_fn(kind, cfg):
72 return lambda seed: train_system(seed,cfg,kind)
73
74def signature(cfg, kind):
75 metric, net, ds=train_system(0,cfg,kind,True)
76 with torch.no_grad(): out=net(torch.as_tensor(ds['xte'],dtype=torch.float32))
77 h=stein_h(out).numpy(); n=len(out)
78 diag=np.diag(h); off=(h.sum()-diag.sum())/(n*(n-1)); v=float(np.sqrt(max(h.mean(),0))); u=float(np.sqrt(max(off,0)))
79 # Bootstrap the trained model's actual outputs, not an analytic toy graph.
80 rng=np.random.RandomState(395); vs=[]; us=[]
81 for _ in range(80):
82 ix=rng.choice(n,64,replace=False); hh=h[np.ix_(ix,ix)]; dd=np.diag(hh)
83 vs.append(np.sqrt(max(float(hh.mean()),0))); us.append(np.sqrt(max(float((hh.sum()-dd.sum())/(63*64)),0)))
84 # Empirical covariance spectrum gives the predicted HS/trace fluctuation ratio.
85 arr=out.numpy().reshape(-1); c=np.atleast_2d(np.cov(arr, rowvar=False)); ev=np.linalg.eigvalsh(c); ev=np.maximum(ev,0)
86 pred=float(np.sqrt(np.sqrt((ev*ev).sum())/max(ev.sum(),1e-12)))
87 obs=float(np.std(us,ddof=1)/max(np.std(vs,ddof=1),1e-12))
88 return {"predicted_hs_over_trace_scale":pred,"observed_bootstrap_sd_U_over_V":obs,
89 "trained_output_v":v,"trained_output_u":u,"test_mse":metric,
90 "confirmed": bool(np.isfinite(obs) and abs(np.log(max(obs,1e-12)/max(pred,1e-12)))<1.0)}
91
92def main():
93 base=sweep_baseline(lambda c: train_fn('v',c), GRID, seeds=SWEEP_SEEDS)
94 # Equal-sized idea sweep on exactly the union of baseline learning rates.
95 idea_trials=[]
96 for c in GRID:
97 r=evaluate(train_fn('u',c), seeds=SWEEP_SEEDS)
98 idea_trials.append({"cfg":c,"mean":r['mean']})
99 best=min(idea_trials,key=lambda q:q['mean'])['cfg']
100 idea=evaluate(train_fn('u',best), seeds=SEEDS)
101 sig=signature(best,'u')
102 rep=make_report('gaussian_score_matching_1d','mlp_tiny',base,idea,
103 {"mechanism_signature":sig,"custom_track":{"name":"gaussian_score_matching_1d","file":"ksd_track_1d.py","domain":"loss"},
104 "idea_sweep":idea_trials,"training":{"epochs":EPOCHS,"batch":BATCH,"gamma":GAMMA}})
105 Path('bench_report.json').write_text(json.dumps(rep,indent=2))
106 print(json.dumps(rep,indent=2))
107if __name__=='__main__': main()