SabaPivot's picture
download
raw
13.2 kB
"""Aggregate the three experimental claims into compact summaries + figures."""
import glob
import json
import os
from collections import defaultdict
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
OUT = os.path.join(ROOT, "outputs")
FIGS = os.path.join(ROOT, "figs")
os.makedirs(FIGS, exist_ok=True)
plt.rcParams.update({"font.size": 11, "figure.dpi": 130})
summary = {}
# ================================================================= claim 4
p4 = os.path.join(OUT, "claim4_runs.json")
if os.path.exists(p4):
d = json.load(open(p4))
W = d["W_hat"]
groups = defaultdict(list)
for r in d["runs"]:
key = (
r["mode"],
r["sigma_sq"],
r["C"],
r["rho"],
r.get("alpha_init", "paper_code"),
)
groups[key].append(r)
s4 = {"W_hat": W, "configs": []}
fig, ax = plt.subplots(1, 2, figsize=(10.5, 3.8))
for key, rs in sorted(groups.items(), key=lambda kv: str(kv[0])):
mode, sig, C, rho, ai = key
its = rs[0]["iters"]
L = min(len(r["values"]) for r in rs)
vals = np.array([r["values"][:L] for r in rs])
gaps = W - vals
mean, std = gaps.mean(0), gaps.std(0)
n_div = sum(1 for r in rs if r["nonfinite_iter"] is not None)
n_ovf = sum(1 for r in rs if r["overflow_iter"] is not None)
blown = sum(1 for r in rs if (W - r["values"][-1]) > 1.0)
# first iteration at which the mean gap goes below thresholds
def first_below(th):
for i, g in enumerate(mean):
if g < th:
return its[i]
return None
cfg = {
"mode": mode,
"sigma_sq": sig,
"C": C,
"rho": rho,
"alpha_init": ai,
"n_seeds": len(rs),
"iters": its[:L],
"mean_gap": mean.tolist(),
"std_gap": std.tolist(),
"final_mean_gap": float(mean[-1]),
"nonfinite_runs": n_div,
"overflow_runs": n_ovf,
"runs_with_final_gap_gt_1": blown,
"max_exp_arg": float(max(r["max_exp_arg"] for r in rs)),
"iters_to_gap_0.1": first_below(0.1),
"iters_to_gap_0.05": first_below(0.05),
"iters_to_gap_0.02": first_below(0.02),
}
s4["configs"].append(cfg)
if (mode == "dual" and C == 1e-3) or (
mode == "semidual" and ai == "paper_code"
):
lbl = (
rf"baseline dual $\sigma^2$={sig}, C={C}"
if mode == "dual"
else rf"proposed $\rho$={rho}, C={C}"
)
ax[0].plot(its[:L], mean, lw=1.5, label=lbl)
ax[0].fill_between(
its[:L], np.maximum(mean - std, 1e-6), mean + std, alpha=0.2
)
ax[0].set_yscale("log")
ax[0].set_xscale("log")
ax[0].set_xlabel("kernel-SGD iteration")
ax[0].set_ylabel("optimality gap")
ax[0].set_title(r"eOT, $\varepsilon=0.01$ (20 seeds, mean $\pm$ 1 s.d.)")
ax[0].legend(fontsize=7)
ax[0].grid(alpha=0.3, which="both")
# right panel: baseline with larger stepsizes
for key, rs in sorted(groups.items(), key=lambda kv: str(kv[0])):
mode, sig, C, rho, ai = key
if mode != "dual" or C == 1e-3:
continue
its = rs[0]["iters"]
L = min(len(r["values"]) for r in rs)
gaps = W - np.array([r["values"][:L] for r in rs])
ax[1].plot(
its[:L],
np.abs(gaps.mean(0)),
lw=1.2,
ls="--",
label=rf"$\sigma^2$={sig}, C={C}",
)
ax[1].set_yscale("log")
ax[1].set_xscale("log")
ax[1].set_xlabel("kernel-SGD iteration")
ax[1].set_ylabel("|optimality gap|")
ax[1].set_title("baseline dual with larger stepsizes (divergence)")
ax[1].legend(fontsize=6, ncol=2)
ax[1].grid(alpha=0.3, which="both")
fig.tight_layout()
fig.savefig(os.path.join(FIGS, "claim4.png"), bbox_inches="tight")
plt.close(fig)
summary["claim4"] = s4
# ================================================================= claim 5
p5 = os.path.join(OUT, "claim5_kl_dro.json")
if os.path.exists(p5):
d = json.load(open(p5))
recs = d["runs"]
etas = sorted({r["eta"] for r in recs})
batches = sorted({r["batch"] for r in recs})
rhos = sorted({r["rho"] for r in recs if r["rho"] is not None})
nseeds = d["seeds"]
rows = []
for b in batches:
for meth, rho in [("baseline", None)] + [("proposed", r) for r in rhos]:
per = {}
for e in etas:
rs = [
x
for x in recs
if x["method"] == meth
and x["batch"] == b
and x["eta"] == e
and x["rho"] == rho
]
ok = [x for x in rs if x["converged"]]
per[e] = {
"converged": len(ok),
"n": len(rs),
"mean_final": (
float(np.mean([x["final"] for x in ok])) if ok else None
),
}
stable = [e for e in etas if per[e]["converged"] == nseeds]
best = None
for e in stable:
if best is None or per[e]["mean_final"] < best[1]:
best = (e, per[e]["mean_final"])
rows.append(
{
"method": meth,
"rho": rho,
"batch": b,
"max_stable_eta": max(stable) if stable else None,
"best_eta_allseeds": best[0] if best else None,
"best_objective": best[1] if best else None,
"per_eta": {str(k): v for k, v in per.items()},
}
)
summary["claim5"] = {
"L0": d["L0"],
"n": d["n"],
"lam": d["lam"],
"epochs": d["n_epochs"],
"seeds": nseeds,
"rows": rows,
}
fig, ax = plt.subplots(1, 2, figsize=(10.5, 3.8))
labels, mat = [], []
for r in rows:
labels.append(
"baseline" if r["method"] == "baseline" else rf"prop. $\rho$={r['rho']:g}"
)
mat.append((r["batch"], r["max_stable_eta"]))
uniq = []
for lb in labels:
if lb not in uniq:
uniq.append(lb)
M = np.full((len(uniq), len(batches)), np.nan)
for r in rows:
lb = "baseline" if r["method"] == "baseline" else rf"prop. $\rho$={r['rho']:g}"
i, j = uniq.index(lb), batches.index(r["batch"])
M[i, j] = np.log10(r["max_stable_eta"]) if r["max_stable_eta"] else np.nan
im = ax[0].imshow(M, cmap="viridis", aspect="auto", vmin=-8, vmax=-4)
ax[0].set_xticks(range(len(batches)))
ax[0].set_xticklabels([f"|D|={b}" for b in batches])
ax[0].set_yticks(range(len(uniq)))
ax[0].set_yticklabels(uniq, fontsize=8)
for i in range(M.shape[0]):
for j in range(M.shape[1]):
ax[0].text(
j,
i,
"div." if np.isnan(M[i, j]) else f"$10^{{{int(M[i,j])}}}$",
ha="center",
va="center",
color="w",
fontsize=8,
)
ax[0].set_title(r"largest stable stepsize $\eta$ (all 10 seeds)")
fig.colorbar(im, ax=ax[0], label=r"$\log_{10}\eta$")
for b, style in zip(batches, ["-", "--", ":"]):
base = [r for r in rows if r["method"] == "baseline" and r["batch"] == b][0]
rs = [
x
for x in recs
if x["method"] == "baseline"
and x["batch"] == b
and x["eta"] == base["best_eta_allseeds"]
]
if rs:
ep = sorted(int(k) for k in rs[0]["traj"])
cur = np.mean([[x["traj"][str(e)] for e in ep] for x in rs], axis=0)
ax[1].plot(
ep,
cur,
style,
color="#c0392b",
lw=1.4,
label=rf"baseline |D|={b}, $\eta$={base['best_eta_allseeds']:g}",
)
prop = sorted(
[
r
for r in rows
if r["method"] == "proposed"
and r["batch"] == b
and r["best_objective"] is not None
],
key=lambda r: r["best_objective"],
)[0]
rs = [
x
for x in recs
if x["method"] == "proposed"
and x["batch"] == b
and x["eta"] == prop["best_eta_allseeds"]
and x["rho"] == prop["rho"]
]
if rs:
ep = sorted(int(k) for k in rs[0]["traj"])
cur = np.mean([[x["traj"][str(e)] for e in ep] for x in rs], axis=0)
ax[1].plot(
ep,
cur,
style,
color="#27ae60",
lw=1.4,
label=rf"proposed |D|={b}, $\rho$={prop['rho']:g}, $\eta$={prop['best_eta_allseeds']:g}",
)
ax[1].set_xlabel("epoch")
ax[1].set_ylabel(r"$L(\theta)$")
ax[1].set_title("California Housing KL-DRO, best stable settings")
ax[1].legend(fontsize=6)
ax[1].grid(alpha=0.3)
ax[1].set_yscale("log")
fig.tight_layout()
fig.savefig(os.path.join(FIGS, "claim5.png"), bbox_inches="tight")
plt.close(fig)
# ================================================================= claim 6
p6 = os.path.join(OUT, "claim6_uot_dro_big.json")
if os.path.exists(p6):
d = json.load(open(p6))
recs = d["runs"]
rows = []
for meth in ["baseline", "proposed", "erm"]:
for lr in sorted({r["lr"] for r in recs if r["method"] == meth}, reverse=True):
rs = [r for r in recs if r["method"] == meth and r["lr"] == lr]
fin = [
r["final_F"]
for r in rs
if not r["diverged"] and r["final_F"] is not None
]
rows.append(
{
"method": meth,
"lr": lr,
"n": len(rs),
"diverged": sum(r["diverged"] for r in rs),
"fpe_iters": [r["fpe_iter"] for r in rs],
"mean_final_F": float(np.mean(fin)) if fin else None,
"std_final_F": float(np.std(fin)) if fin else None,
"mean_test_acc": float(
np.mean([r["hist"][-1]["test_acc"] for r in rs])
),
"mean_val_acc": float(
np.mean([r["hist"][-1]["val_acc"] for r in rs])
),
}
)
summary["claim6"] = {
"config": d["config"],
"noise_rate": d["noise_rate"],
"rows": rows,
}
fig, ax = plt.subplots(1, 2, figsize=(10.5, 3.8))
colors = {"baseline": "#c0392b", "proposed": "#27ae60", "erm": "#7f8c8d"}
styles = {1e-3: ":", 1e-4: "-", 1e-5: "--", 1e-6: "-."}
for meth in ["baseline", "proposed", "erm"]:
for lr in sorted({r["lr"] for r in recs if r["method"] == meth}, reverse=True):
rs = [r for r in recs if r["method"] == meth and r["lr"] == lr]
L = min(len(r["hist"]) for r in rs)
steps = [h["step"] for h in rs[0]["hist"][:L]]
F = np.array(
[
[h["F"] if h["F"] is not None else np.nan for h in r["hist"][:L]]
for r in rs
]
)
ax[0].plot(
steps,
np.nanmean(F, 0),
styles.get(lr, "-"),
color=colors[meth],
lw=1.4,
label=rf"{meth}, $\eta$={lr:g}"
+ (" (FPE)" if any(r["diverged"] for r in rs) else ""),
)
acc = np.array([[h["test_acc"] for h in r["hist"][:L]] for r in rs])
ax[1].plot(
steps,
acc.mean(0),
styles.get(lr, "-"),
color=colors[meth],
lw=1.4,
label=rf"{meth}, $\eta$={lr:g}",
)
ax[0].set_xlabel("SGD step (batch size 1)")
ax[0].set_ylabel(r"$F(\theta)$ of Eq. (20)")
ax[0].set_title("MNIST UOT-DRO train objective")
ax[0].legend(fontsize=6)
ax[0].grid(alpha=0.3)
ax[1].set_xlabel("SGD step")
ax[1].set_ylabel("clean test accuracy")
ax[1].set_title("test accuracy (25% feature-dependent label noise)")
ax[1].legend(fontsize=6)
ax[1].grid(alpha=0.3)
fig.tight_layout()
fig.savefig(os.path.join(FIGS, "claim6.png"), bbox_inches="tight")
plt.close(fig)
with open(os.path.join(OUT, "summary_456.json"), "w") as fh:
json.dump(summary, fh, indent=2)
print(
json.dumps(
{
k: (
v
if k != "claim4"
else {"W_hat": v["W_hat"], "n_configs": len(v["configs"])}
)
for k, v in summary.items()
},
indent=1,
)[:3000]
)

Xet Storage Details

Size:
13.2 kB
·
Xet hash:
125341221db1763464d1f3c19d7d8f4ee40dd207ae1c07f205895933962b60dd

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