SabaPivot's picture
download
raw
7.96 kB
"""Claim 4 -- Algorithm 1: two-stage tuning.
Stage 1 (offline): subsample M observations, get theta_hat, estimate the
sandwich covariance Shat = Jhat^{-1} Ihat Jhat^{-1} (/N).
Stage 2: solve the coupled equations (11)+(12) for the preconditioned step-size
matrix Lambda so that the stationary covariance equals Shat, then run
preconditioned SG(L)D.
Testbed: the misspecified heteroskedastic linear regression of Section 6.1,
y_n | x_n ~ N(x_n' theta*, 1+||x_n||^2), x_n ~ N(0,I_D),
with a fraction p of outliers (mean shift b, variance inflation s^2), reduced to
D = 10, N = 2000 so that the D^2 x D^2 linear solves stay on CPU.
Checks
(i) Stage 1 is consistent: ||Shat_M - Shat_N||/||Shat_N|| decays like M^{-1/2}.
(ii) Stage 2's root solve: residual of Eq. (11), and whether the closed-form
start Lambda_0 = (S H + H S)(C + H S H)^{-1} (which the authors' released
code uses instead) already solves it.
(iii) Internal consistency: the EXACT stationary covariance of the proxy under
the solved Lambda must equal the target Shat to machine precision.
(iv) End-to-end: preconditioned SGD run with each tuning rule; error to the
full-data sandwich target.
(v) Prop 4.4 mixing time 2/mu_min(Lambda H) - 1 versus the measured integrated
autocorrelation time, and the "O(1) epochs" statement.
(vi) Cost: Lambda-computation time / MCMC time (paper Table D.1: 1e-5 - 1e-3).
"""
import json
import sys
import time
import numpy as np
sys.path.insert(0, "/home/ubuntu/samuel/sgmcmc-uq-repro/scripts")
from common import NoiseModel, stationary_cov_proxy, eq11_residual, relf
from linreg import LinReg, sandwich, tune_Lambda
OUT = "/home/ubuntu/samuel/sgmcmc-uq-repro/outputs"
SEED = 20260725
D, N = 10, 2000
P_OUT, B_SHIFT, S_INFL = 0.01, 5.0, 5.0
rng = np.random.default_rng(SEED)
X = rng.standard_normal((N, D))
th_star = rng.standard_normal(D)
sd = np.sqrt(1.0 + (X**2).sum(1))
y = X @ th_star + sd * rng.standard_normal(N)
out_idx = rng.choice(N, size=int(P_OUT * N), replace=False)
y[out_idx] = (
X[out_idx] @ th_star
+ B_SHIFT
+ S_INFL * sd[out_idx] * rng.standard_normal(len(out_idx))
)
print(
f"misspecified heteroskedastic linear regression: N={N} D={D} "
f"{len(out_idx)} outliers"
)
t0 = time.time()
full = LinReg(X, y, sigma=1.0, loss="log")
th_full = full.fit()
g_full = full.grad_n(th_full)
J_full = full.hess_n(th_full)
S_target = sandwich(J_full.mean(0), g_full.T @ g_full / N, N)
res = {
"claim": "Algorithm 1",
"seed": SEED,
"N": N,
"D": D,
"n_outliers": int(len(out_idx)),
"note_alg1_line5": (
"Algorithm 1 line 5 prints Shat <- Jhat^-1 Ihat Jhat^-1 "
"with no 1/N; the covariance of an N-sample estimator "
"requires the 1/N, which the authors' released code does "
"apply. The 1/N is used here."
),
}
# ------------------------------------------------------------------ (i) ----
print("\n=== (i) Stage 1: sandwich estimate from a subsample of size M ===")
stage1 = []
for M in [100, 200, 400, 800, 1600, 2000]:
errs = []
for rep in range(20):
r2 = np.random.default_rng(SEED + 1000 * rep + M)
idx = r2.choice(N, size=M, replace=False)
sub = LinReg(X[idx], y[idx], sigma=1.0, loss="log")
th_M = sub.fit()
gM = sub.grad_n(th_M)
S_M = sandwich(sub.hess_n(th_M).mean(0), gM.T @ gM / M, N)
errs.append(relf(S_M, S_target))
stage1.append(
dict(M=M, mean_rel_err=float(np.mean(errs)), sd=float(np.std(errs)), n_reps=20)
)
print(
f" M={M:<6} ||Shat_M - Shat_N||/||Shat_N|| = {np.mean(errs):.4f} "
f"(sd {np.std(errs):.4f})"
)
sl = np.polyfit(
np.log([r["M"] for r in stage1[:-1]]),
np.log([r["mean_rel_err"] for r in stage1[:-1]]),
1,
)[0]
print(f" fitted decay exponent in M: {sl:.3f} (root-M consistency predicts -0.5)")
res["stage1"] = dict(rows=stage1, fitted_exponent=float(sl))
# ---------------------------------------------------------- (ii,iii,iv,v,vi)
print("\n=== (ii-vi) Stage 2 ===")
nm = NoiseModel(g_full, J_full, th_full, None, N)
H = J_full.mean(0)
Ihat = g_full.T @ g_full / N
A_cov = X.T @ X / N
sig2 = float(np.var(y - X @ th_full))
stage2 = []
for B in [16, 200]:
n_iters = int(200 * N / B)
for method in ["CT", "LR+WS", "DQ+const", "DQ+exact"]:
tl = time.time()
Lam, rr, Lam0 = tune_Lambda(
method, H, S_target, Ihat, H, B, nm=nm, A_cov=A_cov, sig2=sig2, sigma=1.0
)
tune_t = time.time() - tl
# (iii) exact stationary covariance of the proxy under this Lambda
try:
S_pred = stationary_cov_proxy(nm, Lam, B)
pred_err = relf(S_pred, S_target)
r11 = eq11_residual(nm, Lam, S_pred, B)
except Exception:
pred_err, r11 = float("nan"), float("nan")
# residual of the closed-form start
R0 = nm.C(S_target, B) + H @ S_target @ H
F0 = Lam0 @ H @ S_target + S_target @ H @ Lam0 - Lam0 @ R0 @ Lam0
cf_res = float(np.linalg.norm(F0) / np.linalg.norm(Lam0 @ H @ S_target))
ms = time.time()
covs, div = full.sgd_paths(
th_full,
Lam,
B,
n_iters,
R=30,
rng=np.random.default_rng(SEED + B),
burn_frac=0.5,
)
mcmc_t = time.time() - ms
errs = np.array([relf(c, S_target) for c in covs])
errs[div] = np.inf
fin = errs[np.isfinite(errs)]
row = dict(
B=B,
method=method,
tuning_residual=float(rr),
closedform_residual=cf_res,
exact_proxy_cov_vs_target=pred_err,
eq11_residual=r11,
sgd_cov_err_mean=float(np.mean(errs)) if len(fin) == 30 else float("inf"),
sgd_cov_err_median=float(np.median(errs)),
n_diverged=int(div.sum()),
tune_time_s=tune_t,
mcmc_time_s=mcmc_t,
tune_over_mcmc=tune_t / mcmc_t,
n_iters=n_iters,
)
# (v) mixing time for the tuned chain
if method == "DQ+exact":
ev = np.linalg.eigvals(Lam @ H).real
tau_pred = 2.0 / ev.min() - 1.0
rr2 = np.random.default_rng(SEED + 5)
covs2, _ = full.sgd_paths(
th_full, Lam, B, 4000, R=1, rng=rr2, burn_frac=0.0
)
# measure tau_int on one long chain
Xp = []
TH = th_full.copy()
path = np.zeros((20000, D))
for t in range(20000):
idx = rr2.integers(0, N, size=B)
gg = full.grad_n(TH, X[idx], y[idx]).mean(0)
TH = TH - Lam @ gg
path[t] = TH
v = np.linalg.eigh(S_target)[1][:, -1]
f = path[5000:] @ v
f = f - f.mean()
ac = np.correlate(f, f, "full")[len(f) - 1 :]
ac = ac / ac[0]
cut = np.argmax(ac < 0.05) if np.any(ac < 0.05) else len(ac)
tau_int = 1 + 2 * ac[1:cut].sum()
row.update(
tau_pred_prop44=float(tau_pred),
tau_int_measured=float(tau_int),
epochs_to_mix=float(tau_int * B / N),
)
print(
f" Prop 4.4 predicted tau = {tau_pred:.1f}; measured tau_int = "
f"{tau_int:.1f} ({tau_int*B/N:.3f} epochs)"
)
stage2.append(row)
print(
f" B={B:<4} {method:<9} root-resid={rr:.2e} closed-form-resid={cf_res:.2e} "
f"exact-proxy-cov-vs-target={pred_err:.2e} SGD cov err="
f"{row['sgd_cov_err_median']:.4g} tune/mcmc={tune_t/mcmc_t:.2e}"
)
res["stage2"] = stage2
res["runtime_s"] = time.time() - t0
with open(f"{OUT}/claim4_alg1.json", "w") as f:
json.dump(res, f, indent=1)
print("\nwrote outputs/claim4_alg1.json runtime %.1f s" % res["runtime_s"])

Xet Storage Details

Size:
7.96 kB
·
Xet hash:
80dfc7139ed082cbd8274e95ed0f1580ff666500ba91817c179a5e4abe5aaa54

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