Buckets:
| """Claim 2 -- Theorem 4.3: explicit formula for the expected minibatch noise | |
| covariance of the proxy, with NO constant-noise assumption: | |
| C_psi = (1/B) [ Ihat - Gamma th th^T Gamma^T / N^2 | |
| + (1/N) sum_n J_n Sigma_psi J_n - Jbar Sigma_psi Jbar ] | |
| (times (N-B)/(N-1) without replacement). | |
| Four independent checks, in increasing order of strength: | |
| (T1) fully empirical Monte Carlo: draw psi from the proxy chain itself, draw | |
| real minibatches, take the empirical covariance of the realised proxy | |
| gradients. Nothing from Eq. (12) is used. | |
| (T2) EXACT finite-domain check. Eq. (12) only uses E[psi-that]=0 and | |
| E[(psi-that)(psi-that)^T]=Sigma. So replace the stationary law by a | |
| finite 2D-atom distribution with exactly those two moments; then the | |
| expectation over psi and the batch covariance are both finite sums and | |
| the identity can be checked to machine precision, with no sampling. | |
| (T3) EXHAUSTIVE without-replacement check: enumerate every one of the C(N,B) | |
| minibatches on a tiny problem and verify the (N-B)/(N-1) factor. | |
| (T4) consequence check: plug C_psi into Eq. (11), solve for Sigma_psi, and | |
| compare against the covariance of a simulated proxy chain. | |
| Counterexample audit: the constant-noise surrogate C = Ihat/B (used by CT and by | |
| DQ+const) and the Ziyin et al. (2022) Eq. (6) large-sample well-specified | |
| surrogate are scored on the same problems. | |
| """ | |
| import itertools | |
| 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 ( | |
| NoiseModel, | |
| Logistic, | |
| stationary_cov_proxy, | |
| relf, | |
| sym, | |
| run_coupled, | |
| cov_from_blocks, | |
| ) | |
| OUT = "/home/ubuntu/samuel/sgmcmc-uq-repro/outputs" | |
| SEED = 20260725 | |
| t0 = time.time() | |
| res = {"claim": "Theorem 4.3", "seed": SEED} | |
| # ---------------------------------------------------------------- T1 ------- | |
| print("=== T1 fully empirical Monte Carlo ===") | |
| t1 = [] | |
| for design in ["gaussian", "heavy", "binary"]: | |
| m, that, nm, K = make(N=500, D=3, seed=SEED, gamma=2.0, design=design) | |
| B, lam = 16, 0.15 | |
| Lam = lam * np.eye(3) | |
| S = stationary_cov_proxy(nm, Lam, B) | |
| rng = np.random.default_rng(SEED + 11) | |
| # run the proxy chain, and at each recorded state form REAL minibatch | |
| # gradients and accumulate their empirical covariance about the exact mean | |
| R, T, burn = 600, 4000, 1000 | |
| U = np.zeros((R, 3)) | |
| gh, wh, X, Gam = nm.g, None, m.X, m.Gamma / m.N | |
| p = 1 / (1 + np.exp(-X @ that)) | |
| wh = p * (1 - p) | |
| acc = np.zeros((3, 3)) | |
| cnt = 0 | |
| for t in range(T): | |
| IDX = rng.integers(0, m.N, size=(R, B)) | |
| Xb = X[IDX] | |
| sU = np.einsum("rbd,rd->rb", Xb, U) | |
| G = ( | |
| gh[IDX].mean(1) | |
| + np.einsum("rb,rbd->rd", wh[IDX] * sU, Xb) / B | |
| + U @ Gam.T | |
| + that @ Gam.T | |
| ) | |
| Gmean = nm.H @ U.T # E[G|psi] = H (psi - that) | |
| if t >= burn: | |
| E = G - Gmean.T | |
| acc += E.T @ E | |
| cnt += R | |
| U = U - G @ Lam.T | |
| C_mc = acc / cnt | |
| C_f = nm.C(S, B) | |
| t1.append( | |
| dict( | |
| design=design, | |
| rel_err_formula_vs_MC=relf(C_mc, C_f), | |
| n_gradient_samples=int(cnt), | |
| norm_C=float(np.linalg.norm(C_f, "fro")), | |
| ) | |
| ) | |
| print( | |
| f" design={design:<9} ||C_MC - C_eq12||/||C_eq12|| = {relf(C_mc, C_f):.3e} " | |
| f"({cnt} minibatch gradients)" | |
| ) | |
| res["T1_empirical_mc"] = t1 | |
| # ---------------------------------------------------------------- T2 ------- | |
| print("=== T2 exact finite-domain check (no sampling at all) ===") | |
| def exact_finite_check(nm, Sigma, B, replace=True): | |
| """psi-that takes 2D values +-sqrt(D) c_i (columns of the Cholesky factor), | |
| each with probability 1/(2D). Then E[u]=0 and E[u u^T]=Sigma exactly.""" | |
| D = nm.D | |
| Lc = np.linalg.cholesky(Sigma) | |
| atoms = np.concatenate([np.sqrt(D) * Lc.T, -np.sqrt(D) * Lc.T], 0) # (2D,D) | |
| w = np.full(2 * D, 1.0 / (2 * D)) | |
| # verify the atoms reproduce the moments | |
| mom1 = (w[:, None] * atoms).sum(0) | |
| mom2 = np.einsum("a,ad,ae->de", w, atoms, atoms) | |
| acc = np.zeros((D, D)) | |
| for a, wa in zip(atoms, w): | |
| V = nm.g + np.einsum("nde,e->nd", nm.J, a) # per-sample proxy gradient | |
| vbar = V.mean(0) | |
| Cb = (V.T @ V / nm.N - np.outer(vbar, vbar)) / B | |
| if not replace: | |
| Cb = Cb * (nm.N - B) / (nm.N - 1) | |
| acc += wa * Cb | |
| return sym(acc), float(np.linalg.norm(mom1)), relf(mom2, Sigma) | |
| t2 = [] | |
| rng2 = np.random.default_rng(SEED + 99) | |
| for design in ["gaussian", "heavy", "binary"]: | |
| for gamma in [0.0, 2.0, 25.0]: | |
| m, that, nm, K = make(N=400, D=4, seed=SEED + 3, gamma=gamma, design=design) | |
| for B in [1, 8, 64]: | |
| for rep in [True, False]: | |
| # Eq. (12) is an identity for ANY Sigma with mean 0, so test both | |
| # the actual stationary covariance (when it exists) and random PD ones. | |
| cands = [] | |
| try: | |
| Sst = stationary_cov_proxy(nm, 0.1 * np.eye(4), B, replace=rep) | |
| if np.linalg.eigvalsh(Sst)[0] > 0: | |
| cands.append(("stationary", Sst)) | |
| except Exception: | |
| pass | |
| for r in range(2): | |
| Z = rng2.standard_normal((4, 4)) | |
| cands.append( | |
| ( | |
| "random%d" % r, | |
| (Z @ Z.T) * 10.0 ** (-2 - r) + 1e-4 * np.eye(4), | |
| ) | |
| ) | |
| for tag, S in cands: | |
| C_ex, m1, m2e = exact_finite_check(nm, S, B, rep) | |
| C_f = nm.C(S, B, rep) | |
| t2.append( | |
| dict( | |
| design=design, | |
| gamma=gamma, | |
| B=B, | |
| replace=rep, | |
| sigma=tag, | |
| rel_err=relf(C_ex, C_f), | |
| atom_mean_norm=m1, | |
| atom_second_moment_err=m2e, | |
| ) | |
| ) | |
| worst = max(r["rel_err"] for r in t2) | |
| print( | |
| f" {len(t2)} exact configurations (design x prior x B x with/without " | |
| f"replacement); worst relative error = {worst:.3e}" | |
| ) | |
| res["T2_exact_finite_domain"] = dict( | |
| n_configs=len(t2), worst_rel_err=float(worst), detail=t2 | |
| ) | |
| # ---------------------------------------------------------------- T3 ------- | |
| print("=== T3 exhaustive enumeration of every minibatch ===") | |
| rng = np.random.default_rng(5) | |
| t3 = [] | |
| for N, B in [(6, 2), (8, 3), (9, 4), (10, 5)]: | |
| D = 3 | |
| X = rng.standard_normal((N, D)) | |
| yv = (rng.random(N) < 0.5).astype(float) | |
| Gam = 3.0 * np.eye(D) | |
| mm = Logistic(X, yv, Gam) | |
| th = mm.map_estimate() | |
| nmm = NoiseModel(mm.grad_n(th), mm.hess_n(th), th, Gam, N) | |
| Sig = sym(rng.standard_normal((D, D))) | |
| Sig = Sig @ Sig.T * 1e-3 + 1e-3 * np.eye(D) | |
| # exact expectation over psi via the finite atom construction of T2 | |
| Lc = np.linalg.cholesky(Sig) | |
| atoms = np.concatenate([np.sqrt(D) * Lc.T, -np.sqrt(D) * Lc.T], 0) | |
| for replace in [True, False]: | |
| acc = np.zeros((D, D)) | |
| for a in atoms: | |
| V = nmm.g + np.einsum("nde,e->nd", nmm.J, a) | |
| if replace: | |
| combos = itertools.product(range(N), repeat=B) | |
| else: | |
| combos = itertools.combinations(range(N), B) | |
| combos = list(combos) | |
| G = np.array([V[list(c)].mean(0) for c in combos]) | |
| gm = G.mean(0) | |
| acc += ((G - gm).T @ (G - gm) / len(combos)) / len(atoms) | |
| C_f = nmm.C(Sig, B, replace) | |
| t3.append( | |
| dict( | |
| N=N, | |
| B=B, | |
| replace=replace, | |
| n_minibatches=len(combos), | |
| rel_err=relf(acc, C_f), | |
| ) | |
| ) | |
| print( | |
| f" N={N} B={B} replace={replace}: {len(combos)} minibatches enumerated, " | |
| f"rel err = {relf(acc, C_f):.3e}" | |
| ) | |
| res["T3_exhaustive"] = t3 | |
| # ---------------------------------------------------------------- T4 ------- | |
| print("=== T4 consequence: Eq.(11)+Eq.(12) predicts the proxy covariance ===") | |
| m, that, nm, K = make(N=500, D=3, seed=SEED, gamma=2.0) | |
| t4 = [] | |
| A_cov = m.X.T @ m.X / m.N | |
| for lam in [0.05, 0.15, 0.35]: | |
| Lam = lam * np.eye(3) | |
| S_exact = stationary_cov_proxy(nm, Lam, 16) | |
| o = run_coupled( | |
| m, | |
| that, | |
| Lam, | |
| 16, | |
| T=8000, | |
| R=1200, | |
| rng=np.random.default_rng(SEED + int(1000 * lam)), | |
| burn=2000, | |
| nblock=10, | |
| ) | |
| Sp = cov_from_blocks(o["accP"], o["sP"], o["cnt"]) | |
| # competitor noise models, solved through the SAME Eq. (11) | |
| def solve_with(Cfun): | |
| D = nm.D | |
| M = np.eye(D) - Lam @ nm.H | |
| A = np.zeros((D * D, D * D)) | |
| E = np.zeros((D, D)) | |
| for i in range(D): | |
| for j in range(D): | |
| E[:] = 0 | |
| E[i, j] = 1 | |
| A[:, i * D + j] = (M @ E @ M.T + Lam @ Cfun(E, lin=True) @ Lam).ravel() | |
| b = (Lam @ Cfun(np.zeros((D, D)), lin=False) @ Lam).ravel() | |
| return sym(np.linalg.solve(np.eye(D * D) - A, b).reshape(D, D)) | |
| S_const = solve_with(lambda E, lin: (np.zeros_like(E) if lin else nm.Ihat / 16)) | |
| sig2 = float(np.var(m.y - 1 / (1 + np.exp(-m.X @ that)))) | |
| def C_ziyin(E, lin): | |
| if lin: | |
| return (A_cov @ E @ A_cov + np.trace(A_cov @ E) * A_cov) / 16 | |
| return sig2 * A_cov / 16 | |
| S_ziyin = solve_with(C_ziyin) | |
| t4.append( | |
| dict( | |
| lam=lam, | |
| exact_vs_sim=relf(S_exact, Sp), | |
| constnoise_vs_sim=relf(S_const, Sp), | |
| ziyin_vs_sim=relf(S_ziyin, Sp), | |
| ) | |
| ) | |
| print( | |
| f" lam={lam}: Eq.(12) {relf(S_exact, Sp):.4f} | constant-noise " | |
| f"{relf(S_const, Sp):.4f} | Ziyin Eq.(6) {relf(S_ziyin, Sp):.4f} " | |
| f"(vs simulated proxy covariance)" | |
| ) | |
| res["T4_consequence"] = t4 | |
| res["runtime_s"] = time.time() - t0 | |
| with open(f"{OUT}/claim2_thm43.json", "w") as f: | |
| json.dump(res, f, indent=1) | |
| print("\nwrote outputs/claim2_thm43.json runtime %.1f s" % res["runtime_s"]) | |
Xet Storage Details
- Size:
- 10.4 kB
- Xet hash:
- 12cd3fb6e6cb9f9be787b3fedb6dd8ab081b4b676963ffec46b281f57e46f545
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.