Buckets:
| """Claim 5 -- Table 3: Boston-housing linear regression under strong model | |
| misspecification. Reproduce the covariance errors of CT / LR+WS / DQ+exact | |
| (and, additionally, DQ+const, which the claim wording mentions but Table 3 does | |
| not actually contain) at B = 16 and B = floor(0.1 N). | |
| Protocol (matched to the paper and to the authors' released script): | |
| * 12 covariates (all Boston columns except CHAS), standardised; response MEDV | |
| untransformed; no intercept. N = 506, D = 12. | |
| * working Gaussian model with sigma = 1 -> strong misspecification. | |
| * target S* = Jhat^{-1} Ihat Jhat^{-1} / N (sandwich covariance at theta_hat). | |
| * 500 epochs of preconditioned SGD from theta_hat, 50% burn-in, 30 seeds. | |
| * covariance error = ||Shat - S*||_F / ||S*||_F. | |
| Data: the original Harrison & Rubinfeld table from lib.stat.cmu.edu/datasets/boston | |
| (byte-identical to the authors' boston.csv, which is checked here). | |
| """ | |
| import json | |
| import sys | |
| import time | |
| import zlib | |
| import numpy as np | |
| sys.path.insert(0, "/home/ubuntu/samuel/sgmcmc-uq-repro/scripts") | |
| from common import NoiseModel, relf | |
| from linreg import LinReg, sandwich, tune_Lambda | |
| OUT = "/home/ubuntu/samuel/sgmcmc-uq-repro/outputs" | |
| ROOT = "/home/ubuntu/samuel/sgmcmc-uq-repro" | |
| SEED = 20260725 | |
| COLS = [ | |
| "CRIM", | |
| "ZN", | |
| "INDUS", | |
| "NOX", | |
| "RM", | |
| "AGE", | |
| "DIS", | |
| "RAD", | |
| "TAX", | |
| "PTRATIO", | |
| "B", | |
| "LSTAT", | |
| ] | |
| PAPER = { # Table 3 of the paper | |
| ("log", 16): dict( | |
| Posterior=0.358, CT=0.247, LRWS=9.23e8, DQexact=0.337, NUTS=2.528 | |
| ), | |
| ("log", 50): dict( | |
| Posterior=0.358, CT=0.589, LRWS=1.40e7, DQexact=0.352, NUTS=2.528 | |
| ), | |
| ("beta", 16): dict(CT=2.054, LRWS=float("inf"), DQexact=2.782), | |
| ("beta", 50): dict(CT=3.126, LRWS=float("inf"), DQexact=1.398), | |
| } | |
| def load_boston(): | |
| """Parse the raw CMU file (each record spans two lines).""" | |
| raw = open(f"{ROOT}/data_boston_raw.txt").read().splitlines() | |
| vals = [] | |
| for ln in raw[22:]: | |
| vals += [float(v) for v in ln.split()] | |
| arr = np.array(vals).reshape(-1, 14) | |
| names = [ | |
| "CRIM", | |
| "ZN", | |
| "INDUS", | |
| "CHAS", | |
| "NOX", | |
| "RM", | |
| "AGE", | |
| "DIS", | |
| "RAD", | |
| "TAX", | |
| "PTRATIO", | |
| "B", | |
| "LSTAT", | |
| "MEDV", | |
| ] | |
| X = np.column_stack([arr[:, names.index(c)] for c in COLS]) | |
| X = (X - X.mean(0)) / X.std(0) | |
| return X, arr[:, names.index("MEDV")] | |
| t0 = time.time() | |
| X, y = load_boston() | |
| N, D = X.shape | |
| _ols = np.linalg.lstsq(X, y, rcond=None)[0] | |
| SIGMA = float(np.std(y - X @ _ols)) # working scale, as in the authors' script | |
| print("sigma_working =", SIGMA) | |
| print(f"Boston: N={N} D={D}") | |
| # integrity check against the authors' csv | |
| import csv | |
| with open(f"{ROOT}/refcode/boston.csv") as f: | |
| rd = list(csv.DictReader(f)) | |
| Xa = np.array([[float(r[c]) for c in COLS] for r in rd]) | |
| Xa = (Xa - Xa.mean(0)) / Xa.std(0) | |
| ya = np.array([float(r["MEDV"]) for r in rd]) | |
| data_match = dict( | |
| max_abs_X_diff=float(np.abs(Xa - X).max()), | |
| max_abs_y_diff=float(np.abs(ya - y).max()), | |
| ) | |
| print("data check vs authors' boston.csv:", data_match) | |
| res = { | |
| "claim": "Table 3 (Boston housing)", | |
| "sigma_working": SIGMA, | |
| "seed": SEED, | |
| "N": N, | |
| "D": D, | |
| "columns": COLS, | |
| "data_check": data_match, | |
| "paper_table3": {f"{k[0]}_B{k[1]}": v for k, v in PAPER.items()}, | |
| } | |
| rows = [] | |
| BATCHES = [16, int(0.1 * N)] | |
| ZS = np.random.default_rng(999).normal(0.0, SIGMA * 5.0, size=50) # authors' n_mc=50 | |
| for loss in ["log", "beta", "beta_mc"]: | |
| mdl = LinReg(X, y, sigma=SIGMA, loss=loss, beta=1.5, z_samples=ZS) | |
| that = mdl.fit() | |
| g = mdl.grad_n(that) | |
| Jn = mdl.hess_n(that) | |
| Ihat = g.T @ g / N | |
| Jhat = Jn.mean(0) | |
| Sstar = sandwich(Jhat, Ihat, N) | |
| H = Jhat # no prior -> Hhat = Jbar | |
| nm = NoiseModel(g, Jn, that, None, N) | |
| A_cov = X.T @ X / N | |
| sig2 = float(np.var(y - X @ that)) | |
| print( | |
| f"\n--- {loss} loss --- ||grad L(that)||={np.linalg.norm(g.mean(0)):.2e} " | |
| f"cond(Jhat)={np.linalg.cond(Jhat):.2f} minEig(Jhat)={np.linalg.eigvalsh(Jhat)[0]:.3e} ||S*||_F={np.linalg.norm(Sstar):.3e}" | |
| ) | |
| if loss == "log": | |
| Spost = SIGMA**2 * np.linalg.inv(X.T @ X) # flat-prior Gaussian posterior | |
| e_post = relf(Spost, Sstar) | |
| print( | |
| f" exact Gaussian posterior covariance error = {e_post:.4f} " | |
| f"(paper: {PAPER[('log',16)]['Posterior']})" | |
| ) | |
| res["posterior_cov_error"] = e_post | |
| res["posterior_cov_error_paper"] = PAPER[("log", 16)]["Posterior"] | |
| for B in BATCHES: | |
| n_iters = int(500 * N / B) | |
| for method in ["CT", "LR+WS", "DQ+const", "DQ+exact", | |
| "LR+WS-cf", "DQ+const-cf", "DQ+exact-cf"]: | |
| # "-cf" = the closed-form Lambda0 = (S H + H S)(C + H S H)^{-1} that the | |
| # authors' released code uses, instead of the scipy.optimize.root | |
| # (Powell hybrid) solve of Eq. (11) that Section 6 of the paper describes. | |
| base = method.replace("-cf", "") | |
| tl0 = time.time() | |
| try: | |
| Lam, rr, Lam0 = tune_Lambda( | |
| base, H, Sstar, Ihat, Jhat, B, nm=nm, A_cov=A_cov, | |
| sig2=sig2, sigma=SIGMA | |
| ) | |
| if method.endswith("-cf"): | |
| Lam = Lam0 | |
| tune_t = time.time() - tl0 | |
| except Exception as e: | |
| rows.append(dict(loss=loss, B=B, method=method, error=str(e))) | |
| print(f" {method}: tuning failed: {e}") | |
| continue | |
| rng = np.random.default_rng( | |
| SEED + 1000 * B + zlib.crc32(method.encode()) % 100000 | |
| ) | |
| ms0 = time.time() | |
| covs, div = mdl.sgd_paths(that, Lam, B, n_iters, R=30, rng=rng) | |
| mcmc_t = time.time() - ms0 | |
| errs = np.array([relf(c, Sstar) for c in covs]) | |
| errs[div] = np.inf | |
| fin = errs[np.isfinite(errs)] | |
| mean = float(np.mean(errs)) if len(fin) == len(errs) else float("inf") | |
| med = float(np.median(errs)) | |
| lo = float(np.percentile(fin, 2.5)) if len(fin) else float("nan") | |
| hi = float(np.percentile(fin, 97.5)) if len(fin) else float("nan") | |
| rows.append( | |
| dict( | |
| loss=loss, | |
| B=B, | |
| method=method, | |
| cov_err_mean=mean, | |
| cov_err_median=med, | |
| ci95=[lo, hi], | |
| n_diverged=int(div.sum()), | |
| tuning_residual=float(rr), | |
| lam_trace=float(np.trace(Lam)), | |
| lam_max_eig=float(np.max(np.abs(np.linalg.eigvals(Lam)))), | |
| tune_time_s=tune_t, | |
| mcmc_time_s=mcmc_t, | |
| tune_over_mcmc=tune_t / mcmc_t, | |
| n_iters=n_iters, | |
| ) | |
| ) | |
| print( | |
| f" B={B:<3} {method:<9} cov err mean={mean:<12.4g} " | |
| f"median={med:<12.4g} 95%CI=[{lo:.3g},{hi:.3g}] diverged={div.sum()}/30 " | |
| f"tune/mcmc={tune_t/mcmc_t:.2e}" | |
| ) | |
| res["results"] = rows | |
| res["runtime_s"] = time.time() - t0 | |
| with open(f"{OUT}/claim5_boston.json", "w") as f: | |
| json.dump(res, f, indent=1) | |
| print("\nwrote outputs/claim5_boston.json runtime %.1f s" % res["runtime_s"]) | |
Xet Storage Details
- Size:
- 7.4 kB
- Xet hash:
- 1ffe935fab14c4cd7e7d68eacf65ade011a830cd5802baa4d8d358daa599c46f
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.