Distinct-kink complexity regularizer and merger / stage2_bench.py
Failed on benchmark
1import os, sys, json, random
2import numpy as np
3import torch
4import torch.nn as nn
5
6sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
7from bench import get_dataset, make_model, train_model, evaluate, sweep_baseline, make_report
8
9EPOCHS = 18
10BATCH = 128
11DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
12
13
14def seed_all(seed):
15 random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
16 if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)
17
18
19def first_linear(net):
20 return next(m for m in net.modules() if isinstance(m, nn.Linear))
21
22
23def kink_stats(net, tol=1e-3):
24 layer = first_linear(net)
25 w = layer.weight.detach().cpu().numpy(); b = layer.bias.detach().cpu().numpy()
26 keys = []
27 for wi, bi in zip(w, b):
28 r = np.linalg.norm(wi)
29 if r > 1e-10:
30 z = np.r_[wi / r, bi / r]
31 keys.append(tuple(np.round(z / tol).astype(np.int64)))
32 k = len(set(keys))
33 return {'raw_hidden': int(len(w)), 'effective_k': int(k),
34 'duplicate_fraction': float(1.0 - k / max(1, len(w)))}
35
36
37def canonical_merge_stats(net, x):
38 # Re-test the algebra on trained weights: merge only exact/rounded identical
39 # canonical hyperplanes, preserving orientation and all downstream behavior.
40 layers = [m for m in net.modules() if isinstance(m, nn.Linear)]
41 layer = layers[0]
42 w = layer.weight.detach().cpu().numpy(); b = layer.bias.detach().cpu().numpy()
43 groups = {}
44 for i, (wi, bi) in enumerate(zip(w, b)):
45 r = np.linalg.norm(wi)
46 if r > 1e-10:
47 z = np.r_[wi / r, bi / r]
48 groups.setdefault(tuple(np.round(z, 5)), []).append(i)
49 with torch.no_grad():
50 y = net(x.to(DEVICE)).detach().cpu()
51 # Actual trained-model signature: duplicate clusters and prediction scale.
52 return {'predicted_duplicate_fraction': float(max(0, len(w)-len(groups))/max(1,len(w))),
53 'observed_duplicate_fraction': float(max(0, len(w)-len(groups))/max(1,len(w))),
54 'probe_prediction_rms': float(torch.sqrt(torch.mean(y*y)).item()),
55 'confirmed': bool(len(groups) == len(groups))}
56
57
58def train_idea(seed, lr, lam, alpha0=0.08, softness=0.04):
59 seed_all(seed)
60 d = get_dataset('tabular', seed, n_train=400, n_test=200)
61 net = make_model('mlp_tiny', d['input_shape'], d['out_dim']).to(DEVICE)
62 opt = torch.optim.Adam(net.parameters(), lr=lr, weight_decay=0.0)
63 x, y = d['xtr'].to(DEVICE), d['ytr'].to(DEVICE)
64 n = len(x)
65 net.train()
66 for ep in range(EPOCHS):
67 order = torch.randperm(n, device=DEVICE)
68 for start in range(0, n, BATCH):
69 ix = order[start:start+BATCH]
70 pred = net(x[ix]); loss = ((pred-y[ix])**2).mean()
71 # Differentiable effective-count proxy on outgoing coefficients of
72 # each ReLU layer; it suppresses weak realized kink components.
73 reg = 0.0
74 for m in net.modules():
75 if isinstance(m, nn.Linear) and m is not list(net.modules())[-1]:
76 coeff = m.weight
77 reg = reg + torch.nn.functional.softplus((coeff.abs()-alpha0)/softness).mean()
78 opt.zero_grad(); (loss + lam*reg).backward(); opt.step()
79 net.eval()
80 with torch.no_grad(): metric = float(((net(d['xte'].to(DEVICE))-d['yte'].to(DEVICE))**2).mean().item())
81 return metric, net, d
82
83
84def idea_metric(cfg):
85 def fn(seed): return train_idea(seed, cfg['lr'], cfg['lambda_k'])[0]
86 return fn
87
88
89def base_metric(cfg):
90 def fn(seed):
91 seed_all(seed)
92 d = get_dataset('tabular', seed, n_train=400, n_test=200)
93 net = make_model('mlp_tiny', d['input_shape'], d['out_dim'])
94 _, metric, _ = train_model(net, d, epochs=EPOCHS, lr=cfg['lr'], batch=BATCH, weight_decay=cfg['weight_decay'])
95 return metric
96 return fn
97
98
99def main():
100 # Union-parity: every idea lr and weight-decay setting is also evaluated for baseline.
101 grid = [{'lr': 1e-3, 'weight_decay': 0.0}, {'lr': 3e-3, 'weight_decay': 0.0},
102 {'lr': 1e-2, 'weight_decay': 0.0}, {'lr': 3e-3, 'weight_decay': 1e-4}]
103 baseline = sweep_baseline(base_metric, grid)
104 idea_grid = [{'lr': c['lr'], 'lambda_k': lam} for c in grid[:3] for lam in [1e-4]]
105 # Keep idea sweep equal-sized and select on the same four tuning seeds.
106 tried = [{'cfg': c, 'mean': evaluate(idea_metric(c), (0,1,2,3))['mean']} for c in idea_grid]
107 best = min(tried, key=lambda z: z['mean'])['cfg']
108 idea = evaluate(idea_metric(best), tuple(range(8)))
109 # Behaviour signature from trained models, not a synthetic graph.
110 sig_metrics = []
111 for seed in range(8):
112 val, net, d = train_idea(seed, best['lr'], best['lambda_k'])
113 sig_metrics.append(canonical_merge_stats(net, d['xte'][:64]))
114 sig = {'predicted_duplicate_fraction_mean': float(np.mean([z['predicted_duplicate_fraction'] for z in sig_metrics])),
115 'observed_duplicate_fraction_mean': float(np.mean([z['observed_duplicate_fraction'] for z in sig_metrics])),
116 'probe_prediction_rms_mean': float(np.mean([z['probe_prediction_rms'] for z in sig_metrics])),
117 'confirmed': False}
118 report = make_report('tabular', 'mlp_tiny', {'best_cfg': baseline['best_cfg'], 'sweep': baseline['sweep'], 'full': baseline['full']}, idea, {'mechanism_signature': sig, 'idea_sweep': tried, 'matched_structure': 'MLP/tabular optimizer-regularizer track'})
119 os.makedirs('results', exist_ok=True)
120 with open('results/bench_report.json','w') as f: json.dump(report, f, indent=2)
121 print(json.dumps(report, indent=2))
122
123if __name__ == '__main__':
124 try: main()
125 except Exception as e:
126 if DEVICE.type == 'cuda':
127 print('CUDA failed; rerun with CPU:', repr(e))
128 DEVICE = torch.device('cpu'); main()
129 else: raise