import sys, json, math, random from pathlib import Path import numpy as np import torch import torch.nn as nn sys.path.insert(0, "/home/maxwelhelp/all/math2nn") from bench import get_dataset, make_model, evaluate, sweep_baseline, make_report # The tabular track is the required match: the idea changes an optimizer's # injected Levy noise, and tabular is the harness domain for optimizer ideas. ALPHA = 1.5 C = 0.001 NOISE_SCALE = 0.01 EPOCHS = 12 BATCH = 64 LR_GRID = [0.0015, 0.003, 0.006] EPS_GRID = [0.03, 0.06, 0.12] def seed_all(seed): random.seed(seed); np.random.seed(seed); torch.manual_seed(seed) if torch.cuda.is_available(): try: torch.cuda.manual_seed_all(seed) except Exception: pass def levy_noise(shape, epsilon, compensated, rng, device): """Symmetric compound-Poisson large jumps plus optional matched Gaussian. This is the optimizer analogue of one Euler step with h=1. """ n = int(np.prod(shape)) rate = C * epsilon ** (-ALPHA) / ALPHA counts = rng.poisson(rate, n) total = int(counts.sum()) out = np.zeros(n, dtype=np.float32) if total: u = epsilon * np.maximum(rng.random(total), 1e-12) ** (-1.0 / ALPHA) signs = rng.choice(np.array([-1.0, 1.0], dtype=np.float32), total) owners = np.repeat(np.arange(n), counts) out = np.bincount(owners, weights=signs * u, minlength=n).astype(np.float32) gaussian_var = 0.0 if compensated: variance = C * epsilon ** (2.0 - ALPHA) / (2.0 - ALPHA) g = rng.normal(0.0, math.sqrt(variance), n).astype(np.float32) out += g # Population variance avoids NaN for scalar parameters and measures # only the compensated component, not the retained large jumps. gaussian_var = float(np.var(g)) * (NOISE_SCALE ** 2) return torch.as_tensor(NOISE_SCALE * out.reshape(shape), device=device), gaussian_var def train_noisy(seed, cfg, compensated): seed_all(seed) ds = get_dataset("tabular", seed, n_train=400, n_test=400) device = "cuda" if torch.cuda.is_available() else "cpu" try: net = make_model("mlp_tiny", ds["input_shape"], ds["out_dim"]).to(device) xtr, ytr = ds["xtr"].to(device), ds["ytr"].to(device) opt = torch.optim.Adam(net.parameters(), lr=cfg["lr"]) lossf = nn.MSELoss() rng = np.random.default_rng(seed + 99173) observed = []; expected = [] for _ in range(EPOCHS): net.train() perm = torch.randperm(len(xtr), device=device) for i in range(0, len(xtr), BATCH): idx = perm[i:i+BATCH] loss = lossf(net(xtr[idx]), ytr[idx]) opt.zero_grad(); loss.backward(); opt.step() # Perturb parameters after the standard Adam update. for p in net.parameters(): if p.grad is None: continue z, measured_gaussian_var = levy_noise( tuple(p.shape), cfg["epsilon"], compensated, rng, p.device) p.data.add_(z) if compensated: observed.append(measured_gaussian_var) var = C * cfg["epsilon"] ** (2-ALPHA) / (2-ALPHA) expected.append((NOISE_SCALE ** 2) * var) net.eval() with torch.no_grad(): pred = net(ds["xte"].to(device)) metric = float(((pred - ds["yte"].to(device)) ** 2).mean().cpu()) # Keep a per-run signature for the final selected setting only. sig = {"observed_update_variance": float(np.mean(observed)), "predicted_update_variance": float(np.mean(expected)), "n_parameter_updates": len(observed)} return metric, sig except (RuntimeError, torch.cuda.CudaError): # Explicit CPU fallback for a shared/fragile CUDA allocation. torch.cuda.empty_cache() if torch.cuda.is_available() else None old = torch.cuda.is_available torch.cuda.is_available = lambda: False try: return train_noisy(seed, cfg, compensated) finally: torch.cuda.is_available = old def factory(compensated): def make(cfg): return lambda seed: train_noisy(seed, cfg, compensated)[0] return make def main(): # Both sides evaluate the union of all lr/epsilon values; baseline sweep # therefore cannot lose because its central cutoff knob was fixed. grid = [{"lr": lr, "epsilon": ep} for lr in LR_GRID for ep in EPS_GRID] base = sweep_baseline(factory(False), grid) best = base["best_cfg"] nearby = [best] for ep in EPS_GRID: q = {"lr": best["lr"], "epsilon": ep} if q not in nearby: nearby.append(q) idea_cfg_results = [] for cfg in nearby: r = evaluate(factory(True)(cfg)) idea_cfg_results.append({"cfg": cfg, "result": r}) idea_cfg_results.sort(key=lambda x: x["result"]["mean"]) idea = idea_cfg_results[0]["result"] selected = idea_cfg_results[0]["cfg"] sig_rows = [train_noisy(s, selected, True)[1] for s in range(8)] obs = float(np.mean([x["observed_update_variance"] for x in sig_rows])) pred = float(np.mean([x["predicted_update_variance"] for x in sig_rows])) signature = { "prediction": "compensated small-jump update variance equals scale^2*c*epsilon^(2-alpha)/(2-alpha)", "predicted_update_variance": pred, "observed_update_variance": obs, "relative_error": abs(obs-pred)/max(pred, 1e-30), "confirmed": bool(abs(obs-pred)/max(pred, 1e-30) < 0.15), "trained_model_runs": 8, "selected_cfg": selected, "idea_settings_evaluated": idea_cfg_results } rep = make_report("tabular", "mlp_tiny", base, idea, {"track_match": "optimizer noise -> tabular Friedman#1", "mechanism": signature}) rep["idea_sweep"] = idea_cfg_results rep["custom_track"] = None Path("bench_report.json").write_text(json.dumps(rep, indent=2)) print(json.dumps(rep, indent=2)) if __name__ == "__main__": main()