Spectral quadrature features / spectral_quadrature_bench.py

Failed on benchmark

Raw ⬇ ZIP
  1import json
  2import math
  3import sys
  4from pathlib import Path
  5
  6import numpy as np
  7import torch
  8from scipy.special import roots_genlaguerre
  9
 10sys.path.insert(0, "/home/maxwelhelp/all/math2nn")
 11from bench import get_dataset, train_model, sweep_baseline, make_report, evaluate
 12
 13SEED = 2977
 14N_FEATURES = 64
 15EPOCHS = 12
 16NTR, NTE = 1200, 300
 17LR_GRID = [{"lr": 1e-3}, {"lr": 3e-3}, {"lr": 1e-2}]
 18
 19
 20def quadrature_frequencies(d=10, n_features=N_FEATURES):
 21    """Product quadrature: generalized-Laguerre radial rule x fixed sphere rule.
 22    If r^2/2 ~ Gamma(d/2,1), Laguerre integrates its radial density; directions
 23    are a fixed antipodal quasi-uniform set, giving a deterministic Gaussian
 24    spectral rule with nonnegative equal weights.
 25    """
 26    ndir, nq = 16, n_features // 16
 27    t, wt = roots_genlaguerre(nq, d / 2.0 - 1.0)
 28    wt = wt / math.gamma(d / 2.0)
 29    rng = np.random.RandomState(18431)
 30    u = rng.normal(size=(ndir // 2, d))
 31    u /= np.linalg.norm(u, axis=1, keepdims=True)
 32    dirs = np.concatenate([u, -u], axis=0)
 33    # Pairing removes odd angular bias and is deterministic once constructed.
 34    W = np.concatenate([np.sqrt(2.0 * r) * dirs[j:j+1]
 35                        for r in t for j in range(ndir)], axis=0)
 36    a = np.repeat(wt / ndir, ndir)
 37    return W.astype(np.float32), a.astype(np.float32)
 38
 39
 40def random_frequencies(d=10, n_features=N_FEATURES, seed=0):
 41    return np.random.RandomState(seed).normal(size=(n_features, d)).astype(np.float32), \
 42           np.full(n_features, 1.0 / n_features, dtype=np.float32)
 43
 44
 45class FourierMLP(torch.nn.Module):
 46    def __init__(self, W, weights):
 47        super().__init__()
 48        self.register_buffer("W", torch.as_tensor(W))
 49        self.register_buffer("sqrt_a", torch.sqrt(torch.as_tensor(weights)))
 50        self.net = torch.nn.Sequential(
 51            torch.nn.Linear(2 * len(W), 64), torch.nn.ReLU(),
 52            torch.nn.Linear(64, 64), torch.nn.ReLU(), torch.nn.Linear(64, 1))
 53
 54    def embedding(self, x):
 55        z = x @ self.W.T
 56        f = torch.cat((torch.cos(z), torch.sin(z)), dim=1)
 57        return f * self.sqrt_a.repeat(2).unsqueeze(0)
 58
 59    def forward(self, x):
 60        return self.net(self.embedding(x))
 61
 62
 63def prep(seed):
 64    d = get_dataset("tabular", seed, n_train=NTR, n_test=NTE)
 65    # Standardization is shared preprocessing and prevents frequency scale from
 66    # being dominated by a coordinate's units.
 67    mu, sd = d["xtr"].mean(0, keepdim=True), d["xtr"].std(0, keepdim=True).clamp_min(1e-5)
 68    for k in ("xtr", "xte"):
 69        d[k] = (d[k] - mu) / sd
 70    return d
 71
 72
 73def train_value(kind, cfg, seed, return_model=False):
 74    torch.manual_seed(SEED + 100 * seed + (0 if kind == "baseline" else 10000))
 75    d = prep(seed)
 76    if kind == "baseline":
 77        W, a = random_frequencies(seed=SEED + seed)
 78    else:
 79        W, a = quadrature_frequencies()
 80    model, metric, _ = train_model(FourierMLP(W, a), d, epochs=EPOCHS,
 81                                   lr=cfg["lr"], batch=128, weight_decay=0.0,
 82                                   log=lambda *_: None)
 83    if return_model:
 84        return metric, model, d, W, a
 85    return metric
 86
 87
 88def make_train(kind):
 89    return lambda cfg: (lambda seed: train_value(kind, cfg, seed))
 90
 91
 92def spectral_error(model, d, W, a):
 93    with torch.no_grad():
 94        x = d["xte"][:120]
 95        z = x @ torch.as_tensor(W).T
 96        F = torch.cat((torch.cos(z), torch.sin(z)), 1) * torch.sqrt(torch.as_tensor(a)).repeat(2).unsqueeze(0)
 97        Kh = F @ F.T
 98        dist = ((x[:, None, :] - x[None, :, :]) ** 2).sum(-1)
 99        K = torch.exp(-0.5 * dist)
100        le = torch.linalg.eigvalsh(K).flip(0)[:10]
101        lh = torch.linalg.eigvalsh(Kh).flip(0)[:10]
102        return float(torch.mean(torch.abs(le-lh) / le.clamp_min(1e-7)))
103
104
105def main():
106    # Baseline sweep and full evaluation are performed by the canonical protocol.
107    base = sweep_baseline(make_train("baseline"), LR_GRID)
108    # Same union of learning rates on idea side; select its best on the same
109    # four tuning seeds, then evaluate that setting on all eight paired seeds.
110    idea_trials = []
111    for cfg in LR_GRID:
112        vals = [train_value("idea", cfg, s) for s in range(4)]
113        idea_trials.append({"cfg": cfg, "mean": float(np.mean(vals))})
114    best_cfg = min(idea_trials, key=lambda x: x["mean"])["cfg"]
115    idea = {"best_cfg": best_cfg, "sweep": idea_trials,
116            "full": __import__("bench").protocol.evaluate(make_train("idea")(best_cfg))}
117
118    # Re-test the claimed spectral effect on the actual trained systems.
119    _, bmodel, bd, bW, ba = train_value("baseline", best_cfg, 0, True)
120    _, imodel, idd, iW, ia = train_value("idea", best_cfg, 0, True)
121    bspec = spectral_error(bmodel, bd, bW, ba)
122    ispec = spectral_error(imodel, idd, iW, ia)
123    sig = {"prediction": "deterministic quadrature has lower top-10 RBF Gram eigenvalue error",
124           "predicted_baseline_top10_relative_error": bspec,
125           "predicted_idea_top10_relative_error": ispec,
126           "observed_delta_idea_minus_baseline": ispec - bspec,
127           "confirmed": bool(ispec < bspec)}
128    report = make_report("tabular", "fourier_mlp_shared", base, idea["full"],
129                         {"mechanism_signature": sig,
130                          "notes": "Tabular is the matched built-in track for an input embedding/kernel feature intervention."})
131    Path("bench_report.json").write_text(json.dumps(report, indent=2))
132    print(json.dumps(report, indent=2))
133
134
135if __name__ == "__main__":
136    main()