import sys, json, random from pathlib import Path import numpy as np import torch import torch.nn as nn import torch.nn.functional as F sys.path.insert(0, '/home/maxwelhelp/all/math2nn') from bench import get_dataset, make_model, train_model, evaluate, sweep_baseline, make_report SEEDS = tuple(range(8)) LRS = [1e-3, 3e-3, 6e-3] GAMMAS = [0.05, 0.15, 0.30] EPOCHS = 2 NTRAIN, NTEST = 400, 100 _DATA = {} def alpha_bar(gamma, T=20): t = int(round(float(gamma) * T)) return float(np.prod(1.0 - np.linspace(.01, .30, T)[:t])) if t else 1.0 class PartialReNoiseCNN(nn.Module): """A trained CNN system with an anchored partial architecture kernel. The valid parent uses 3x3 convolutions. Each mutation is a shape-compatible 1x1 convolution. Per forward pass, each layer retains its parent operation with alpha_bar(gamma), otherwise uses the mutated operation. """ def __init__(self, out_dim, gamma): super().__init__() self.gamma = float(gamma) self.a = alpha_bar(gamma) self.parent = nn.ModuleList([ nn.Conv2d(3, 32, 3, padding=1), nn.Conv2d(32, 64, 3, padding=1), nn.Conv2d(64, 96, 3, padding=1)]) self.mutant = nn.ModuleList([ nn.Conv2d(3, 32, 1), nn.Conv2d(32, 64, 1), nn.Conv2d(64, 96, 1)]) self.fc1 = nn.Linear(96 * 4 * 4, 128) self.fc2 = nn.Linear(128, out_dim) self.last_mask = None def forward(self, x, force_parent=False): masks = [] for i, (p, m) in enumerate(zip(self.parent, self.mutant)): use_parent = force_parent or (not self.training) or (torch.rand((), device=x.device) < self.a) masks.append(float(use_parent)) x = p(x) if use_parent else m(x) x = F.relu(x) x = F.max_pool2d(x, 2) self.last_mask = masks x = x.flatten(1) return self.fc2(F.relu(self.fc1(x))) def seed_all(seed): random.seed(seed); np.random.seed(seed); torch.manual_seed(seed) if torch.cuda.is_available(): try: torch.cuda.manual_seed_all(seed) except Exception: pass def train_mutated(ds, gamma, lr, epochs=EPOCHS): seed_all(int(ds['_seed'])) net = PartialReNoiseCNN(int(ds['out_dim']), gamma) device = 'cuda' if torch.cuda.is_available() else 'cpu' try: net.to(device) opt = torch.optim.Adam(net.parameters(), lr=lr) x, y = ds['xtr'].to(device), ds['ytr'].to(device) bs = min(128, len(x)); net.train() for _ in range(epochs): order = torch.randperm(len(x), device=device) for j in range(0, len(x), bs): ix = order[j:j+bs]; opt.zero_grad(set_to_none=True) loss = F.cross_entropy(net(x[ix]), y[ix]); loss.backward(); opt.step() net.eval() with torch.no_grad(): pred = net(ds['xte'].to(device)).argmax(1) metric = float((pred != ds['yte'].to(device)).float().mean().cpu()) return metric, net except Exception: net.to('cpu'); opt = torch.optim.Adam(net.parameters(), lr=lr) x, y = ds['xtr'], ds['ytr']; bs=min(128,len(x)); net.train() for _ in range(epochs): order=torch.randperm(len(x)) for j in range(0,len(x),bs): ix=order[j:j+bs]; opt.zero_grad(set_to_none=True) loss=F.cross_entropy(net(x[ix]),y[ix]); loss.backward(); opt.step() net.eval() with torch.no_grad(): metric=float((net(ds['xte']).argmax(1)!=ds['yte']).float().mean()) return metric, net def baseline_one(seed, lr): seed_all(seed); ds=_DATA.setdefault(int(seed), get_dataset('vision', seed, NTRAIN, NTEST)); ds=dict(ds); ds['_seed']=seed _, metric, _ = train_model(make_model('cnn_small', ds['input_shape'], ds['out_dim']), ds, epochs=EPOCHS, lr=lr, batch=128, log=lambda *_: None) return float(metric) def idea_one(seed, gamma, lr): ds=_DATA.setdefault(int(seed), get_dataset('vision', seed, NTRAIN, NTEST)); ds=dict(ds); ds['_seed']=seed return train_mutated(ds, gamma, lr)[0] def main(): # Baseline sweep uses exactly the union of all idea learning rates. base = sweep_baseline(lambda cfg: (lambda s: baseline_one(s, cfg['lr'])), [{'lr': lr} for lr in LRS], seeds=SEEDS) # Explicitly evaluate the idea at best baseline lr and two nearby settings. best_lr = float(base['best_cfg']['lr']) idea_lrs = LRS candidates=[] for gamma in GAMMAS: for lr in idea_lrs: vals=[idea_one(s,gamma,lr) for s in SEEDS] candidates.append({'gamma':gamma,'lr':lr,'mean':float(np.mean(vals)),'std':float(np.std(vals)), 'per_seed':vals,'n':len(vals)}) best=min(candidates,key=lambda z:z['mean']) idea_res={k:best[k] for k in ('mean','std','per_seed','n')} idea_res['config']={'gamma':best['gamma'],'lr':best['lr'],'baseline_best_lr':best_lr} # Re-test the trained behavior: operation retention is measured from masks. sig=[] for g in [0.05,0.15,0.30,1.0]: ds=get_dataset('vision', 0, 128, 64); ds['_seed']=0 _, net=train_mutated(ds,g,best['lr'],epochs=1) net.train(); _=net(ds['xtr'][:64]) observed=float(np.mean(net.last_mask)) sig.append({'gamma':g,'predicted_retention':alpha_bar(g),'observed_retention':observed, 'absolute_error':abs(observed-alpha_bar(g))}) monotonic=all(sig[i]['predicted_retention'] >= sig[i+1]['predicted_retention'] for i in range(len(sig)-1)) signature={'prediction':'parent-operation retention decreases monotonically with gamma according to alpha_bar', 'trained_model_measurements':sig,'confirmed':bool(monotonic and max(x['absolute_error'] for x in sig)<=0.20)} report=make_report('vision','cnn_small',base,idea_res,extra=signature) report['protocol_notes']={'structural_match':'vision CNN architecture mutation', 'paired_seeds':list(SEEDS),'baseline_grid':LRS,'idea_grid':LRS,'epochs':EPOCHS, 'metric':'classification error, lower is better'} Path('bench_report.json').write_text(json.dumps(report,indent=2)) print(json.dumps(report,indent=2)) if __name__=='__main__': main()