Moment-Sharp Spectral-Norm Control / run_stage2.py

Mechanism confirmed, baseline not beaten

Raw ⬇ ZIP
  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()