SabaPivot's picture
download
raw
8.19 kB
"""Claim 1 -- Theorem 4.1: ||Sigma_theta - Sigma_psi|| / ||Sigma_theta|| <= C_v lambda^{1/2}
|sigma_{theta,d} - sigma_{psi,d}| / sigma_{theta,d} <= C_s lambda^{1/2}
Independent test.
* Sigma_psi is computed EXACTLY (no simulation): the one-step second-moment
identity of the proxy recursion is affine in Sigma, so it is solved as a
D^2 linear system. (This also re-derives Prop. 4.2 / Eq. (11).)
* Sigma_theta is estimated from SGD run on the TRUE logistic loss. The proxy
is run on the SAME minibatches, so Sigma_theta_hat - Sigma_psi_hat is a
control variate with a variance orders of magnitude smaller than either
covariance alone; the reported Sigma_theta = Sigma_psi_exact + (hat diff).
* The empirical scaling exponent of the relative error in lambda is fitted and
compared with the exponent 1/2 that the theorem asserts as an upper bound.
* Boundary audit: step size pushed past lambda < 1/(2L), and Assumption (C)
(strong convexity) removed by a rank-deficient design with no prior.
CPU only. Seeds are explicit.
"""
import json
import sys
import time
import numpy as np
sys.path.insert(0, "/home/ubuntu/samuel/sgmcmc-uq-repro/scripts")
from setup_problem import make
from common import (
run_coupled,
stationary_cov_proxy,
eq11_residual,
cov_from_blocks,
relf,
w2_marginal_lower,
)
OUT = "/home/ubuntu/samuel/sgmcmc-uq-repro/outputs"
SEED = 20260725
B = 16
LAMBDAS = [0.02, 0.05, 0.10, 0.20, 0.35, 0.50]
t_start = time.time()
m, that, nm, K = make(N=500, D=3, seed=SEED, gamma=2.0)
D = m.D
print("constants", K)
print("1/(2L) =", 1.0 / (2 * K["L"]), " mu_hat", K["mu_hat"], " L_hat", K["L_hat"])
rows = []
for lam in LAMBDAS:
Lam = lam * np.eye(D)
S_psi = stationary_cov_proxy(nm, Lam, B)
r11 = eq11_residual(nm, Lam, S_psi, B)
T = int(4000 + 400 / lam)
burn = T // 5
rng = np.random.default_rng(SEED + int(1e5 * lam))
out = run_coupled(
m, that, Lam, B, T=T, R=1000, rng=rng, burn=burn, thin=40, nblock=10
)
nb = out["accT"].shape[0]
def stat(mask):
St = cov_from_blocks(out["accT"], out["sT"], out["cnt"], mask)
Sp = cov_from_blocks(out["accP"], out["sP"], out["cnt"], mask)
Sth = S_psi + (St - Sp) # control-variate estimate
return float(np.linalg.norm(Sth - S_psi, 'fro') / np.linalg.norm(Sth, 'fro'))
from common import jackknife
rel_cov, se_cov = jackknife(stat, nb)
St = cov_from_blocks(out["accT"], out["sT"], out["cnt"])
Sp = cov_from_blocks(out["accP"], out["sP"], out["cnt"])
S_theta = S_psi + (St - Sp)
sd_t, sd_p = np.sqrt(np.diag(S_theta)), np.sqrt(np.diag(S_psi))
rel_sd = float(np.max(np.abs(sd_t - sd_p) / sd_t))
# coupling / W2 quantities (used again by claim 3)
d2 = out["accD2"].sum() / out["cnt"].sum()
w2_up = float(np.sqrt(d2))
w2_lo = w2_marginal_lower(out["thetaS"], out["psiS"])
rows.append(
dict(
lam=lam,
B=B,
T=T,
R=1000,
eq11_residual=r11,
norm_Sigma_psi=float(np.linalg.norm(S_psi, "fro")),
norm_Sigma_theta=float(np.linalg.norm(S_theta, "fro")),
rel_cov_err=rel_cov,
rel_cov_err_se=se_cov,
rel_sd_err=rel_sd,
Cv_emp=rel_cov / np.sqrt(lam),
Cs_emp=rel_sd / np.sqrt(lam),
w2_coupling_upper=w2_up,
w2_marginal_lower=w2_lo,
raw_sim_proxy_err=relf(Sp, S_psi),
)
)
print(
f"lam={lam:<6} relcov={rel_cov:.5f}+-{se_cov:.5f} relsd={rel_sd:.5f} "
f"Cv={rel_cov/np.sqrt(lam):.4f} W2up={w2_up:.3e} eq11res={r11:.1e}"
)
lg = np.log(np.array([r["lam"] for r in rows]))
lc = np.log(np.array([r["rel_cov_err"] for r in rows]))
ls = np.log(np.array([r["rel_sd_err"] for r in rows]))
slope_cov, ic = np.polyfit(lg, lc, 1)
slope_sd, _ = np.polyfit(lg, ls, 1)
r2 = 1 - np.sum((lc - (slope_cov * lg + ic)) ** 2) / np.sum((lc - lc.mean()) ** 2)
print(f"\nfitted exponent cov: {slope_cov:.3f} (R^2={r2:.4f}) sd: {slope_sd:.3f}")
print(
"theorem asserts the error is bounded by C_v lambda^0.5 -> any fitted "
"exponent >= 0.5 is consistent; < 0.5 would falsify."
)
# ---------------- boundary audits -----------------------------------------
audits = {}
# (a) step size past lambda < 1/(2L): proxy chain must lose stationarity
Lhat = K["L_hat"]
aud = []
for f in [0.5, 1.0, 1.5, 1.9, 2.0, 2.2]:
lam = f / Lhat
Lam = lam * np.eye(D)
rho = float(np.max(np.abs(np.linalg.eigvals(np.eye(D) - Lam @ nm.H))))
try:
S = stationary_cov_proxy(nm, Lam, B)
ev = float(np.linalg.eigvalsh(S)[0])
ok = ev > 0
except Exception:
ev, ok = float("nan"), False
aud.append(
dict(
lam=lam,
lam_times_Lhat=f,
spectral_radius=rho,
min_eig_Sigma_psi=ev,
positive_definite=bool(ok),
)
)
print(
f" audit-a lam*Lhat={f}: rho(I-Lam H)={rho:.4f} minEig(Sigma_psi)={ev:.3e} PD={ok}"
)
audits["stepsize_boundary"] = aud
# (b) Assumption (C) removed: rank-deficient design, no prior -> mu = mu_hat = 0
m2, that2, nm2, K2 = make(N=500, D=3, seed=SEED, gamma=1e-8, rank_deficient=True)
print(
f" audit-b mu_hat with Assumption (C) violated = {K2['mu_hat']:.3e} "
f"(vs {K['mu_hat']:.3e} when it holds)"
)
audits["strong_convexity_violation"] = dict(
mu_hat_violated=K2["mu_hat"],
mu_hat_ok=K["mu_hat"],
cond_H_violated=float(np.linalg.cond(nm2.H)),
cond_H_ok=float(np.linalg.cond(nm.H)),
)
try:
Sbad = stationary_cov_proxy(nm2, 0.1 * np.eye(3), B)
audits["strong_convexity_violation"]["norm_Sigma_psi"] = float(np.linalg.norm(Sbad))
audits["strong_convexity_violation"]["min_eig"] = float(np.linalg.eigvalsh(Sbad)[0])
print(
" ||Sigma_psi|| when mu=0:",
np.linalg.norm(Sbad),
" min eig:",
np.linalg.eigvalsh(Sbad)[0],
)
except Exception as e:
audits["strong_convexity_violation"]["error"] = str(e)
# (c) heavy-tailed covariates: assumptions still hold but M, tau4 inflate -> C_v grows
m3, that3, nm3, K3 = make(N=500, D=3, seed=SEED, gamma=2.0, design="heavy")
lam = 0.2
Lam = lam * np.eye(3)
S3 = stationary_cov_proxy(nm3, Lam, B)
o3 = run_coupled(
m3,
that3,
Lam,
B,
T=6000,
R=1000,
rng=np.random.default_rng(SEED + 7),
burn=1500,
nblock=10,
)
St3 = cov_from_blocks(o3["accT"], o3["sT"], o3["cnt"])
Sp3 = cov_from_blocks(o3["accP"], o3["sP"], o3["cnt"])
Sth3 = S3 + (St3 - Sp3)
rel3 = float(np.linalg.norm(Sth3 - S3, 'fro') / np.linalg.norm(Sth3, 'fro'))
base = [r for r in rows if r["lam"] == lam][0]["rel_cov_err"]
audits["heavy_tailed_design"] = dict(
lam=lam,
Mbar_gauss=K["Mbar"],
Mbar_heavy=K3["Mbar"],
tau4_gauss=K["tau4"],
tau4_heavy=K3["tau4"],
rel_cov_err_gauss=base,
rel_cov_err_heavy=rel3,
Cv_gauss=base / np.sqrt(lam),
Cv_heavy=rel3 / np.sqrt(lam),
)
print(
f' audit-c heavy-tailed design: Mbar {K["Mbar"]:.3f}->{K3["Mbar"]:.3f}, '
f"rel cov err {base:.4f}->{rel3:.4f}"
)
res = dict(
claim="Theorem 4.1",
model="logistic regression + Gaussian prior",
N=500,
D=3,
B=B,
beta="inf (SGD)",
seed=SEED,
constants=K,
lambda_upper_bound_1_over_2L=1.0 / (2 * K["L"]),
sweep=rows,
fitted_exponent_cov=float(slope_cov),
fitted_exponent_sd=float(slope_sd),
fit_r2=float(r2),
Cv_max=float(max(r["Cv_emp"] for r in rows)),
Cs_max=float(max(r["Cs_emp"] for r in rows)),
audits=audits,
runtime_s=time.time() - t_start,
)
with open(f"{OUT}/claim1_thm41.json", "w") as f:
json.dump(res, f, indent=1)
np.save(
f"{OUT}/claim1_lambda_sweep.npy",
np.array(
[
[
r["lam"],
r["rel_cov_err"],
r["rel_cov_err_se"],
r["rel_sd_err"],
r["w2_coupling_upper"],
r["w2_marginal_lower"],
]
for r in rows
]
),
)
print("\nwrote outputs/claim1_thm41.json runtime %.1f s" % (time.time() - t_start))

Xet Storage Details

Size:
8.19 kB
·
Xet hash:
85eabf88c92d26cd6dd546baf16cadb1273ababac73bf66fe94d1099a4a73b1e

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