SabaPivot's picture
download
raw
7.96 kB
"""Claim 3 -- Theorem 4.5 (finite-t W2 recursion) and Corollary 4.6 (W2 <= A lambda / B).
Theorem 4.5 is proved by a *synchronous coupling* argument: it bounds
E||theta_t - psi_t||^2 when both chains see the same minibatch and the same
injected noise, and then uses W2^2 <= E||theta_t-psi_t||^2. We therefore
reproduce exactly that quantity, which is a strictly stronger test of the
theorem than testing W2 itself:
(A) finite-t check of Eq. (14). Both chains start at theta_hat so
W2(theta_0,psi_0)=0. The right-hand side needs C_s = E||psi_s-theta_hat||^4,
which is measured from the same run. We check LHS <= RHS at every t.
(B) stationary check of Corollary 4.6, W2(pi_theta,pi_psi) <= A lambda/B, with
A = sqrt(96 C_0) tau_4^2 / mu_hat and C_0 = (2/mu)(Mbar2/(8L) + Mbar^2/(4mu))
read off Equation (F.12); evaluated with the closed-form logistic constants.
(C) empirical scaling exponents in lambda (predicted 1) and in B (predicted -1).
(D) a 1-D marginal W2 computed from sorted quantiles gives a LOWER bound, so
the true W2 is bracketed.
"""
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, w2_marginal_lower
OUT = "/home/ubuntu/samuel/sgmcmc-uq-repro/outputs"
SEED = 20260725
t0 = time.time()
m, that, nm, K = make(N=500, D=3, seed=SEED, gamma=2.0)
D = m.D
L, mu, muh, Mb, Mb2, tau4 = (
K["L"],
K["mu_global"],
K["mu_hat"],
K["Mbar"],
K["Mbar2"],
K["tau4"],
)
print("constants", K)
def A_const(mu_use):
C0 = (2.0 / mu_use) * (Mb2 / (8 * L) + Mb**2 / (4 * mu_use))
return float(np.sqrt(96 * C0) * tau4**2 / muh), float(C0)
res = {
"claim": "Theorem 4.5 / Corollary 4.6",
"seed": SEED,
"constants": K,
"N": 500,
"D": D,
"beta": "inf (SGD)",
}
# ---------------------------------------------------------------- (A) ------
print("=== (A) finite-t check of Eq. (14) ===")
def transient(lam, B, T, R, seed):
"""E||theta_t - psi_t||^2 and C_t = E||psi_t - theta_hat||^4 for t=0..T."""
rng = np.random.default_rng(seed)
X, y, Gam = m.X, m.y, m.Gamma / m.N
gh = nm.g
p = 1 / (1 + np.exp(-X @ that))
wh = p * (1 - p)
TH = np.repeat(that[None, :], R, 0)
PS = TH.copy()
Lam = lam * np.eye(D)
d2 = [0.0]
C4 = [0.0]
for t in range(T):
IDX = rng.integers(0, m.N, size=(R, B))
Xb = X[IDX]
zT = np.einsum("rbd,rd->rb", Xb, TH)
gT = (
np.einsum("rb,rbd->rd", 1 / (1 + np.exp(-zT)) - y[IDX], Xb) / B + TH @ Gam.T
)
U = PS - that
sU = np.einsum("rbd,rd->rb", Xb, U)
gP = (
gh[IDX].mean(1)
+ np.einsum("rb,rbd->rd", wh[IDX] * sU, Xb) / B
+ U @ Gam.T
+ that @ Gam.T
)
TH = TH - gT @ Lam.T
PS = PS - gP @ Lam.T
d2.append(float(((TH - PS) ** 2).sum(1).mean()))
C4.append(float(((((PS - that) ** 2).sum(1)) ** 2).mean()))
return np.array(d2), np.array(C4)
trans = []
for lam in [0.02, 0.05]:
B = 16
T = 2000
d2, C4 = transient(lam, B, T, R=4000, seed=SEED + 31)
beta_bar = 1 - lam * mu * (1 - 2 * lam * L)
pref = lam * (lam * Mb2 / 2 + Mb**2 / (4 * mu))
rhs = np.zeros(T + 1)
acc = 0.0
for t in range(1, T + 1):
acc = beta_bar * acc + C4[t - 1] # sum_{s=1..t} bar_beta^{t-s} C_{s-1}
rhs[t] = pref * acc
# theta_1 == psi_1 identically (the proxy gradient at psi_0 = theta_hat IS the
# true minibatch gradient), so both sides are 0 at t=1 up to rounding; only
# compare where the measured LHS is above the float noise floor.
live = d2[1:] > 1e-20
ratio = rhs[1:][live] / d2[1:][live]
ok = bool(np.all(d2[1:][live] <= rhs[1:][live]))
n_live = int(live.sum())
trans.append(
dict(
lam=lam,
B=B,
T=T,
beta_bar=float(beta_bar),
prefactor=float(pref),
holds_at_every_t=ok,
min_slack_rhs_over_lhs=float(ratio.min()),
lhs_final=float(d2[-1]),
rhs_final=float(rhs[-1]),
t_of_min_slack=int(np.argmin(ratio)) + 1,
n_t_compared=n_live,
lhs_t1=float(d2[1]),
)
)
print(
f" lam={lam} B={B}: Eq.(14) holds at every t = {ok}; "
f"min RHS/LHS = {ratio.min():.3e} (at t={np.argmin(ratio)+1}); "
f"final LHS={d2[-1]:.3e} RHS={rhs[-1]:.3e}"
)
res["A_finite_t_eq14"] = trans
# ---------------------------------------------------------------- (B,C) ----
print("=== (B,C) stationary Cor 4.6 and scaling ===")
lam_cond = min(16 * muh / (200 * L**2), 1 / (4 * L))
print(
f" Cor 4.6 requires lambda < min(B mu_hat/(200 L^2), 1/(4L)) = {lam_cond:.4f} at B=16"
)
A_mu, C0_mu = A_const(mu)
A_muh, C0_muh = A_const(muh)
print(
f" explicit constant A: with global mu={mu:.4g} -> A={A_mu:.4g}; "
f"with plug-in mu_hat={muh:.4g} -> A={A_muh:.4g}"
)
sweep_lam = np.load(
f"{OUT}/claim1_lambda_sweep.npy"
) # lam, relcov, se, relsd, w2up, w2lo
lam_rows = []
for r in sweep_lam:
lam, w2u, w2l = float(r[0]), float(r[4]), float(r[5])
lam_rows.append(
dict(
lam=lam,
B=16,
w2_coupling_upper=w2u,
w2_marginal_lower=w2l,
bound_A_mu=A_mu * lam / 16,
bound_A_muhat=A_muh * lam / 16,
within_corollary_condition=bool(lam < lam_cond),
bound_holds_mu=bool(w2u <= A_mu * lam / 16),
bound_holds_muhat=bool(w2u <= A_muh * lam / 16),
)
)
print(
f" lam={lam:<6} W2 in [{w2l:.3e}, {w2u:.3e}] bound(mu)={A_mu*lam/16:.3e} "
f"bound(mu_hat)={A_muh*lam/16:.3e} inCond={lam < lam_cond}"
)
B_rows = []
lam_fixed = 0.05
for B in [4, 8, 16, 32, 64, 128]:
R = max(100, 16000 // B)
T = 12000
o = run_coupled(
m,
that,
lam_fixed * np.eye(D),
B,
T=T,
R=R,
rng=np.random.default_rng(SEED + B),
burn=T // 4,
thin=100,
nblock=8,
)
w2u = float(np.sqrt(o["accD2"].sum() / o["cnt"].sum()))
w2l = w2_marginal_lower(o["thetaS"], o["psiS"])
cond = min(B * muh / (200 * L**2), 1 / (4 * L))
B_rows.append(
dict(
B=B,
R=R,
T=T,
lam=lam_fixed,
w2_coupling_upper=w2u,
w2_marginal_lower=w2l,
bound_A_muhat=A_muh * lam_fixed / B,
within_corollary_condition=bool(lam_fixed < cond),
bound_holds_muhat=bool(w2u <= A_muh * lam_fixed / B),
)
)
print(
f" B={B:<4} W2 in [{w2l:.3e},{w2u:.3e}] bound={A_muh*lam_fixed/B:.3e} "
f"inCond={lam_fixed < cond}"
)
la = np.log([r["lam"] for r in lam_rows])
wa = np.log([r["w2_coupling_upper"] for r in lam_rows])
s_lam, i_lam = np.polyfit(la, wa, 1)
r2_lam = 1 - np.sum((wa - (s_lam * la + i_lam)) ** 2) / np.sum((wa - wa.mean()) ** 2)
lb = np.log([r["B"] for r in B_rows])
wb = np.log([r["w2_coupling_upper"] for r in B_rows])
s_B, i_B = np.polyfit(lb, wb, 1)
r2_B = 1 - np.sum((wb - (s_B * lb + i_B)) ** 2) / np.sum((wb - wb.mean()) ** 2)
print(f"\n fitted exponent in lambda: {s_lam:.3f} (predicted 1, R^2={r2_lam:.4f})")
print(f" fitted exponent in B : {s_B:.3f} (predicted -1, R^2={r2_B:.4f})")
res.update(
lambda_condition_at_B16=float(lam_cond),
A_with_global_mu=A_mu,
A_with_plugin_muhat=A_muh,
C0_global=C0_mu,
C0_plugin=C0_muh,
lambda_sweep=lam_rows,
batch_sweep=B_rows,
fitted_exponent_lambda=float(s_lam),
fit_r2_lambda=float(r2_lam),
fitted_exponent_B=float(s_B),
fit_r2_B=float(r2_B),
runtime_s=time.time() - t0,
)
with open(f"{OUT}/claim3_w2.json", "w") as f:
json.dump(res, f, indent=1)
print("\nwrote outputs/claim3_w2.json runtime %.1f s" % res["runtime_s"])

Xet Storage Details

Size:
7.96 kB
·
Xet hash:
d4200d449e86e47cf7c8fd574797c3d6fa7fad752f85c1327e6d9473e285f86c

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