Inverse-Square Fractional Attention / bench_run.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, sweep_baseline, make_report
  9from bench.protocol import evaluate
 10
 11SEEDS = tuple(range(8))
 12SWEEP_SEEDS = (0, 1, 2, 3)
 13EPOCHS = 18
 14NTRAIN, NTEST = 2000, 500
 15D, DEPTH, HEADS = 64, 2, 2
 16S, SIGMA, LAMBDA = 0.8, 0.35, 0.7
 17
 18
 19def kernel_np(x, y, d=3, s=S, sigma=SIGMA, eps=1e-5):
 20    r = np.linalg.norm(x-y, axis=-1) + eps
 21    ux = np.linalg.norm(x, axis=-1) + eps
 22    uy = np.linalg.norm(y, axis=-1) + eps
 23    m = np.minimum(np.minimum(ux/r, uy/r), 1.0)
 24    return r**(s-d) * m**(-sigma)
 25
 26
 27def math_check():
 28    rng = np.random.RandomState(7)
 29    x, y = rng.uniform(-1, 1, (2, 20, 3))
 30    k1 = kernel_np(x, y)
 31    k2 = kernel_np(3*x, 3*y)
 32    hom = np.max(np.abs(k2/(k1*3**(S-3))-1))
 33    near = np.array([[1e-5, 0, 0.]])
 34    far = np.array([[.8, 0, 0.]])
 35    amp = float(kernel_np(near, far)[0] / ((np.linalg.norm(near-far)+1e-5)**(S-3)))
 36    pred = float(((np.linalg.norm(near[0])+1e-5)/(np.linalg.norm(near[0]-far[0])+1e-5))**(-SIGMA))
 37    return {'homogeneity_max_relative_error': float(hom),
 38            'near_origin_amplification': amp, 'predicted_amplification': pred,
 39            'admissible_d3': bool(0 < S < 3-2*SIGMA),
 40            'relative_amplification_error': float(abs(amp/pred-1))}
 41
 42
 43def coords3(length, device):
 44    # A bounded 3-D temporal embedding; the forecast endpoint is the singular center.
 45    t = torch.linspace(-1., 0., length, device=device)
 46    return torch.stack((t, .35*torch.sin(math.pi*t), .35*torch.cos(math.pi*t)-.35), -1)
 47
 48
 49class CoordTransformer(nn.Module):
 50    def __init__(self, win, out_dim, biased=False):
 51        super().__init__(); self.win=win; self.biased=biased
 52        self.inp=nn.Linear(1, D); self.pos=nn.Parameter(torch.zeros(1, win, D))
 53        nn.init.normal_(self.pos, std=.02)
 54        self.layers=nn.ModuleList()
 55        for _ in range(DEPTH):
 56            self.layers.append(nn.ModuleDict({
 57                'norm1': nn.LayerNorm(D), 'attn': nn.MultiheadAttention(D, HEADS, dropout=0., batch_first=True),
 58                'norm2': nn.LayerNorm(D), 'ff': nn.Sequential(nn.Linear(D,128), nn.ReLU(), nn.Linear(128,D))}))
 59        self.head=nn.Linear(win*D, out_dim)
 60        self.last_attn=None
 61
 62    def forward(self, x):
 63        b, n = x.shape
 64        h=self.inp(x.unsqueeze(-1))+self.pos[:, :n]
 65        c=coords3(n, x.device)
 66        bias=None
 67        if self.biased:
 68            dif=c[:,None,:]-c[None,:,:]
 69            r=torch.sqrt((dif*dif).sum(-1)+1e-10)+1e-5
 70            u=torch.sqrt((c*c).sum(-1)+1e-10)+1e-5
 71            m=torch.minimum(torch.minimum(u[:,None]/r,u[None,:]/r),torch.ones_like(r))
 72            K=r**(S-3)*m**(-SIGMA)
 73            bias=(LAMBDA*torch.log(K+1e-8)).unsqueeze(0).expand(b,-1,-1)
 74            bias=bias.repeat_interleave(HEADS, 0)
 75        weights=None
 76        for li in self.layers:
 77            z=li['norm1'](h)
 78            h2, weights=li['attn'](z,z,z, attn_mask=bias, need_weights=True, average_attn_weights=False)
 79            h=h+h2; h=h+li['ff'](li['norm2'](h))
 80        self.last_attn=weights.detach() if weights is not None else None
 81        return self.head(h.reshape(b,-1))
 82
 83
 84def seed_all(seed):
 85    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
 86
 87
 88def train_one(cfg, seed, biased, capture=False):
 89    seed_all(seed)
 90    ds=get_dataset('sequence', seed, n_train=NTRAIN, n_test=NTEST)
 91    net=CoordTransformer(ds['input_shape'][0], ds['out_dim'], biased=biased)
 92    trained, metric, hist=train_model(net, ds, epochs=EPOCHS, lr=cfg['lr'], batch=128, weight_decay=cfg['weight_decay'], log=lambda *_: None)
 93    if trained is None: raise RuntimeError('bench training failed')
 94    if capture:
 95        trained.eval()
 96        with torch.no_grad():
 97            dev=next(trained.parameters()).device
 98            _=trained(ds['xte'][:64].to(dev))
 99            att=trained.last_attn.cpu().numpy()  # [B,H,N,N]
100        return float(metric), {'model': trained, 'ds': ds, 'att': att}
101    return float(metric)
102
103
104def make_train(cfg, biased):
105    return lambda seed: train_one(cfg, seed, biased)
106
107
108def main():
109    checks=math_check()
110    # Three learning rates are the shared union of both searches; baseline has no extra method knob.
111    grid=[{'lr':lr, 'weight_decay':wd} for lr,wd in
112          [(0.0015,0.0),(0.003,0.0),(0.006,0.0)]]
113    base=sweep_baseline(lambda cfg: make_train(cfg, False), grid, seeds=SWEEP_SEEDS)
114    # Same 3-point budget and same lr union for the idea; lambda is fixed a priori from stage 1.
115    idea_cfgs=grid
116    idea_sweep=[]
117    for cfg in idea_cfgs:
118        r=evaluate(make_train(cfg, True), seeds=SWEEP_SEEDS)
119        idea_sweep.append({'cfg':cfg, 'mean':r['mean']})
120    best_idea_cfg=min(idea_cfgs, key=lambda c: next(z['mean'] for z in idea_sweep if z['cfg']==c))
121    idea_full=evaluate(make_train(best_idea_cfg, True), seeds=SEEDS)
122    # Re-test trained-model attention behavior on paired seed 0, not an analytic-only signature.
123    bm, bo=train_one(base['best_cfg'], 0, False, True)
124    im, io=train_one(best_idea_cfg, 0, True, True)
125    a0=bo['att']; a1=io['att']; n=a0.shape[-1]
126    # Query endpoint (last token): compare attention odds on oldest vs latest key.
127    old, recent = float(a0[:,:,-1,0].mean()), float(a0[:,:,-1,-1].mean())
128    old_i, recent_i = float(a1[:,:,-1,0].mean()), float(a1[:,:,-1,-1].mean())
129    observed_ratio=(recent_i/(old_i+1e-12))/(recent/(old+1e-12))
130    c=np.stack([np.linspace(-1,0,n), .35*np.sin(np.pi*np.linspace(-1,0,n)), .35*np.cos(np.pi*np.linspace(-1,0,n))-.35],-1)
131    predicted_ratio=float(kernel_np(c[-1:], c[-1:])[0]/kernel_np(c[-1:], c[:1])[0])
132    signature={'predicted_near_vs_far_kernel_ratio':predicted_ratio,
133               'observed_attention_odds_ratio_after_bias':observed_ratio,
134               'baseline_endpoint_recent_attention':recent,
135               'idea_endpoint_recent_attention':recent_i,
136               'confirmed': bool(observed_ratio > 1.0 and observed_ratio/predicted_ratio > 1/3 and observed_ratio/predicted_ratio < 3)}
137    report=make_report('sequence','transformer_tiny',
138        {'best_cfg':base['best_cfg'],'sweep':base['sweep'],'full':base['full']}, idea_full,
139        {'mechanism_signature':signature,'idea_sweep':idea_sweep,'math_check':checks,
140         'idea_best_cfg':best_idea_cfg,'budget':{'epochs':EPOCHS,'n_train':NTRAIN,'n_test':NTEST,'seeds':list(SEEDS)}})
141    Path('bench_report.json').write_text(json.dumps(report, indent=2))
142    print(json.dumps(report, indent=2))
143
144if __name__=='__main__': main()