Moment-Sharp Spectral-Norm Control / run_stage2.py
Mechanism confirmed, baseline not beaten
1import json, math, random, sys
2import numpy as np
3import torch
4
5sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
6from bench import get_dataset, make_model, train_model, evaluate, sweep_baseline, make_report
7from moment_sharp_bench import moment_sharp_rescale, mechanism_signature
8
9TRACK, MODEL = 'tabular', 'mlp_tiny'
10EPOCHS, BATCH, NTR, NTE = 12, 128, 1000, 400
11# Union of baseline and idea settings: all idea lrs are baseline-evaluated.
12LRS = [0.0015, 0.003, 0.006]
13WDS = [0.0, 1e-4, 1e-3]
14BASE_GRID = [{'lr': lr, 'weight_decay': wd} for lr in LRS for wd in WDS]
15IDEA_GRID = [{'lr': 0.003, 'weight_decay': 1e-4, 'target_sigma': 2.0},
16 {'lr': 0.0015, 'weight_decay': 1e-4, 'target_sigma': 2.0},
17 {'lr': 0.006, 'weight_decay': 1e-4, 'target_sigma': 2.0}]
18SWEEP_SEEDS = (0, 1, 2, 3)
19FULL_SEEDS = tuple(range(8))
20
21
22def seed_all(seed):
23 random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
24 if torch.cuda.is_available():
25 try: torch.cuda.manual_seed_all(seed)
26 except Exception: pass
27
28
29def baseline_fn(cfg):
30 def run(seed):
31 seed_all(seed)
32 ds = get_dataset(TRACK, seed, n_train=NTR, n_test=NTE)
33 net = make_model(MODEL, ds['input_shape'], ds['out_dim'])
34 _, metric, _ = train_model(net, ds, epochs=EPOCHS, lr=cfg['lr'],
35 batch=BATCH, weight_decay=cfg['weight_decay'], log=lambda *_: None)
36 return float(metric)
37 return run
38
39
40def idea_run(cfg, seed, want_signature=False):
41 seed_all(seed)
42 ds = get_dataset(TRACK, seed, n_train=NTR, n_test=NTE)
43 net = make_model(MODEL, ds['input_shape'], ds['out_dim'])
44 # Same AdamW and minibatch schedule as bench, with control after each update.
45 device = 'cuda' if torch.cuda.is_available() else 'cpu'
46 try:
47 net = net.to(device)
48 x, y = ds['xtr'].to(device), ds['ytr'].to(device)
49 opt = torch.optim.AdamW(net.parameters(), lr=cfg['lr'], weight_decay=cfg['weight_decay'])
50 gen = torch.Generator(device=device); gen.manual_seed(seed + 10000)
51 ema = {}
52 net.train()
53 for _ in range(EPOCHS):
54 order = torch.randperm(len(x), generator=gen, device=device)
55 for start in range(0, len(x), BATCH):
56 ix = order[start:start+BATCH]
57 opt.zero_grad(set_to_none=True)
58 pred = net(x[ix]); loss = torch.nn.functional.mse_loss(pred, y[ix])
59 loss.backward(); opt.step()
60 moment_sharp_rescale(net, target_sigma=cfg['target_sigma'], probes=8,
61 ema=ema, generator=gen)
62 net.eval()
63 with torch.no_grad():
64 metric = float(torch.nn.functional.mse_loss(net(ds['xte'].to(device)), ds['yte'].to(device)).cpu())
65 sig = mechanism_signature(net, target_sigma=cfg['target_sigma']) if want_signature else None
66 return metric, sig
67 except Exception:
68 # Explicit CUDA -> CPU fallback, preserving deterministic config/seed.
69 seed_all(seed)
70 device = 'cpu'; net = make_model(MODEL, ds['input_shape'], ds['out_dim'])
71 x, y = ds['xtr'], ds['ytr']
72 opt = torch.optim.AdamW(net.parameters(), lr=cfg['lr'], weight_decay=cfg['weight_decay'])
73 gen = torch.Generator().manual_seed(seed + 10000); ema = {}
74 net.train()
75 for _ in range(EPOCHS):
76 order = torch.randperm(len(x), generator=gen)
77 for start in range(0, len(x), BATCH):
78 ix = order[start:start+BATCH]; opt.zero_grad(set_to_none=True)
79 torch.nn.functional.mse_loss(net(x[ix]), y[ix]).backward(); opt.step()
80 moment_sharp_rescale(net, target_sigma=cfg['target_sigma'], probes=8, ema=ema, generator=gen)
81 net.eval()
82 with torch.no_grad(): metric = float(torch.nn.functional.mse_loss(net(ds['xte']), ds['yte']))
83 sig = mechanism_signature(net, target_sigma=cfg['target_sigma']) if want_signature else None
84 return metric, sig
85
86
87def idea_fn(cfg):
88 return lambda seed: idea_run(cfg, seed)[0]
89
90
91def main():
92 base = sweep_baseline(baseline_fn, BASE_GRID, seeds=SWEEP_SEEDS)
93 # Evaluate each idea setting on the full paired seeds; select by mean.
94 idea_trials = []
95 for cfg in IDEA_GRID:
96 r = evaluate(idea_fn(cfg), seeds=FULL_SEEDS)
97 idea_trials.append({'cfg': cfg, 'result': r})
98 best_trial = min(idea_trials, key=lambda z: z['result']['mean'])
99 best_cfg = best_trial['cfg']
100 idea_res = best_trial['result']
101 signatures = [idea_run(best_cfg, s, want_signature=True)[1] for s in FULL_SEEDS]
102 sig = {'per_seed': signatures,
103 'predicted_bound_sigma_mean': float(np.mean([np.mean(x['predicted_bound_sigma']) for x in signatures])),
104 'observed_true_sigma_mean': float(np.mean([np.mean(x['observed_true_sigma']) for x in signatures])),
105 'max_bound_minus_observed': float(max(x['max_bound_minus_observed'] for x in signatures)),
106 'confirmed': bool(all(x['confirmed'] for x in signatures))}
107 extra = {'mechanism_signature': sig, 'idea_trials': idea_trials,
108 'protocol_note': 'tabular is structurally matched: regularization/stability of linear layers.'}
109 report = make_report(TRACK, MODEL, base, idea_res, extra)
110 report['math_check'] = json.load(open('stage2_report.json'))['math_check'] if __import__('os').path.exists('stage2_report.json') else None
111 with open('bench_report.json', 'w') as f: json.dump(report, f, indent=2)
112 print(json.dumps(report, indent=2))
113
114if __name__ == '__main__': main()