File size: 9,503 Bytes
032a961 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | #!/usr/bin/env python3
"""Generate all paper figures from real experiment artifacts."""
import json
import sys
import numpy as np
import os as _os
ROOT = _os.path.dirname(_os.path.dirname(_os.path.abspath(__file__)))
ART = f"{ROOT}/artifacts"
sys.path.insert(0, ROOT)
import matplotlib
matplotlib.use("Agg")
import matplotlib.font_manager as fm
fm.fontManager.addfont('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf')
import matplotlib.pyplot as plt
plt.rcParams.update({
"font.family": "DejaVu Sans",
"font.size": 9,
"axes.spines.top": False,
"axes.spines.right": False,
"axes.grid": True,
"grid.alpha": 0.25,
"figure.dpi": 200,
})
OUT = f"{ART}/figures"
import os
os.makedirs(OUT, exist_ok=True)
metrics = json.load(open(f"{ART}/metrics.json"))
ACCENT = "#2e7d5b"
ACCENT2 = "#b8860b"
GRAY = "#666666"
# ---------------------------------------------------------------- fig: EPT
from spira import SPIRA
model = SPIRA.load(f"{ART}/spira_model.npz")
sample = (b"It was the best of times, it was the worst of times, it was the age "
b"of wisdom, it was the age of foolishness, it was the epoch of belief, "
b"it was the epoch of incredulity, it was the season of Light, "
b"it was the season of Darkness")
sur = model.ept.surprise(sample)
patches = model.ept.segment(sample)
fig, axes = plt.subplots(2, 1, figsize=(7.0, 3.2), height_ratios=[1.2, 1],
constrained_layout=True, sharex=True)
axes[0].fill_between(range(len(sur)), sur, color=ACCENT, alpha=0.55, step="mid")
axes[0].plot(range(len(sur)), sur, color=ACCENT, lw=0.8)
axes[0].set_ylabel("surprisal (bits)")
axes[0].set_title("EPT: per-byte surprisal and constant-information patch boundaries", fontsize=9)
b = model.ept.boundaries(sample)
for bx in b[:-1]:
axes[0].axvline(bx, color=ACCENT2, lw=0.7, alpha=0.85)
# patch length bars
axes[1].bar(b[:-1], [b[i+1]-b[i] for i in range(len(b)-1)], width=1.0,
color=ACCENT, alpha=0.7, align="edge")
axes[1].axhline(float(np.mean([b[i+1]-b[i] for i in range(len(b)-1)])),
color=ACCENT2, lw=1, ls="--", label=f"mean {np.mean([b[i+1]-b[i] for i in range(len(b)-1)]):.1f} B")
axes[1].set_xlabel("byte position")
axes[1].set_ylabel("patch length (B)")
axes[1].legend(frameon=False, fontsize=8)
fig.savefig(f"{OUT}/fig_ept.pdf")
plt.close(fig)
print("fig_ept done:", len(patches), "patches, avg", np.mean([len(p) for p in patches]))
# ------------------------------------------------------- fig: compression
comp = metrics["compression"]
methods = ["gzip -9", "BPE-4k\n(unigram)", "BPE-4k\n(bigram)", "BPE-16k\n(bigram)", "EPT\n(bigram)", "EPT\n(unigram)"]
vals = [comp["gzip"]["bits_per_byte"],
comp["bpe_4096"]["bits_per_byte_unigram"],
comp["bpe_4096"]["bits_per_byte_bigram"],
comp["bpe_16384"]["bits_per_byte_bigram"],
comp["ept"]["bits_per_byte_bigram"],
comp["ept"]["bits_per_byte_unigram"]]
colors = [GRAY, "#9aa5b1", "#9aa5b1", "#9aa5b1", ACCENT, ACCENT]
fig, ax = plt.subplots(figsize=(4.6, 2.9), constrained_layout=True)
bars = ax.bar(range(len(vals)), vals, color=colors, width=0.65)
for i, v in enumerate(vals):
ax.text(i, v + 0.08, f"{v:.2f}", ha="center", fontsize=8)
ax.set_xticks(range(len(methods)))
ax.set_xticklabels(methods, fontsize=7.5)
ax.set_ylabel("bits per byte (with dictionary)")
ax.set_title("Coded size on the held-out test stream", fontsize=9)
fig.savefig(f"{OUT}/fig_compression.pdf")
plt.close(fig)
print("fig_compression done")
# -------------------------------------------------- fig: one-shot+corrupt
one = metrics["one_shot"]
corr = metrics["correction"]
corr_sp = metrics.get("correction_sparse_am", {})
ks = [0, 1, 2, 3]
am_curve = [corr[f"corrupt_{k}_of_4"]["am_acc"] for k in ks]
fig, ax = plt.subplots(figsize=(4.6, 2.9), constrained_layout=True)
ax.plot(ks, [v * 100 for v in am_curve], "o-", color=ACCENT, lw=1.6,
label="full hippocampus (46% load)")
if corr_sp:
am_sp = [corr_sp[f"corrupt_{k}_of_4"]["am_acc"] for k in ks]
ax.plot(ks, [v * 100 for v in am_sp], "s--", color=ACCENT2, lw=1.4,
label="sparse hippocampus (book only)")
for k, v in zip(ks, am_sp):
ax.annotate(f"{v*100:.1f}%", (k, v * 100), textcoords="offset points",
xytext=(0, 7), ha="center", fontsize=8, color=ACCENT2)
for k, v in zip(ks, am_curve):
ax.annotate(f"{v*100:.1f}%", (k, v * 100), textcoords="offset points",
xytext=(0, -13), ha="center", fontsize=8)
ax.set_xlabel("corrupted context slots (of 4)")
ax.set_ylabel("exact recall (%)")
ax.set_ylim(-14, 110)
ax.set_xticks(ks)
ax.set_title("Pattern completion under context corruption\n(completion trades against hippocampal load)", fontsize=9)
ax.legend(frameon=False, fontsize=8, loc="center right")
fig.savefig(f"{OUT}/fig_correction.pdf")
plt.close(fig)
print("fig_correction done:", am_curve)
# ------------------------------------------------- fig: vocab coverage
vc = metrics["vocab_coverage_curve"]
vs = [c["vocab"] for c in vc]
ct = [c["cov_test"] * 100 for c in vc]
co = [c["cov_oneshot"] * 100 for c in vc]
fig, ax = plt.subplots(figsize=(4.6, 2.9), constrained_layout=True)
ax.plot(vs, ct, "o-", color=ACCENT, lw=1.6, ms=3.5, label="held-out test")
ax.plot(vs, co, "s-", color=ACCENT2, lw=1.4, ms=3.5, label="unseen book (one-shot)")
ax.axvline(4096, color=GRAY, lw=0.9, ls=":")
ax.text(4096 * 1.06, 88, "v1: 4096", fontsize=7.5, color=GRAY)
ax.axvline(16384, color=GRAY, lw=0.9, ls=":")
ax.text(16384 * 1.06, 88, "v2: 16384", fontsize=7.5, color=GRAY)
ax.annotate(f"{ct[-1]:.1f}%", (vs[-1], ct[-1]), textcoords="offset points",
xytext=(2, 6), fontsize=8)
ax.set_xscale("log", base=2)
ax.set_xticks(vs)
ax.set_xticklabels([str(v) for v in vs], fontsize=7.5)
ax.set_xlabel("readout vocabulary size (top-V by train frequency)")
ax.set_ylabel("held-out target coverage (%)")
ax.set_title("Zipf knee of the readout vocabulary\n(constant-information patches flatten the tail)", fontsize=9)
ax.legend(frameon=False, fontsize=8, loc="lower right")
fig.savefig(f"{OUT}/fig_vocab.pdf")
plt.close(fig)
print("fig_vocab done")
# ------------------------------------------------- fig: replay consolidation
rep = metrics["replay"]
xs = [0] + [c["replayed_tokens"] for c in rep["curve"]]
ys = [rep["before"]["hier_acc"] * 100] + [c["hier_acc"] * 100 for c in rep["curve"]]
ysi = [rep["before"]["hier_acc_invocab"] * 100] + [c["hier_acc_invocab"] * 100 for c in rep["curve"]]
fig, ax = plt.subplots(figsize=(4.9, 3.0), constrained_layout=True)
ax.plot(xs, ys, "o-", color=ACCENT, lw=1.6, ms=3.5, label="cortex-only recall")
ax.plot(xs, ysi, "s--", color=ACCENT2, lw=1.4, ms=3.2, label="cortex-only, in-vocab only")
ax.axhline(rep["am_recall_after_replay"]["am_acc"] * 100, color=GRAY, lw=1.1, ls=":")
ax.text(xs[-1] * 0.06, rep["am_recall_after_replay"]["am_acc"] * 100 - 4.5,
f"hippocampus (unchanged): {rep['am_recall_after_replay']['am_acc']*100:.1f}%",
fontsize=7.5, color=GRAY)
ax.axhline(rep["after"]["hier_acc"] * 100, color=ACCENT, lw=0.8, ls="--", alpha=0.5)
ax.annotate(f"full eval after replay: {rep['after']['hier_acc']*100:.1f}%\n"
f"(in-vocab {rep['after']['hier_acc_invocab']*100:.1f}%)",
(xs[-1], rep["after"]["hier_acc"] * 100), textcoords="offset points",
xytext=(-96, 6), fontsize=7.5, color=ACCENT)
ax.set_xlabel("hippocampal tokens replayed through the cortex")
ax.set_ylabel("exact next-patch recall (%)")
ax.set_title("Systems consolidation: the cortex learns the book\nonly through hippocampal replay (never seen awake)", fontsize=9)
ax.legend(frameon=False, fontsize=8, loc="center right")
fig.savefig(f"{OUT}/fig_replay.pdf")
plt.close(fig)
print("fig_replay done")
# ------------------------------------------------------------ fig: raster
z = np.load(f"{ART}/spira_model.npz.raster.npz")
ticks, neurons = z["ticks"], z["neurons"]
fig, ax = plt.subplots(figsize=(6.8, 2.7), constrained_layout=True)
ax.scatter(ticks, neurons, s=0.55, c=ACCENT, linewidths=0)
ax.axhline(512 * 0.8, color=ACCENT2, lw=0.8, ls="--")
ax.text(5, 512 * 0.8 + 6, "inhibitory population (Dale's law)", fontsize=7, color=ACCENT2)
ax.set_xlabel("tick (token)")
ax.set_ylabel("reservoir neuron")
ax.set_title(f"Spike raster — {len(ticks)} spikes over 1500 ticks "
f"({len(ticks)/1500:.0f} spikes/tick)", fontsize=9)
fig.savefig(f"{OUT}/fig_raster.pdf")
plt.close(fig)
print("fig_raster done")
# ------------------------------------------------- fig: learning curve
import glob
logs = []
for line in open(f"{ART}/chunks.jsonl"):
try:
logs.append(json.loads(line))
except Exception:
pass
# training acc EMAs were logged inside msg lines; rebuild a coarse curve from
# chunk results recorded in the metrics/history + chunks.jsonl messages
accs = []
for e in logs:
if "acc=" in e.get("msg", ""):
try:
accs.append(float(e["msg"].split("acc=")[1].split()[0]))
except Exception:
pass
fig, ax = plt.subplots(figsize=(4.6, 2.9), constrained_layout=True)
if accs:
xs = np.arange(len(accs)) * 25000
ax.plot(xs, [a * 100 for a in accs], "-", color=ACCENT, lw=1.4)
ax.set_xlabel("tick (token)")
ax.set_ylabel("online next-patch accuracy (%)")
ax.set_title("Online learning during training (vocab-masked, incl. UNK targets)", fontsize=9)
fig.savefig(f"{OUT}/fig_learning.pdf")
plt.close(fig)
print("fig_learning done:", len(accs), "points")
|