import sys, json, math, time from pathlib import Path import numpy as np import torch from torch import nn sys.path.insert(0, '/home/maxwelhelp/all/math2nn') from bench import get_dataset, make_model, train_model, evaluate, sweep_baseline, make_report TRACK='dynamics'; MODEL='rnn_small'; BATCH=128 # Union grid: every idea setting is also a baseline setting. GRID=[{'lr':1.5e-3,'epochs':10},{'lr':3e-3,'epochs':10},{'lr':6e-3,'epochs':10}] def seed_all(seed): np.random.seed(seed); torch.manual_seed(seed) if torch.cuda.is_available(): try: torch.cuda.manual_seed_all(seed) except Exception: pass def baseline_fn(cfg): def run(seed): seed_all(seed) d=get_dataset(TRACK, seed, n_train=400, n_test=160) net=make_model(MODEL,d['input_shape'],d['out_dim']) _, metric, _=train_model(net,d,epochs=cfg['epochs'],lr=cfg['lr'],batch=BATCH,log=lambda *_:None) return float(metric) return run def adaptive_train(seed, cfg, return_sig=False): seed_all(seed) d=get_dataset(TRACK, seed, n_train=400, n_test=160) net=make_model(MODEL,d['input_shape'],d['out_dim']) # Explicit fallback ladder, matching bench's robust device contract. device='cuda' if torch.cuda.is_available() else 'cpu' try: net=net.to(device) x,y=d['xtr'].to(device),d['ytr'].to(device) xt,yt=d['xte'].to(device),d['yte'].to(device) except Exception: device='cpu'; net=net.to(device); x,y=d['xtr'],d['ytr']; xt,yt=d['xte'],d['yte'] opt=torch.optim.Adam(net.parameters(),lr=cfg['lr']) loss_fn=nn.MSELoss() n=len(x); order=np.arange(n); rng=np.random.default_rng(seed+991) prev_pred=None; stats=[]; total_steps=0 # The FIFO window is over recent mini-batches, analogous to stale flow data. K=4; Kmin=1; Kmax=4; eta_min=.35; drift_max=.12 recent=[] for ep in range(cfg['epochs']): rng.shuffle(order) batches=[order[i:i+BATCH] for i in range(0,n,BATCH)] recent.extend(batches) recent=recent[-K:] # behavior diagnostic on a deterministic probe, measured from this model net.eval() with torch.no_grad(): pred=net(x).detach() resid=(pred-y).flatten().cpu().numpy() scale=float(np.std(resid)+1e-6) lw=-np.abs(resid)/scale z=lw-lw.max(); w=np.exp(z); eta=float(w.sum()**2/(len(w)*(w*w).sum()+1e-12)) drift=0.0 if prev_pred is None else float(torch.mean((pred-prev_pred)**2).sqrt().cpu()) prev_pred=pred # Drift-normalized by current output scale; this is observable model behavior. pscale=float(pred.std().cpu()+1e-6); drift/=pscale stats.append((eta,drift,K)) if drift>drift_max: K=max(Kmin,K//2) elif drift<.04 and eta>.7: K=min(Kmax,K+1) # Equal-budget intervention: always perform exactly the baseline's # batches_per_epoch updates. K only controls which recent batches are # replayed, never the optimizer-step count. steps_per_epoch=math.ceil(n/BATCH) selected=[] for j in range(steps_per_epoch): selected.append(recent[j % len(recent)]) for bi in selected: idx=torch.as_tensor(bi,device=device,dtype=torch.long) opt.zero_grad(set_to_none=True); out=net(x[idx]); loss=loss_fn(out,y[idx]); loss.backward(); opt.step(); total_steps+=1 net.eval() with torch.no_grad(): metric=float(torch.mean((net(xt)-yt)**2).cpu()) sig={'mean_eta':float(np.mean([s[0] for s in stats])), 'min_eta':float(np.min([s[0] for s in stats])), 'mean_drift':float(np.mean([s[1] for s in stats])), 'low_eta_levels':int(sum(s[0]0 and sig['mean_drift']>0) rep=make_report(TRACK,MODEL,base,idea,{'mechanism_signature':sig,'math_check':math_check, 'idea_sweep':idea_sweep,'runtime_sec':time.time()-t}) Path('bench_report.json').write_text(json.dumps(rep,indent=2)) print(json.dumps(rep,indent=2)) if __name__=='__main__': main()