import sys, json, math, random from pathlib import Path import numpy as np import torch import torch.nn as nn sys.path.insert(0, '/home/maxwelhelp/all/math2nn') from bench import get_dataset, make_model, evaluate, sweep_baseline, make_report OUT = Path(__file__).with_name('bench_report.json') EPOCHS = 18 BATCH = 128 RHO_AUG = 0.50 DENSITIES = [0.25, 0.5, 0.75] # baseline real-space density; union with idea lr grid is shared LRS = [1e-3, 3e-3, 1e-2] def seed_all(seed): random.seed(seed); np.random.seed(seed); torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) def device(): return 'cuda' if torch.cuda.is_available() else 'cpu' def flatten_params(net): # Prune every affine weight tensor, excluding biases. Keep tensor shapes for forward. refs = [] for mod in net.modules(): if isinstance(mod, nn.Linear): refs.append(mod) return refs def run_one(seed, lr, augmented, density=0.5): seed_all(seed) ds = get_dataset('tabular', seed=seed, n_train=400, n_test=200) # Make identical random base architecture/initialization for paired systems. net = make_model('mlp_tiny', ds['input_shape'], ds['out_dim']) refs = flatten_params(net) dev = device() try: net = net.to(dev) xtr = torch.as_tensor(ds['xtr'], dtype=torch.float32, device=dev) ytr = torch.as_tensor(ds['ytr'], dtype=torch.float32, device=dev) xte = torch.as_tensor(ds['xte'], dtype=torch.float32, device=dev) yte = torch.as_tensor(ds['yte'], dtype=torch.float32, device=dev) # Freeze all weights; score parameters are the only trainable quantities. for p in net.parameters(): p.requires_grad_(False) real_scores = [nn.Parameter(torch.randn_like(m.weight, device=dev)) for m in refs] dummy_scores = [nn.Parameter(torch.randn_like(m.weight, device=dev)) for m in refs] if augmented else [] params = real_scores + dummy_scores opt = torch.optim.Adam(params, lr=lr) # Exact fixed candidate quota. For a doubled layer, K=floor(rho_aug*2M). quotas = [] for m in refs: M = m.weight.numel() quotas.append(max(1, min(2*M if augmented else M, int(math.floor((RHO_AUG if augmented else density) * (2*M if augmented else M)))))) for epoch in range(EPOCHS): perm = torch.randperm(xtr.shape[0], device=dev) for start in range(0, xtr.shape[0], BATCH): ix = perm[start:start+BATCH] hlist = [] for m, sr, sd, K in zip(refs, real_scores, dummy_scores if augmented else [None]*len(refs), quotas): sa = torch.cat([sr.reshape(-1), sd.reshape(-1)]) if augmented else sr.reshape(-1) hard = torch.zeros_like(sa); hard[torch.topk(sa, K).indices] = 1. st = (hard - sa).detach() + sa hlist.append(st[:m.weight.numel()].reshape_as(m.weight)) # Functional forward with masked weights, preserving the shared MLP. z = xtr[ix] li = 0 for mod in net.modules(): if isinstance(mod, nn.Linear): z = torch.nn.functional.linear(z, mod.weight * hlist[li], mod.bias) li += 1 if li < len(refs): z = torch.relu(z) loss = torch.mean((z - ytr[ix]) ** 2) opt.zero_grad(); loss.backward(); opt.step() with torch.no_grad(): masks=[]; real_counts=[]; dummy_counts=[] for m, sr, sd, K in zip(refs, real_scores, dummy_scores if augmented else [None]*len(refs), quotas): sa = torch.cat([sr.reshape(-1), sd.reshape(-1)]) if augmented else sr.reshape(-1) inds = torch.topk(sa, K).indices 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)) real_counts.append(int((inds < m.weight.numel()).sum().item())) dummy_counts.append(int(K - real_counts[-1])) z=xte; li=0 for mod in net.modules(): if isinstance(mod, nn.Linear): z=torch.nn.functional.linear(z, mod.weight*masks[li], mod.bias); li+=1 if li