visv-Bro's picture
download
raw
11.6 kB
"""Claims 2-5 audit — EntUCB regret vs the paper's bounds.
--claim 2 : entropic regret of fixed-eps EntUCB vs the two-term bound
sigma sqrt(2T log(2/delta)) + 2 Cbar beta_T(delta) sqrt(T logdet(...))
[OpenReview Thm 5.1; arXiv v2: width+martingale terms of Thm 4.1]
--claim 3 : Kantorovich regret of EntUCB with eps_t = eta t^-eta (Thm 4.1 /
OpenReview Thm 5.2): sublinearity + full bound; plus the
Carlier-Pegon entropic-gap audit Ent - Kant <= kappa eps log(1/eps).
--claim 4 : parametric sqrt(NT) rate (Prop 5.5 / OpenReview Cor 5.3): sweep N, T.
--claim 5 : basis-decay interpolation (Thm 5.3 / OpenReview Cor 5.4):
zeta(n) = 1 - n^-q, n_t = ceil(t^{1/(q+1)}), regret exponent vs
(q+2)/(2q+2); includes a mis-scheduled control.
All runs verify the optimism certificate and confidence-set coverage per step.
"""
import argparse
import json
import os
import sys
import time
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from botlib import parallel_runs, martingale_term
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "results")
os.makedirs(OUT, exist_ok=True)
PROCS = int(os.environ.get("PROCS", "0"))
def summarize(rs):
agg = {
"ts": rs[0].ts.tolist(),
"kant_regret_mean": np.mean([r.kant_regret for r in rs], axis=0).tolist(),
"kant_regret_max": np.max([r.kant_regret for r in rs], axis=0).tolist(),
"kant_regret_noisy_max": np.max([r.kant_regret_noisy for r in rs], axis=0).tolist(),
"ent_regret_mean": np.mean([r.ent_regret for r in rs], axis=0).tolist(),
"ent_regret_max": np.max([r.ent_regret for r in rs], axis=0).tolist(),
"ent_regret_noisy_max": np.max([r.ent_regret_noisy for r in rs], axis=0).tolist(),
"bound_literal_mean": np.mean([r.bound_literal for r in rs], axis=0).tolist(),
"bound_literal_min": np.min([r.bound_literal for r in rs], axis=0).tolist(),
"bound_canonical_min": np.min([r.bound_canonical for r in rs], axis=0).tolist(),
"beta_mean": np.mean([r.beta for r in rs], axis=0).tolist(),
"logdet_mean": np.mean([r.logdet for r in rs], axis=0).tolist(),
"cert_viol_frac_max": float(max(r.cert_viol_frac for r in rs)),
"coverage_rate": float(np.mean([r.covered_all for r in rs])),
"n_seeds": len(rs),
}
return agg
def fit_exponent(ts, curve, lo_frac=0.25):
ts, curve = np.asarray(ts, float), np.asarray(curve, float)
m = (ts >= lo_frac * ts[-1]) & (curve > 0)
if m.sum() < 3:
return float("nan")
A = np.vstack([np.log(ts[m]), np.ones(m.sum())]).T
slope, _ = np.linalg.lstsq(A, np.log(curve[m]), rcond=None)[0]
return float(slope)
def base_cfg(**kw):
cfg = dict(K=8, Kp=8, inst_seed=0, cost_kind="smooth", T=4000, sigma=0.1,
delta=0.1, lam=1.0, Cbar_mult=1.1, run_seed=0, opt_iters=3)
cfg.update(kw)
return cfg
def claim2(full: bool):
K = 8 if full else 4
T = 4000 if full else 250
seeds = 16 if full else 3
out = {"config": dict(K=K, T=T, seeds=seeds, sigma=0.1, delta=0.1, lam=1.0)}
for eps in [0.05, 0.01]:
cfgs = [base_cfg(K=K, Kp=K, T=T, inst_seed=500 + s % 6,
eps={"kind": "const", "v": eps}, run_seed=6000 + s)
for s in range(seeds)]
rs = parallel_runs(cfgs, PROCS)
agg = summarize(rs)
ts = np.array(agg["ts"])
worst = np.array(agg["ent_regret_noisy_max"])
bnd_lit = np.array(agg["bound_literal_min"])
bnd_can = np.array(agg["bound_canonical_min"])
agg["holds_literal"] = bool(np.all(worst <= bnd_lit))
agg["holds_canonical"] = bool(np.all(worst <= bnd_can))
agg["min_ratio_literal"] = float(np.min(bnd_lit / np.maximum(worst, 1e-12)))
agg["ent_regret_exponent"] = fit_exponent(ts, np.maximum(agg["ent_regret_mean"], 1e-12))
out[f"eps_{eps}"] = agg
print(f"[claim2 eps={eps}] holds_literal={agg['holds_literal']} "
f"(min ratio {agg['min_ratio_literal']:.2f}); holds_canonical={agg['holds_canonical']}; "
f"growth exp {agg['ent_regret_exponent']:.2f}; cert viol {agg['cert_viol_frac_max']:.4f}; "
f"coverage {agg['coverage_rate']:.2f}", flush=True)
return out
def claim3(full: bool):
K = 8 if full else 4
T = 4000 if full else 250
seeds = 8 if full else 3
out = {"config": dict(K=K, T=T, seeds=seeds)}
for eta in ([0.6, 0.75, 0.9] if full else [0.75]):
cfgs = [base_cfg(K=K, Kp=K, T=T, inst_seed=500 + s % 6,
eps={"kind": "power", "eta": eta}, run_seed=7000 + s,
kappa=0.0, eta_for_bound=eta)
for s in range(seeds)]
rs = parallel_runs(cfgs, PROCS)
agg = summarize(rs)
ts = np.array(agg["ts"])
agg["kant_exponent_mean"] = fit_exponent(ts, np.maximum(agg["kant_regret_mean"], 1e-12))
agg["holds_literal_noisy"] = bool(np.all(
np.array(agg["kant_regret_noisy_max"]) <= np.array(agg["bound_literal_min"])))
out[f"eta_{eta}"] = agg
print(f"[claim3 eta={eta}] kant exponent {agg['kant_exponent_mean']:.3f}; "
f"bound holds {agg['holds_literal_noisy']}; cert {agg['cert_viol_frac_max']:.4f}", flush=True)
# Carlier-Pegon gap audit
from botlib import sinkhorn_log
import ot as pot
gap_res = {}
for alpha, costf in [(1.0, lambda X, Y: np.abs(X - Y)),
(0.5, lambda X, Y: np.sqrt(np.abs(X - Y)))]:
for m in ([50, 100, 200] if full else [40]):
x = (np.arange(m) + 0.5) / m
mu = np.full(m, 1 / m)
X, Y = np.meshgrid(x, x, indexing="ij")
cost = costf(X, Y)
kv = float(np.sum(pot.emd(mu, mu, cost) * cost))
epss = np.geomspace(0.2, 0.002, 10)
gaps = []
for eps in epss:
P, _, _ = sinkhorn_log(mu, mu, cost, float(eps), n_iter=30000, tol=1e-11)
ent = float(np.sum(P * cost)) + float(eps) * float(np.sum(P[P > 0] * np.log(P[P > 0] * m * m)))
gaps.append(ent - kv)
slope = np.array(gaps) / (epss * np.log(1 / epss))
gap_res[f"alpha_{alpha}_m_{m}"] = {"eps": epss.tolist(), "gap": gaps,
"gap_over_epsloge": slope.tolist(),
"kappa_bound": 1.0 / alpha}
print(f"[claim3 gap] alpha={alpha} m={m}: slope in [{slope.min():.3f},{slope.max():.3f}] "
f"vs kappa {1 / alpha:.1f}", flush=True)
out["carlier_pegon"] = gap_res
return out
def claim4(full: bool):
Ks = [3, 4, 6, 8] if full else [3, 4]
Ts = [500, 1000, 2000, 4000] if full else [150, 300]
seeds = 8 if full else 2
lam, eta, sigma, delta = 1.0, 0.5, 0.1, 0.1
out = {"config": dict(Ks=Ks, Ts=Ts, seeds=seeds, eta=eta)}
table = []
for K in Ks:
N = K * K
cfgs = [base_cfg(K=K, Kp=K, T=max(Ts), inst_seed=900 + s % 6,
cost_kind="planted", planted={"kind": "unit", "seed": 800 + s},
eps={"kind": "power", "eta": eta}, run_seed=8000 + s,
kappa=0.0, eta_for_bound=eta, Cbar_mult=1.1)
for s in range(seeds)]
rs = parallel_runs(cfgs, PROCS)
agg = summarize(rs)
ts = np.array(agg["ts"])
for T in Ts:
i = int(np.argmin(np.abs(ts - T)))
bnd = (2 * 1.1 * np.sqrt(N * T) * np.log(1 / lam + T * 1.1 ** 2 / N)
+ martingale_term(sigma, delta, T))
table.append(dict(N=N, T=int(ts[i]), regret=agg["kant_regret_mean"][i],
regret_worst=agg["kant_regret_max"][i], prop55_bound=bnd))
out[f"N_{N}_curve"] = {"ts": ts.tolist(),
"kant_regret_mean": agg["kant_regret_mean"],
"kant_regret_max": agg["kant_regret_max"]}
out[f"N_{N}_audit"] = {k: agg[k] for k in ["cert_viol_frac_max", "coverage_rate", "n_seeds"]}
print(f"[claim4 N={N}] final regret {agg['kant_regret_mean'][-1]:.2f}; "
f"cert {agg['cert_viol_frac_max']:.4f}; coverage {agg['coverage_rate']:.2f}", flush=True)
out["table"] = table
tb = [r for r in table if r["regret"] > 0]
Amat = np.array([[np.log(r["N"]), np.log(r["T"]), 1.0] for r in tb])
y = np.log([r["regret"] for r in tb])
coef, *_ = np.linalg.lstsq(Amat, y, rcond=None)
out["fit_exponents"] = {"N_exp": float(coef[0]), "T_exp": float(coef[1])}
out["bound_holds_all"] = bool(all(r["regret_worst"] <= r["prop55_bound"] for r in table))
print(f"[claim4] R ~ N^{coef[0]:.2f} T^{coef[1]:.2f} (claim 0.5, 0.5); "
f"bound holds: {out['bound_holds_all']}", flush=True)
return out
def claim5(full: bool):
K = 16 if full else 8
N = K * K
T = 4000 if full else 400
seeds = 4 if full else 2
qs = [0.5, 1.0, 2.0, 4.0] if full else [0.5, 2.0]
eta = 0.5
out = {"config": dict(K=K, N=N, T=T, seeds=seeds, qs=qs, eta=eta)}
for q in qs:
cfgs = [base_cfg(K=K, Kp=K, T=T, inst_seed=1300 + s % 4,
cost_kind="planted", planted={"kind": "decay", "q": q, "seed": 1200 + s},
eps={"kind": "power", "eta": eta},
n_sched={"kind": "power", "q": q},
run_seed=9000 + s, kappa=0.0, eta_for_bound=eta)
for s in range(seeds)]
rs = parallel_runs(cfgs, PROCS)
agg = summarize(rs)
agg["exponent"] = fit_exponent(np.array(agg["ts"]), np.maximum(agg["kant_regret_mean"], 1e-12))
agg["predicted_exponent"] = (q + 2) / (2 * q + 2)
out[f"q_{q}"] = agg
print(f"[claim5 q={q}] exponent {agg['exponent']:.3f} vs predicted "
f"{agg['predicted_exponent']:.3f}; cert {agg['cert_viol_frac_max']:.4f}; "
f"coverage {agg['coverage_rate']:.2f}", flush=True)
# control: schedule tuned for q=4 on a q=0.5 instance
cfgs = [base_cfg(K=K, Kp=K, T=T, inst_seed=1300 + s % 4,
cost_kind="planted", planted={"kind": "decay", "q": 0.5, "seed": 1200 + s},
eps={"kind": "power", "eta": eta},
n_sched={"kind": "power", "q": 4.0},
run_seed=9000 + s, kappa=0.0, eta_for_bound=eta)
for s in range(seeds)]
rs = parallel_runs(cfgs, PROCS)
agg = summarize(rs)
agg["exponent"] = fit_exponent(np.array(agg["ts"]), np.maximum(agg["kant_regret_mean"], 1e-12))
out["control_missched"] = agg
print(f"[claim5 control] q_true=0.5, q_sched=4: exponent {agg['exponent']:.3f} "
f"(well-tuned was {out['q_0.5']['exponent']:.3f})", flush=True)
return out
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--claim", type=int, required=True, choices=[2, 3, 4, 5])
ap.add_argument("--full", action="store_true")
ap.add_argument("--tag", default="")
ap.add_argument("--out", default=OUT)
args = ap.parse_args()
t0 = time.time()
out = {2: claim2, 3: claim3, 4: claim4, 5: claim5}[args.claim](args.full)
out["wall_seconds"] = time.time() - t0
tag = args.tag or ("full" if args.full else "smoke")
os.makedirs(args.out, exist_ok=True)
path = os.path.join(args.out, f"claim{args.claim}_{tag}.json")
with open(path, "w") as f:
json.dump(out, f, indent=2)
print(f"saved {path} ({out['wall_seconds']:.1f}s)")
if __name__ == "__main__":
main()

Xet Storage Details

Size:
11.6 kB
·
Xet hash:
064f0473a6b27a7976370e1d2c92f8c872c7764f053c3ba26e25afeef0e4fc47

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