Retract input-injection claim; correct batch-size pooling error
Browse files- src/plot_rmdm.py +93 -0
src/plot_rmdm.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""The matched R-MDM comparison: what input injection is worth, and where.
|
| 2 |
+
|
| 3 |
+
Reads figures/rmdm_compare.json, written by eval_ckpt.py from the step-80,000
|
| 4 |
+
checkpoints of three runs that differ in exactly one component. Nothing here is
|
| 5 |
+
hand-entered.
|
| 6 |
+
"""
|
| 7 |
+
import json, os, sys
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
import matplotlib
|
| 11 |
+
matplotlib.use("Agg")
|
| 12 |
+
import matplotlib.pyplot as plt
|
| 13 |
+
|
| 14 |
+
OUT = os.environ.get("OUT", "figures")
|
| 15 |
+
src = os.path.join(OUT, "rmdm_compare.json")
|
| 16 |
+
if not os.path.exists(src):
|
| 17 |
+
sys.exit(f"missing {src}")
|
| 18 |
+
res = json.load(open(src))
|
| 19 |
+
|
| 20 |
+
Ks = [1, 4, 8, 16, 32, 61]
|
| 21 |
+
off = [r for r in res.values() if not r["inject"]]
|
| 22 |
+
on = [r for r in res.values() if r["inject"]]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def curve(rs):
|
| 26 |
+
"""Mean board accuracy at each budget, plus the per-seed spread."""
|
| 27 |
+
m = [np.mean([r["decode"][str(k)]["board"] for r in rs]) * 100 for k in Ks]
|
| 28 |
+
lo = [np.min([r["decode"][str(k)]["board"] for r in rs]) * 100 for k in Ks]
|
| 29 |
+
hi = [np.max([r["decode"][str(k)]["board"] for r in rs]) * 100 for k in Ks]
|
| 30 |
+
return np.array(m), np.array(lo), np.array(hi)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
fig, (ax, ax2) = plt.subplots(1, 2, figsize=(11.4, 4.3))
|
| 34 |
+
|
| 35 |
+
for rs, lab, col in ((off, f"R-MDM faithful, injection OFF (n={len(off)})", "#c0392b"),
|
| 36 |
+
(on, f"+ input injection (n={len(on)})", "#1a5276")):
|
| 37 |
+
if not rs:
|
| 38 |
+
continue
|
| 39 |
+
m, lo, hi = curve(rs)
|
| 40 |
+
ax.plot(Ks, m, "o-", color=col, lw=2, ms=5.5, label=lab)
|
| 41 |
+
if len(rs) > 1:
|
| 42 |
+
ax.fill_between(Ks, lo, hi, color=col, alpha=.15, lw=0)
|
| 43 |
+
ax.set_xscale("log", base=2)
|
| 44 |
+
ax.set_xlabel("denoising passes (NFE)")
|
| 45 |
+
ax.set_ylabel("board accuracy (%)")
|
| 46 |
+
ax.set_title("Matched comparison: identical architecture,\n"
|
| 47 |
+
"step embedding on both sides, 80k steps")
|
| 48 |
+
ax.text(1.03, 12, "shaded = 2 seeds of the same\nconfiguration, 67 points apart",
|
| 49 |
+
fontsize=6.6, color="#c0392b")
|
| 50 |
+
ax.grid(alpha=.3); ax.legend(fontsize=7.5, loc="lower right")
|
| 51 |
+
|
| 52 |
+
# --- is the injection-ON seed even outside the injection-OFF seed range? -----
|
| 53 |
+
# With one seed on the ON side, the only defensible question is whether it lands
|
| 54 |
+
# outside the range spanned by the two OFF seeds. At five of six budgets it does
|
| 55 |
+
# not, so there is nothing to claim there.
|
| 56 |
+
if off and on:
|
| 57 |
+
mo, lo, hi = curve(off)
|
| 58 |
+
mn, _, _ = curve(on)
|
| 59 |
+
x = np.arange(len(Ks))
|
| 60 |
+
ax2.vlines(x, lo, hi, color="#c0392b", lw=9, alpha=.32,
|
| 61 |
+
label="injection OFF: range of 2 seeds")
|
| 62 |
+
ax2.plot(x, mo, "_", color="#c0392b", ms=17, mew=2.4, label="injection OFF: mean")
|
| 63 |
+
inside = (mn >= lo) & (mn <= hi)
|
| 64 |
+
ax2.scatter(x[inside], mn[inside], s=64, color="#7f8c8d", zorder=4,
|
| 65 |
+
marker="o", label="injection ON (n=1): inside the OFF range")
|
| 66 |
+
ax2.scatter(x[~inside], mn[~inside], s=110, color="#1a5276", zorder=4,
|
| 67 |
+
marker="*", label="injection ON (n=1): outside")
|
| 68 |
+
for i in range(len(Ks)):
|
| 69 |
+
ax2.annotate("inside" if inside[i] else "outside", (x[i], mn[i]),
|
| 70 |
+
fontsize=6.4, xytext=(0, 9 if not inside[i] else -14),
|
| 71 |
+
textcoords="offset points", ha="center",
|
| 72 |
+
color="#1a5276" if not inside[i] else "#666")
|
| 73 |
+
ax2.set_xticks(x); ax2.set_xticklabels(Ks)
|
| 74 |
+
ax2.set_xlabel("denoising passes (NFE)")
|
| 75 |
+
ax2.set_ylabel("board accuracy (%)")
|
| 76 |
+
ax2.set_title("At 5 of 6 budgets the injection-ON run lands\n"
|
| 77 |
+
"inside the spread of two injection-OFF seeds", fontsize=10)
|
| 78 |
+
ax2.grid(alpha=.3, axis="y"); ax2.legend(fontsize=6.3, loc="lower right")
|
| 79 |
+
ax2.set_ylim(0, 104)
|
| 80 |
+
|
| 81 |
+
fig.tight_layout()
|
| 82 |
+
fig.savefig(f"{OUT}/sud_fig9_rmdm.png", dpi=170)
|
| 83 |
+
print(f"wrote {OUT}/sud_fig9_rmdm.png")
|
| 84 |
+
|
| 85 |
+
print("\nmatched R-MDM comparison (step 80,000):")
|
| 86 |
+
print(f" {'NFE':>5}{'inj OFF':>10}{'inj ON':>10}{'delta':>9}")
|
| 87 |
+
for i, k in enumerate(Ks):
|
| 88 |
+
mo = np.mean([r["decode"][str(k)]["board"] for r in off]) * 100
|
| 89 |
+
mn = np.mean([r["decode"][str(k)]["board"] for r in on]) * 100
|
| 90 |
+
print(f" {k:>5}{mo:>9.2f}%{mn:>9.2f}%{mn-mo:>+8.2f}")
|
| 91 |
+
bo = np.mean([r["best"] for r in off]) * 100
|
| 92 |
+
bn = np.mean([r["best"] for r in on]) * 100
|
| 93 |
+
print(f" {'best':>5}{bo:>9.2f}%{bn:>9.2f}%{bn-bo:>+8.2f}")
|