Created
July 9, 2026 12:47
-
-
Save inem/3b2bf19a1fed2ee0fb4dd3da8c7e9e41 to your computer and use it in GitHub Desktop.
Harness for knowledge-as-dynamics issue #1: read-carrier (2D union/lineage/onion), k-NN + Wiener/GP carriers on the MNIST tier, basin(N) curve, d-scaling 8-32, orderability frontier, uncertainty-exponent sweep. Self-contained (mlx+numpy).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """Read-carrier vs fit-carrier on the knowledge-as-dynamics union test. | |
| Claim under test: the losses reported in Helou 2026 (knowledge-as-dynamics) — | |
| union seam loss (0.82 vs ceiling 0.98), lineage decay, and the "onion filter" | |
| (disordered knowledge cannot be inherited) — are properties of the CARRIER | |
| (SGD regression into a parametric AE), not of the knowledge channel | |
| (the latent vector field V(z) = Enc(Dec(z)) - z). | |
| Carrier here: a lattice snapshot of the field (512x512 grid, 262k samples = | |
| 7.8x FEWER field evaluations than their student's 8000 steps x 256 batch), | |
| read back by nearest-cell lookup. No gradients, no training. Composition = | |
| the same gated union, read at lookup time (adelic shape: local-per-territory | |
| tables + gate at read). Inheritance = resample the lookup field on a jittered | |
| lattice. | |
| World/teacher/oracle logic adapted line-for-line from the reviewed source of | |
| github.com/Veso-AI-Open-Source/knowledge-as-dynamics (MIT), files | |
| src/m5_fielddistill/{ae,train,gi}.py, so numbers are comparable to their | |
| runs/gi.json (ceiling 0.98, union student 0.82, lineage 0.91->0.83, | |
| contaminated B-side 0.24->0.19). | |
| Run: uv run --no-project --with mlx --with numpy python spike_2d.py | |
| """ | |
| import json | |
| import time | |
| from pathlib import Path | |
| import mlx.core as mx | |
| import mlx.nn as nn | |
| import mlx.optimizers as optim | |
| import numpy as np | |
| BASIN_EPS = 0.3 | |
| ORACLE_STEPS = 400 | |
| GENS = 5 | |
| GRID_N = 512 # lattice resolution -> 262,144 field samples | |
| OUT = Path(__file__).parent / "spike_2d_results.json" | |
| # ---------- model + field (adapted from their ae.py) ---------- | |
| class MLP(nn.Module): | |
| def __init__(self, dims): | |
| super().__init__() | |
| self.layers = [nn.Linear(a, b) for a, b in zip(dims[:-1], dims[1:])] | |
| def __call__(self, x): | |
| n = len(self.layers) | |
| for i, layer in enumerate(self.layers): | |
| x = layer(x) | |
| if i < n - 1: | |
| x = nn.gelu(x) | |
| return x | |
| class AE(nn.Module): | |
| def __init__(self, d_in=16, d_lat=2, width=64): | |
| super().__init__() | |
| self.enc = MLP([d_in, width, width, d_lat]) | |
| self.dec = MLP([d_lat, width, width, d_in]) | |
| def __call__(self, x): | |
| return self.dec(self.enc(x)) | |
| def field(model, z): | |
| return model.enc(model.dec(z)) - z | |
| def np_field(model, z): | |
| return np.array(field(model, mx.array(z.astype(np.float32)))) | |
| def np_step(model, z): | |
| zm = mx.array(z.astype(np.float32)) | |
| return np.array(model.enc(model.dec(zm))) | |
| def find_attractors(endpoints, vnorms, vtol=5e-3, merge_r=0.15): | |
| pts = endpoints[vnorms < vtol] | |
| centers = [] | |
| for p in pts: | |
| if all(np.linalg.norm(p - c) > merge_r for c in centers): | |
| centers.append(p) | |
| return np.array(centers) if centers else np.zeros((0, endpoints.shape[1])) | |
| def train_teacher(x_train, steps=4000, bs=256, lr=1e-3, seed=0, noise=0.1): | |
| mx.random.seed(seed) | |
| model = AE(d_in=x_train.shape[1]) | |
| mx.eval(model.parameters()) | |
| opt = optim.Adam(learning_rate=lr) | |
| X = mx.array(x_train) | |
| rng = np.random.default_rng(seed) | |
| d = x_train.shape[1] | |
| def loss_fn(m, xb, xn): | |
| return mx.mean((m(xn) - xb) ** 2) | |
| lg = nn.value_and_grad(model, loss_fn) | |
| for _ in range(steps): | |
| idx = mx.array(rng.integers(0, len(x_train), bs)) | |
| xb = X[idx] | |
| xn = xb + noise * mx.array(rng.standard_normal((bs, d)).astype(np.float32)) | |
| _, grads = lg(model, xb, xn) | |
| opt.update(model, grads) | |
| mx.eval(model.parameters(), opt.state) | |
| return model | |
| # ---------- world + union frame (adapted from their gi.py) ---------- | |
| def make_world8(n_per=500, radius=2.2, std=0.32, d_high=16): | |
| rng = np.random.default_rng(0) | |
| angles = np.arange(8) / 8 * 2 * np.pi | |
| centers = radius * np.stack([np.cos(angles), np.sin(angles)], axis=1) | |
| labels = np.repeat(np.arange(8), n_per) | |
| x2 = centers[labels] + std * rng.normal(size=(len(labels), 2)) | |
| W1 = rng.normal(size=(2, d_high)) / np.sqrt(2) | |
| b1 = 0.1 * rng.normal(size=(d_high,)) | |
| W2 = rng.normal(size=(d_high, d_high)) / np.sqrt(d_high) | |
| xh = (np.tanh(x2 @ W1 + b1) @ W2).astype(np.float32) | |
| return xh, labels | |
| class UnionFrame: | |
| """Isometric graft only (their mode='iso'): B's chart translated into empty | |
| frame territory; hard nearest-centroid gate between V_A and grafted V_B.""" | |
| def __init__(self, A, B, x_all, labels): | |
| self.A, self.B = A, B | |
| xA, xB = x_all[labels < 4], x_all[labels >= 4] | |
| zA_own = np.array(A.enc(mx.array(xA))) | |
| zB_own = np.array(B.enc(mx.array(xB))) | |
| gap = 1.5 | |
| d = np.array([zA_own[:, 0].max() - zB_own[:, 0].min() + gap, | |
| zA_own[:, 1].mean() - zB_own[:, 1].mean()], dtype=np.float32) | |
| eye = np.eye(2, dtype=np.float32) | |
| self.T = (eye, d, eye) | |
| W, b, _ = self.T | |
| graft = zB_own @ W + b | |
| labA, labB = labels[labels < 4], labels[labels >= 4] | |
| cents = [zA_own[labA == c].mean(0) for c in range(4)] | |
| cents += [graft[labB == c].mean(0) for c in range(4, 8)] | |
| self.centroids = np.stack(cents) | |
| allz = np.concatenate([zA_own, graft]) | |
| lo, hi = allz.min(0), allz.max(0) | |
| span = hi - lo | |
| self.lo, self.hi = (lo - 0.35 * span).astype(np.float32), (hi + 0.35 * span).astype(np.float32) | |
| self.clip_lo, self.clip_hi = self.lo - 2 * span, self.hi + 2 * span | |
| def gate(self, z): | |
| d = np.linalg.norm(z[:, None, :] - self.centroids[None], axis=-1) | |
| return d.argmin(axis=1) >= 4 | |
| def field_np(self, z): | |
| vA = np_field(self.A, z) | |
| W, b, Winv = self.T | |
| zB = (z - b) @ Winv | |
| fB = np_step(self.B, zB) | |
| vB = (fB @ W + b) - z | |
| return np.where(self.gate(z)[:, None], vB, vA) | |
| def iterate_np(self, z, steps=ORACLE_STEPS): | |
| z = z.copy() | |
| for _ in range(steps): | |
| z = np.clip(z + self.field_np(z), self.clip_lo, self.clip_hi) | |
| return z | |
| def oracle(self, rng, n_probes=400, grid_n=30): | |
| probes = (self.lo + rng.random((n_probes, 2)).astype(np.float32) * (self.hi - self.lo)) | |
| ep = self.iterate_np(probes) | |
| ep_pert = self.iterate_np( | |
| probes + 0.05 * rng.standard_normal(probes.shape).astype(np.float32)) | |
| d = np.linalg.norm(ep - ep_pert, axis=1) | |
| side = self.gate(probes) | |
| xs = np.linspace(self.lo[0], self.hi[0], grid_n, dtype=np.float32) | |
| ys = np.linspace(self.lo[1], self.hi[1], grid_n, dtype=np.float32) | |
| gx, gy = np.meshgrid(xs, ys) | |
| G = np.stack([gx.ravel(), gy.ravel()], axis=1) | |
| epg = self.iterate_np(G) | |
| vn = np.linalg.norm(self.field_np(epg), axis=1) | |
| attractors = find_attractors(epg, vn) | |
| return dict(probes=probes, ep=ep, side=side, | |
| ceil_joint=float((d < BASIN_EPS).mean()), | |
| ceil_A=float((d[~side] < BASIN_EPS).mean()), | |
| ceil_B=float((d[side] < BASIN_EPS).mean()), | |
| attractors=attractors, G=G) | |
| # ---------- the read carrier ---------- | |
| class LatticeCarrier: | |
| """Piecewise-constant snapshot of a field on a regular lattice. | |
| Knowledge = the table. Read = nearest-cell lookup. No training.""" | |
| def __init__(self, field_fn, lo, hi, n=GRID_N, chunk=8192, origin_jitter=None): | |
| self.lo, self.hi, self.n = lo.copy(), hi.copy(), n | |
| self.h = (hi - lo) / (n - 1) | |
| if origin_jitter is not None: | |
| self.lo = self.lo + origin_jitter * self.h # shifted lattice for lineage resampling | |
| xs = self.lo[0] + np.arange(n, dtype=np.float32) * self.h[0] | |
| ys = self.lo[1] + np.arange(n, dtype=np.float32) * self.h[1] | |
| gx, gy = np.meshgrid(xs, ys, indexing="ij") | |
| pts = np.stack([gx.ravel(), gy.ravel()], axis=1) | |
| V = np.empty_like(pts) | |
| for i in range(0, len(pts), chunk): | |
| V[i:i + chunk] = field_fn(pts[i:i + chunk]) | |
| self.V = V.reshape(n, n, 2) | |
| self.n_samples = len(pts) | |
| def lookup(self, z): | |
| ij = np.rint((z - self.lo) / self.h).astype(np.int64) | |
| ij = np.clip(ij, 0, self.n - 1) | |
| return self.V[ij[:, 0], ij[:, 1]] | |
| def iterate(self, z, clip_lo, clip_hi, steps=ORACLE_STEPS): | |
| z = z.copy() | |
| for _ in range(steps): | |
| z = np.clip(z + self.lookup(z), clip_lo, clip_hi) | |
| return z | |
| def eval_carrier(carrier, frame, orc): | |
| ep = carrier.iterate(orc["probes"], frame.clip_lo, frame.clip_hi) | |
| d = np.linalg.norm(ep - orc["ep"], axis=1) | |
| side = orc["side"] | |
| epg = carrier.iterate(orc["G"], frame.clip_lo, frame.clip_hi) | |
| vn = np.linalg.norm(carrier.lookup(epg), axis=1) | |
| attr = find_attractors(epg, vn) | |
| tgt = orc["attractors"] | |
| recovered = int(sum(np.min(np.linalg.norm(tgt[i] - attr, axis=1)) < BASIN_EPS | |
| for i in range(len(tgt)))) if len(attr) else 0 | |
| return dict(basin_joint=float((d < BASIN_EPS).mean()), | |
| basin_A=float((d[~side] < BASIN_EPS).mean()), | |
| basin_B=float((d[side] < BASIN_EPS).mean()), | |
| n_attractors=int(len(attr)), | |
| sites_recovered=recovered, sites_total=int(len(tgt))) | |
| # ---------- main ---------- | |
| def main(): | |
| t0 = time.time() | |
| rng = np.random.default_rng(7) | |
| xh, labels = make_world8() | |
| xA, xB = xh[labels < 4], xh[labels >= 4] | |
| print("training teachers A, B (denoising), B_mem (memorization) ...") | |
| A = train_teacher(xA, noise=0.1, seed=0) | |
| B = train_teacher(xB, noise=0.1, seed=0) | |
| B_mem = train_teacher(xB, noise=0.0, seed=0) | |
| frame = UnionFrame(A, B, xh, labels) | |
| orc = frame.oracle(rng) | |
| print(f"union oracle: {len(orc['attractors'])} attractors, ceilings " | |
| f"joint {orc['ceil_joint']:.2f} A {orc['ceil_A']:.2f} B {orc['ceil_B']:.2f}") | |
| # --- union: read carrier vs their SGD student (0.82 mean, 2.048M evals) --- | |
| car = LatticeCarrier(frame.field_np, frame.lo, frame.hi) | |
| m = eval_carrier(car, frame, orc) | |
| print(f"read carrier ({car.n_samples} samples): joint {m['basin_joint']:.2f} " | |
| f"A {m['basin_A']:.2f} B {m['basin_B']:.2f} " | |
| f"sites {m['sites_recovered']}/{m['sites_total']}") | |
| out = dict(oracle=dict(ceil_joint=orc["ceil_joint"], ceil_A=orc["ceil_A"], | |
| ceil_B=orc["ceil_B"], n_attractors=int(len(orc["attractors"]))), | |
| carrier_samples=car.n_samples, union=m) | |
| # --- lineage: resample lookup field on a jittered lattice, 5 generations --- | |
| lineage = [m] | |
| cur = car | |
| for g in range(2, GENS + 1): | |
| jit = rng.random(2).astype(np.float32) # origin shift in [0, h) | |
| cur = LatticeCarrier(cur.lookup, frame.lo, frame.hi, origin_jitter=jit) | |
| e = eval_carrier(cur, frame, orc) | |
| lineage.append(e) | |
| print(f" lineage gen {g}: joint {e['basin_joint']:.2f} " | |
| f"A {e['basin_A']:.2f} B {e['basin_B']:.2f}") | |
| out["lineage"] = lineage | |
| # --- onion filter: does disordered knowledge survive the read channel? --- | |
| frame_c = UnionFrame(A, B_mem, xh, labels) | |
| orc_c = frame_c.oracle(rng) | |
| print(f"contaminated oracle: ceilings joint {orc_c['ceil_joint']:.2f} " | |
| f"A {orc_c['ceil_A']:.2f} Bmem {orc_c['ceil_B']:.2f}") | |
| car_c = LatticeCarrier(frame_c.field_np, frame_c.lo, frame_c.hi) | |
| lineage_c = [eval_carrier(car_c, frame_c, orc_c)] | |
| cur = car_c | |
| for g in range(2, GENS + 1): | |
| jit = rng.random(2).astype(np.float32) | |
| cur = LatticeCarrier(cur.lookup, frame_c.lo, frame_c.hi, origin_jitter=jit) | |
| lineage_c.append(eval_carrier(cur, frame_c, orc_c)) | |
| for g, e in enumerate(lineage_c, 1): | |
| print(f" contaminated gen {g}: joint {e['basin_joint']:.2f} " | |
| f"A {e['basin_A']:.2f} Bmem {e['basin_B']:.2f}") | |
| out["lineage_contaminated"] = lineage_c | |
| OUT.write_text(json.dumps(out, indent=2)) | |
| print(f"\nwrote {OUT} ({time.time() - t0:.0f}s)") | |
| print("\ntheir gi.json for comparison: union student 0.82 (ceiling 0.98), " | |
| "lineage 0.91->0.83, contaminated B-side 0.24->0.19") | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "oracle": { | |
| "ceil_joint": 0.9725, | |
| "ceil_A": 0.9692307692307692, | |
| "ceil_B": 0.975609756097561, | |
| "n_attractors": 8 | |
| }, | |
| "carrier_samples": 262144, | |
| "union": { | |
| "basin_joint": 1.0, | |
| "basin_A": 1.0, | |
| "basin_B": 1.0, | |
| "n_attractors": 8, | |
| "sites_recovered": 8, | |
| "sites_total": 8 | |
| }, | |
| "lineage": [ | |
| { | |
| "basin_joint": 1.0, | |
| "basin_A": 1.0, | |
| "basin_B": 1.0, | |
| "n_attractors": 8, | |
| "sites_recovered": 8, | |
| "sites_total": 8 | |
| }, | |
| { | |
| "basin_joint": 0.995, | |
| "basin_A": 1.0, | |
| "basin_B": 0.9902439024390244, | |
| "n_attractors": 8, | |
| "sites_recovered": 8, | |
| "sites_total": 8 | |
| }, | |
| { | |
| "basin_joint": 0.99, | |
| "basin_A": 1.0, | |
| "basin_B": 0.9804878048780488, | |
| "n_attractors": 8, | |
| "sites_recovered": 8, | |
| "sites_total": 8 | |
| }, | |
| { | |
| "basin_joint": 0.99, | |
| "basin_A": 1.0, | |
| "basin_B": 0.9804878048780488, | |
| "n_attractors": 8, | |
| "sites_recovered": 8, | |
| "sites_total": 8 | |
| }, | |
| { | |
| "basin_joint": 0.99, | |
| "basin_A": 1.0, | |
| "basin_B": 0.9804878048780488, | |
| "n_attractors": 8, | |
| "sites_recovered": 8, | |
| "sites_total": 8 | |
| } | |
| ], | |
| "lineage_contaminated": [ | |
| { | |
| "basin_joint": 0.8425, | |
| "basin_A": 1.0, | |
| "basin_B": 0.7028301886792453, | |
| "n_attractors": 19, | |
| "sites_recovered": 18, | |
| "sites_total": 19 | |
| }, | |
| { | |
| "basin_joint": 0.8425, | |
| "basin_A": 1.0, | |
| "basin_B": 0.7028301886792453, | |
| "n_attractors": 20, | |
| "sites_recovered": 18, | |
| "sites_total": 19 | |
| }, | |
| { | |
| "basin_joint": 0.8375, | |
| "basin_A": 1.0, | |
| "basin_B": 0.6933962264150944, | |
| "n_attractors": 19, | |
| "sites_recovered": 18, | |
| "sites_total": 19 | |
| }, | |
| { | |
| "basin_joint": 0.8325, | |
| "basin_A": 0.9946808510638298, | |
| "basin_B": 0.6886792452830188, | |
| "n_attractors": 20, | |
| "sites_recovered": 18, | |
| "sites_total": 19 | |
| }, | |
| { | |
| "basin_joint": 0.835, | |
| "basin_A": 0.9946808510638298, | |
| "basin_B": 0.6933962264150944, | |
| "n_attractors": 19, | |
| "sites_recovered": 18, | |
| "sites_total": 19 | |
| } | |
| ] | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """alpha(noise) sweep: is the memorization->generalization transition a | |
| fractal->smooth basin-boundary transition? | |
| F-73 observed (direction only): ordered teachers alpha 0.73-1.04, sub-ordered | |
| 0.56-0.70. Here the dedicated test at d=8 (transition known to sit between | |
| noise 0.1 and 0.2 from the frontier grid), noise in {0, .05, .1, .15, .2, .3}, | |
| 3 teacher seeds per level, same world. | |
| Pre-registered criteria (BEFORE running): | |
| (a) mean alpha at noise=0.0 (memorization) < 0.7; | |
| (b) mean alpha at noise=0.3 (deep ordered) > 0.85; | |
| (c) the noise level where mean alpha crosses 0.85 lies within one grid step | |
| of where mean conv crosses 0.9 (co-located transitions); | |
| (d) informative either way: if alpha transitions at LOWER noise than conv, | |
| alpha is the more sensitive order parameter; if alpha stays flat while | |
| conv snaps, the fractal-boundary story is DEAD. | |
| Run (one seed per job): uv run --no-project --with mlx --with numpy python spike_alpha.py <seed> | |
| Appends to spike_alpha_results.jsonl | |
| """ | |
| import json | |
| import sys | |
| import time | |
| import mlx.core as mx | |
| import numpy as np | |
| from spike_dscaling import (HERE, ITER, N_PROBES, EPS_FRACS, field, | |
| find_attractors, iterate, make_world, train_teacher) | |
| OUT = HERE / "spike_alpha_results.jsonl" | |
| D = 8 | |
| NOISES = (0.0, 0.05, 0.1, 0.15, 0.2, 0.3) | |
| def probe(noise, seed): | |
| t0 = time.time() | |
| rng = np.random.default_rng(D) # same world for all configs | |
| xh = make_world(D, rng) | |
| teacher = train_teacher(xh, D, seed=seed, noise=noise) | |
| rng = np.random.default_rng(10_000 + seed) # fresh probe rng per seed | |
| z_tr = np.array(teacher.enc(mx.array(xh))) | |
| mu, sd = z_tr.mean(0), z_tr.std(0) | |
| scale = float(np.linalg.norm(sd)) | |
| lo = (mu - 8 * sd).astype(np.float32) | |
| hi = (mu + 8 * sd).astype(np.float32) | |
| starts = np.concatenate([ | |
| z_tr[rng.integers(0, len(z_tr), 256)] + 0.5 * sd * rng.standard_normal((256, D)), | |
| mu + 2.0 * sd * rng.standard_normal((256, D)), | |
| ]).astype(np.float32) | |
| ep_s = iterate(teacher, starts, ITER, lo, hi) | |
| vn = np.linalg.norm(np.array(field(teacher, mx.array(ep_s))), axis=1) | |
| attrs = find_attractors(ep_s, vn, vtol=5e-3 * scale, merge_r=0.1 * scale) | |
| conv = float((vn < 5e-3 * scale).mean()) | |
| if len(attrs) > 1: | |
| dm = np.linalg.norm(attrs[:, None] - attrs[None, :], axis=-1) | |
| basin_eps = 0.25 * float(np.median(dm[dm > 0])) | |
| else: | |
| basin_eps = 0.25 * scale | |
| probes = np.concatenate([ | |
| z_tr[rng.integers(0, len(z_tr), N_PROBES // 2)] | |
| + 0.5 * sd * rng.standard_normal((N_PROBES // 2, D)), | |
| mu + 2.0 * sd * rng.standard_normal((N_PROBES // 2, D)), | |
| ]).astype(np.float32) | |
| ep_ref = iterate(teacher, probes, ITER, lo, hi) | |
| ep_pert = iterate(teacher, probes + (0.05 * scale) | |
| * rng.standard_normal(probes.shape).astype(np.float32), ITER, lo, hi) | |
| ceiling = float((np.linalg.norm(ep_pert - ep_ref, axis=1) < basin_eps).mean()) | |
| fr = [] | |
| for f in EPS_FRACS: | |
| eps = f * basin_eps | |
| ep_e = iterate(teacher, probes + eps | |
| * rng.standard_normal(probes.shape).astype(np.float32), ITER, lo, hi) | |
| fr.append(float((np.linalg.norm(ep_e - ep_ref, axis=1) > basin_eps).mean())) | |
| mask = [i for i, v in enumerate(fr) if v > 0] | |
| if len(mask) >= 3: | |
| xs = np.log([EPS_FRACS[i] for i in mask]) | |
| ys = np.log([fr[i] for i in mask]) | |
| alpha = float(np.polyfit(xs, ys, 1)[0]) | |
| else: | |
| alpha = float("nan") | |
| rec = dict(d=D, noise=noise, seed=seed, n_attractors=int(len(attrs)), | |
| conv=conv, ceiling=ceiling, alpha=alpha, f_eps=fr, | |
| seconds=round(time.time() - t0)) | |
| with OUT.open("a") as fh: | |
| fh.write(json.dumps(rec) + "\n") | |
| print(f"noise={noise} seed={seed}: attrs {len(attrs)} conv {conv:.2f} " | |
| f"ceil {ceiling:.2f} alpha {alpha:.2f} [{rec['seconds']}s]") | |
| if __name__ == "__main__": | |
| seed = int(sys.argv[1]) | |
| for noise in NOISES: | |
| probe(noise, seed) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {"d": 8, "noise": 0.0, "seed": 0, "n_attractors": 0, "conv": 0.0, "ceiling": 0.6640625, "alpha": 0.5697017566380679, "f_eps": [0.078125, 0.109375, 0.17578125, 0.2421875, 0.37109375, 0.55859375], "seconds": 16} | |
| {"d": 8, "noise": 0.0, "seed": 1, "n_attractors": 3, "conv": 0.03515625, "ceiling": 0.71484375, "alpha": 0.5280631012561426, "f_eps": [0.1328125, 0.14453125, 0.2890625, 0.41015625, 0.60546875, 0.6796875], "seconds": 14} | |
| {"d": 8, "noise": 0.0, "seed": 2, "n_attractors": 1, "conv": 0.04296875, "ceiling": 0.73828125, "alpha": 0.6844503847547813, "f_eps": [0.05078125, 0.05859375, 0.13671875, 0.20703125, 0.33203125, 0.45703125], "seconds": 16} | |
| {"d": 8, "noise": 0.05, "seed": 1, "n_attractors": 0, "conv": 0.0, "ceiling": 0.79296875, "alpha": 0.6345902892578807, "f_eps": [0.03515625, 0.06640625, 0.08984375, 0.14453125, 0.21875, 0.33984375], "seconds": 15} | |
| {"d": 8, "noise": 0.05, "seed": 0, "n_attractors": 5, "conv": 0.025390625, "ceiling": 0.79296875, "alpha": 0.6375153522736858, "f_eps": [0.07421875, 0.14453125, 0.25390625, 0.390625, 0.53125, 0.6875], "seconds": 17} | |
| {"d": 8, "noise": 0.05, "seed": 2, "n_attractors": 3, "conv": 0.12890625, "ceiling": 0.7265625, "alpha": 0.5447994650729416, "f_eps": [0.09765625, 0.17578125, 0.26171875, 0.37890625, 0.4921875, 0.6875], "seconds": 18} | |
| {"d": 8, "noise": 0.1, "seed": 1, "n_attractors": 5, "conv": 0.150390625, "ceiling": 0.7109375, "alpha": 0.6973353951228579, "f_eps": [0.0390625, 0.09375, 0.1328125, 0.19140625, 0.37109375, 0.46875], "seconds": 14} | |
| {"d": 8, "noise": 0.1, "seed": 0, "n_attractors": 13, "conv": 0.44140625, "ceiling": 0.765625, "alpha": 0.6777192206306923, "f_eps": [0.04296875, 0.1015625, 0.15234375, 0.203125, 0.34765625, 0.51953125], "seconds": 17} | |
| {"d": 8, "noise": 0.1, "seed": 2, "n_attractors": 11, "conv": 0.560546875, "ceiling": 0.7734375, "alpha": 0.6973173148330541, "f_eps": [0.01953125, 0.0390625, 0.04296875, 0.07421875, 0.12109375, 0.26171875], "seconds": 16} | |
| {"d": 8, "noise": 0.15, "seed": 1, "n_attractors": 4, "conv": 1.0, "ceiling": 0.9609375, "alpha": 1.1816321722514036, "f_eps": [0.00390625, 0.00390625, 0.015625, 0.03515625, 0.06640625, 0.1875], "seconds": 15} | |
| {"d": 8, "noise": 0.15, "seed": 0, "n_attractors": 4, "conv": 0.998046875, "ceiling": 0.9453125, "alpha": 0.9408143304947397, "f_eps": [0.0078125, 0.01171875, 0.03515625, 0.07421875, 0.078125, 0.20703125], "seconds": 19} | |
| {"d": 8, "noise": 0.15, "seed": 2, "n_attractors": 7, "conv": 1.0, "ceiling": 0.9609375, "alpha": 0.9867104779116247, "f_eps": [0.0078125, 0.01171875, 0.0234375, 0.07421875, 0.10546875, 0.19921875], "seconds": 18} | |
| {"d": 8, "noise": 0.2, "seed": 1, "n_attractors": 6, "conv": 1.0, "ceiling": 0.96875, "alpha": 1.051078857802584, "f_eps": [0.00390625, 0.01171875, 0.01171875, 0.0234375, 0.09375, 0.16015625], "seconds": 16} | |
| {"d": 8, "noise": 0.2, "seed": 0, "n_attractors": 4, "conv": 1.0, "ceiling": 0.96875, "alpha": 1.2673750739438054, "f_eps": [0.0, 0.00390625, 0.015625, 0.0390625, 0.09375, 0.12890625], "seconds": 18} | |
| {"d": 8, "noise": 0.2, "seed": 2, "n_attractors": 4, "conv": 1.0, "ceiling": 0.9765625, "alpha": 1.0499832163298541, "f_eps": [0.00390625, 0.0078125, 0.01171875, 0.0234375, 0.0703125, 0.1484375], "seconds": 18} | |
| {"d": 8, "noise": 0.3, "seed": 1, "n_attractors": 7, "conv": 1.0, "ceiling": 0.9765625, "alpha": 0.8525921427926573, "f_eps": [0.0078125, 0.0078125, 0.015625, 0.02734375, 0.06640625, 0.12109375], "seconds": 16} | |
| {"d": 8, "noise": 0.3, "seed": 0, "n_attractors": 8, "conv": 1.0, "ceiling": 0.96484375, "alpha": 1.0865992291658317, "f_eps": [0.00390625, 0.00390625, 0.01953125, 0.0234375, 0.05859375, 0.14453125], "seconds": 19} | |
| {"d": 8, "noise": 0.3, "seed": 2, "n_attractors": 6, "conv": 1.0, "ceiling": 0.93359375, "alpha": 0.9140233316505503, "f_eps": [0.0, 0.01171875, 0.0390625, 0.03125, 0.09375, 0.1796875], "seconds": 18} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """Closing runs: (1) GP carrier with the honest sigma (0.5 x median — selection | |
| by NMSE alone picked the oversmoothed kernel, our own mean-residual trap); | |
| (2) the basin(N) coverage curve for the NN carrier: 65k / 262k / 4M | |
| (1M = 0.32 known from spike_mnist.py). Rising without saturation => the 16D | |
| wall is budget; plateau below ceiling => intrinsic. | |
| Run: uv run --no-project --with mlx --with numpy python spike_curve.py | |
| """ | |
| import json | |
| import time | |
| import mlx.core as mx | |
| import numpy as np | |
| from spike_mnist import (HERE, ITER_STEPS, build_table, knn_field, | |
| train_or_load_teacher) | |
| from spike_wiener import basin_metrics, field_metrics, gp_iterate, gp_predict, rbf_solve | |
| OUT = HERE / "spike_curve_results.json" | |
| M_GP = 16384 | |
| SIGMA_MULT = 0.5 | |
| NS = (65536, 262144, 4_000_000) | |
| def nn_run(teacher, meta, Z, V): | |
| Zsq = mx.sum(Z * Z, axis=1) | |
| mx.eval(Zsq) | |
| z = mx.array(meta["probes"]) | |
| lo, hi = mx.array(meta["clip_lo"]), mx.array(meta["clip_hi"]) | |
| for _ in range(ITER_STEPS): | |
| z = z + knn_field(Z, V, Zsq, z, 1) | |
| z = mx.maximum(mx.minimum(z, hi), lo) | |
| mx.eval(z) | |
| return basin_metrics(teacher, meta, np.array(z)) | |
| def main(): | |
| t0 = time.time() | |
| teacher, meta = train_or_load_teacher() | |
| print(f"teacher: ceiling {float(meta['self_agree']):.2f}, " | |
| f"basin_eps {float(meta['basin_eps']):.3f}") | |
| results = [] | |
| # --- (1) GP at honest sigma --- | |
| Z, V = build_table(teacher, meta) # 1M, cached seed | |
| Zn, Vn = np.array(Z), np.array(V) | |
| rng = np.random.default_rng(42) | |
| perm = rng.permutation(len(Zn)) | |
| fin_idx = perm[4096:4096 + M_GP] # same split as spike_wiener | |
| ZM, VM = Zn[fin_idx], Vn[fin_idx] | |
| d_med = np.median(np.linalg.norm( | |
| ZM[rng.integers(0, M_GP, 2000)] - ZM[rng.integers(0, M_GP, 2000)], axis=1)) | |
| sigma = SIGMA_MULT * d_med | |
| alpha = rbf_solve(ZM, VM, sigma) | |
| ZM_m = mx.array(ZM); alpha_m = mx.array(alpha.astype(np.float32)) | |
| Zsq_m = mx.sum(ZM_m * ZM_m, axis=1) | |
| mx.eval(ZM_m, alpha_m, Zsq_m) | |
| Vh = np.array(gp_predict(ZM_m, Zsq_m, alpha_m, sigma, mx.array(meta["zeval"]))) | |
| nmse, cos = field_metrics(Vh, meta["Vt_eval"]) | |
| ep = gp_iterate(ZM_m, Zsq_m, alpha_m, sigma, meta["probes"], | |
| meta["clip_lo"], meta["clip_hi"]) | |
| basin, dec, med = basin_metrics(teacher, meta, ep) | |
| print(f"GP M={M_GP} sigma=0.5x: basin {basin:.2f} dec {dec:.2f} " | |
| f"median-err {med:.2f} nmse {nmse:.3f} cos {cos:.2f}") | |
| results.append(dict(carrier=f"gp_M{M_GP}_s0.5", basin_agreement=basin, | |
| dec_agree=dec, endpoint_median=med, | |
| field_nmse=nmse, field_cos=cos)) | |
| # --- (2) basin(N) curve for NN k=1 --- | |
| for n in NS: | |
| t1 = time.time() | |
| if n <= len(Zn): | |
| Zt, Vt_ = mx.array(Zn[perm[:n]]), mx.array(Vn[perm[:n]]) | |
| else: | |
| Zt, Vt_ = build_table(teacher, meta, n=n, seed=2) | |
| basin, dec, med = nn_run(teacher, meta, Zt, Vt_) | |
| print(f"NN k=1 N={n}: basin {basin:.2f} dec {dec:.2f} " | |
| f"median-err {med:.2f} [{time.time()-t1:.0f}s]") | |
| results.append(dict(carrier=f"nn1_N{n}", basin_agreement=basin, | |
| dec_agree=dec, endpoint_median=med)) | |
| del Zt, Vt_ | |
| OUT.write_text(json.dumps(dict(ceiling=float(meta["self_agree"]), | |
| known=dict(nn1_N1000000=0.32), | |
| results=results), indent=2)) | |
| print(f"\nknown: NN-1M 0.32 | ceiling {float(meta['self_agree']):.2f} " | |
| f"[{time.time()-t0:.0f}s total]") | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "ceiling": 0.703125, | |
| "known": { | |
| "nn1_N1000000": 0.32 | |
| }, | |
| "results": [ | |
| { | |
| "carrier": "gp_M16384_s0.5", | |
| "basin_agreement": 0.484375, | |
| "dec_agree": 0.47265625, | |
| "endpoint_median": 3.0590169429779053, | |
| "field_nmse": 0.12240374088287354, | |
| "field_cos": 0.8978049159049988 | |
| }, | |
| { | |
| "carrier": "nn1_N65536", | |
| "basin_agreement": 0.0234375, | |
| "dec_agree": 0.015625, | |
| "endpoint_median": 8.348264694213867 | |
| }, | |
| { | |
| "carrier": "nn1_N262144", | |
| "basin_agreement": 0.18359375, | |
| "dec_agree": 0.1171875, | |
| "endpoint_median": 4.842211723327637 | |
| }, | |
| { | |
| "carrier": "nn1_N4000000", | |
| "basin_agreement": 0.484375, | |
| "dec_agree": 0.44140625, | |
| "endpoint_median": 2.630967140197754 | |
| } | |
| ] | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """d-scaling of the read/GP carriers + basin-boundary fractality. | |
| Questions (F-72 open item "d-scaling"): | |
| 1. Does the correlation-structure gain (GP-16k vs NN at matched/larger N) | |
| shrink with latent dimension d? d in {2,4,8,16,24,32}. | |
| 2. Is anything special around d=24 (the last Viazovska/universal-optimality | |
| dimension)? Here only as an empirical smoothness check of gain(d). | |
| 3. Fractality: uncertainty exponent alpha of the TEACHER's basin boundaries | |
| (Grebogi/McDonald/Ott/Yorke): fraction of probes whose endpoint changes | |
| under eps-perturbation scales as f(eps) ~ eps^alpha; boundary box | |
| dimension D_b = d - alpha. Smooth boundary => alpha ~ 1; fractal => <1. | |
| Coverage cost of basin transfer should track alpha/d, not d alone. | |
| World per d: 8 Gaussian blobs, centers r=1.7 * random unit vectors in R^d, | |
| std 0.32 (separation ratio ~constant across d), lifted tanh-nonlinearly to | |
| R^64. Denoising AE teacher 64-128-128-d, noise 0.1, 4000 steps. Ceiling, | |
| conv_frac, basin_eps computed per d (basin_eps = 0.25 * median | |
| inter-attractor distance). All basins vs the teacher's own endpoints. | |
| Run: uv run --no-project --with mlx --with numpy python spike_dscaling.py 2 4 8 16 | |
| uv run --no-project --with mlx --with numpy python spike_dscaling.py 24 32 | |
| Appends per-dim results to spike_dscaling_results.jsonl | |
| """ | |
| import json | |
| import sys | |
| import time | |
| from pathlib import Path | |
| import mlx.core as mx | |
| import mlx.nn as nn | |
| import mlx.optimizers as optim | |
| import numpy as np | |
| HERE = Path(__file__).parent | |
| OUT = HERE / "spike_dscaling_results.jsonl" | |
| D_IN = 64 | |
| N_BLOBS = 8 | |
| R_CENTERS = 1.7 | |
| STD = 0.32 | |
| N_PER = 500 | |
| NOISE = 0.1 | |
| STEPS, BS, LR = 4000, 256, 1e-3 | |
| ITER = 800 | |
| N_PROBES = 256 | |
| NS = (16384, 262144, 1_000_000) | |
| M_GP = 16384 | |
| SIGMA_MULT = 0.5 | |
| JITTER = 1e-8 | |
| EPS_FRACS = (1 / 64, 1 / 32, 1 / 16, 1 / 8, 1 / 4, 1 / 2) # x basin_eps | |
| class MLP(nn.Module): | |
| def __init__(self, dims): | |
| super().__init__() | |
| self.layers = [nn.Linear(a, b) for a, b in zip(dims[:-1], dims[1:])] | |
| def __call__(self, x): | |
| n = len(self.layers) | |
| for i, layer in enumerate(self.layers): | |
| x = layer(x) | |
| if i < n - 1: | |
| x = nn.gelu(x) | |
| return x | |
| class AE(nn.Module): | |
| def __init__(self, d_lat): | |
| super().__init__() | |
| self.enc = MLP([D_IN, 128, 128, d_lat]) | |
| self.dec = MLP([d_lat, 128, 128, D_IN]) | |
| def __call__(self, x): | |
| return self.dec(self.enc(x)) | |
| def field(model, z): | |
| return model.enc(model.dec(z)) - z | |
| def iterate(model, z0, steps, lo, hi): | |
| z = mx.array(z0) | |
| lo, hi = mx.array(lo), mx.array(hi) | |
| for i in range(steps): | |
| z = model.enc(model.dec(z)) | |
| z = mx.maximum(mx.minimum(z, hi), lo) | |
| if i % 50 == 0: | |
| mx.eval(z) | |
| mx.eval(z) | |
| return np.array(z) | |
| def find_attractors(endpoints, vnorms, vtol, merge_r): | |
| pts = endpoints[vnorms < vtol] | |
| centers = [] | |
| for p in pts: | |
| if all(np.linalg.norm(p - c) > merge_r for c in centers): | |
| centers.append(p) | |
| return np.array(centers) if centers else np.zeros((0, endpoints.shape[1])) | |
| def make_world(d, rng): | |
| u = rng.normal(size=(N_BLOBS, d)) | |
| centers = R_CENTERS * u / np.linalg.norm(u, axis=1, keepdims=True) | |
| labels = np.repeat(np.arange(N_BLOBS), N_PER) | |
| x = centers[labels] + STD * rng.normal(size=(len(labels), d)) | |
| W1 = rng.normal(size=(d, D_IN)) / np.sqrt(d) | |
| b1 = 0.1 * rng.normal(size=(D_IN,)) | |
| W2 = rng.normal(size=(D_IN, D_IN)) / np.sqrt(D_IN) | |
| return (np.tanh(x @ W1 + b1) @ W2).astype(np.float32) | |
| def train_teacher(xh, d, seed=0, noise=NOISE): | |
| mx.random.seed(seed) | |
| model = AE(d) | |
| mx.eval(model.parameters()) | |
| opt = optim.Adam(learning_rate=LR) | |
| X = mx.array(xh) | |
| rng = np.random.default_rng(seed) | |
| def loss_fn(m, xb, xn): | |
| return mx.mean((m(xn) - xb) ** 2) | |
| lg = nn.value_and_grad(model, loss_fn) | |
| for _ in range(STEPS): | |
| idx = mx.array(rng.integers(0, len(xh), BS)) | |
| xb = X[idx] | |
| xn = xb + noise * mx.array(rng.standard_normal((BS, D_IN)).astype(np.float32)) | |
| _, g = lg(model, xb, xn) | |
| opt.update(model, g) | |
| mx.eval(model.parameters(), opt.state) | |
| return model | |
| def build_table(teacher, mu, sd, d, n, seed=1): | |
| rng = np.random.default_rng(1000 + seed) | |
| zs, total = [], 0 | |
| bs = 8192 | |
| while total < n: | |
| half = bs // 2 | |
| broad = (mu + 2.0 * sd * rng.standard_normal((half, d))).astype(np.float32) | |
| core = mx.array((mu + 1.5 * sd * rng.standard_normal((bs - half, d))).astype(np.float32)) | |
| for _ in range(int(rng.integers(2, 25))): | |
| core = teacher.enc(teacher.dec(core)) | |
| mx.eval(core) | |
| zs.append(np.concatenate([broad, np.array(core)])) | |
| total += bs | |
| Z = np.concatenate(zs)[:n] | |
| V = np.empty_like(Z) | |
| for i in range(0, n, 8192): | |
| V[i:i + 8192] = np.array(field(teacher, mx.array(Z[i:i + 8192]))) | |
| return Z, V | |
| def knn_iterate(Zt, Vt, z0, lo, hi, steps=ITER): | |
| Zsq = mx.sum(Zt * Zt, axis=1) | |
| z = mx.array(z0) | |
| lo, hi = mx.array(lo), mx.array(hi) | |
| for _ in range(steps): | |
| d2 = Zsq[None, :] - 2 * (z @ Zt.T) | |
| z = z + Vt[mx.argmin(d2, axis=1)] | |
| z = mx.maximum(mx.minimum(z, hi), lo) | |
| mx.eval(z) | |
| return np.array(z) | |
| def gp_carrier(ZM, VM, sigma): | |
| Z = ZM.astype(np.float64) | |
| sq = (Z ** 2).sum(1) | |
| d2 = sq[:, None] - 2 * Z @ Z.T + sq[None, :] | |
| K = np.exp(-np.maximum(d2, 0) / (2 * sigma ** 2)) | |
| K[np.diag_indices_from(K)] += JITTER * K.shape[0] | |
| alpha = np.linalg.solve(K, VM.astype(np.float64)) | |
| return mx.array(ZM), mx.array(alpha.astype(np.float32)) | |
| def gp_iterate(ZM_m, alpha_m, sigma, z0, lo, hi, steps=ITER): | |
| Zsq = mx.sum(ZM_m * ZM_m, axis=1) | |
| z = mx.array(z0) | |
| lo, hi = mx.array(lo), mx.array(hi) | |
| for _ in range(steps): | |
| d2 = Zsq[None, :] - 2 * (z @ ZM_m.T) + mx.sum(z * z, axis=1)[:, None] | |
| z = z + mx.exp(-mx.maximum(d2, 0.0) / (2 * sigma ** 2)) @ alpha_m | |
| z = mx.maximum(mx.minimum(z, hi), lo) | |
| mx.eval(z) | |
| return np.array(z) | |
| def run_dim(d, noise=NOISE): | |
| t0 = time.time() | |
| rng = np.random.default_rng(d) | |
| xh = make_world(d, rng) | |
| teacher = train_teacher(xh, d, noise=noise) | |
| z_tr = np.array(teacher.enc(mx.array(xh))) | |
| mu, sd = z_tr.mean(0), z_tr.std(0) | |
| scale = float(np.linalg.norm(sd)) | |
| lo = (mu - 8 * sd).astype(np.float32) | |
| hi = (mu + 8 * sd).astype(np.float32) | |
| starts = np.concatenate([ | |
| z_tr[rng.integers(0, len(z_tr), 256)] + 0.5 * sd * rng.standard_normal((256, d)), | |
| mu + 2.0 * sd * rng.standard_normal((256, d)), | |
| ]).astype(np.float32) | |
| ep_s = iterate(teacher, starts, ITER, lo, hi) | |
| vn = np.linalg.norm(np.array(field(teacher, mx.array(ep_s))), axis=1) | |
| attrs = find_attractors(ep_s, vn, vtol=5e-3 * scale, merge_r=0.1 * scale) | |
| conv = float((vn < 5e-3 * scale).mean()) | |
| if len(attrs) > 1: | |
| dm = np.linalg.norm(attrs[:, None] - attrs[None, :], axis=-1) | |
| basin_eps = 0.25 * float(np.median(dm[dm > 0])) | |
| else: | |
| basin_eps = 0.25 * scale | |
| probes = np.concatenate([ | |
| z_tr[rng.integers(0, len(z_tr), N_PROBES // 2)] | |
| + 0.5 * sd * rng.standard_normal((N_PROBES // 2, d)), | |
| mu + 2.0 * sd * rng.standard_normal((N_PROBES // 2, d)), | |
| ]).astype(np.float32) | |
| ep_ref = iterate(teacher, probes, ITER, lo, hi) | |
| ep_pert = iterate(teacher, probes + (0.05 * scale) | |
| * rng.standard_normal(probes.shape).astype(np.float32), ITER, lo, hi) | |
| ceiling = float((np.linalg.norm(ep_pert - ep_ref, axis=1) < basin_eps).mean()) | |
| # --- fractality: uncertainty exponent of teacher basin boundaries --- | |
| fr = [] | |
| for f in EPS_FRACS: | |
| eps = f * basin_eps | |
| ep_e = iterate(teacher, probes + eps | |
| * rng.standard_normal(probes.shape).astype(np.float32), ITER, lo, hi) | |
| fr.append(float((np.linalg.norm(ep_e - ep_ref, axis=1) > basin_eps).mean())) | |
| mask = [i for i, v in enumerate(fr) if v > 0] | |
| if len(mask) >= 3: | |
| xs = np.log([EPS_FRACS[i] for i in mask]) | |
| ys = np.log([fr[i] for i in mask]) | |
| alpha = float(np.polyfit(xs, ys, 1)[0]) | |
| else: | |
| alpha = float("nan") | |
| # --- carriers --- | |
| Z, V = build_table(teacher, mu, sd, d, max(NS)) | |
| res_nn = {} | |
| for n in NS: | |
| ep = knn_iterate(mx.array(Z[:n]), mx.array(V[:n]), probes, lo, hi) | |
| res_nn[n] = float((np.linalg.norm(ep - ep_ref, axis=1) < basin_eps).mean()) | |
| perm = np.random.default_rng(42).permutation(len(Z))[:M_GP] | |
| ZM, VM = Z[perm], V[perm] | |
| d_med = float(np.median(np.linalg.norm( | |
| ZM[rng.integers(0, M_GP, 2000)] - ZM[rng.integers(0, M_GP, 2000)], axis=1))) | |
| sigma = SIGMA_MULT * d_med | |
| ZM_m, alpha_m = gp_carrier(ZM, VM, sigma) | |
| ep = gp_iterate(ZM_m, alpha_m, sigma, probes, lo, hi) | |
| basin_gp = float((np.linalg.norm(ep - ep_ref, axis=1) < basin_eps).mean()) | |
| rec = dict(d=d, noise=noise, n_attractors=int(len(attrs)), conv=conv, ceiling=ceiling, | |
| basin_eps=basin_eps, scale=scale, alpha=alpha, f_eps=fr, | |
| nn={str(k): v for k, v in res_nn.items()}, gp_16k=basin_gp, | |
| seconds=round(time.time() - t0)) | |
| with OUT.open("a") as fh: | |
| fh.write(json.dumps(rec) + "\n") | |
| print(f"d={d}: attrs {len(attrs)} conv {conv:.2f} ceil {ceiling:.2f} " | |
| f"alpha {alpha:.2f} | NN {res_nn[NS[0]]:.2f}/{res_nn[NS[1]]:.2f}/" | |
| f"{res_nn[NS[2]]:.2f} | GP-16k {basin_gp:.2f} [{rec['seconds']}s]") | |
| if __name__ == "__main__": | |
| for arg in sys.argv[1:]: | |
| if ":" in arg: | |
| ds, ns = arg.split(":") | |
| run_dim(int(ds), noise=float(ns)) | |
| else: | |
| run_dim(int(arg)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {"d": 2, "n_attractors": 2, "conv": 0.9609375, "ceiling": 0.9765625, "basin_eps": 0.9323017597198486, "scale": 1.5630770921707153, "alpha": 0.8738169732842052, "f_eps": [0.0078125, 0.00390625, 0.00390625, 0.03125, 0.0390625, 0.08984375], "nn": {"16384": 0.953125, "262144": 0.953125, "1000000": 0.953125}, "gp_16k": 0.9765625, "seconds": 144} | |
| {"d": 24, "n_attractors": 2, "conv": 0.05859375, "ceiling": 0.75, "basin_eps": 1.2125375270843506, "scale": 1.6877076625823975, "alpha": 0.6965928751616873, "f_eps": [0.05859375, 0.140625, 0.2109375, 0.3828125, 0.4765625, 0.734375], "nn": {"16384": 0.0, "262144": 0.0, "1000000": 0.0}, "gp_16k": 0.0234375, "seconds": 147} | |
| {"d": 4, "n_attractors": 3, "conv": 0.98828125, "ceiling": 0.9609375, "basin_eps": 0.6162179112434387, "scale": 2.109988212585449, "alpha": 0.5631492364423594, "f_eps": [0.0, 0.01953125, 0.0234375, 0.0234375, 0.04296875, 0.1015625], "nn": {"16384": 0.93359375, "262144": 0.96484375, "1000000": 0.984375}, "gp_16k": 0.5625, "seconds": 157} | |
| {"d": 32, "n_attractors": 7, "conv": 0.39453125, "ceiling": 0.7890625, "basin_eps": 0.15200293064117432, "scale": 1.5878543853759766, "alpha": 0.7009823095079734, "f_eps": [0.01953125, 0.03515625, 0.0625, 0.08984375, 0.15625, 0.22265625], "nn": {"16384": 0.0, "262144": 0.0, "1000000": 0.0}, "gp_16k": 0.00390625, "seconds": 157} | |
| {"d": 8, "n_attractors": 15, "conv": 0.4375, "ceiling": 0.79296875, "basin_eps": 0.9196683764457703, "scale": 2.4838883876800537, "alpha": 0.5861776112612977, "f_eps": [0.05859375, 0.109375, 0.13671875, 0.203125, 0.3203125, 0.48828125], "nn": {"16384": 0.015625, "262144": 0.37890625, "1000000": 0.4453125}, "gp_16k": 0.55078125, "seconds": 97} | |
| {"d": 16, "n_attractors": 8, "conv": 0.494140625, "ceiling": 0.84765625, "basin_eps": 1.6302363872528076, "scale": 2.1428210735321045, "alpha": 0.6907876805490315, "f_eps": [0.0625, 0.0546875, 0.140625, 0.2265625, 0.37890625, 0.5078125], "nn": {"16384": 0.0, "262144": 0.00390625, "1000000": 0.26953125}, "gp_16k": 0.58203125, "seconds": 136} | |
| {"d": 8, "noise": 0.35, "n_attractors": 8, "conv": 1.0, "ceiling": 0.97265625, "basin_eps": 0.9710250496864319, "scale": 2.6066699028015137, "alpha": 0.727158124277616, "f_eps": [0.01171875, 0.015625, 0.01171875, 0.03125, 0.05859375, 0.1484375], "nn": {"16384": 0.80078125, "262144": 0.9609375, "1000000": 0.96875}, "gp_16k": 0.98828125, "seconds": 160} | |
| {"d": 24, "noise": 0.35, "n_attractors": 8, "conv": 1.0, "ceiling": 0.91015625, "basin_eps": 1.0080623626708984, "scale": 2.7132787704467773, "alpha": 0.7588185869465107, "f_eps": [0.01953125, 0.015625, 0.05859375, 0.06640625, 0.1015625, 0.24609375], "nn": {"16384": 0.00390625, "262144": 0.13671875, "1000000": 0.1796875}, "gp_16k": 0.90625, "seconds": 167} | |
| {"d": 16, "noise": 0.35, "n_attractors": 8, "conv": 1.0, "ceiling": 0.95703125, "basin_eps": 1.0177536010742188, "scale": 2.6999692916870117, "alpha": 1.0379641440300558, "f_eps": [0.00390625, 0.01171875, 0.0234375, 0.03125, 0.07421875, 0.1875], "nn": {"16384": 0.1640625, "262144": 0.32421875, "1000000": 0.6171875}, "gp_16k": 0.95703125, "seconds": 163} | |
| {"d": 32, "noise": 0.35, "n_attractors": 8, "conv": 1.0, "ceiling": 0.91796875, "basin_eps": 0.9295673370361328, "scale": 2.519338369369507, "alpha": 0.9928596952474479, "f_eps": [0.01171875, 0.01171875, 0.0390625, 0.08203125, 0.140625, 0.28125], "nn": {"16384": 0.0, "262144": 0.03125, "1000000": 0.04296875}, "gp_16k": 0.85546875, "seconds": 170} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """Orderability frontier: does the contractive (ordered) phase shrink with | |
| latent dimension — and is there a d beyond which no denoising level orders | |
| the teacher? | |
| The d-scaling spike showed teachers at fixed noise 0.1 fall out of the | |
| ordered regime for d >= 8 (conv 0.44-0.06), making carrier comparison | |
| ill-posed there. Here: teacher-only grid over (d, noise), reporting | |
| attractor count / conv_frac / ceiling. Ordered = conv >= 0.9. | |
| Run: uv run --no-project --with mlx --with numpy python spike_frontier.py 8 16 | |
| uv run --no-project --with mlx --with numpy python spike_frontier.py 24 32 | |
| Appends to spike_frontier_results.jsonl | |
| """ | |
| import json | |
| import sys | |
| import time | |
| import mlx.core as mx | |
| import numpy as np | |
| from spike_dscaling import (HERE, ITER, N_PROBES, AE, field, find_attractors, | |
| iterate, make_world, train_teacher) | |
| OUT = HERE / "spike_frontier_results.jsonl" | |
| NOISES = (0.2, 0.35, 0.5) | |
| def probe_teacher(d, noise): | |
| t0 = time.time() | |
| rng = np.random.default_rng(d) | |
| xh = make_world(d, rng) | |
| teacher = train_teacher(xh, d, noise=noise) | |
| z_tr = np.array(teacher.enc(mx.array(xh))) | |
| mu, sd = z_tr.mean(0), z_tr.std(0) | |
| scale = float(np.linalg.norm(sd)) | |
| lo = (mu - 8 * sd).astype(np.float32) | |
| hi = (mu + 8 * sd).astype(np.float32) | |
| starts = np.concatenate([ | |
| z_tr[rng.integers(0, len(z_tr), 256)] + 0.5 * sd * rng.standard_normal((256, d)), | |
| mu + 2.0 * sd * rng.standard_normal((256, d)), | |
| ]).astype(np.float32) | |
| ep_s = iterate(teacher, starts, ITER, lo, hi) | |
| vn = np.linalg.norm(np.array(field(teacher, mx.array(ep_s))), axis=1) | |
| attrs = find_attractors(ep_s, vn, vtol=5e-3 * scale, merge_r=0.1 * scale) | |
| conv = float((vn < 5e-3 * scale).mean()) | |
| if len(attrs) > 1: | |
| dm = np.linalg.norm(attrs[:, None] - attrs[None, :], axis=-1) | |
| basin_eps = 0.25 * float(np.median(dm[dm > 0])) | |
| else: | |
| basin_eps = 0.25 * scale | |
| probes = np.concatenate([ | |
| z_tr[rng.integers(0, len(z_tr), N_PROBES // 2)] | |
| + 0.5 * sd * rng.standard_normal((N_PROBES // 2, d)), | |
| mu + 2.0 * sd * rng.standard_normal((N_PROBES // 2, d)), | |
| ]).astype(np.float32) | |
| ep_ref = iterate(teacher, probes, ITER, lo, hi) | |
| ep_pert = iterate(teacher, probes + (0.05 * scale) | |
| * rng.standard_normal(probes.shape).astype(np.float32), ITER, lo, hi) | |
| ceiling = float((np.linalg.norm(ep_pert - ep_ref, axis=1) < basin_eps).mean()) | |
| rec = dict(d=d, noise=noise, n_attractors=int(len(attrs)), conv=conv, | |
| ceiling=ceiling, seconds=round(time.time() - t0)) | |
| with OUT.open("a") as fh: | |
| fh.write(json.dumps(rec) + "\n") | |
| print(f"d={d} noise={noise}: attrs {len(attrs)} conv {conv:.2f} " | |
| f"ceil {ceiling:.2f} [{rec['seconds']}s]") | |
| if __name__ == "__main__": | |
| for arg in sys.argv[1:]: | |
| for noise in NOISES: | |
| probe_teacher(int(arg), noise) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {"d": 8, "noise": 0.2, "n_attractors": 4, "conv": 1.0, "ceiling": 0.95703125, "seconds": 27} | |
| {"d": 24, "noise": 0.2, "n_attractors": 5, "conv": 1.0, "ceiling": 0.93359375, "seconds": 27} | |
| {"d": 8, "noise": 0.35, "n_attractors": 8, "conv": 1.0, "ceiling": 0.97265625, "seconds": 14} | |
| {"d": 24, "noise": 0.35, "n_attractors": 8, "conv": 1.0, "ceiling": 0.91015625, "seconds": 13} | |
| {"d": 8, "noise": 0.5, "n_attractors": 8, "conv": 1.0, "ceiling": 0.95703125, "seconds": 13} | |
| {"d": 24, "noise": 0.5, "n_attractors": 8, "conv": 1.0, "ceiling": 0.95703125, "seconds": 13} | |
| {"d": 16, "noise": 0.2, "n_attractors": 7, "conv": 1.0, "ceiling": 0.94140625, "seconds": 15} | |
| {"d": 32, "noise": 0.2, "n_attractors": 7, "conv": 1.0, "ceiling": 0.90234375, "seconds": 17} | |
| {"d": 16, "noise": 0.35, "n_attractors": 8, "conv": 1.0, "ceiling": 0.95703125, "seconds": 15} | |
| {"d": 32, "noise": 0.35, "n_attractors": 8, "conv": 1.0, "ceiling": 0.91796875, "seconds": 13} | |
| {"d": 16, "noise": 0.5, "n_attractors": 8, "conv": 1.0, "ceiling": 0.93359375, "seconds": 14} | |
| {"d": 32, "noise": 0.5, "n_attractors": 8, "conv": 1.0, "ceiling": 0.921875, "seconds": 15} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """Read-carrier at 16D: does the MNIST precision wall apply to a non-fit carrier? | |
| Their result (knowledge-as-dynamics, runs/mnist_summary.json): at latent 16, | |
| ALL methods fail at basin level — field arm 0.09, best (outdistill) 0.19, | |
| teacher self-agreement ceiling 0.71. Diagnosis: SGD regression from sparse | |
| probes plateaus at NMSE 0.049; residual compounds over ~800 iterations. | |
| Carrier here: a table of (z, V_teacher(z)) pairs sampled from THEIR OWN | |
| sampling distribution (teacher latent moments + deep teacher trajectories — | |
| same calibration-level leak they document), read back by k-NN lookup. | |
| 1M samples vs their student's 3.07M field evaluations (12000 steps x 256). | |
| No gradients. If this also fails, the wall is dimension-intrinsic (coverage); | |
| if it holds, the wall is a property of the fit carrier. | |
| Teacher/eval protocol adapted line-for-line from the reviewed source of | |
| github.com/Veso-AI-Open-Source/knowledge-as-dynamics (MIT), | |
| src/m5_fielddistill/mnist.py, so numbers are comparable. | |
| Run: uv run --no-project --with mlx --with numpy python spike_mnist.py | |
| """ | |
| import gzip | |
| import json | |
| import time | |
| import urllib.request | |
| from pathlib import Path | |
| import mlx.core as mx | |
| import mlx.nn as nn | |
| import mlx.optimizers as optim | |
| import numpy as np | |
| HERE = Path(__file__).parent | |
| DATA = HERE / "data" | |
| MIRROR = "https://ossci-datasets.s3.amazonaws.com/mnist/" | |
| FILES = {"train_x": "train-images-idx3-ubyte.gz", "train_y": "train-labels-idx1-ubyte.gz", | |
| "test_x": "t10k-images-idx3-ubyte.gz", "test_y": "t10k-labels-idx1-ubyte.gz"} | |
| D_IN, D_LAT = 784, 16 | |
| ENC_DIMS = [784, 256, 128, 16] | |
| DEC_DIMS = [16, 128, 256, 784] | |
| N_TRAIN, N_TEST = 8000, 2000 | |
| STEPS, BS, LR = 3500, 256, 1e-3 | |
| TEACHER_NOISE = 0.3 # their best (0.2-0.5 sweep) | |
| ITER_STEPS = 800 | |
| N_PROBES = 256 | |
| N_TABLE = 1_000_000 | |
| TEACHER_W = HERE / "mnist_teacher.safetensors" | |
| META = HERE / "mnist_meta.npz" | |
| OUT = HERE / "spike_mnist_results.json" | |
| class MLP(nn.Module): | |
| def __init__(self, dims): | |
| super().__init__() | |
| self.layers = [nn.Linear(a, b) for a, b in zip(dims[:-1], dims[1:])] | |
| def __call__(self, x): | |
| n = len(self.layers) | |
| for i, layer in enumerate(self.layers): | |
| x = layer(x) | |
| if i < n - 1: | |
| x = nn.gelu(x) | |
| return x | |
| class AE(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.enc = MLP(ENC_DIMS) | |
| self.dec = MLP(DEC_DIMS) | |
| def __call__(self, x): | |
| return self.dec(self.enc(x)) | |
| def field(model, z): | |
| return model.enc(model.dec(z)) - z | |
| def iterate(model, z, steps, clip_lo, clip_hi): | |
| lo, hi = mx.array(clip_lo), mx.array(clip_hi) | |
| for i in range(steps): | |
| z = model.enc(model.dec(z)) | |
| z = mx.maximum(mx.minimum(z, hi), lo) | |
| if i % 20 == 0: | |
| mx.eval(z) | |
| mx.eval(z) | |
| return z | |
| def find_attractors(endpoints, vnorms, vtol, merge_r): | |
| pts = endpoints[vnorms < vtol] | |
| centers = [] | |
| for p in pts: | |
| if all(np.linalg.norm(p - c) > merge_r for c in centers): | |
| centers.append(p) | |
| return np.array(centers) if centers else np.zeros((0, endpoints.shape[1])) | |
| def load_mnist(): | |
| DATA.mkdir(parents=True, exist_ok=True) | |
| arrs = {} | |
| for key, fname in FILES.items(): | |
| p = DATA / fname | |
| if not p.exists(): | |
| print(f"downloading {fname} ...") | |
| urllib.request.urlretrieve(MIRROR + fname, p) | |
| raw = gzip.open(p, "rb").read() | |
| if "x" in key: | |
| arrs[key] = (np.frombuffer(raw, np.uint8, offset=16) | |
| .reshape(-1, 784).astype(np.float32) / 255.0) | |
| else: | |
| arrs[key] = np.frombuffer(raw, np.uint8, offset=8).astype(np.int64) | |
| return (arrs["train_x"][:N_TRAIN], arrs["test_x"][:N_TEST]) | |
| def train_or_load_teacher(): | |
| xtr, xte = load_mnist() | |
| mx.random.seed(0) | |
| model = AE() | |
| mx.eval(model.parameters()) | |
| if TEACHER_W.exists() and META.exists(): | |
| model.load_weights(str(TEACHER_W)) | |
| mx.eval(model.parameters()) | |
| return model, dict(np.load(META)) | |
| t0 = time.time() | |
| opt = optim.Adam(learning_rate=LR) | |
| X = mx.array(xtr) | |
| rng = np.random.default_rng(0) | |
| def loss_fn(m, xb, xn): | |
| return mx.mean((m(xn) - xb) ** 2) | |
| lg = nn.value_and_grad(model, loss_fn) | |
| for i in range(STEPS): | |
| idx = mx.array(rng.integers(0, N_TRAIN, BS)) | |
| xb = X[idx] | |
| xn = xb + TEACHER_NOISE * mx.array(rng.standard_normal((BS, D_IN)).astype(np.float32)) | |
| _, g = lg(model, xb, xn) | |
| opt.update(model, g) | |
| mx.eval(model.parameters(), opt.state) | |
| if (i + 1) % 1000 == 0: | |
| print(f" teacher step {i+1}/{STEPS} [{time.time()-t0:.0f}s]") | |
| z_tr = np.array(model.enc(X)) | |
| z_te = np.array(model.enc(mx.array(xte))) | |
| mu, sd = z_tr.mean(0), z_tr.std(0) | |
| scale = float(np.linalg.norm(sd)) | |
| clip_lo = (mu - 8 * sd).astype(np.float32) | |
| clip_hi = (mu + 8 * sd).astype(np.float32) | |
| rng = np.random.default_rng(7) | |
| starts = np.concatenate([ | |
| z_te[rng.integers(0, N_TEST, 256)] + 0.5 * sd * rng.standard_normal((256, D_LAT)), | |
| mu + 2.0 * sd * rng.standard_normal((256, D_LAT)), | |
| ]).astype(np.float32) | |
| ep = np.array(iterate(model, mx.array(starts), ITER_STEPS, clip_lo, clip_hi)) | |
| vn = np.linalg.norm(np.array(field(model, mx.array(ep))), axis=1) | |
| attractors = find_attractors(ep, vn, vtol=1e-2, merge_r=0.1 * scale) | |
| if len(attractors) > 1: | |
| dm = np.linalg.norm(attractors[:, None] - attractors[None, :], axis=-1) | |
| basin_eps = 0.25 * float(np.median(dm[dm > 0])) | |
| else: | |
| basin_eps = 0.25 * scale | |
| probes = np.concatenate([ | |
| z_te[rng.integers(0, N_TEST, N_PROBES // 2)] | |
| + 0.5 * sd * rng.standard_normal((N_PROBES // 2, D_LAT)), | |
| mu + 2.0 * sd * rng.standard_normal((N_PROBES // 2, D_LAT)), | |
| ]).astype(np.float32) | |
| ep_probes = np.array(iterate(model, mx.array(probes), ITER_STEPS, clip_lo, clip_hi)) | |
| ep_pert = np.array(iterate(model, mx.array( | |
| probes + (0.05 * scale) * rng.standard_normal(probes.shape).astype(np.float32)), | |
| ITER_STEPS, clip_lo, clip_hi)) | |
| self_agree = float((np.linalg.norm(ep_pert - ep_probes, axis=1) < basin_eps).mean()) | |
| conv_frac = float((vn < 1e-2).mean()) | |
| zeval = np.concatenate([ | |
| z_te[rng.integers(0, N_TEST, 512)] + 0.2 * sd * rng.standard_normal((512, D_LAT)), | |
| z_te[rng.integers(0, N_TEST, 512)] + 1.0 * sd * rng.standard_normal((512, D_LAT)), | |
| mu + 2.0 * sd * rng.standard_normal((512, D_LAT)), | |
| ]).astype(np.float32) | |
| Vt_eval = np.array(field(model, mx.array(zeval))) | |
| model.save_weights(str(TEACHER_W)) | |
| np.savez(META, mu=mu, sd=sd, scale=scale, clip_lo=clip_lo, clip_hi=clip_hi, | |
| attractors=attractors, basin_eps=basin_eps, probes=probes, | |
| ep_probes=ep_probes, self_agree=self_agree, conv_frac=conv_frac, | |
| zeval=zeval, Vt_eval=Vt_eval) | |
| print(f"teacher: {len(attractors)} attractors, conv {conv_frac:.2f}, " | |
| f"ceiling {self_agree:.2f}, basin_eps {basin_eps:.3f}, scale {scale:.2f}") | |
| return model, dict(np.load(META)) | |
| # ---------- the read carrier: k-NN table over sampled field ---------- | |
| def build_table(teacher, meta, n=N_TABLE, seed=1): | |
| """Same sampling distribution as their field arm's sample_z: half broad | |
| prior from teacher latent moments, half deep teacher-trajectory points.""" | |
| mu, sd = meta["mu"], meta["sd"] | |
| rng = np.random.default_rng(1000 + seed) | |
| zs = [] | |
| bs = 4096 | |
| t0 = time.time() | |
| while sum(len(z) for z in zs) < n: | |
| half = bs // 2 | |
| broad = (mu + 2.0 * sd * rng.standard_normal((half, D_LAT))).astype(np.float32) | |
| core = mx.array((mu + 1.5 * sd * rng.standard_normal((bs - half, D_LAT))) | |
| .astype(np.float32)) | |
| depth = int(rng.integers(2, 25)) | |
| for _ in range(depth): | |
| core = teacher.enc(teacher.dec(core)) | |
| mx.eval(core) | |
| zs.append(np.concatenate([broad, np.array(core)])) | |
| Z = np.concatenate(zs)[:n] | |
| V = np.empty_like(Z) | |
| for i in range(0, n, 8192): | |
| V[i:i + 8192] = np.array(field(teacher, mx.array(Z[i:i + 8192]))) | |
| print(f"table: {n} (z, V) pairs [{time.time()-t0:.0f}s]") | |
| return mx.array(Z), mx.array(V) | |
| def knn_field(Z, V, Zsq, zq, k): | |
| """k-NN readout of the table at query points zq (mlx, matmul distances).""" | |
| d2 = Zsq[None, :] - 2 * (zq @ Z.T) # + |zq|^2, constant per row | |
| if k == 1: | |
| idx = mx.argmin(d2, axis=1) | |
| return V[idx] | |
| idx = mx.argpartition(d2, kth=k - 1, axis=1)[:, :k] | |
| dk = mx.take_along_axis(d2, idx, axis=1) | |
| dk = dk - mx.min(dk, axis=1, keepdims=True) | |
| w = mx.exp(-dk / (mx.mean(dk, axis=1, keepdims=True) + 1e-9)) | |
| w = w / mx.sum(w, axis=1, keepdims=True) | |
| return mx.sum(V[idx] * w[:, :, None], axis=1) | |
| def carrier_iterate(Z, V, Zsq, z0, clip_lo, clip_hi, k, steps=ITER_STEPS): | |
| z = mx.array(z0) | |
| lo, hi = mx.array(clip_lo), mx.array(clip_hi) | |
| for i in range(steps): | |
| z = z + knn_field(Z, V, Zsq, z, k) | |
| z = mx.maximum(mx.minimum(z, hi), lo) | |
| mx.eval(z) | |
| return np.array(z) | |
| def eval_carrier(teacher, meta, Z, V, k): | |
| Zsq = mx.sum(Z * Z, axis=1) | |
| mx.eval(Zsq) | |
| t0 = time.time() | |
| ep = carrier_iterate(Z, V, Zsq, meta["probes"], meta["clip_lo"], meta["clip_hi"], k) | |
| dists = np.linalg.norm(ep - meta["ep_probes"], axis=1) | |
| basin = float((dists < float(meta["basin_eps"])).mean()) | |
| img_s = np.array(teacher.dec(mx.array(ep.astype(np.float32)))) | |
| img_t = np.array(teacher.dec(mx.array(meta["ep_probes"]))) | |
| dec_agree = float((((img_s - img_t) ** 2).mean(axis=1) < 0.01).mean()) | |
| # field fidelity off the table's own points (their zeval mix) | |
| Vh = np.array(knn_field(Z, V, Zsq, mx.array(meta["zeval"]), k)) | |
| Vt = meta["Vt_eval"] | |
| nmse = float(((Vh - Vt) ** 2).mean() / (Vt ** 2).mean()) | |
| mag = np.linalg.norm(Vt, axis=1) | |
| mask = mag > np.percentile(mag, 10) | |
| cos = float((np.sum(Vh[mask] * Vt[mask], axis=1) | |
| / (np.linalg.norm(Vh[mask], axis=1) * mag[mask] + 1e-12)).mean()) | |
| return dict(k=k, basin_agreement=basin, dec_agree=dec_agree, | |
| endpoint_median=float(np.median(dists)), | |
| field_nmse=nmse, field_cos=cos, | |
| eval_seconds=round(time.time() - t0, 1)) | |
| def main(): | |
| teacher, meta = train_or_load_teacher() | |
| print(f"teacher: {len(meta['attractors'])} attractors, conv {float(meta['conv_frac']):.2f}, " | |
| f"ceiling {float(meta['self_agree']):.2f}, basin_eps {float(meta['basin_eps']):.3f}") | |
| Z, V = build_table(teacher, meta) | |
| results = [] | |
| for k in (1, 8): | |
| r = eval_carrier(teacher, meta, Z, V, k) | |
| results.append(r) | |
| print(f"read carrier k={k}: basin {r['basin_agreement']:.2f} " | |
| f"dec {r['dec_agree']:.2f} median-endpoint-err {r['endpoint_median']:.2f} " | |
| f"nmse {r['field_nmse']:.3f} cos {r['field_cos']:.2f} " | |
| f"[{r['eval_seconds']}s]") | |
| OUT.write_text(json.dumps(dict( | |
| ceiling=float(meta["self_agree"]), basin_eps=float(meta["basin_eps"]), | |
| n_table=N_TABLE, results=results), indent=2)) | |
| print(f"\ntheir mnist_summary.json for comparison: field 0.09, fieldplus 0.07, " | |
| f"outdistill 0.19, ceiling 0.71 (nmse: field 0.049)") | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "ceiling": 0.703125, | |
| "basin_eps": 2.450249195098877, | |
| "n_table": 1000000, | |
| "results": [ | |
| { | |
| "k": 1, | |
| "basin_agreement": 0.31640625, | |
| "dec_agree": 0.2421875, | |
| "endpoint_median": 3.8904805183410645, | |
| "field_nmse": 0.37175649404525757, | |
| "field_cos": 0.5767523050308228, | |
| "eval_seconds": 42.9 | |
| }, | |
| { | |
| "k": 8, | |
| "basin_agreement": 0.3125, | |
| "dec_agree": 0.2421875, | |
| "endpoint_median": 3.4760305881500244, | |
| "field_nmse": 0.22140631079673767, | |
| "field_cos": 0.6998631358146667, | |
| "eval_seconds": 426.6 | |
| } | |
| ] | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """Wiener/GP carrier at 16D: cosmology's answer to the sparse-probe field problem. | |
| Third carrier type on the MNIST tier. Same teacher, same 1M-sample table as | |
| spike_mnist.py, but the field is reconstructed by kernel ridge regression | |
| (RBF kernel = GP posterior mean = Wiener filter for a stationary field): | |
| interpolation that respects the field's correlation structure, the method | |
| cosmologists use to reconstruct velocity fields from sparse galaxy probes | |
| (POTENT / Hoffman-Ribak constrained realizations). | |
| Controls: | |
| - NN carrier on a table subsampled to the SAME M points as the GP's | |
| inducing set (isolates correlation-structure gain from sample-count gain). | |
| - Length scale selected on held-out table points only (no extra teacher access). | |
| Reference numbers: NN-1M 0.32, their field arm 0.09, outdistill 0.19, ceiling 0.70. | |
| Run: uv run --no-project --with mlx --with numpy python spike_wiener.py | |
| """ | |
| import json | |
| import time | |
| import mlx.core as mx | |
| import numpy as np | |
| from spike_mnist import (HERE, ITER_STEPS, build_table, knn_field, | |
| train_or_load_teacher) | |
| OUT = HERE / "spike_wiener_results.json" | |
| M_SELECT = 4096 # inducing size for length-scale selection (cheap solves) | |
| M_FINAL = 16384 # inducing size for the real run | |
| N_VAL = 4096 # held-out table points for selection | |
| JITTER = 1e-8 | |
| SIGMA_MULTS = (0.25, 0.5, 1.0, 2.0) | |
| def rbf_solve(ZM, VM, sigma): | |
| """alpha = (K + jitter*tr/M * I)^-1 VM, K_ij = exp(-|zi-zj|^2 / 2sigma^2). f64.""" | |
| Z = ZM.astype(np.float64) | |
| sq = (Z ** 2).sum(1) | |
| d2 = sq[:, None] - 2 * Z @ Z.T + sq[None, :] | |
| K = np.exp(-np.maximum(d2, 0) / (2 * sigma ** 2)) | |
| K[np.diag_indices_from(K)] += JITTER * K.shape[0] | |
| return np.linalg.solve(K, VM.astype(np.float64)) | |
| def gp_predict(ZM_m, Zsq_m, alpha_m, sigma, zq): | |
| d2 = Zsq_m[None, :] - 2 * (zq @ ZM_m.T) + mx.sum(zq * zq, axis=1)[:, None] | |
| Kq = mx.exp(-mx.maximum(d2, 0.0) / (2 * sigma ** 2)) | |
| return Kq @ alpha_m | |
| def gp_iterate(ZM_m, Zsq_m, alpha_m, sigma, z0, clip_lo, clip_hi, steps=ITER_STEPS): | |
| z = mx.array(z0) | |
| lo, hi = mx.array(clip_lo), mx.array(clip_hi) | |
| for _ in range(steps): | |
| z = z + gp_predict(ZM_m, Zsq_m, alpha_m, sigma, z) | |
| z = mx.maximum(mx.minimum(z, hi), lo) | |
| mx.eval(z) | |
| return np.array(z) | |
| def field_metrics(Vh, Vt): | |
| nmse = float(((Vh - Vt) ** 2).mean() / (Vt ** 2).mean()) | |
| mag = np.linalg.norm(Vt, axis=1) | |
| mask = mag > np.percentile(mag, 10) | |
| cos = float((np.sum(Vh[mask] * Vt[mask], axis=1) | |
| / (np.linalg.norm(Vh[mask], axis=1) * mag[mask] + 1e-12)).mean()) | |
| return nmse, cos | |
| def basin_metrics(teacher, meta, ep): | |
| dists = np.linalg.norm(ep - meta["ep_probes"], axis=1) | |
| basin = float((dists < float(meta["basin_eps"])).mean()) | |
| img_s = np.array(teacher.dec(mx.array(ep.astype(np.float32)))) | |
| img_t = np.array(teacher.dec(mx.array(meta["ep_probes"]))) | |
| dec_agree = float((((img_s - img_t) ** 2).mean(axis=1) < 0.01).mean()) | |
| return basin, dec_agree, float(np.median(dists)) | |
| def main(): | |
| t0 = time.time() | |
| teacher, meta = train_or_load_teacher() | |
| print(f"teacher: ceiling {float(meta['self_agree']):.2f}, " | |
| f"basin_eps {float(meta['basin_eps']):.3f}") | |
| Z, V = build_table(teacher, meta) # same seed -> same 1M table | |
| Zn, Vn = np.array(Z), np.array(V) | |
| rng = np.random.default_rng(42) | |
| perm = rng.permutation(len(Zn)) | |
| val_idx = perm[:N_VAL] | |
| Zval, Vval = Zn[val_idx], Vn[val_idx] | |
| pool = perm[N_VAL:] | |
| # --- length-scale selection at M_SELECT on held-out table points --- | |
| sel_idx = pool[:M_SELECT] | |
| ZM, VM = Zn[sel_idx], Vn[sel_idx] | |
| d_med = np.median(np.linalg.norm( | |
| ZM[rng.integers(0, M_SELECT, 2000)] - ZM[rng.integers(0, M_SELECT, 2000)], axis=1)) | |
| print(f"median pairwise distance (table subsample): {d_med:.2f}") | |
| best = None | |
| for mult in SIGMA_MULTS: | |
| sigma = mult * d_med | |
| alpha = rbf_solve(ZM, VM, sigma) | |
| ZM_m = mx.array(ZM); alpha_m = mx.array(alpha.astype(np.float32)) | |
| Zsq_m = mx.sum(ZM_m * ZM_m, axis=1) | |
| Vh = np.array(gp_predict(ZM_m, Zsq_m, alpha_m, sigma, mx.array(Zval))) | |
| nmse, cos = field_metrics(Vh, Vval) | |
| print(f" sigma = {mult:.2f} x median: held-out nmse {nmse:.4f} cos {cos:.2f}") | |
| if best is None or nmse < best[1]: | |
| best = (mult, nmse) | |
| mult = best[0] | |
| print(f"selected sigma = {mult} x median") | |
| # --- final GP carrier at M_FINAL --- | |
| fin_idx = pool[:M_FINAL] | |
| ZM, VM = Zn[fin_idx], Vn[fin_idx] | |
| sigma = mult * d_med | |
| t1 = time.time() | |
| alpha = rbf_solve(ZM, VM, sigma) | |
| print(f"solved {M_FINAL} x {M_FINAL} system [{time.time()-t1:.0f}s]") | |
| ZM_m = mx.array(ZM); alpha_m = mx.array(alpha.astype(np.float32)) | |
| Zsq_m = mx.sum(ZM_m * ZM_m, axis=1) | |
| mx.eval(ZM_m, alpha_m, Zsq_m) | |
| Vh = np.array(gp_predict(ZM_m, Zsq_m, alpha_m, sigma, mx.array(meta["zeval"]))) | |
| nmse, cos = field_metrics(Vh, meta["Vt_eval"]) | |
| ep = gp_iterate(ZM_m, Zsq_m, alpha_m, sigma, meta["probes"], | |
| meta["clip_lo"], meta["clip_hi"]) | |
| basin, dec, med = basin_metrics(teacher, meta, ep) | |
| gp_res = dict(carrier=f"gp_M{M_FINAL}", sigma_mult=mult, basin_agreement=basin, | |
| dec_agree=dec, endpoint_median=med, field_nmse=nmse, field_cos=cos) | |
| print(f"GP carrier (M={M_FINAL}): basin {basin:.2f} dec {dec:.2f} " | |
| f"median-err {med:.2f} nmse {nmse:.3f} cos {cos:.2f}") | |
| # --- control: NN carrier on the SAME M points (budget-matched) --- | |
| Zs, Vs = mx.array(ZM), mx.array(VM) | |
| Zsq_s = mx.sum(Zs * Zs, axis=1) | |
| mx.eval(Zs, Vs, Zsq_s) | |
| z = mx.array(meta["probes"]) | |
| lo, hi = mx.array(meta["clip_lo"]), mx.array(meta["clip_hi"]) | |
| for _ in range(ITER_STEPS): | |
| z = z + knn_field(Zs, Vs, Zsq_s, z, 1) | |
| z = mx.maximum(mx.minimum(z, hi), lo) | |
| mx.eval(z) | |
| basin_n, dec_n, med_n = basin_metrics(teacher, meta, np.array(z)) | |
| Vh_n = np.array(knn_field(Zs, Vs, Zsq_s, mx.array(meta["zeval"]), 1)) | |
| nmse_n, cos_n = field_metrics(Vh_n, meta["Vt_eval"]) | |
| nn_res = dict(carrier=f"nn1_M{M_FINAL}", basin_agreement=basin_n, dec_agree=dec_n, | |
| endpoint_median=med_n, field_nmse=nmse_n, field_cos=cos_n) | |
| print(f"NN carrier (same M={M_FINAL}): basin {basin_n:.2f} dec {dec_n:.2f} " | |
| f"median-err {med_n:.2f} nmse {nmse_n:.3f} cos {cos_n:.2f}") | |
| OUT.write_text(json.dumps(dict( | |
| ceiling=float(meta["self_agree"]), results=[gp_res, nn_res]), indent=2)) | |
| print(f"\nreference: NN-1M 0.32 | their field 0.09 | outdistill 0.19 | " | |
| f"ceiling {float(meta['self_agree']):.2f} [{time.time()-t0:.0f}s total]") | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "ceiling": 0.703125, | |
| "results": [ | |
| { | |
| "carrier": "gp_M16384", | |
| "sigma_mult": 2.0, | |
| "basin_agreement": 0.0, | |
| "dec_agree": 0.0, | |
| "endpoint_median": 7.072707653045654, | |
| "field_nmse": 0.10303101688623428, | |
| "field_cos": 0.809187114238739 | |
| }, | |
| { | |
| "carrier": "nn1_M16384", | |
| "basin_agreement": 0.0, | |
| "dec_agree": 0.00390625, | |
| "endpoint_median": 11.727006912231445, | |
| "field_nmse": 0.5944164395332336, | |
| "field_cos": 0.3631992042064667 | |
| } | |
| ] | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment