algorise's picture
download
raw
8.47 kB
"""Experiments for all 6 claims of arXiv:2602.13960.
Claim 1: W1(Y^(a), N(0,S_Y)) <= U sqrt(a) log(1/a) (iid Thm 3.1 +
Markov Thm 4.1) -- combined evidence from the instantiations below.
Claim 2: SGD, smooth strongly convex (Prop 3.1).
Claim 3: linear SA with Hurwitz matrix + contractive nonlinear SA
(Props 3.2, 3.3).
Claim 4: non-uniform Berry-Esseen tail bound ~ C a^{1/4} log^{1/2}(1/a)/a.
Claim 5: Markovian noise extension (Prop 4.1) -- including the falsification
check that the *naive* marginal-variance Gaussian is wrong.
Claim 6: general convex (f = |x|^h/h): Gibbs limit at scaling a^{1/h}
(Prop 5.1).
Usage: python repro/run_experiments.py (writes results/claims.json)
"""
from __future__ import annotations
import json
import os
import time
import numpy as np
from scipy import stats
import sa
HERE = os.path.dirname(os.path.abspath(__file__))
RES = os.path.join(os.path.dirname(HERE), "results")
os.makedirs(RES, exist_ok=True)
ALPHAS = [0.2, 0.1, 0.05, 0.025, 0.0125, 0.00625, 0.003125]
N = 400_000
N_TAIL = 1_600_000
def claim2_sgd():
print("== Claim 2: SGD smooth strongly convex (Prop 3.1) ==")
var_y = sa.sgd1d_limit_var()
w1 = []
for a in ALPHAS:
t0 = time.time()
x = sa.sgd1d_stationary(a, N, seed=int(1e6 * a))
y = x / np.sqrt(a)
w1.append(sa.w1_to_gaussian(y, var_y))
print(f" a={a:<8g} W1={w1[-1]:.5f} [{time.time()-t0:.0f}s]")
m, b, r2 = sa.fit_powerlaw(ALPHAS, w1)
U, r2u = sa.fit_sqrtlog(ALPHAS, w1)
print(f" power-law slope={m:.3f} (theory 0.5 + log) R2={r2:.4f}; "
f"U-model U={U:.3f} R2={r2u:.4f}")
return {"alphas": ALPHAS, "w1": w1, "var_y": var_y, "slope": m,
"slope_r2": r2, "U": U, "U_r2": r2u, "N": N}
def claim3_linear_contractive():
print("== Claim 3: linear SA + contractive nonlinear SA ==")
out = {}
S_lin = sa.lyapunov_cov(sa.A_LIN, sa.SIG_LIN)
S_con = sa.lyapunov_cov(sa.C_CONTRACT * sa._R - np.eye(2), sa.SIG_LIN)
for name, sampler, S in [("linear", sa.linear_sa_stationary, S_lin),
("contractive", sa.contractive_sa_stationary,
S_con)]:
w1 = []
for a in ALPHAS:
t0 = time.time()
x = sampler(a, N, seed=int(2e6 * a) + hash(name) % 1000)
y = x / np.sqrt(a)
v, _ = sa.sliced_w1_to_gaussian(y, S)
w1.append(v)
print(f" {name:11s} a={a:<8g} sliced-W1={v:.5f} "
f"[{time.time()-t0:.0f}s]")
m, b, r2 = sa.fit_powerlaw(ALPHAS, w1)
U, r2u = sa.fit_sqrtlog(ALPHAS, w1)
print(f" {name}: slope={m:.3f} R2={r2:.4f}; U={U:.3f} R2={r2u:.4f}")
out[name] = {"alphas": ALPHAS, "w1": w1, "Sigma_Y": S.tolist(),
"slope": m, "slope_r2": r2, "U": U, "U_r2": r2u,
"N": N}
return out
def claim4_tails():
print("== Claim 4: Berry-Esseen-type tail bounds ==")
zeta = np.array([1.0, 1.0]) / np.sqrt(2.0)
S = sa.lyapunov_cov(sa.A_LIN, sa.SIG_LIN)
sd = float(np.sqrt(zeta @ S @ zeta))
a_levels = [0.5, 1.0, 1.5, 2.0, 2.5, 3.0]
alphas = [0.1, 0.05, 0.025, 0.0125, 0.00625]
rows = []
sup_scaled = []
for al in alphas:
t0 = time.time()
x = sa.linear_sa_stationary(al, N_TAIL, seed=int(3e6 * al))
proj = (x / np.sqrt(al)) @ zeta
deltas = []
for a in a_levels:
p_emp = float((proj > a * sd).mean())
p_gauss = float(stats.norm.sf(a))
deltas.append(abs(p_emp - p_gauss))
# the bound is C a^{1/4} log^{1/2}/a => a*Delta should be ~flat in a
scaled = [a * d for a, d in zip(a_levels, deltas)]
sup_scaled.append(max(scaled))
rows.append({"alpha": al, "deltas": deltas, "scaled": scaled})
print(f" a={al:<8g} sup_a a*|Delta|={max(scaled):.5f} "
f"Delta(0.5)={deltas[0]:.5f} Delta(3)={deltas[-1]:.6f} "
f"[{time.time()-t0:.0f}s]")
m, b, r2 = sa.fit_powerlaw(alphas, sup_scaled)
# decay in deviation level: mean over alphas of Delta(a)*a / Delta(a)...
print(f" sup_a a|Delta| power-law slope={m:.3f} "
f"(theory 0.25 + log^0.5) R2={r2:.4f}")
return {"alphas": alphas, "a_levels": a_levels, "rows": rows,
"sup_scaled": sup_scaled, "slope": m, "slope_r2": r2,
"sd": sd, "N": N_TAIL}
def claim5_markov():
print("== Claim 5: Markovian noise (Prop 4.1) ==")
q, c = 0.25, 0.5
var_correct, var_naive = sa.sgd1d_limit_var(markov=(q, c))
w1c, w1n = [], []
for a in ALPHAS:
t0 = time.time()
x = sa.sgd1d_stationary(a, N, seed=int(4e6 * a), markov=(q, c))
y = x / np.sqrt(a)
w1c.append(sa.w1_to_gaussian(y, var_correct))
w1n.append(sa.w1_to_gaussian(y, var_naive))
print(f" a={a:<8g} W1(correct)={w1c[-1]:.5f} "
f"W1(naive)={w1n[-1]:.5f} [{time.time()-t0:.0f}s]")
m, b, r2 = sa.fit_powerlaw(ALPHAS, w1c)
U, r2u = sa.fit_sqrtlog(ALPHAS, w1c)
print(f" correct-S_Y slope={m:.3f} R2={r2:.4f}; U={U:.3f} R2={r2u:.4f}")
print(f" naive-S_Y floor: min W1 = {min(w1n):.4f} (does NOT vanish)")
return {"alphas": ALPHAS, "w1_correct": w1c, "w1_naive": w1n,
"var_correct": var_correct, "var_naive": var_naive,
"slope": m, "slope_r2": r2, "U": U, "U_r2": r2u,
"q": q, "c": c, "N": N}
def claim6_gibbs():
print("== Claim 6: Gibbs limit for general convex (Prop 5.1) ==")
out = {}
alphas6 = [0.2, 0.1, 0.05, 0.025, 0.0125, 0.00625]
for h in [2, 4]:
qf = sa.gibbs_target_quantiles(h)
mean_abs, w1g, w1_bestgauss = [], [], []
for a in alphas6:
t0 = time.time()
x = sa.gibbs_sgd_stationary(a, N, seed=int(5e6 * a) + h, h=h)
y = x / a ** (1.0 / h)
mean_abs.append(float(np.abs(x).mean()))
w1g.append(sa.w1_to_quantiles(y, qf))
w1_bestgauss.append(sa.w1_to_gaussian(y, float(np.var(y))))
print(f" h={h} a={a:<8g} E|X|={mean_abs[-1]:.5f} "
f"W1(Gibbs)={w1g[-1]:.5f} "
f"W1(best Gauss)={w1_bestgauss[-1]:.5f} "
f"[{time.time()-t0:.0f}s]")
m, b, r2 = sa.fit_powerlaw(alphas6, mean_abs)
out[f"h{h}"] = {"alphas": alphas6, "mean_abs": mean_abs,
"w1_gibbs": w1g, "w1_best_gauss": w1_bestgauss,
"scale_slope": m, "scale_r2": r2,
"theory_slope": 1.0 / h, "N": N}
print(f" h={h}: E|X| ~ alpha^{m:.4f} (theory {1.0/h:.4f}) "
f"R2={r2:.5f}")
# kurtosis check at the smallest alpha for h=4 (non-Gaussianity)
x = sa.gibbs_sgd_stationary(alphas6[-1], N, seed=99, h=4)
y = x / alphas6[-1] ** 0.25
kurt = float(stats.kurtosis(y, fisher=False))
out["h4_kurtosis"] = kurt
# Gibbs law kurtosis for exp(-y^4/2)-type: ~2.19 (sub-Gaussian, flat top)
print(f" h=4 kurtosis={kurt:.3f} (Gaussian=3; Gibbs |y|^4 law ~ 2.19)")
return out
def main():
t0 = time.time()
res = {}
res["claim2_sgd"] = claim2_sgd()
res["claim3"] = claim3_linear_contractive()
res["claim4_tails"] = claim4_tails()
res["claim5_markov"] = claim5_markov()
res["claim6_gibbs"] = claim6_gibbs()
# claim 1 aggregates the W1 rates of claims 2/3/5
res["claim1_summary"] = {
"sgd_iid": {"slope": res["claim2_sgd"]["slope"],
"U": res["claim2_sgd"]["U"],
"U_r2": res["claim2_sgd"]["U_r2"]},
"linear_iid": {"slope": res["claim3"]["linear"]["slope"],
"U": res["claim3"]["linear"]["U"],
"U_r2": res["claim3"]["linear"]["U_r2"]},
"contractive_iid": {"slope": res["claim3"]["contractive"]["slope"],
"U": res["claim3"]["contractive"]["U"],
"U_r2": res["claim3"]["contractive"]["U_r2"]},
"sgd_markov": {"slope": res["claim5_markov"]["slope"],
"U": res["claim5_markov"]["U"],
"U_r2": res["claim5_markov"]["U_r2"]},
}
res["wall_seconds"] = time.time() - t0
with open(os.path.join(RES, "claims.json"), "w", encoding="utf-8") as f:
json.dump(res, f, indent=1)
print(f"done in {res['wall_seconds']:.0f}s -> results/claims.json")
if __name__ == "__main__":
main()

Xet Storage Details

Size:
8.47 kB
·
Xet hash:
ce85cf398756721308ebc4bad75ded14ebb3344df1287925a5510c01111a3c17

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.