ProCreations's picture
download
raw
6.46 kB
"""Claim 5 — analyze GPU-grid results vs paper Table 1.
Reads per-run CSVs (cifar_{opt}_lam{lam}_seed{s}.csv) from the grid, computes
AUC (mean over epochs) per metric, aggregates across seeds, renders the
comparison table + training curves (paper Fig. 8 analogue), and checks the
paper's directional claims per optimizer:
acc AUC up | sigreg AUC down | rank AUC up | dormancy AUC down.
"""
import argparse
import csv
import glob
import json
import os
import re
import sys
import numpy as np
sys.path.insert(0, os.path.dirname(__file__))
from plot_style import SERIES, apply_style
import matplotlib.pyplot as plt
PAPER = { # Table 1 (arXiv:2602.19373v3, App. C.1)
("adam", 0.0): dict(acc=33.0, sig=49.1, rank=149.0, dorm=18.5),
("adam", 1.0): dict(acc=50.0, sig=7.6, rank=262.9, dorm=0.4),
("radam", 0.0): dict(acc=29.8, sig=42.3, rank=148.8, dorm=12.6),
("radam", 1.0): dict(acc=42.8, sig=8.4, rank=251.8, dorm=0.6),
("kron", 0.0): dict(acc=62.6, sig=28.5, rank=167.4, dorm=7.9),
("kron", 1.0): dict(acc=72.7, sig=7.2, rank=252.2, dorm=0.9),
}
def load_runs(out_dir):
runs = {}
for path in sorted(glob.glob(os.path.join(out_dir, "cifar_*.csv"))):
m = re.match(r"cifar_(\w+)_lam([\d.]+)_seed(\d+)\.csv",
os.path.basename(path))
if not m:
continue
opt, lam, seed = m.group(1), float(m.group(2)), int(m.group(3))
with open(path) as f:
rows = list(csv.DictReader(f))
runs[(opt, lam, seed)] = rows
return runs
def auc(rows, key, scale=1.0):
return float(np.mean([float(r[key]) for r in rows])) * scale
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--out_dir", default="repro_isogaussian_drl/outputs/claim5_gpu")
ap.add_argument("--plot_dir", default="repro_isogaussian_drl/outputs/claim5")
ap.add_argument("--dorm_key", default="dormant_mlp",
choices=["dormant_mlp", "dormant_all", "dormant_cnn"])
args = ap.parse_args()
os.makedirs(args.plot_dir, exist_ok=True)
apply_style()
runs = load_runs(args.out_dir)
if not runs:
print("no runs found"); sys.exit(1)
epochs = max(len(rows) for rows in runs.values())
print(f"loaded {len(runs)} runs, {epochs} epochs")
# per-(opt, lam): aggregate AUCs across seeds
agg = {}
for (opt, lam, seed), rows in runs.items():
# Paper Table-1 units: every column is 100 x the time-averaged raw
# metric (verified against Figs. 3/8 axis ranges) — including feature
# rank, i.e. "149.0" = raw RankMe 1.49. Scale rank by 100 to match.
a = dict(acc=auc(rows, "train_acc", 100), sig=auc(rows, "train_sigreg", 100),
rank=auc(rows, "rankme", 100), dorm=auc(rows, args.dorm_key, 100))
agg.setdefault((opt, lam), []).append((seed, a))
table_rows = []
checks = []
for opt in ("adam", "radam", "kron"):
for lam in (0.0, 1.0):
if (opt, lam) not in agg:
continue
seeds = agg[(opt, lam)]
mean = {k: float(np.mean([a[k] for _, a in seeds])) for k in seeds[0][1]}
spread = {k: float(np.ptp([a[k] for _, a in seeds])) for k in seeds[0][1]}
p = PAPER[(opt, lam)]
table_rows.append(dict(optimizer=opt, lam=lam, n_seeds=len(seeds),
**{f"{k}_mean": v for k, v in mean.items()},
**{f"{k}_range": v for k, v in spread.items()},
**{f"{k}_paper": p[k] for k in p}))
if (opt, 0.0) in agg and (opt, 1.0) in agg:
m0 = {k: float(np.mean([a[k] for _, a in agg[(opt, 0.0)]])) for k in "acc sig rank dorm".split()}
m1 = {k: float(np.mean([a[k] for _, a in agg[(opt, 1.0)]])) for k in "acc sig rank dorm".split()}
ok = (m1["acc"] > m0["acc"] and m1["sig"] < m0["sig"]
and m1["rank"] > m0["rank"] and m1["dorm"] < m0["dorm"])
checks.append((opt, ok, m0, m1))
print(f"[{opt}] SIGReg effect: acc {m0['acc']:.1f}->{m1['acc']:.1f} | "
f"sig {m0['sig']:.1f}->{m1['sig']:.1f} | rank {m0['rank']:.1f}->"
f"{m1['rank']:.1f} | dorm {m0['dorm']:.1f}->{m1['dorm']:.1f} "
f"-> {'PASS (all 4 directions match Table 1)' if ok else 'MISMATCH'}")
with open(os.path.join(args.plot_dir, "table1_comparison.csv"), "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=list(table_rows[0]))
w.writeheader(); w.writerows(table_rows)
# Fig. 8 analogue: curves per optimizer (seed-averaged)
metrics = [("train_acc", "Train Accuracy", 1.0), ("train_sigreg", "SIGReg Loss", 1.0),
("rankme", "Feature Rank (RankMe)", 1.0), (args.dorm_key, "Dormant Neurons [%]", 100.0)]
fig, axes = plt.subplots(1, 4, figsize=(16, 3.2))
colors = {("adam", 0.0): SERIES[0], ("adam", 1.0): SERIES[0],
("radam", 0.0): SERIES[3], ("radam", 1.0): SERIES[3],
("kron", 0.0): SERIES[5], ("kron", 1.0): SERIES[5]}
for ax, (key, label, scale) in zip(axes, metrics):
for (opt, lam), seeds in sorted(agg.items()):
series = []
for seed, _ in seeds:
rows = runs[(opt, lam, seed)]
series.append([float(r[key]) * scale for r in rows])
n = min(map(len, series))
y = np.mean([s[:n] for s in series], axis=0)
ax.plot(np.arange(n), y, color=colors[(opt, lam)],
linestyle="-" if lam > 0 else ":",
linewidth=1.8,
label=f"{opt}{'+SIGReg' if lam > 0 else ''}")
for e in (20, 40, 60, 80):
ax.axvline(e, color="#bbb", linewidth=0.7, linestyle="--")
ax.set_xlabel("Epoch"); ax.set_title(label)
axes[0].legend(fontsize=7, ncols=2)
fig.suptitle("Non-stationary CIFAR-10 (labels reshuffled every 20 epochs) — reproduction", y=1.04)
fig.tight_layout()
fig.savefig(os.path.join(args.plot_dir, "claim5_curves.png"), bbox_inches="tight")
ok = all(c[1] for c in checks) and len(checks) == 3
with open(os.path.join(args.plot_dir, "claim5_verdict.json"), "w") as f:
json.dump({"per_optimizer_pass": {c[0]: c[1] for c in checks}}, f, indent=2)
print("CLAIM 5 DIRECTIONAL CHECK:", "PASS" if ok else "PARTIAL/FAIL")
sys.exit(0 if ok else 1)
if __name__ == "__main__":
main()

Xet Storage Details

Size:
6.46 kB
·
Xet hash:
f701cc0d9432ee4e4149794ff067c5702850ef60d8a899183d9949c5e482d066

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