Incidence-Matrix Structured Action Head / stage2_bench.py

✓✓ Beats tuned baseline

Raw ⬇ ZIP
 1import sys, json, random, math
 2import numpy as np
 3import torch
 4from torch import nn
 5sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
 6from bench import get_dataset as bench_get_dataset, train_model, evaluate, sweep_baseline, make_report
 7from custom_incidence_actions import MAX_ATOMS, MAX_ACTIONS, D
 8
 9SEEDS = tuple(range(8)); SWEEP = tuple(range(4))
10
11def seed_all(s):
12    random.seed(s); np.random.seed(s); torch.manual_seed(s)
13    if torch.cuda.is_available(): torch.cuda.manual_seed_all(s)
14
15class IncidenceHead(nn.Module):
16    def __init__(self, width=32, alpha=0.0):
17        super().__init__(); self.alpha=alpha
18        self.atom = nn.Sequential(nn.Linear(D,width), nn.ReLU(), nn.Linear(width,1))
19    def forward(self, x):
20        atoms=x[:, :MAX_ATOMS*D].view(-1,MAX_ATOMS,D)
21        A=x[:, MAX_ATOMS*D:].view(-1,MAX_ACTIONS,MAX_ATOMS)
22        s=self.atom(atoms).squeeze(-1)
23        counts=A.sum(-1)
24        z=torch.bmm(A,s.unsqueeze(-1)).squeeze(-1)
25        z=z/(counts.clamp_min(1e-6)**self.alpha)
26        return z.masked_fill(counts.eq(0), -1e9)
27
28class PaddedActionMLP(nn.Module):
29    def __init__(self, width=32):
30        super().__init__()
31        # Standard padded action head: each legal row receives its padded aggregate
32        # atom representation and a count, then a shared MLP emits its action logit.
33        self.net=nn.Sequential(nn.Linear(D+1,width),nn.ReLU(),nn.Linear(width,1))
34    def forward(self,x):
35        atoms=x[:, :MAX_ATOMS*D].view(-1,MAX_ATOMS,D)
36        A=x[:, MAX_ATOMS*D:].view(-1,MAX_ACTIONS,MAX_ATOMS)
37        counts=A.sum(-1)
38        feat=torch.bmm(A,atoms)
39        feat=feat/(counts.unsqueeze(-1).clamp_min(1.0))
40        z=self.net(torch.cat([feat, counts.unsqueeze(-1)],-1)).squeeze(-1)
41        return z.masked_fill(counts.eq(0), -1e9)
42
43def run(kind,cfg,seed,return_model=False):
44    seed_all(seed); ds=bench_get_dataset('incidence_actions', seed, 400, 160)
45    model=IncidenceHead(alpha=cfg.get('alpha',0.0)) if kind=='idea' else PaddedActionMLP()
46    model, metric, hist=train_model(model,ds,epochs=cfg['epochs'],lr=cfg['lr'],batch=128,weight_decay=0.0)
47    if return_model: return metric,model,ds
48    return metric
49
50def trial(kind): return lambda cfg: (lambda seed: run(kind,cfg,seed))
51
52def main():
53    # Union parity: every idea lr is included in this baseline sweep.
54    grid=[{'lr':lr,'epochs':12} for lr in (0.001,0.003,0.006)]
55    base=sweep_baseline(trial('base'),grid,seeds=SWEEP)
56    best=base['best_cfg']
57    idea_grid=[{'lr':best['lr'],'epochs':best['epochs'],'alpha':a} for a in (0.0,0.5,1.0)]
58    tried=[]
59    for c in idea_grid:
60        r=evaluate(trial('idea')(c),seeds=SWEEP)
61        tried.append({'cfg':c,'mean':r['mean']})
62    bestidea=min(idea_grid,key=lambda c: next(q['mean'] for q in tried if q['cfg']==c))
63    idea=evaluate(trial('idea')(bestidea),seeds=SEEDS)
64    idea.update({'best_cfg':bestidea,'sweep':tried})
65    # Re-test the quantitative identity on outputs of one actually trained model.
66    metric,m,ds=run('idea',bestidea,0,True); m.eval()
67    with torch.no_grad():
68        dev=next(m.parameters()).device
69        x=ds['xte'][:64].to(dev); atoms=x[:,:MAX_ATOMS*D].view(-1,MAX_ATOMS,D)
70        A=x[:,MAX_ATOMS*D:].view(-1,MAX_ACTIONS,MAX_ATOMS)
71        s=m.atom(atoms).squeeze(-1); counts=A.sum(-1)
72        observed=m(x); predicted=torch.bmm(A,s.unsqueeze(-1)).squeeze(-1)/(counts.clamp_min(1e-6)**bestidea['alpha'])
73        valid=counts.gt(0)
74        err=(observed[valid]-predicted[valid]).abs()
75    sig={'prediction':'trained incidence logits equal A times trained atomic scores (alpha-normalized)','n_observed_logits':int(valid.sum()),'predicted_max_abs_error':float(err.max()),'observed_mean_abs_error':float(err.mean()),'confirmed':bool(float(err.max())<1e-5)}
76    report=make_report('incidence_actions','custom_incidence_head',base,idea,{'custom_track':{'name':'incidence_actions','file':'incidence_actions.py','domain':'structured_action_classification'},'baseline_sweep_union_lrs':[c['lr'] for c in grid],'idea_sweep':tried,'mechanism_signature':sig})
77    with open('bench_report.json','w') as f: json.dump(report,f,indent=2)
78    print(json.dumps(report,indent=2))
79if __name__=='__main__': main()