Carrier-Probed Hidden-State Training / carrier_bench.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, evaluate, sweep_baseline, make_report
  7
  8SEEDS = tuple(range(8))
  9EPOCHS = 15
 10BATCH = 128
 11# Union of learning rates is shared by baseline and idea-side candidates.
 12LR_GRID = [1e-3, 3e-3, 1e-2]
 13AMP_GRID = [0.05, 0.15, 0.30]
 14GAMMA = 0.15
 15
 16def seed_all(seed):
 17    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
 18    if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)
 19
 20def nominal_delta(x, amp, carrier):
 21    """First-order known pendulum response for the carrier on the final state.
 22    This is used only as the training consistency target, not as the benchmark metric."""
 23    z = x.view(-1, 8, 3)
 24    th, om, u = z[:, -1, 0], z[:, -1, 1], z[:, -1, 2]
 25    dt = 0.05
 26    # Four substeps, nominal g and damping, matching the data generator form.
 27    th0, om0 = th, om
 28    carr = carrier if torch.is_tensor(carrier) else torch.full_like(u, float(carrier))
 29    # The benchmark target is a one-step local response at the final observed state.
 30    # Use the final element of the injected sequence, broadcast over the batch.
 31    uc = u + carr[:, -1] if carr.ndim == 2 else u + carr
 32    for _ in range(4):
 33        om = om + (-9.81 / 10 * torch.sin(th) - .25 * om + 2.0 * uc) * dt / 4
 34        th = th + om * dt / 4
 35    for _ in range(4):
 36        om0 = om0 + (-9.81 / 10 * torch.sin(th0) - .25 * om0 + 2.0 * u) * dt / 4
 37        th0 = th0 + om0 * dt / 4
 38    return th - th0
 39
 40def carrier_tensor(n, amp, device):
 41    # Structured low-frequency carrier across the 8-step control channel.
 42    t = torch.arange(8, device=device, dtype=torch.float32)
 43    c = torch.cos(2.0 * t / 8.0 * 2.0 * np.pi)
 44    return (amp * c).view(1, 8, 1).expand(n, -1, -1)
 45
 46def train_carrier(ds, epochs, lr, amp, seed):
 47    seed_all(seed)
 48    model = make_model('rnn_small', ds['input_shape'], ds['out_dim'])
 49    # Robust explicit device fallback, as this loop is the intervention itself.
 50    device = 'cuda' if torch.cuda.is_available() else 'cpu'
 51    try:
 52        model = model.to(device)
 53        opt = torch.optim.Adam(model.parameters(), lr=lr)
 54        lossf = nn.MSELoss()
 55        x, y = ds['xtr'].to(device), ds['ytr'].to(device)
 56        for _ in range(epochs):
 57            model.train(); perm = torch.randperm(len(x), device=device)
 58            for i in range(0, len(x), BATCH):
 59                ix = perm[i:i+BATCH]; xb, yb = x[ix], y[ix]
 60                c = carrier_tensor(len(ix), amp, device)
 61                xp = xb.view(-1, 8, 3).clone()
 62                xp[:, :, 2:3] = xp[:, :, 2:3] + c
 63                # Carrier rollout prediction and passive prediction share weights.
 64                pred_p = model(xb)
 65                pred_c = model(xp.reshape(len(ix), -1))
 66                target_delta = nominal_delta(xb, amp, c[:, :, 0])
 67                loss = lossf(pred_p, yb) + GAMMA * lossf(pred_c - pred_p, target_delta[:, None])
 68                opt.zero_grad(); loss.backward(); opt.step()
 69        model.eval()
 70        with torch.no_grad():
 71            out = model(ds['xte'].to(device))
 72            metric = float(((out - ds['yte'].to(device)) ** 2).mean())
 73        return metric, model, device
 74    except RuntimeError:
 75        model = model.to('cpu')
 76        opt = torch.optim.Adam(model.parameters(), lr=lr)
 77        x, y = ds['xtr'], ds['ytr']
 78        for _ in range(epochs):
 79            perm = torch.randperm(len(x))
 80            for i in range(0, len(x), BATCH):
 81                ix=perm[i:i+BATCH]; xb,yb=x[ix],y[ix]
 82                c=carrier_tensor(len(ix),amp,'cpu'); xp=xb.view(-1,8,3).clone(); xp[:,:,2:3]+=c
 83                pp=model(xb); pc=model(xp.reshape(len(ix),-1)); td=nominal_delta(xb,amp,c[:,:,0])
 84                loss=nn.functional.mse_loss(pp,yb)+GAMMA*nn.functional.mse_loss(pc-pp,td[:,None])
 85                opt.zero_grad(); loss.backward(); opt.step()
 86        with torch.no_grad(): metric=float(((model(ds['xte'])-ds['yte'])**2).mean())
 87        return metric, model, 'cpu'
 88
 89def main():
 90    ds0 = get_dataset('dynamics', 0, n_train=400, n_test=200)
 91    # Canonical baseline sweep; all candidate learning rates are included here.
 92    def base_fn(cfg):
 93        def run(seed):
 94            seed_all(seed); ds=get_dataset('dynamics', seed, 400, 200)
 95            net=make_model('rnn_small', ds['input_shape'], ds['out_dim'])
 96            _, metric, _=train_model(net, ds, epochs=EPOCHS, lr=cfg['lr'], batch=BATCH, log=lambda *_: None)
 97            return metric
 98        return run
 99    base_block=sweep_baseline(base_fn, [{'lr':v} for v in LR_GRID])
100    best_lr=base_block['best_cfg']['lr']
101    idea_blocks=[]
102    for amp in AMP_GRID:
103        r=evaluate(lambda seed, a=amp: train_carrier(get_dataset('dynamics', seed, 400, 200), EPOCHS, best_lr, a, seed)[0], SEEDS)
104        idea_blocks.append({'amp':amp,'result':r})
105    best=min(idea_blocks, key=lambda q:q['result']['mean'])
106    # Re-train best configuration to retain models for an NN-scale behavior signature.
107    sig=[]
108    for seed in SEEDS:
109        ds=get_dataset('dynamics', seed, 400, 200)
110        metric, model, dev=train_carrier(ds,EPOCHS,best_lr,best['amp'],seed)
111        with torch.no_grad():
112            x=ds['xte'][:128].to(dev); c=carrier_tensor(len(x),best['amp'],dev)
113            xp=x.view(-1,8,3).clone(); xp[:,:,2:3]+=c
114            dp=(model(xp.reshape(len(x),-1))-model(x)).squeeze(1).cpu().numpy()
115            actual=nominal_delta(x,best['amp'],c[:,:,0]).cpu().numpy()
116        sig.append((float(np.mean(np.abs(dp))),float(np.mean(np.abs(actual))),float(np.corrcoef(dp,actual)[0,1])))
117    sig_arr=np.asarray(sig)
118    mechanism={'carrier_amp':best['amp'],'predicted_abs_delta_mean':float(sig_arr[:,0].mean()),'observed_nominal_abs_delta_mean':float(sig_arr[:,1].mean()),'predicted_observed_correlation_mean':float(sig_arr[:,2].mean()),'quadratic_prediction':'not confirmed at NN scale: only one amplitude was behaviorally tested','confirmed':False}
119    report=make_report('dynamics','rnn_small',base_block,best['result'],{'mechanism_signature':mechanism,'idea_sweep':idea_blocks,'track_justification':'Dynamics matches the proposed hidden-state observability/reachability and stability/control structure.'})
120    report['idea_sweep']=idea_blocks
121    with open('bench_report.json','w') as f: json.dump(report,f,indent=2)
122    print(json.dumps(report,indent=2))
123if __name__=='__main__': main()