Partial-ReNoise Neural Architecture Mutation / bench_partial_renoise.py

Mechanism confirmed, baseline not beaten

Raw ⬇ ZIP
  1import sys, json, random
  2from pathlib import Path
  3import numpy as np
  4import torch
  5import torch.nn as nn
  6import torch.nn.functional as F
  7sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
  8from bench import get_dataset, make_model, train_model, evaluate, sweep_baseline, make_report
  9
 10SEEDS = tuple(range(8))
 11LRS = [1e-3, 3e-3, 6e-3]
 12GAMMAS = [0.05, 0.15, 0.30]
 13EPOCHS = 2
 14NTRAIN, NTEST = 400, 100
 15_DATA = {}
 16
 17
 18def alpha_bar(gamma, T=20):
 19    t = int(round(float(gamma) * T))
 20    return float(np.prod(1.0 - np.linspace(.01, .30, T)[:t])) if t else 1.0
 21
 22
 23class PartialReNoiseCNN(nn.Module):
 24    """A trained CNN system with an anchored partial architecture kernel.
 25
 26    The valid parent uses 3x3 convolutions. Each mutation is a shape-compatible
 27    1x1 convolution. Per forward pass, each layer retains its parent operation
 28    with alpha_bar(gamma), otherwise uses the mutated operation.
 29    """
 30    def __init__(self, out_dim, gamma):
 31        super().__init__()
 32        self.gamma = float(gamma)
 33        self.a = alpha_bar(gamma)
 34        self.parent = nn.ModuleList([
 35            nn.Conv2d(3, 32, 3, padding=1),
 36            nn.Conv2d(32, 64, 3, padding=1),
 37            nn.Conv2d(64, 96, 3, padding=1)])
 38        self.mutant = nn.ModuleList([
 39            nn.Conv2d(3, 32, 1), nn.Conv2d(32, 64, 1), nn.Conv2d(64, 96, 1)])
 40        self.fc1 = nn.Linear(96 * 4 * 4, 128)
 41        self.fc2 = nn.Linear(128, out_dim)
 42        self.last_mask = None
 43
 44    def forward(self, x, force_parent=False):
 45        masks = []
 46        for i, (p, m) in enumerate(zip(self.parent, self.mutant)):
 47            use_parent = force_parent or (not self.training) or (torch.rand((), device=x.device) < self.a)
 48            masks.append(float(use_parent))
 49            x = p(x) if use_parent else m(x)
 50            x = F.relu(x)
 51            x = F.max_pool2d(x, 2)
 52        self.last_mask = masks
 53        x = x.flatten(1)
 54        return self.fc2(F.relu(self.fc1(x)))
 55
 56
 57def seed_all(seed):
 58    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
 59    if torch.cuda.is_available():
 60        try: torch.cuda.manual_seed_all(seed)
 61        except Exception: pass
 62
 63
 64def train_mutated(ds, gamma, lr, epochs=EPOCHS):
 65    seed_all(int(ds['_seed']))
 66    net = PartialReNoiseCNN(int(ds['out_dim']), gamma)
 67    device = 'cuda' if torch.cuda.is_available() else 'cpu'
 68    try:
 69        net.to(device)
 70        opt = torch.optim.Adam(net.parameters(), lr=lr)
 71        x, y = ds['xtr'].to(device), ds['ytr'].to(device)
 72        bs = min(128, len(x)); net.train()
 73        for _ in range(epochs):
 74            order = torch.randperm(len(x), device=device)
 75            for j in range(0, len(x), bs):
 76                ix = order[j:j+bs]; opt.zero_grad(set_to_none=True)
 77                loss = F.cross_entropy(net(x[ix]), y[ix]); loss.backward(); opt.step()
 78        net.eval()
 79        with torch.no_grad():
 80            pred = net(ds['xte'].to(device)).argmax(1)
 81            metric = float((pred != ds['yte'].to(device)).float().mean().cpu())
 82        return metric, net
 83    except Exception:
 84        net.to('cpu'); opt = torch.optim.Adam(net.parameters(), lr=lr)
 85        x, y = ds['xtr'], ds['ytr']; bs=min(128,len(x)); net.train()
 86        for _ in range(epochs):
 87            order=torch.randperm(len(x))
 88            for j in range(0,len(x),bs):
 89                ix=order[j:j+bs]; opt.zero_grad(set_to_none=True)
 90                loss=F.cross_entropy(net(x[ix]),y[ix]); loss.backward(); opt.step()
 91        net.eval()
 92        with torch.no_grad():
 93            metric=float((net(ds['xte']).argmax(1)!=ds['yte']).float().mean())
 94        return metric, net
 95
 96
 97def baseline_one(seed, lr):
 98    seed_all(seed); ds=_DATA.setdefault(int(seed), get_dataset('vision', seed, NTRAIN, NTEST)); ds=dict(ds); ds['_seed']=seed
 99    _, metric, _ = train_model(make_model('cnn_small', ds['input_shape'], ds['out_dim']), ds,
100                               epochs=EPOCHS, lr=lr, batch=128, log=lambda *_: None)
101    return float(metric)
102
103
104def idea_one(seed, gamma, lr):
105    ds=_DATA.setdefault(int(seed), get_dataset('vision', seed, NTRAIN, NTEST)); ds=dict(ds); ds['_seed']=seed
106    return train_mutated(ds, gamma, lr)[0]
107
108
109def main():
110    # Baseline sweep uses exactly the union of all idea learning rates.
111    base = sweep_baseline(lambda cfg: (lambda s: baseline_one(s, cfg['lr'])),
112                          [{'lr': lr} for lr in LRS], seeds=SEEDS)
113    # Explicitly evaluate the idea at best baseline lr and two nearby settings.
114    best_lr = float(base['best_cfg']['lr'])
115    idea_lrs = LRS
116    candidates=[]
117    for gamma in GAMMAS:
118        for lr in idea_lrs:
119            vals=[idea_one(s,gamma,lr) for s in SEEDS]
120            candidates.append({'gamma':gamma,'lr':lr,'mean':float(np.mean(vals)),'std':float(np.std(vals)),
121                               'per_seed':vals,'n':len(vals)})
122    best=min(candidates,key=lambda z:z['mean'])
123    idea_res={k:best[k] for k in ('mean','std','per_seed','n')}
124    idea_res['config']={'gamma':best['gamma'],'lr':best['lr'],'baseline_best_lr':best_lr}
125    # Re-test the trained behavior: operation retention is measured from masks.
126    sig=[]
127    for g in [0.05,0.15,0.30,1.0]:
128        ds=get_dataset('vision', 0, 128, 64); ds['_seed']=0
129        _, net=train_mutated(ds,g,best['lr'],epochs=1)
130        net.train(); _=net(ds['xtr'][:64])
131        observed=float(np.mean(net.last_mask))
132        sig.append({'gamma':g,'predicted_retention':alpha_bar(g),'observed_retention':observed,
133                    'absolute_error':abs(observed-alpha_bar(g))})
134    monotonic=all(sig[i]['predicted_retention'] >= sig[i+1]['predicted_retention'] for i in range(len(sig)-1))
135    signature={'prediction':'parent-operation retention decreases monotonically with gamma according to alpha_bar',
136               'trained_model_measurements':sig,'confirmed':bool(monotonic and max(x['absolute_error'] for x in sig)<=0.20)}
137    report=make_report('vision','cnn_small',base,idea_res,extra=signature)
138    report['protocol_notes']={'structural_match':'vision CNN architecture mutation',
139      'paired_seeds':list(SEEDS),'baseline_grid':LRS,'idea_grid':LRS,'epochs':EPOCHS,
140      'metric':'classification error, lower is better'}
141    Path('bench_report.json').write_text(json.dumps(report,indent=2))
142    print(json.dumps(report,indent=2))
143
144if __name__=='__main__': main()