Zero-Augmented Double-Scoring / run_bench.py

Mechanism confirmed, baseline not beaten

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, make_model, evaluate, sweep_baseline, make_report
  9
 10OUT = Path(__file__).with_name('bench_report.json')
 11EPOCHS = 18
 12BATCH = 128
 13RHO_AUG = 0.50
 14DENSITIES = [0.25, 0.5, 0.75]  # baseline real-space density; union with idea lr grid is shared
 15LRS = [1e-3, 3e-3, 1e-2]
 16
 17
 18def seed_all(seed):
 19    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
 20    if torch.cuda.is_available():
 21        torch.cuda.manual_seed_all(seed)
 22
 23
 24def device():
 25    return 'cuda' if torch.cuda.is_available() else 'cpu'
 26
 27
 28def flatten_params(net):
 29    # Prune every affine weight tensor, excluding biases. Keep tensor shapes for forward.
 30    refs = []
 31    for mod in net.modules():
 32        if isinstance(mod, nn.Linear): refs.append(mod)
 33    return refs
 34
 35
 36def run_one(seed, lr, augmented, density=0.5):
 37    seed_all(seed)
 38    ds = get_dataset('tabular', seed=seed, n_train=400, n_test=200)
 39    # Make identical random base architecture/initialization for paired systems.
 40    net = make_model('mlp_tiny', ds['input_shape'], ds['out_dim'])
 41    refs = flatten_params(net)
 42    dev = device()
 43    try:
 44        net = net.to(dev)
 45        xtr = torch.as_tensor(ds['xtr'], dtype=torch.float32, device=dev)
 46        ytr = torch.as_tensor(ds['ytr'], dtype=torch.float32, device=dev)
 47        xte = torch.as_tensor(ds['xte'], dtype=torch.float32, device=dev)
 48        yte = torch.as_tensor(ds['yte'], dtype=torch.float32, device=dev)
 49        # Freeze all weights; score parameters are the only trainable quantities.
 50        for p in net.parameters(): p.requires_grad_(False)
 51        real_scores = [nn.Parameter(torch.randn_like(m.weight, device=dev)) for m in refs]
 52        dummy_scores = [nn.Parameter(torch.randn_like(m.weight, device=dev)) for m in refs] if augmented else []
 53        params = real_scores + dummy_scores
 54        opt = torch.optim.Adam(params, lr=lr)
 55        # Exact fixed candidate quota. For a doubled layer, K=floor(rho_aug*2M).
 56        quotas = []
 57        for m in refs:
 58            M = m.weight.numel()
 59            quotas.append(max(1, min(2*M if augmented else M,
 60                                     int(math.floor((RHO_AUG if augmented else density) * (2*M if augmented else M))))))
 61        for epoch in range(EPOCHS):
 62            perm = torch.randperm(xtr.shape[0], device=dev)
 63            for start in range(0, xtr.shape[0], BATCH):
 64                ix = perm[start:start+BATCH]
 65                hlist = []
 66                for m, sr, sd, K in zip(refs, real_scores, dummy_scores if augmented else [None]*len(refs), quotas):
 67                    sa = torch.cat([sr.reshape(-1), sd.reshape(-1)]) if augmented else sr.reshape(-1)
 68                    hard = torch.zeros_like(sa); hard[torch.topk(sa, K).indices] = 1.
 69                    st = (hard - sa).detach() + sa
 70                    hlist.append(st[:m.weight.numel()].reshape_as(m.weight))
 71                # Functional forward with masked weights, preserving the shared MLP.
 72                z = xtr[ix]
 73                li = 0
 74                for mod in net.modules():
 75                    if isinstance(mod, nn.Linear):
 76                        z = torch.nn.functional.linear(z, mod.weight * hlist[li], mod.bias)
 77                        li += 1
 78                        if li < len(refs): z = torch.relu(z)
 79                loss = torch.mean((z - ytr[ix]) ** 2)
 80                opt.zero_grad(); loss.backward(); opt.step()
 81        with torch.no_grad():
 82            masks=[]; real_counts=[]; dummy_counts=[]
 83            for m, sr, sd, K in zip(refs, real_scores, dummy_scores if augmented else [None]*len(refs), quotas):
 84                sa = torch.cat([sr.reshape(-1), sd.reshape(-1)]) if augmented else sr.reshape(-1)
 85                inds = torch.topk(sa, K).indices
 86                mask = torch.zeros(m.weight.numel(), device=inds.device); real_ind = inds[inds < m.weight.numel()]; mask[real_ind] = 1.; masks.append(mask.reshape_as(m.weight))
 87                real_counts.append(int((inds < m.weight.numel()).sum().item()))
 88                dummy_counts.append(int(K - real_counts[-1]))
 89            z=xte; li=0
 90            for mod in net.modules():
 91                if isinstance(mod, nn.Linear):
 92                    z=torch.nn.functional.linear(z, mod.weight*masks[li], mod.bias); li+=1
 93                    if li<len(refs): z=torch.relu(z)
 94            metric=float(torch.mean((z-yte)**2).item())
 95        return metric, {'real_density': float(np.mean([r/m.weight.numel() for r,m in zip(real_counts,refs)])),
 96                        'dummy_fraction': float(sum(dummy_counts)/sum(quotas)), 'real_counts': real_counts}
 97    except Exception as e:
 98        if dev == 'cuda':
 99            torch.cuda.empty_cache()
100            # deterministic CPU retry
101            return run_one_cpu(seed, lr, augmented, density)
102        raise
103
104
105def run_one_cpu(seed, lr, augmented, density=0.5):
106    old=torch.cuda.is_available
107    # Explicit CPU implementation by temporarily using a compact equivalent call is avoided;
108    # rerun after forcing tensors/model to CPU through the shared function's code path.
109    # In normal bench environments CUDA or CPU path above succeeds; this fallback is a direct small model.
110    seed_all(seed); ds=get_dataset('tabular', seed=seed, n_train=400, n_test=200)
111    net=make_model('mlp_tiny', ds['input_shape'], ds['out_dim']); refs=flatten_params(net)
112    xtr=torch.tensor(ds['xtr'],dtype=torch.float32); ytr=torch.tensor(ds['ytr'],dtype=torch.float32)
113    xte=torch.tensor(ds['xte'],dtype=torch.float32); yte=torch.tensor(ds['yte'],dtype=torch.float32)
114    for p in net.parameters(): p.requires_grad_(False)
115    sr=[nn.Parameter(torch.randn_like(m.weight)) for m in refs]; sd=[nn.Parameter(torch.randn_like(m.weight)) for m in refs] if augmented else []
116    opt=torch.optim.Adam(sr+sd,lr=lr); qs=[max(1,int(math.floor((RHO_AUG if augmented else density)*(2*m.weight.numel() if augmented else m.weight.numel())))) for m in refs]
117    for _ in range(EPOCHS):
118      for st in range(0,len(xtr),BATCH):
119        hs=[]
120        for m,a,b,k in zip(refs,sr,sd if augmented else [None]*len(refs),qs):
121          q=torch.cat([a.flatten(),b.flatten()]) if augmented else a.flatten(); hard=torch.zeros_like(q); hard[torch.topk(q,k).indices]=1; hs.append(((hard-q).detach()+q)[:m.weight.numel()].reshape_as(m.weight))
122        z=xtr[st:st+BATCH]; j=0
123        for mod in net.modules():
124          if isinstance(mod,nn.Linear): z=nn.functional.linear(z,mod.weight*hs[j],mod.bias); j+=1; z=torch.relu(z) if j<len(refs) else z
125        loss=((z-ytr[st:st+BATCH])**2).mean(); opt.zero_grad(); loss.backward(); opt.step()
126    with torch.no_grad():
127      hs=[]; rc=[]; dc=[]
128      for m,a,b,k in zip(refs,sr,sd if augmented else [None]*len(refs),qs):
129        q=torch.cat([a.flatten(),b.flatten()]) if augmented else a.flatten(); ind=torch.topk(q,k).indices; mask=torch.zeros(m.weight.numel()); real_ind=ind[ind<m.weight.numel()]; mask[real_ind]=1.; hs.append(mask.reshape_as(m.weight)); rc.append(int(real_ind.numel())); dc.append(k-rc[-1])
130      z=xte;j=0
131      for mod in net.modules():
132       if isinstance(mod,nn.Linear): z=nn.functional.linear(z,mod.weight*hs[j],mod.bias);j+=1;z=nn.functional.relu(z) if j<len(refs) else z
133      return float(((z-yte)**2).mean()), {'real_density':float(np.mean([r/m.weight.numel() for r,m in zip(rc,refs)])),'dummy_fraction':float(sum(dc)/sum(qs)),'real_counts':rc}
134
135
136def main():
137    # Baseline method knobs (density and lr) and all idea lrs share the same union.
138    grid=[{'lr':lr,'density':rho} for lr in LRS for rho in DENSITIES]
139    def mk(cfg): return lambda s: run_one(s,cfg['lr'],False,cfg['density'])[0]
140    base=sweep_baseline(mk,grid)
141    # Three idea settings: baseline-best lr plus two nearby values, all in baseline grid.
142    idea_lrs=sorted(set(LRS))
143    idea_trials=[]
144    for lr in idea_lrs:
145        r=evaluate(lambda s, lr=lr: run_one(s,lr,True)[0], seeds=(0,1,2,3))
146        idea_trials.append({'cfg':{'lr':lr,'rho_aug':RHO_AUG},'mean':r['mean']})
147    best_idea_lr=min(idea_trials,key=lambda x:x['mean'])['cfg']['lr']
148    idea=evaluate(lambda s: run_one(s,best_idea_lr,True)[0])
149    sig=[run_one(s,best_idea_lr,True)[1] for s in range(8)]
150    sig_b=[run_one(s,base['best_cfg']['lr'],False,base['best_cfg']['density'])[1] for s in range(8)]
151    obs_dummy=float(np.mean([x['dummy_fraction'] for x in sig]))
152    obs_density=float(np.mean([x['real_density'] for x in sig]))
153    signature={'prediction':'For iid initialized real/dummy scores and fixed Top-K, expected dummy fraction is 0.5 and expected real density is rho_aug=0.5.',
154      'predicted_dummy_fraction':0.5,'observed_dummy_fraction_mean':obs_dummy,
155      'predicted_real_density':RHO_AUG,'observed_real_density_mean':obs_density,
156      'baseline_observed_real_density':float(np.mean([x['real_density'] for x in sig_b])),
157      'confirmed':bool(abs(obs_dummy-.5)<.12 and abs(obs_density-RHO_AUG)<.12)}
158    rep=make_report('tabular','mlp_tiny',base,idea,{'track_choice':'tabular matches pruning/regularization of a frozen MLP; identical MLP and task are used for both systems.','shared_lr_grid':LRS,'baseline_density_grid':DENSITIES,'idea_sweep':idea_trials,'idea_best_cfg':{'lr':best_idea_lr,'rho_aug':RHO_AUG},'signature':signature})
159    rep['bench_report']=rep.copy()
160    rep['idea']['sweep']=idea_trials
161    OUT.write_text(json.dumps(rep,indent=2)); print(json.dumps(rep,indent=2))
162
163if __name__=='__main__': main()