Calorimetric Training Transition Detector / bench_calorimetric.py

Failed on benchmark

Raw ⬇ ZIP
  1import sys, json, math, random
  2import numpy as np
  3import torch
  4import torch.nn as nn
  5sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
  6from bench import get_dataset, make_model, evaluate, sweep_baseline, make_report
  7
  8TRACK='tabular'; MODEL='mlp_tiny'; SEEDS=tuple(range(8))
  9# Union grid is used for both methods: baseline receives every lr tried by idea.
 10LRS=[1e-3, 3e-3, 6e-3]
 11TRAINED_SIGNATURE=[]
 12EPOCHS=18; BATCH=64
 13
 14def seed_all(seed):
 15    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
 16    if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)
 17
 18def loss_fn(ds): return nn.MSELoss()
 19
 20def train_baseline(seed, cfg):
 21    seed_all(seed); ds=get_dataset(TRACK, seed, 400, 200)
 22    net=make_model(MODEL, ds['input_shape'], ds['out_dim'])
 23    # Baseline is standard Adam; custom loop is used so the model and minibatch
 24    # ordering are exactly matched to the intervention.
 25    return run(net, ds, cfg['lr'], pulse=False, seed=seed)[0]
 26
 27def run(net, ds, lr, pulse=False, seed=0):
 28    device='cuda' if torch.cuda.is_available() else 'cpu'
 29    try:
 30        return _run(net, ds, lr, pulse, seed, device)
 31    except RuntimeError:
 32        return _run(net.cpu(), ds, lr, pulse, seed, 'cpu')
 33
 34def _run(net, ds, lr, pulse, seed, device):
 35    net=net.to(device); x=ds['xtr'].to(device); y=ds['ytr'].to(device)
 36    opt=torch.optim.Adam(net.parameters(), lr=lr)
 37    lf=loss_fn(ds); rng=torch.Generator(device=device); rng.manual_seed(seed+991)
 38    qs=[]; responses=[]; triggers=0; recent=[]
 39    for ep in range(EPOCHS):
 40        net.train(); perm=torch.randperm(len(x), generator=rng, device=device)
 41        # Probe every 4 epochs. q is squared parameter displacement / lr.
 42        pulse_now=False
 43        if pulse and ep > 0 and ep % 4 == 0:
 44            # short temperature pulse = doubled batch noise proxy by halving batch
 45            pulse_now=True
 46        batch=BATCH//2 if pulse_now else BATCH
 47        epq=[]
 48        for i in range(0,len(x),batch):
 49            idx=perm[i:i+batch]; old=[p.detach().clone() for p in net.parameters() if p.requires_grad]
 50            out=net(x[idx].view(len(idx),-1)); loss=lf(out,y[idx])
 51            opt.zero_grad(); loss.backward(); opt.step()
 52            q=sum(((p.detach()-o)**2).sum().item() for p,o in zip((p for p in net.parameters() if p.requires_grad),old))/max(lr,1e-12)
 53            epq.append(q)
 54        qmean=float(np.mean(epq)); qs.append(qmean)
 55        if pulse_now:
 56            base=float(np.mean(recent[-2:])) if recent else qmean
 57            excess=qmean-base
 58            responses.append({'epoch':ep,'q_ss':base,'q_pulse':qmean,'C_train':excess/(1.0/(len(x)))})
 59            # detector only changes training when dissipation is an outlier.
 60            if len(recent)>=2 and qmean > 1.8*float(np.median(recent[-4:])):
 61                for g in opt.param_groups: g['lr']*=0.7
 62                triggers+=1
 63        else: recent.append(qmean)
 64    net.eval()
 65    with torch.no_grad(): metric=float(((net(ds['xte'].to(device)).view(-1,1)-ds['yte'].to(device))**2).mean())
 66    return metric, {'q':qs,'responses':responses,'triggers':triggers}
 67
 68def train_idea(seed,cfg):
 69    seed_all(seed); ds=get_dataset(TRACK, seed, 400, 200)
 70    net=make_model(MODEL, ds['input_shape'], ds['out_dim'])
 71    metric, detail = run(net, ds, cfg['lr'], pulse=True, seed=seed)
 72    TRAINED_SIGNATURE.append(detail)
 73    return metric
 74
 75def main():
 76    # Cheap numerical verification of the transfer formula and stability claim.
 77    eta=np.linspace(.2,1.99,10); lam=1.; T=1.; delta=.05; P=20
 78    Cs=[]
 79    for e in eta:
 80        a=1-e*lam; v=2*e*T/(1-a*a); v2=v; q0=4*T/(1+a); ex=0
 81        for _ in range(P):
 82            v2=a*a*v2+2*e*(T+delta); ex += (1-a)**2*v2/e+2*(T+delta)-q0
 83        Cs.append(ex/delta)
 84    sanity={'stable_below_2':bool(np.all(np.abs(1-eta)<1)), 'C_monotone':bool(np.all(np.diff(Cs)>0)), 'C_values':Cs}
 85    grid=[{'lr':v} for v in LRS]
 86    base=sweep_baseline(lambda c: (lambda s: train_baseline(s,c)),grid)
 87    # Three idea settings are exactly the shared union grid, with best selected.
 88    idea_trials=[]
 89    for c in grid:
 90        r=evaluate(lambda s,c=c: train_idea(s,c),SEEDS)
 91        idea_trials.append({'cfg':c,'mean':r['mean']})
 92    best=min(idea_trials,key=lambda z:z['mean'])['cfg']
 93    idea=evaluate(lambda s: train_idea(s,best),SEEDS)
 94    ratios=[r['q_pulse']/r['q_ss'] for d in TRAINED_SIGNATURE for r in d.get('responses',[]) if r['q_ss']>1e-12]
 95    trained={'n_pulses':len(ratios), 'mean_pulse_to_baseline_q_ratio':float(np.mean(ratios)) if ratios else None, 'mean_triggers':float(np.mean([d.get('triggers',0) for d in TRAINED_SIGNATURE])) if TRAINED_SIGNATURE else 0.0}
 96    signature={'prediction':'temperature/minibatch-noise pulse produces transient excess update dissipation', 'predicted':{'pulse_to_baseline_q_ratio':'>1'}, 'observed_on_trained_models':trained, 'confirmed':bool(ratios and trained['mean_pulse_to_baseline_q_ratio']>1.0), 'toy_sanity':sanity}
 97    rep=make_report(TRACK,MODEL,base,idea,signature)
 98    rep['idea']['sweep']=idea_trials; rep['sanity_check']=sanity
 99    with open('bench_report.json','w') as f: json.dump(rep,f,indent=2)
100    print(json.dumps(rep,indent=2))
101if __name__=='__main__': main()