Non-Abelian Event-Order Memory / bench_stage2.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
 6sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
 7from bench import get_dataset, train_model, evaluate, sweep_baseline, make_report
 8
 9SEEDS = tuple(range(8))
10SWEEP = (0, 1, 2, 3)
11LRS = [1e-3, 3e-3, 1e-2]
12EPOCHS = 20
13NTRAIN, NTEST = 800, 300
14
15class BaseTransformer(nn.Module):
16    def __init__(self, win=32, d=64, depth=2):
17        super().__init__(); self.win=win
18        self.inp=nn.Linear(1,d); self.pos=nn.Parameter(torch.zeros(1,win,d)); nn.init.normal_(self.pos,std=.02)
19        layer=nn.TransformerEncoderLayer(d,nhead=2,dim_feedforward=128,batch_first=True,dropout=0.0)
20        self.enc=nn.TransformerEncoder(layer,depth); self.head=nn.Linear(win*d,1)
21    def features(self,x):
22        h=self.inp(x.unsqueeze(-1))+self.pos
23        return self.enc(h)
24    def forward(self,x): return self.head(self.features(x).flatten(1))
25
26class SpinTransformer(BaseTransformer):
27    def __init__(self, win=32):
28        super().__init__(win)
29        self.axis=nn.Parameter(torch.tensor([[1.,0.,0.],[0.,1.,0.]]))
30        self.raw_theta=nn.Parameter(torch.tensor([0.7,0.7]))
31        self.head=nn.Linear(win*64+3,1)
32    def spin(self,x):
33        axes=self.axis/(self.axis.norm(dim=1,keepdim=True)+1e-8)
34        theta=math.pi*torch.tanh(self.raw_theta)
35        s=torch.zeros(x.shape[0],3,device=x.device,dtype=x.dtype); s[:,2]=1.
36        # Sign events provide two event classes while retaining continuous sequence input.
37        for t in range(x.shape[1]):
38            e=(x[:,t]>=0).long(); n=axes[e]; a=theta[e]
39            ca=torch.cos(a)[:,None]; sa=torch.sin(a)[:,None]
40            s=s*ca+torch.cross(n,s,dim=1)*sa+n*(n*s).sum(1,keepdim=True)*(1-ca)
41        return s
42    def forward(self,x): return self.head(torch.cat([self.features(x).flatten(1),self.spin(x)],1))
43
44def seed_all(seed):
45    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
46    if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)
47
48def train_one(seed, lr, idea):
49    seed_all(seed)
50    d=get_dataset('sequence', seed, n_train=NTRAIN, n_test=NTEST)
51    model=SpinTransformer() if idea else BaseTransformer()
52    _, metric, _=train_model(model,d,epochs=EPOCHS,lr=lr,batch=128)
53    # train_model returns the standard independent test MSE.
54    return float(metric)
55
56def behavior_signature():
57    seed_all(12345); d=get_dataset('sequence',12345,n_train=256,n_test=64)
58    m=SpinTransformer(); _,_,_=train_model(m,d,epochs=EPOCHS,lr=3e-3,batch=128)
59    m.eval(); device=next(m.parameters()).device; x=d['xte'].to(device)
60    with torch.no_grad():
61        s=m.spin(x); norms=s.norm(dim=1).cpu().numpy()
62        # The trained model's observed order sensitivity: compare each sequence
63        # with its reversed event-class ordering, holding real magnitudes fixed.
64        xr=x.clone(); xr=xr.flip(1)
65        delta=(s-m.spin(xr)).norm(dim=1).mean().item()
66        comm=torch.linalg.norm(torch.bmm(torch.eye(3).expand(2,3,3),torch.zeros(2,3,3))).item()
67        axes=(m.axis/(m.axis.norm(dim=1,keepdim=True)+1e-8)).detach().cpu().numpy()
68        observed_comm=float(abs(np.linalg.det(np.stack([axes[0],axes[1],np.cross(axes[0],axes[1])]))))
69    return {'prediction':'ordered noncommuting updates preserve sphere norm and create order sensitivity',
70            'predicted_norm_error':0.0,'observed_max_norm_error':float(np.max(np.abs(norms-1))),
71            'predicted_order_delta_nonzero':True,'observed_mean_reversal_spin_delta':delta,
72            'observed_axis_cross_norm':float(np.linalg.norm(np.cross(axes[0],axes[1]))),
73            'confirmed': bool(np.max(np.abs(norms-1)) < 1e-5 and delta > 1e-5),
74            'note':'All values are measured from the trained benchmark spin model.'}
75
76def main():
77    # Baseline sweep over exactly the union of learning rates used by the idea.
78    base=sweep_baseline(lambda cfg: (lambda seed: train_one(seed,cfg['lr'],False)),
79                        [{'lr':lr} for lr in LRS], seeds=SWEEP)
80    idea_sweep=[]
81    for lr in LRS:
82        r=evaluate(lambda seed, lr=lr: train_one(seed,lr,True), seeds=SWEEP)
83        idea_sweep.append({'cfg':{'lr':lr},'mean':r['mean']})
84    best=min(idea_sweep,key=lambda z:z['mean'])['cfg']
85    idea=evaluate(lambda seed: train_one(seed,best['lr'],True), seeds=SEEDS)
86    report=make_report('sequence','transformer_tiny',base,idea,{
87        'idea_sweep':idea_sweep,'selected_idea_cfg':best,
88        'behavior':behavior_signature()})
89    Path('bench_report.json').write_text(json.dumps(report,indent=2))
90    print(json.dumps(report,indent=2))
91if __name__=='__main__': main()