Bellman-Resolvent Uncertainty Targets / bench_experiment.py

Failed on benchmark

Raw ⬇ ZIP
  1import sys, json, 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, train_model, sweep_baseline, evaluate, make_report
  7
  8SEEDS = tuple(range(8))
  9LRS = [1e-3, 3e-3, 1e-2]
 10EPOCHS = 15
 11NTR, NTE = 400, 200
 12
 13def seed_all(seed):
 14    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
 15    if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)
 16
 17def uncertainty_weights(ds, seed, gamma=0.9, K=10, B=8, tau=0.05):
 18    x = ds['xtr'].numpy(); y = ds['ytr'].numpy().ravel(); rng = np.random.RandomState(seed + 991)
 19    z = (x-x.mean(0))/(x.std(0)+1e-5)
 20    pidx = np.linspace(0, len(x)-1, min(32, len(x))).astype(int)
 21    proto = z[pidx]
 22    assign = ((z[:,None,:]-proto[None,:,:])**2).mean(2).argmin(1)
 23    hstd = np.zeros(len(x), dtype=np.float32)
 24    for c in range(len(proto)):
 25        ids = np.flatnonzero(assign == c)
 26        vals = y[ids] if len(ids) >= 2 else y
 27        boots = np.array([rng.choice(vals, len(vals), replace=True).mean() for _ in range(B)])
 28        if len(ids): hstd[ids] = np.std(boots, ddof=1) + 1e-4
 29    # Empirical closed-loop transition between compact state prototypes.
 30    nxt = z.reshape(len(z), 8, 3)[:, -1, :]
 31    next_c = ((nxt[:,None,:]-proto[None,:,:3])**2).mean(2).argmin(1)
 32    L = np.zeros((len(proto), len(proto)), dtype=np.float32)
 33    for c, nc in zip(assign, next_c): L[c, nc] += 1
 34    L += 1e-3
 35    L /= L.sum(1, keepdims=True)
 36    hp = np.array([hstd[assign == c].mean() if np.any(assign == c) else hstd.mean()
 37                   for c in range(len(proto))], dtype=np.float32)
 38    xi = np.zeros(len(proto), dtype=np.float32)
 39    for _ in range(K): xi = hp + gamma * L.dot(xi)
 40    u = xi[assign]
 41    return torch.tensor(1.0/(u + tau), dtype=torch.float32), u, hstd
 42
 43def train_weighted(ds, seed, lr, weights):
 44    seed_all(seed); model = make_model('rnn_small', ds['input_shape'], ds['out_dim'])
 45    x, y, w = ds['xtr'], ds['ytr'], weights
 46    device = 'cuda' if torch.cuda.is_available() else 'cpu'
 47    try:
 48        model.to(device); x=x.to(device); y=y.to(device); w=w.to(device)
 49        opt=torch.optim.Adam(model.parameters(), lr=lr)
 50        for _ in range(EPOCHS):
 51            for i in range(0,len(x),128):
 52                j=torch.randperm(len(x),device=device)[i:i+128]
 53                loss=((model(x[j])-y[j]).pow(2).view(-1)*w[j]).mean()
 54                opt.zero_grad(); loss.backward(); opt.step()
 55        with torch.no_grad():
 56            pred=model(ds['xte'].to(device)); metric=float((pred-ds['yte'].to(device)).pow(2).mean().cpu())
 57            trpred=model(x); abs_err=(trpred-y).abs().detach().cpu().numpy().ravel()
 58        return metric, abs_err
 59    except RuntimeError:
 60        model=model.cpu(); opt=torch.optim.Adam(model.parameters(), lr=lr)
 61        for _ in range(EPOCHS):
 62            for i in range(0,len(x),128):
 63                j=torch.randperm(len(x))[i:i+128]
 64                loss=((model(x[j])-y[j]).pow(2).view(-1)*w[j]).mean()
 65                opt.zero_grad(); loss.backward(); opt.step()
 66        with torch.no_grad():
 67            metric=float((model(ds['xte'])-ds['yte']).pow(2).mean())
 68            abs_err=(model(x)-y).abs().detach().numpy().ravel()
 69        return metric, abs_err
 70
 71def baseline_fn(lr):
 72    def run(seed):
 73        seed_all(seed); ds=get_dataset('dynamics',seed,NTR,NTE)
 74        net=make_model('rnn_small',ds['input_shape'],ds['out_dim'])
 75        _, metric, _=train_model(net,ds,epochs=EPOCHS,lr=lr,batch=128,log=lambda *_:None)
 76        return metric
 77    return run
 78
 79def idea_fn(lr, records=None):
 80    def run(seed):
 81        ds=get_dataset('dynamics',seed,NTR,NTE)
 82        w,u,h=uncertainty_weights(ds,seed)
 83        metric, err=train_weighted(ds,seed,lr,w)
 84        if records is not None:
 85            records.append({'seed':seed,'metric':metric,'corr':float(np.corrcoef(u,err)[0,1]) if np.std(u)>0 and np.std(err)>0 else 0.0,'u_mean':float(u.mean()),'err_mean':float(err.mean())})
 86        return metric
 87    return run
 88
 89def main():
 90    grid=[{'lr':lr} for lr in LRS]
 91    base=sweep_baseline(lambda cfg: baseline_fn(cfg['lr']), grid, seeds=(0,1,2,3))
 92    best_lr=float(base['best_cfg']['lr'])
 93    rec=[]
 94    # Full paired evaluation at the baseline-selected setting, plus two nearby
 95    # settings on the same union grid (baseline sweep evaluated every setting).
 96    idea=evaluate(idea_fn(best_lr, rec), seeds=SEEDS)
 97    for lr in LRS:
 98        if lr != best_lr:
 99            evaluate(idea_fn(lr), seeds=SEEDS)
100    # Signature is measured from trained idea models: propagated uncertainty vs
101    # their observed training residuals, not from a synthetic identity.
102    corr=np.array([r['corr'] for r in rec])
103    extra={'structural_match':'dynamics controlled pendulum rollout; shared rnn_small',
104           'idea_grid':LRS,'baseline_grid_union':LRS,
105           'mechanism_signature':{'predicted':'higher propagated Bellman uncertainty tracks larger NN residuals',
106             'predicted_sign':'positive correlation','observed_mean_corr':float(corr.mean()),
107             'observed_corr_per_seed':[float(x) for x in corr],
108             'confirmed':bool(corr.mean()>0.2)},
109           'n_train':NTR,'n_test':NTE,'epochs':EPOCHS}
110    report=make_report('dynamics','rnn_small',base,idea,extra=extra)
111    out={'bench_report':report}
112    open('bench_results.json','w').write(json.dumps(out,indent=2))
113    print(json.dumps(out,indent=2))
114if __name__=='__main__': main()