Task-Visible Axis Pruning / stage2_bench.py

Failed on benchmark

Raw ⬇ ZIP
  1import sys, json, random
  2import numpy as np
  3import torch
  4from torch import nn
  5sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
  6from bench import get_dataset, make_model, train_model, evaluate, sweep_baseline, make_report
  7
  8SEEDS = tuple(range(8)); SWEEP_SEEDS = tuple(range(4))
  9NTR, NTE, EPOCHS, FT_EPOCHS = 400, 200, 22, 5
 10LRS = [1e-3, 3e-3, 1e-2]
 11KEEP = 0.50
 12
 13def seed_all(s):
 14    random.seed(s); np.random.seed(s); torch.manual_seed(s)
 15    if torch.cuda.is_available():
 16        try: torch.cuda.manual_seed_all(s)
 17        except Exception: pass
 18
 19def dataset(seed):
 20    d = get_dataset('tabular', seed=seed, n_train=NTR, n_test=NTE)
 21    # train_model expects tensors; preserve all metadata
 22    return {**d, 'xtr': torch.as_tensor(d['xtr'], dtype=torch.float32),
 23            'ytr': torch.as_tensor(d['ytr'], dtype=torch.float32),
 24            'xte': torch.as_tensor(d['xte'], dtype=torch.float32),
 25            'yte': torch.as_tensor(d['yte'], dtype=torch.float32)}
 26
 27def linear_layers(net):
 28    return [m for m in net.modules() if isinstance(m, nn.Linear)]
 29
 30def masks_for(net, x, method):
 31    ls = linear_layers(net); h = x.to(ls[0].weight.device); masks=[]
 32    for i, layer in enumerate(ls[:-1]):
 33        with torch.no_grad(): z = layer(h); h = torch.relu(z)
 34        n = z.shape[1]; k=max(1, int(round(n*KEEP)))
 35        outnorm = ls[i+1].weight.detach().cpu().numpy().T
 36        if method == 'magnitude': score=np.linalg.norm(outnorm, axis=1)
 37        else:
 38            a=z.detach().cpu().numpy()
 39            lo=np.quantile(a, .01, axis=0); hi=np.quantile(a, .99, axis=0)
 40            visible=(lo <= 0.0) & (hi >= 0.0)
 41            used=np.linalg.norm(outnorm, axis=1) > 1e-8
 42            # visibility is the structural priority; downstream norm breaks ties
 43            score=visible.astype(np.float64)*1e6 + np.linalg.norm(outnorm,axis=1)
 44            score=np.where(used, score, -1e6)
 45        idx=np.argsort(score)[-k:]
 46        m=np.zeros(n, dtype=np.float32); m[idx]=1; masks.append(m)
 47    return masks
 48
 49def apply_masks(net, masks):
 50    ls=linear_layers(net)
 51    with torch.no_grad():
 52        for i,m in enumerate(masks):
 53            t=torch.as_tensor(m, device=ls[i].weight.device)
 54            ls[i].weight.mul_(t[:,None]); ls[i].bias.mul_(t)
 55            ls[i+1].weight.mul_(t[None,:])
 56
 57def run(method, lr, seed, want_signature=False):
 58    seed_all(seed); d=dataset(seed)
 59    # This is the same base architecture and same optimizer path on both sides.
 60    net=make_model('mlp_tiny', d['input_shape'], d['out_dim'])
 61    net, _, _=train_model(net,d,epochs=EPOCHS,lr=lr,batch=128,log=lambda *a,**k:None)
 62    masks=masks_for(net,d['xtr'],method); apply_masks(net,masks)
 63    net, metric, _=train_model(net,d,epochs=FT_EPOCHS,lr=lr,batch=128,log=lambda *a,**k:None)
 64    if want_signature: return float(metric), net, d, masks
 65    return float(metric)
 66
 67def factory(method, cfg):
 68    return lambda seed: run(method, float(cfg['lr']), seed)
 69
 70def mechanism_signature():
 71    metric, net, d, masks=run('task_visible', 3e-3, 0, True)
 72    ls=linear_layers(net); x=d['xte'].to(ls[0].weight.device)
 73    predicted=[]; observed=[]
 74    # On each trained model, test the local affine/zero claim for selected axes.
 75    for i, m in enumerate(masks):
 76        with torch.no_grad():
 77            h=x
 78            for q in range(i): h=torch.relu(ls[q](h))
 79            z=ls[i](h); lo=torch.quantile(z,.01,dim=0); hi=torch.quantile(z,.99,dim=0)
 80        one=((lo>0)|(hi<0)).cpu().numpy() & (m>0)
 81        if not one.any(): continue
 82        # Compare each retained one-sided unit's ReLU contribution with its affine replacement.
 83        w=ls[i].weight.detach(); b=ls[i].bias.detach(); out=ls[i+1].weight.detach()
 84        zz=z.detach()
 85        for j in np.flatnonzero(one):
 86            pred=torch.relu(zz[:,j]); repl=zz[:,j] if float(lo[j])>0 else torch.zeros_like(zz[:,j])
 87            predicted.append(float(torch.max(torch.abs(pred-repl))))
 88            observed.append(float(torch.linalg.vector_norm(out[:,j]).cpu()))
 89    maxerr=max(predicted) if predicted else 0.0
 90    return {'prediction':'strictly one-sided selected ReLU axes are affine or zero on the test patch',
 91            'predicted_max_replacement_error':0.0, 'observed_max_replacement_error':maxerr,
 92            'tested_one_sided_axes':len(predicted), 'downstream_norm_sum':float(sum(observed)),
 93            'confirmed':bool(maxerr < 1e-5)}
 94
 95def main():
 96    grid=[{'lr':x} for x in LRS]
 97    base=sweep_baseline(lambda c: factory('magnitude',c), grid, seeds=SWEEP_SEEDS)
 98    idea_trials=[{'cfg':c,'result':evaluate(factory('task_visible',c),SEEDS)} for c in grid]
 99    best=min(idea_trials,key=lambda q:q['result']['mean'])
100    rep=make_report('tabular','mlp_tiny',base,best['result'],{
101      'track_justification':'ReLU hidden-axis pruning is structurally matched to the tabular MLP track; both systems use the identical trained mlp_tiny and optimizer budgets.',
102      'pruning_fraction':KEEP, 'idea_config':best['cfg'], 'idea_sweep':idea_trials,
103      'parameter_fraction_estimate':KEEP*KEEP, 'mechanism_signature':mechanism_signature()})
104    with open('bench_report.json','w') as f: json.dump(rep,f,indent=2)
105    print(json.dumps(rep,indent=2))
106if __name__=='__main__': main()