Spectral-gap adaptive halting / smoke.py

Failed on benchmark

Raw ⬇ ZIP
  1import sys, json, math, random
  2from pathlib import Path
  3import numpy as np
  4import torch
  5import torch.nn as nn
  6
  7sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
  8from bench import get_dataset, train_model, make_report
  9
 10SEEDS = tuple(range(1))
 11LRS = (1e-3, 3e-3, 1e-2)
 12CAPS = (2, 3, 4)
 13EPOCHS = 1
 14NTRAIN, NTEST = 400, 200
 15
 16class RefinementNet(nn.Module):
 17    """Independently trained tied-GRU refinement system with optional halting."""
 18    def __init__(self, input_shape, out_dim, cap=4, adaptive=False, delta=.01,
 19                 residual_tol=.025, tau_threshold=3.0):
 20        super().__init__()
 21        inp = int(np.prod(input_shape))
 22        self.enc = nn.Sequential(nn.Linear(inp, 32), nn.Tanh())
 23        self.cell = nn.GRUCell(32, 32)
 24        self.head = nn.Linear(32, out_dim)
 25        self.cap, self.adaptive = cap, adaptive
 26        self.delta, self.residual_tol, self.tau_threshold = delta, residual_tol, tau_threshold
 27        self.last_stats = {}
 28
 29    def forward(self, x):
 30        a = self.enc(x)
 31        b = x.shape[0]
 32        h = torch.zeros(b, 32, device=x.device, dtype=x.dtype)
 33        active = torch.ones(b, dtype=torch.bool, device=x.device)
 34        stopped = torch.zeros(b, dtype=torch.bool, device=x.device)
 35        all_r, all_tau, all_lam = [], [], []
 36        for t in range(self.cap):
 37            hnew = self.cell(a, h)
 38            r = (hnew - h).norm(dim=1) / (hnew.norm(dim=1) + 1e-6)
 39            # A directional finite-difference JVP of the tied block.
 40            with torch.no_grad():
 41                v = torch.ones_like(hnew) / math.sqrt(hnew.shape[1])
 42                eps = 1e-3
 43                hp = self.cell(a.detach(), hnew.detach() + eps * v)
 44                hm = self.cell(a.detach(), hnew.detach() - eps * v)
 45                jv = (hp - hm) / (2 * eps)
 46                lam = jv.norm(dim=1) / (v.norm(dim=1) + 1e-8)
 47                tau = math.pi / torch.clamp(1.0 - lam, min=self.delta)
 48            if self.adaptive and t > 0:
 49                good = active & (r < self.residual_tol) & (tau < self.tau_threshold) & (lam < 1.0)
 50                stopped = stopped | good
 51                active = active & ~good
 52            h = torch.where(active[:, None], hnew, h)
 53            all_r.append(r.detach()); all_tau.append(tau.detach()); all_lam.append(lam.detach())
 54        # If no early exit occurred, actual iterations equal cap; otherwise t+1.
 55        # Reconstruct first stopping index from the recorded residual/lambda traces.
 56        rs, ts, ls = torch.stack(all_r), torch.stack(all_tau), torch.stack(all_lam)
 57        if self.adaptive:
 58            it = torch.full((b,), self.cap, dtype=torch.float32, device=x.device)
 59            for t in range(1, self.cap):
 60                good = (rs[t] < self.residual_tol) & (ts[t] < self.tau_threshold) & (ls[t] < 1.0)
 61                it = torch.where((it == self.cap) & good, torch.tensor(float(t + 1), device=x.device), it)
 62        else:
 63            it = torch.full((b,), float(self.cap), device=x.device)
 64        self.last_stats = {'iterations': it.cpu().tolist(), 'residual': rs.cpu().tolist(),
 65                           'tau': ts.cpu().tolist(), 'lambda': ls.cpu().tolist()}
 66        return self.head(h)
 67
 68
 69def seed_all(seed):
 70    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
 71    if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)
 72
 73
 74def run_one(seed, cfg, adaptive):
 75    seed_all(seed)
 76    d = get_dataset('dynamics', seed, n_train=NTRAIN, n_test=NTEST)
 77    net = RefinementNet(d['input_shape'], d['out_dim'], cap=cfg['cap'], adaptive=adaptive,
 78                        delta=.01, residual_tol=cfg['rtol'], tau_threshold=cfg['tau'])
 79    net, metric, _ = train_model(net, d, epochs=EPOCHS, lr=cfg['lr'], batch=128)
 80    return (float(metric), net.last_stats) if net is not None else (float('nan'), {})
 81
 82
 83def eval_cfg(cfg, adaptive, seeds=SEEDS):
 84    vals, stats = [], []
 85    for s in seeds:
 86        m, st = run_one(s, cfg, adaptive); vals.append(m); stats.append(st)
 87    return {'per_seed': vals, 'mean': float(np.nanmean(vals)), 'cfg': cfg, 'stats': stats}
 88
 89
 90def main():
 91    tried = []
 92    for lr in LRS:
 93        for cap in CAPS:
 94            cfg = {'lr': lr, 'cap': cap, 'rtol': .025, 'tau': 3.0}
 95            r = eval_cfg(cfg, False, seeds=range(1))
 96            tried.append({'cfg': cfg, 'mean': r['mean'], 'per_seed': r['per_seed']})
 97    finite = [x for x in tried if np.isfinite(x['mean'])]
 98    best = min(finite, key=lambda z: z['mean'])['cfg']
 99    base = eval_cfg(best, False, SEEDS)
100    idea_cfgs = [best,
101        {'lr': 1e-3 if best['lr'] != 1e-3 else 3e-3, 'cap': best['cap'], 'rtol': .025, 'tau': 3.0},
102        {'lr': best['lr'], 'cap': 4 if best['cap'] != 4 else 3, 'rtol': .025, 'tau': 3.0}]
103    ideas = [eval_cfg(c, True, SEEDS) for c in idea_cfgs]
104    idea = min(ideas, key=lambda z: z['mean'])
105    pred, obs = [], []
106    for st in idea['stats']:
107        if st:
108            pred.extend(np.asarray(st['tau']).reshape(-1).tolist())
109            obs.extend(np.asarray(st['iterations']).reshape(-1).tolist())
110    pred, obs = np.asarray(pred), np.asarray(obs)
111    corr = float(np.corrcoef(pred, obs)[0, 1]) if len(pred) > 1 and np.std(pred) > 0 and np.std(obs) > 0 else 0.0
112    signature = {'quantity': 'trained-model predicted local tau vs observed adaptive iterations',
113        'n_examples': int(len(pred)), 'predicted_tau_mean': float(pred.mean()) if len(pred) else None,
114        'observed_iterations_mean': float(obs.mean()) if len(obs) else None,
115        'correlation': corr, 'confirmed': bool(len(pred) >= 100 and corr > 0.3)}
116    report = make_report('dynamics', 'rnn_small', {'best_cfg': best, 'sweep': tried, 'full': base}, idea,
117                         extra=signature)
118    report['idea_alternatives'] = ideas
119    report['notes'] = 'Matched dynamics track; fixed and adaptive tied-GRU systems trained independently with train_model and identical shared hyperparameters.'
120    Path('bench_report.json').write_text(json.dumps(report, indent=2, allow_nan=False))
121    print(json.dumps(report, indent=2))
122
123if __name__ == '__main__': main()