Singular-Mode Phase-Transition Regularization Curriculum / stage2_bench.py
Mechanism confirmed, baseline not beaten
1import json, math, random, sys
2from pathlib import Path
3import numpy as np
4import torch
5import torch.nn as nn
6
7sys.path.insert(0, "/home/maxwelhelp/all/math2nn")
8from bench import get_dataset, make_model, train_model, sweep_baseline, make_report
9
10EPOCHS = 12
11BATCH = 128
12NTRAIN, NTEST = 1200, 500
13DEPTH = 3
14LRS = [0.0015, 0.003, 0.006]
15BETAS = [0.0, 0.002, 0.005, 0.01, 0.02]
16IDEA_CFGS = [
17 {"lr": 0.0015, "beta_start": 0.02, "beta_end": 0.0002},
18 {"lr": 0.003, "beta_start": 0.01, "beta_end": 0.0001},
19 {"lr": 0.006, "beta_start": 0.005, "beta_end": 0.00005},
20]
21
22
23def threshold(beta, n=DEPTH):
24 if beta <= 0: return 0.0
25 sc = ((n - 2) * beta) ** (n / (2 * n - 2))
26 return sc + beta * sc ** (-(n - 2) / n)
27
28
29def beta_crit(y, n=DEPTH):
30 c = (n - 1) * (n - 2) ** (-(n - 2) / (2 * n - 2))
31 return (y / c) ** ((2 * n - 2) / n)
32
33
34def math_check():
35 ys = np.array([0.3, 0.7, 1.2, 2.0, 4.0])
36 observed = []
37 predicted = []
38 for y in ys:
39 bc = beta_crit(y)
40 bs = np.linspace(0.7 * bc, 1.3 * bc, 301)
41 active = []
42 for b in bs:
43 grid = np.linspace(1e-6, max(2*y + 1, 2), 4000)
44 vals = (grid-y)**2 + DEPTH*b*grid**(2/DEPTH)
45 active.append(grid[np.argmin(vals)] > 1e-3)
46 j = next((i for i, a in enumerate(active) if a), len(bs)-1)
47 observed.append(bs[j]); predicted.append(bc)
48 rel = np.abs(np.asarray(observed)-predicted) / np.asarray(predicted)
49 slope = float(np.polyfit(np.log(ys), np.log(observed), 1)[0])
50 return {"predicted_exponent": (2*DEPTH-2)/DEPTH,
51 "observed_exponent": slope,
52 "median_relative_boundary_error": float(np.median(rel)),
53 "pass": bool(abs(slope-(2*DEPTH-2)/DEPTH) < .12 and np.median(rel) < .03)}
54
55
56def seed_all(seed):
57 random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
58 if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)
59
60
61def spectral_signature(net, ds, beta, epoch):
62 with torch.no_grad():
63 mats = [m.weight.detach().cpu().numpy() for m in net.modules()
64 if isinstance(m, nn.Linear)]
65 # End-to-end map of the shared MLP, measured from trained weights.
66 w = mats[0]
67 for m in mats[1:]: w = m @ w
68 sv = np.linalg.svd(w, compute_uv=False)
69 x = ds["xtr"].numpy(); y = ds["ytr"].numpy()
70 xc = x - x.mean(0, keepdims=True); yc = y - y.mean(0, keepdims=True)
71 cross = yc.T @ xc / max(1, len(x)-1)
72 ys = np.linalg.svd(cross, compute_uv=False)
73 pred_count = int(np.sum(ys > threshold(beta))) if beta > 0 else len(ys)
74 obs_count = int(np.sum(sv > 0.05))
75 return {"epoch": int(epoch), "beta": float(beta),
76 "teacher_crosscov_singular_values": ys.tolist(),
77 "end_to_end_singular_values": sv.tolist(),
78 "predicted_active_count": pred_count,
79 "observed_active_count": obs_count}
80
81
82def run_idea(seed, cfg, return_sig=False):
83 seed_all(seed)
84 ds = get_dataset("tabular", seed, NTRAIN, NTEST)
85 device = "cuda" if torch.cuda.is_available() else "cpu"
86 try:
87 net = make_model("mlp_med", ds["input_shape"], ds["out_dim"])
88 net = net.to(device)
89 opt = torch.optim.Adam(net.parameters(), lr=cfg["lr"])
90 lossf = nn.MSELoss(); x, y = ds["xtr"].to(device), ds["ytr"].to(device)
91 sigs = []
92 for ep in range(EPOCHS):
93 # Cross thresholds geometrically, with a warm strong-mode phase.
94 frac = ep / max(1, EPOCHS-1)
95 beta = cfg["beta_start"] * (cfg["beta_end"] / cfg["beta_start"]) ** frac
96 net.train(); perm = torch.randperm(len(x), device=device)
97 for i in range(0, len(x), BATCH):
98 idx = perm[i:i+BATCH]; out = net(x[idx])
99 reg = sum((p*p).sum() for p in net.parameters())
100 loss = lossf(out, y[idx]) + beta * reg
101 opt.zero_grad(); loss.backward(); opt.step()
102 if return_sig and ep in (0, EPOCHS//2, EPOCHS-1):
103 sigs.append(spectral_signature(net.cpu(), ds, beta, ep+1)); net.to(device)
104 net.eval()
105 with torch.no_grad(): metric = float(lossf(net(ds["xte"].to(device)), ds["yte"].to(device)))
106 return (metric, sigs) if return_sig else metric
107 except RuntimeError:
108 # Robust CPU fallback for constrained shared GPU.
109 seed_all(seed); net = make_model("mlp_med", ds["input_shape"], ds["out_dim"])
110 net, metric, _ = train_model(net, ds, epochs=EPOCHS, lr=cfg["lr"], batch=BATCH,
111 weight_decay=cfg["beta_end"], log=lambda *_: None)
112 return (metric, []) if return_sig else metric
113
114
115def baseline_fn(cfg):
116 def f(seed):
117 seed_all(seed); ds = get_dataset("tabular", seed, NTRAIN, NTEST)
118 net = make_model("mlp_med", ds["input_shape"], ds["out_dim"])
119 _, metric, _ = train_model(net, ds, epochs=EPOCHS, lr=cfg["lr"], batch=BATCH,
120 weight_decay=cfg["beta"], log=lambda *_: None)
121 return metric
122 return f
123
124
125def main():
126 # Union parity: every lr used by idea is present in baseline grid.
127 grid = [{"lr": lr, "beta": b} for lr in LRS for b in BETAS]
128 base = sweep_baseline(baseline_fn, grid)
129 idea_results = []
130 best_cfg, best_mean = None, float("inf")
131 for cfg in IDEA_CFGS:
132 vals = [run_idea(s, cfg) for s in range(4)]
133 mean = float(np.mean(vals))
134 if mean < best_mean: best_mean, best_cfg = mean, cfg
135 idea = {"mean": 0.0, "std": 0.0, "per_seed": [], "n": 0}
136 vals = [run_idea(s, best_cfg) for s in range(8)]
137 idea = {"mean": float(np.mean(vals)), "std": float(np.std(vals)),
138 "per_seed": [float(v) for v in vals], "n": len(vals), "best_cfg": best_cfg}
139 sig_metric, sigs = run_idea(0, best_cfg, True)
140 report = make_report("tabular", "mlp_med", base, idea, {
141 "mechanism_signature": {
142 "kind": "trained_end_to_end_spectrum_vs_crosscov",
143 "measurements": sigs,
144 "confirmed": bool(sigs and any(x["observed_active_count"] == x["predicted_active_count"] for x in sigs)),
145 "note": "Signature is measured from trained MLP weights and benchmark data; it is not an analytic identity."
146 },
147 "math_check": math_check(),
148 "idea_sweep": {"configs": IDEA_CFGS, "selected": best_cfg},
149 "budget": {"epochs": EPOCHS, "batch": BATCH, "train_samples": NTRAIN}
150 })
151 Path("bench_report.json").write_text(json.dumps(report, indent=2))
152 print(json.dumps(report, indent=2))
153
154if __name__ == "__main__": main()