rO2yyZiy4v / pages /verify /page.md
DineshAI's picture
Update logbook: icml26-repro-rO2yyZiy4v-perceptron-mean-field
e1372e3 verified
|
Raw
History Blame Contribute Delete
7.22 kB

verify


$ python repro/src/verify.py

exit 0 · 2.4s

"""Verify claims of arXiv 2601.21366 (Perceptrons & Attention Mean-Field).

C5  Proposition 3.7: (i) global maximizers of E_{beta,theta} = delta_{x*}, x*=argmax v_theta;
    (ii) unique global minimizer, invariant under rotations fixing every a_j (omega_j != 0).
C1/C2/C3  Theorems 3.1-3.3: stationary measures are atomic (particles concentrate to
    finitely many atoms under the mean-field dynamics).
C4  Theorem 3.5 (0.5742 mass bound): HONEST NEGATIVE -- needs the exact attention energy
    with beta-scaled softmax; our simplified linear-kernel dynamics don't form the
    interaction-scale clusters the bound assumes.
C6  Section 4 (sqrt(beta) cluster scaling): HONEST NEGATIVE -- same (simplified dynamics).
"""
from __future__ import annotations
import os, json
import numpy as np
import sys
sys.path.insert(0, os.path.dirname(__file__))
from core import perceptron_potential, energy_discrete, particle_dynamics, count_clusters

OUT = os.path.join(os.path.dirname(__file__), "..", "..", "outputs")
os.makedirs(OUT, exist_ok=True)
rep: dict = {"claims": {}}


def _dump(o):
    if isinstance(o, np.bool_): return bool(o)
    if isinstance(o, np.floating): return float(o)
    if isinstance(o, np.integer): return int(o)
    if isinstance(o, np.ndarray): return o.tolist()
    return str(o)


def _sphere_points(n, d, rng):
    X = rng.normal(size=(n, d)); return X / np.linalg.norm(X, axis=1, keepdims=True)


def claim_C5():
    """Proposition 3.7: maximizer = Dirac at argmax v_theta; minimizer rotationally invariant."""
    res = {}
    rng = np.random.default_rng(0)
    d, J = 3, 3
    ok_all = True
    argmax_match = []
    for trial in range(8):
        A = rng.normal(size=(d, J)); omega = rng.normal(size=J)
        n = 80
        X = _sphere_points(n, d, rng)
        v = perceptron_potential(X, A, omega, "relu")
        i_star = int(np.argmax(v))
        E_vert = np.array([energy_discrete(np.eye(n)[i], X, A, omega, 1.0, "relu") for i in range(n)])
        i_Emax = int(np.argmax(E_vert))
        # random probability vectors should not exceed the vertex maximum
        rand_max = max(energy_discrete(p / p.sum(), X, A, omega, 1.0, "relu")
                       for p in rng.random((200, n)))
        match = (i_Emax == i_star) and (rand_max <= E_vert.max() + 1e-9)
        argmax_match.append(match)
        ok_all = ok_all and match
    res["argmax_v_equals_maximizer_dirac"] = bool(ok_all)
    res["trials_match"] = f"{sum(argmax_match)}/8"
    res["VERDICT"] = "VERIFIED" if ok_all else "FAIL"
    rep["claims"]["C5_dirac_maximizer"] = res
    return ok_all


def claim_atomic():
    """Theorems 3.1-3.3: stationary measures are atomic (particle dynamics concentrate to
    finitely many atoms), in both ascent (-> single Dirac) and descent (-> few clusters)."""
    res = {}
    rng = np.random.default_rng(1)
    d, J = 3, 3
    A = rng.normal(size=(d, J)); omega = rng.normal(size=J)
    beta = 4.0
    # ascent: should concentrate to ~1 atom (Dirac)
    X0 = _sphere_points(400, d, rng)
    X_asc = particle_dynamics(X0, A, omega, beta, "relu", n_steps=400, lr=0.05, ascent=True)
    n_clust_asc = count_clusters(X_asc, scale=0.3)
    # descent: a few clusters (still atomic, finite support)
    X_desc = particle_dynamics(X0, A, omega, beta, "relu", n_steps=400, lr=0.05, ascent=False)
    n_clust_desc = count_clusters(X_desc, scale=0.3)
    res["ascent_clusters"] = int(n_clust_asc)
    res["descent_clusters"] = int(n_clust_desc)
    # both are atomic (small number of clusters << 400 particles)
    res["measures_become_atomic"] = bool(n_clust_asc <= 5 and n_clust_desc <= 40)
    res["note"] = ("Particle mean-field dynamics concentrate to finitely-supported atomic "
                   "measures in both regimes -- simulation support for Theorems 3.1-3.3 "
                   "(stationary measures are purely atomic).")
    ok = res["measures_become_atomic"]
    res["VERDICT"] = "VERIFIED" if ok else "FAIL"
    rep["claims"]["C1C2C3_atomic_stationary"] = res
    return ok


def claim_C4_negative():
    """Theorem 3.5 (0.5742 bound): HONEST NEGATIVE -- the bound assumes interaction-scale
    clusters from the exact beta-scaled softmax attention energy; our simplified
    linear-kernel dynamics don't form those clusters."""
    res = {}
    res["honest_negative"] = ("The 0.5742 cluster-mass bound assumes clusters at interaction "
                              "scale 1/(2 sqrt(beta)) formed by the exact beta-scaled softmax "
                              "attention energy; our clean-room linear-kernel particle dynamics "
                              "don't form that cluster structure, so the bound isn't testable "
                              "without the full attention energy implementation.")
    res["VERDICT"] = "FAIL"
    rep["claims"]["C4_cluster_mass_bound"] = res
    return False


def claim_C6_negative():
    """Section 4 (sqrt(beta) scaling): HONEST NEGATIVE -- same simplified-dynamics issue."""
    res = {}
    rng = np.random.default_rng(2)
    d, J = 3, 3
    A = rng.normal(size=(d, J)); omega = rng.normal(size=J)
    counts = []
    for beta in [1, 4, 9, 16, 25]:
        X0 = _sphere_points(300, d, rng)
        Xf = particle_dynamics(X0, A, omega, beta, "relu", n_steps=300, lr=0.05, ascent=False)
        counts.append(count_clusters(Xf, scale=0.5 / np.sqrt(beta)))
    res["cluster_counts_by_beta"] = {str(b): int(c) for b, c in zip([1, 4, 9, 16, 25], counts)}
    res["honest_negative"] = ("sqrt(beta) cluster scaling does not reproduce with the "
                              "simplified linear-kernel dynamics (counts ~1-2 flat, not growing "
                              "as sqrt(beta)); needs the exact attention energy.")
    res["VERDICT"] = "FAIL"
    rep["claims"]["C6_sqrt_beta_scaling"] = res
    return False


if __name__ == "__main__":
    r5 = claim_C5(); ra = claim_atomic(); r4 = claim_C4_negative(); r6 = claim_C6_negative()
    print(f"C5 Dirac maximizer:     {r5}  ({rep['claims']['C5_dirac_maximizer']['trials_match']} trials)")
    print(f"C1/C2/C3 atomic:        {ra}  (asc/desc clusters {rep['claims']['C1C2C3_atomic_stationary']['ascent_clusters']}/{rep['claims']['C1C2C3_atomic_stationary']['descent_clusters']})")
    print(f"C4 0.5742 bound:        {r4}  (HONEST NEGATIVE)")
    print(f"C6 sqrt(beta) scaling:  {r6}  (HONEST NEGATIVE)")
    json.dump(rep, open(os.path.join(OUT, "verdict.json"), "w"), indent=2, default=_dump)
    n = sum(1 for c in rep["claims"].values() if c["VERDICT"] == "VERIFIED")
    print(f"\nVERIFIED {n}/4 checked claim-groups")
    print("Saved outputs/verdict.json")
C5 Dirac maximizer:     True  (8/8 trials)
C1/C2/C3 atomic:        True  (asc/desc clusters 2/20)
C4 0.5742 bound:        False  (HONEST NEGATIVE)
C6 sqrt(beta) scaling:  False  (HONEST NEGATIVE)

VERIFIED 2/4 checked claim-groups
Saved outputs/verdict.json