Role-Filler Attention / role_filler_bench.py

Failed on benchmark

Raw ⬇ ZIP
  1import sys, json, math, random
  2from pathlib import Path
  3import numpy as np
  4import torch
  5from torch import nn
  6import torch.nn.functional as F
  7sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
  8from bench import get_dataset, train_model, evaluate, sweep_baseline, make_report
  9
 10SEED = 2951
 11EPOCHS = 8
 12NTR, NTE = 400, 200
 13LRS = [1e-3, 3e-3, 1e-2]
 14TAUS = [0.5, 1.0, 2.0]
 15
 16class DenseLayer(nn.Module):
 17    def __init__(self, d=64, tau=1.0):
 18        super().__init__(); self.tau=tau
 19        self.q=nn.Linear(d,d); self.k=nn.Linear(d,d); self.v=nn.Linear(d,d)
 20        self.o=nn.Linear(d,d); self.ff=nn.Sequential(nn.Linear(d,128),nn.ReLU(),nn.Linear(128,d))
 21        self.n1=nn.LayerNorm(d); self.n2=nn.LayerNorm(d)
 22    def forward(self,x):
 23        q,k,v=self.q(x),self.k(x),self.v(x)
 24        a=F.softmax(torch.matmul(q,k.transpose(-1,-2))/(math.sqrt(x.shape[-1])*self.tau),-1)
 25        z=self.o(torch.matmul(a,v)); return self.n2(self.n1(x+z)+self.ff(self.n1(x+z))),a
 26
 27class RoleFillerLayer(nn.Module):
 28    """Role-filler attention: positions are orthonormal roles; projected token states are fillers."""
 29    def __init__(self, win=32, d=64, tau=1.0):
 30        super().__init__(); self.win=win; self.tau=tau
 31        self.role=nn.Parameter(torch.randn(win,d)*0.02)
 32        self.qf=nn.Linear(d,d); self.ffill=nn.Linear(d,d); self.out=nn.Linear(d,d)
 33        self.ff=nn.Sequential(nn.Linear(d,128),nn.ReLU(),nn.Linear(128,d))
 34        self.n1=nn.LayerNorm(d); self.n2=nn.LayerNorm(d)
 35    def forward(self,x):
 36        # O[source, role, filler]; query at each target role specifies target role and filler.
 37        h=x + self.role[:x.shape[1]].unsqueeze(0)
 38        fillers=self.ffill(h)
 39        q=self.qf(h)
 40        scores=torch.einsum('bid,bjd->bij',q,fillers)/(math.sqrt(x.shape[-1])*self.tau)
 41        a=F.softmax(scores,-1)
 42        # Extract the target role filler from each retrieved object and rebind to target.
 43        z=torch.einsum('bij,bjd->bid',a,fillers)
 44        z=self.out(z)
 45        u=self.n1(x+z); return self.n2(u+self.ff(u)),a
 46
 47class Net(nn.Module):
 48    def __init__(self, kind='dense', win=32, d=64, tau=1.0):
 49        super().__init__(); self.kind=kind; self.win=win
 50        self.inp=nn.Linear(1,d); self.pos=nn.Parameter(torch.randn(1,win,d)*.02)
 51        Layer=DenseLayer if kind=='dense' else RoleFillerLayer
 52        self.layers=nn.ModuleList([Layer(d=d,tau=tau) if kind=='dense' else Layer(win=win,d=d,tau=tau) for _ in range(2)])
 53        self.head=nn.Linear(win*d,1); self.last_attention=None
 54    def forward(self,x):
 55        h=self.inp(x.unsqueeze(-1))+self.pos[:,:x.shape[1]]; ats=[]
 56        for layer in self.layers: h,a=layer(h); ats.append(a)
 57        self.last_attention=ats[-1].detach(); return self.head(h.reshape(x.shape[0],-1))
 58
 59def seed_all(seed):
 60    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
 61    if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)
 62
 63def run(kind, cfg, seed, return_model=False):
 64    seed_all(seed)
 65    ds=get_dataset('sequence',seed,n_train=NTR,n_test=NTE)
 66    net=Net(kind=kind,win=ds['input_shape'][0],tau=cfg['tau'])
 67    trained, metric, hist=train_model(net,ds,epochs=EPOCHS,lr=cfg['lr'],batch=128,log=lambda *_:None)
 68    if return_model: return float(metric), trained, ds
 69    return float(metric)
 70
 71def main():
 72    # Baseline sweep includes every idea-side lr and every attention temperature.
 73    grid=[{'lr':lr,'tau':tau} for lr in LRS for tau in TAUS]
 74    base=sweep_baseline(lambda c: lambda s: run('dense',c,s),grid)
 75    # Idea at the best baseline setting and two nearby settings; all are in baseline grid.
 76    idea_cfgs=[base['best_cfg'], {'lr':1e-3,'tau':1.0}, {'lr':1e-2,'tau':1.0}]
 77    idea_runs=[]
 78    for cfg in idea_cfgs:
 79        r=evaluate(lambda s,c=cfg: run('role',c,s))
 80        idea_runs.append({'cfg':cfg,'result':r})
 81    best=min(idea_runs,key=lambda z:z['result']['mean'])
 82    # NN-scale mechanism signature: measured attention entropy for learned exact-role
 83    # (diagonal target/source) versus mismatched-role (off-diagonal) queries.
 84    ent_exact=[]; ent_wrong=[]
 85    for s in range(8):
 86        metric,net,ds=run('role',best['cfg'],s,True)
 87        x=torch.as_tensor(ds['xte'][:64],dtype=torch.float32)
 88        try:
 89            device=next(net.parameters()).device
 90            with torch.no_grad(): net(x.to(device))
 91            a=net.last_attention.cpu().numpy()
 92            diag=np.arange(a.shape[1]); exact=a[:,diag,diag]
 93            wrong=a[:,diag,(diag+1)%a.shape[2]]
 94            # NN-scale signature: exact role binding should receive more mass
 95            # than a mismatched adjacent role.
 96            ent_exact.append(float(exact.mean()))
 97            ent_wrong.append(float(wrong.mean()))
 98        except Exception as e:
 99            print('signature probe failed',repr(e))
100    sig={'prediction':'role-selective exact queries should be more concentrated than mismatched queries',
101         'predicted_exact_minus_mismatched_mass':0.2,'observed_exact_role_mass':float(np.mean(ent_exact)),
102         'observed_mismatched_role_mass':float(np.mean(ent_wrong)),
103         'observed_gap':float(np.mean(ent_exact)-np.mean(ent_wrong)),
104         'confirmed':bool(ent_exact and np.mean(ent_exact)-np.mean(ent_wrong) >= 0.2),
105         'note':'probability mass is measured from attention of trained role-filler models on held-out sequence windows'}
106    rep=make_report('sequence','transformer_tiny',base,best['result'],{'idea_sweep':idea_runs,'mechanism_signature':sig})
107    # Preserve explicit mechanism_signature at top-level as required by stage-2 runner.
108    rep['mechanism_signature']=sig; rep['idea_sweep']=idea_runs
109    Path('bench_report.json').write_text(json.dumps(rep,indent=2))
110    print(json.dumps(rep,indent=2))
111if __name__=='__main__': main()