Moment-Controlled Mutation / stage2_bench.py
Mechanism confirmed, baseline not beaten
1import sys, json, copy, random
2from pathlib import Path
3import numpy as np
4sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
5from bench import get_dataset, make_model, train_model, evaluate, sweep_baseline, make_report
6import torch
7
8TRACK = 'tabular'
9MODEL = 'mlp_tiny'
10N = 8
11GENERATIONS = 16
12SEEDS = tuple(range(8))
13SWEEP_SEEDS = tuple(range(4))
14# Shared union: every idea learning rate is also evaluated by baseline.
15GRID = [
16 {'lr': 1e-3, 'weight_decay': 0.0},
17 {'lr': 3e-3, 'weight_decay': 0.0},
18 {'lr': 1e-2, 'weight_decay': 0.0},
19]
20
21
22def seed_all(seed):
23 random.seed(seed)
24 np.random.seed(seed)
25 torch.manual_seed(seed)
26 if torch.cuda.is_available():
27 torch.cuda.manual_seed_all(seed)
28
29
30def baseline_one(cfg, seed):
31 seed_all(seed)
32 d = get_dataset(TRACK, seed, n_train=400, n_test=400)
33 net = make_model(MODEL, d['input_shape'], d['out_dim'])
34 net, metric, history = train_model(
35 net, d, epochs=GENERATIONS, lr=cfg['lr'], batch=128,
36 weight_decay=cfg['weight_decay'], log=lambda *_: None)
37 return float(metric) if metric is not None else float('inf')
38
39
40def flat(net):
41 return torch.cat([p.detach().reshape(-1) for p in net.parameters()])
42
43
44def put(net, v):
45 pos = 0
46 with torch.no_grad():
47 for p in net.parameters():
48 n = p.numel()
49 p.copy_(v[pos:pos+n].reshape_as(p))
50 pos += n
51
52
53def train_candidate(net, vec, x, y, lr):
54 """One identical SGD update for each population member."""
55 put(net, vec)
56 net.zero_grad(set_to_none=True)
57 loss = torch.mean((net(x) - y) ** 2)
58 loss.backward()
59 with torch.no_grad():
60 out = torch.cat([(p - lr * p.grad).reshape(-1) for p in net.parameters()])
61 return out, float(loss.detach().cpu())
62
63
64def idea_one(cfg, seed, return_trace=False):
65 seed_all(seed)
66 d = get_dataset(TRACK, seed, n_train=400, n_test=400)
67 net = make_model(MODEL, d['input_shape'], d['out_dim'])
68 x, y = d['xtr'], d['ytr']
69 center = flat(net).clone()
70 # A priori fixed controls from the proposal.
71 dmin, dmax, gamma, target = 1e-7, 3e-4, 0.20, 2e-4
72 kappa = 1.5
73 rng = np.random.default_rng(seed + 991)
74 population = center[None, :] + 0.02 * torch.randn((N, center.numel()))
75 records = []
76 for step in range(GENERATIONS):
77 rewards = []
78 updated = []
79 for i in range(N):
80 # Candidate training is the population optimizer's training step.
81 v, train_loss = train_candidate(net, population[i], x, y, cfg['lr'])
82 updated.append(v)
83 rewards.append(-train_loss)
84 population = torch.stack(updated)
85 r = np.asarray(rewards, dtype=np.float64)
86 # Stable normalized selection weights.
87 w = np.exp(kappa * (r - r.max()))
88 w /= w.sum()
89 z = population.detach().cpu().numpy()
90 mu = np.sum(w[:, None] * z, axis=0)
91 c = np.sum(w[:, None] * (z - mu) ** 2, axis=0)
92 q = (z - mu) ** 2 - c
93 rbar = float(np.sum(w * r))
94 r2 = np.sum(w[:, None] * (r[:, None] - rbar) * q, axis=0) / (np.sum(w[:, None] * q*q, axis=0) + 1e-8)
95 r2 = np.clip(r2, -8, 8)
96 rv = float(np.sum(w * (r-rbar)**2))
97 D = np.clip(dmin + gamma * rv / (1 + np.abs(r2)), dmin, dmax)
98 ess = float(1 / np.sum(w*w))
99 # Diversity safeguard, reflecting +2D in the variance equation.
100 D = np.maximum(D, np.maximum(0, (target-c) / 2.0))
101 D = np.clip(D, dmin, dmax)
102 # Resample selected candidates, then apply coordinatewise diffusion.
103 idx = rng.choice(N, size=N, p=w)
104 noise = torch.as_tensor(rng.normal(size=(N, center.numel())), dtype=population.dtype)
105 population = population[idx] + noise * torch.as_tensor(np.sqrt(2*D), dtype=population.dtype)[None, :]
106 if step == GENERATIONS-1 or step % 4 == 0:
107 records.append({'step': step, 'cov_trace': float(np.mean(c)), 'ess': ess,
108 'reward_var': rv, 'mutation_D': float(np.mean(D)),
109 'curvature_median': float(np.median(r2))})
110 # Evaluate the trained selected system, not a readout of baseline weights.
111 final_w = np.exp(kappa * (r-r.max())); final_w /= final_w.sum()
112 best = population[int(np.argmax(final_w))]
113 put(net, best)
114 with torch.no_grad():
115 metric = float(torch.mean((net(d['xte'])-d['yte'])**2).item())
116 return (metric, records) if return_trace else metric
117
118
119def mechanism_signature(cfg, seed=0):
120 metric, trace = idea_one(cfg, seed, True)
121 # Re-test the claimed additive diffusion relation on trained NN parameters:
122 # measured coordinate variance increment from mutation versus predicted 2D.
123 if len(trace) >= 2:
124 a, b = trace[-2], trace[-1]
125 observed = b['cov_trace'] - a['cov_trace']
126 predicted = 2.0 * b['mutation_D']
127 else:
128 observed = predicted = float('nan')
129 rel = abs(observed-predicted)/(abs(predicted)+1e-12)
130 return {'metric': metric, 'observed_cov_increment': observed,
131 'predicted_2D_increment': predicted, 'relative_error': rel,
132 'curvature_median_final': trace[-1]['curvature_median'],
133 'confirmed': bool(np.isfinite(rel) and rel < 0.50)}
134
135
136def main():
137 # Baseline sweep uses the same three lr values and a final eight-seed rerun.
138 base = sweep_baseline(
139 lambda cfg: (lambda seed: baseline_one(cfg, seed)), GRID, seeds=SWEEP_SEEDS)
140 idea_trials = []
141 for cfg in GRID:
142 res = evaluate(lambda seed, cfg=cfg: idea_one(cfg, seed), seeds=SEEDS)
143 idea_trials.append({'cfg': cfg, 'result': res})
144 best_trial = min(idea_trials, key=lambda x: x['result']['mean'])
145 sig = mechanism_signature(best_trial['cfg'], seed=0)
146 report = make_report(TRACK, MODEL, base, best_trial['result'], {
147 'mechanism_signature': sig,
148 'track_choice': 'tabular is the built-in optimizer/training-dynamics match; Friedman regression keeps architecture and task fixed.',
149 'idea_sweep': idea_trials,
150 'budget': {'baseline_epochs': GENERATIONS, 'idea_generations': GENERATIONS, 'population': N}
151 })
152 report['idea']['best_cfg'] = best_trial['cfg']
153 report['mechanism_signature']['trained_model_metric'] = sig.pop('metric')
154 Path('bench_report.json').write_text(json.dumps(report, indent=2))
155 print(json.dumps(report, indent=2))
156
157
158if __name__ == '__main__':
159 main()