Global-statistics context shortcut / bench_stage2.py

Mechanism confirmed, baseline not beaten

Raw ⬇ ZIP
 1import sys, json, random, numpy as np, torch
 2import torch.nn as nn
 3sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
 4from bench import get_dataset, train_model, make_report, sweep_baseline, evaluate
 5
 6SEEDS = tuple(range(8))
 7GRID = [
 8    {'lr': 1e-3, 'weight_decay': 0.0},
 9    {'lr': 3e-3, 'weight_decay': 0.0},
10    {'lr': 1e-3, 'weight_decay': 1e-4},
11]
12EPOCHS = 6
13NTRAIN, NTEST = 400, 200
14
15class GlobalSpatialNorm(nn.Module):
16    def __init__(self, channels, eps=1e-5):
17        super().__init__()
18        self.gamma = nn.Parameter(torch.ones(channels))
19        self.beta = nn.Parameter(torch.zeros(channels))
20        self.eps = eps
21    def forward(self, x):
22        mu = x.mean(dim=(2, 3), keepdim=True)
23        var = (x - mu).square().mean(dim=(2, 3), keepdim=True)
24        return self.gamma[None,:,None,None] * (x-mu) / torch.sqrt(var+self.eps) + self.beta[None,:,None,None]
25
26class MatchedCNN(nn.Module):
27    def __init__(self, out_dim=10, kind='batch'):
28        super().__init__()
29        norm = nn.BatchNorm2d(32) if kind == 'batch' else GlobalSpatialNorm(32)
30        self.norm = norm
31        self.net = nn.Sequential(
32            nn.Conv2d(3, 32, 3, padding=1), nn.ReLU(), norm, nn.MaxPool2d(2),
33            nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
34            nn.Conv2d(64, 96, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
35            nn.Flatten(), nn.Linear(96*4*4, 128), nn.ReLU(), nn.Linear(128, out_dim))
36    def forward(self, x): return self.net(x)
37
38def seed_all(s):
39    random.seed(s); np.random.seed(s); torch.manual_seed(s)
40
41def run(kind, cfg, seed, retain=False):
42    seed_all(10000 + seed)
43    d = get_dataset('vision', seed, n_train=NTRAIN, n_test=NTEST)
44    model = MatchedCNN(d['out_dim'], kind)
45    net, metric, hist = train_model(model, d, epochs=EPOCHS, lr=cfg['lr'],
46                                    weight_decay=cfg['weight_decay'], batch=128, log=lambda *_: None)
47    if net is None: raise RuntimeError('training failed')
48    return float(metric), net, d
49
50def metric_fn(kind, cfg):
51    return lambda seed: run(kind, cfg, seed)[0]
52
53def mechanism_signature():
54    seed_all(4242)
55    d = get_dataset('vision', 0, n_train=32, n_test=8)
56    model = MatchedCNN(d['out_dim'], 'global')
57    model.eval()
58    x = d['xte'][:1].clone().requires_grad_(True)
59    # Feature immediately after the local convolution and ReLU, before classifier.
60    h = model.net[0](x); h = model.net[1](h)
61    t, s, ch = 0, 15, 3
62    scalar = model.norm(h)[0, ch, t//32, t%32]
63    jac = torch.autograd.grad(scalar, h, retain_graph=True)[0][0, ch, s//32, s%32].item()
64    flat = h.detach()[0, ch].reshape(-1)
65    mu = flat.mean()
66    sig = torch.sqrt(((flat-mu)**2).mean()+model.norm.eps)
67    hat_t, hat_s = (flat[t]-mu)/sig, (flat[s]-mu)/sig
68    gamma = model.norm.gamma[ch].detach()
69    pred = (gamma/sig * (-(1+hat_t*hat_s)/flat.numel())).item()
70    return {'location': 'trained global-normalization layer on bench vision model',
71            'predicted_offdiagonal': float(pred), 'observed_offdiagonal': float(jac),
72            'absolute_error': float(abs(pred-jac)), 'n': int(flat.numel()),
73            'confirmed': bool(abs(pred-jac) < 1e-5)}
74
75def main():
76    # Baseline sweep and idea sweep use identical configs and seeds.
77    base = sweep_baseline(lambda cfg: metric_fn('batch', cfg), GRID)
78    idea_trials = []
79    for cfg in GRID:
80        r = evaluate(metric_fn('global', cfg), SEEDS)
81        idea_trials.append({'cfg': cfg, 'result': r})
82    best = min(idea_trials, key=lambda q: q['result']['mean'])
83    report = make_report('vision', 'cnn_small', base, best['result'],
84                        {'signature': mechanism_signature(),
85                         'idea_sweep': idea_trials,
86                         'matched_architecture': True,
87                         'normalization': 'per-example channelwise spatial global statistics'})
88    report['custom_track'] = None
89    with open('bench_report.json', 'w') as f: json.dump(report, f, indent=2)
90    print(json.dumps(report, indent=2))
91
92if __name__ == '__main__': main()