Improve RL training observability and checkpointing
Browse files- pbc3_rl_a20.py +12 -1
- static/index.html +7 -1
- static/train.js +63 -6
- train_api.py +3 -1
pbc3_rl_a20.py
CHANGED
|
@@ -286,11 +286,13 @@ def train(args):
|
|
| 286 |
initial = evaluate(model, reference, source, val_episodes, bases, args)
|
| 287 |
print("initial", json.dumps(initial), flush=True)
|
| 288 |
best = initial["aggregate_score"]
|
|
|
|
| 289 |
export(model, source, args.out)
|
| 290 |
for epoch in range(start_epoch + 1, start_epoch + args.epochs + 1):
|
| 291 |
random.shuffle(train_episodes)
|
| 292 |
rewards = []
|
| 293 |
losses = []
|
|
|
|
| 294 |
for start in range(0, len(train_episodes), args.batch):
|
| 295 |
optimizer.zero_grad()
|
| 296 |
episode_losses = []
|
|
@@ -300,6 +302,7 @@ def train(args):
|
|
| 300 |
)
|
| 301 |
r = reward(bases[(path, preset)], (mse, bpp, work), args)
|
| 302 |
rewards.append(r)
|
|
|
|
| 303 |
if not logps:
|
| 304 |
continue
|
| 305 |
logps = torch.stack(logps)
|
|
@@ -321,6 +324,9 @@ def train(args):
|
|
| 321 |
row = {
|
| 322 |
"epoch": epoch,
|
| 323 |
"train_reward": float(np.mean(rewards)),
|
|
|
|
|
|
|
|
|
|
| 324 |
"loss": float(np.mean(losses)),
|
| 325 |
"validation": validation,
|
| 326 |
}
|
|
@@ -328,7 +334,11 @@ def train(args):
|
|
| 328 |
print(json.dumps(row), flush=True)
|
| 329 |
if validation["aggregate_score"] > best:
|
| 330 |
best = validation["aggregate_score"]
|
|
|
|
| 331 |
export(model, source, args.out)
|
|
|
|
|
|
|
|
|
|
| 332 |
torch.save(model.state_dict(), f"{args.out}.epoch{epoch}.pt")
|
| 333 |
torch.save({
|
| 334 |
"epoch": epoch,
|
|
@@ -339,7 +349,7 @@ def train(args):
|
|
| 339 |
"torch_rng": torch.random.get_rng_state(),
|
| 340 |
"history": history,
|
| 341 |
}, f"{args.out}.resume.pt")
|
| 342 |
-
Path(f"{args.out}.json").write_text(json.dumps({"initial": initial, "best_score": best, "history": history}, indent=2))
|
| 343 |
|
| 344 |
|
| 345 |
def main():
|
|
@@ -350,6 +360,7 @@ def main():
|
|
| 350 |
parser.add_argument("--init", default="pbc3_students/patch_policy_f26_a20_h512_l2_e1200.npz")
|
| 351 |
parser.add_argument("--out", default="pbc3_students/patch_policy_f26_a20_rl.npz")
|
| 352 |
parser.add_argument("--resume", default="")
|
|
|
|
| 353 |
parser.add_argument("--epochs", type=int, default=5)
|
| 354 |
parser.add_argument("--batch", type=int, default=4)
|
| 355 |
parser.add_argument("--limit", type=int, default=0)
|
|
|
|
| 286 |
initial = evaluate(model, reference, source, val_episodes, bases, args)
|
| 287 |
print("initial", json.dumps(initial), flush=True)
|
| 288 |
best = initial["aggregate_score"]
|
| 289 |
+
best_epoch = 0
|
| 290 |
export(model, source, args.out)
|
| 291 |
for epoch in range(start_epoch + 1, start_epoch + args.epochs + 1):
|
| 292 |
random.shuffle(train_episodes)
|
| 293 |
rewards = []
|
| 294 |
losses = []
|
| 295 |
+
train_rows = []
|
| 296 |
for start in range(0, len(train_episodes), args.batch):
|
| 297 |
optimizer.zero_grad()
|
| 298 |
episode_losses = []
|
|
|
|
| 302 |
)
|
| 303 |
r = reward(bases[(path, preset)], (mse, bpp, work), args)
|
| 304 |
rewards.append(r)
|
| 305 |
+
train_rows.append((mse, bpp, work))
|
| 306 |
if not logps:
|
| 307 |
continue
|
| 308 |
logps = torch.stack(logps)
|
|
|
|
| 324 |
row = {
|
| 325 |
"epoch": epoch,
|
| 326 |
"train_reward": float(np.mean(rewards)),
|
| 327 |
+
"train_mse": float(np.mean([r[0] for r in train_rows])),
|
| 328 |
+
"train_bpp": float(np.mean([r[1] for r in train_rows])),
|
| 329 |
+
"train_work": float(np.mean([r[2] for r in train_rows])),
|
| 330 |
"loss": float(np.mean(losses)),
|
| 331 |
"validation": validation,
|
| 332 |
}
|
|
|
|
| 334 |
print(json.dumps(row), flush=True)
|
| 335 |
if validation["aggregate_score"] > best:
|
| 336 |
best = validation["aggregate_score"]
|
| 337 |
+
best_epoch = epoch
|
| 338 |
export(model, source, args.out)
|
| 339 |
+
if args.export_every > 0 and (epoch % args.export_every == 0 or epoch == start_epoch + args.epochs):
|
| 340 |
+
periodic = Path(args.out).with_name(f"{Path(args.out).stem}.epoch{epoch}.npz")
|
| 341 |
+
export(model, source, periodic)
|
| 342 |
torch.save(model.state_dict(), f"{args.out}.epoch{epoch}.pt")
|
| 343 |
torch.save({
|
| 344 |
"epoch": epoch,
|
|
|
|
| 349 |
"torch_rng": torch.random.get_rng_state(),
|
| 350 |
"history": history,
|
| 351 |
}, f"{args.out}.resume.pt")
|
| 352 |
+
Path(f"{args.out}.json").write_text(json.dumps({"initial": initial, "best_score": best, "best_epoch": best_epoch, "history": history}, indent=2))
|
| 353 |
|
| 354 |
|
| 355 |
def main():
|
|
|
|
| 360 |
parser.add_argument("--init", default="pbc3_students/patch_policy_f26_a20_h512_l2_e1200.npz")
|
| 361 |
parser.add_argument("--out", default="pbc3_students/patch_policy_f26_a20_rl.npz")
|
| 362 |
parser.add_argument("--resume", default="")
|
| 363 |
+
parser.add_argument("--export-every", type=int, default=500)
|
| 364 |
parser.add_argument("--epochs", type=int, default=5)
|
| 365 |
parser.add_argument("--batch", type=int, default=4)
|
| 366 |
parser.add_argument("--limit", type=int, default=0)
|
static/index.html
CHANGED
|
@@ -54,6 +54,12 @@
|
|
| 54 |
.tr-presets { display:flex; flex-wrap:wrap; gap:10px 18px; padding:10px 0; color:var(--text); }
|
| 55 |
.tr-presets input { accent-color:var(--red); }
|
| 56 |
.tr-log-tail { max-height:240px; overflow:auto; white-space:pre-wrap; background:rgba(255,255,255,.04); padding:12px; border-radius:10px; color:var(--muted); font:11px 'JetBrains Mono',monospace; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
.tr-table-wrap { overflow-x:auto; }
|
| 58 |
.tr-table { width:100%; border-collapse:collapse; font:12px 'JetBrains Mono',monospace; }
|
| 59 |
.tr-table th, .tr-table td { padding:9px 10px; border-bottom:1px solid rgba(255,255,255,.08); text-align:right; white-space:nowrap; }
|
|
@@ -69,7 +75,7 @@
|
|
| 69 |
#hold-tr-c { --hc:#ff8a3d; } #hold-tr-b { --hc:#38bdf8; } #hold-tr-q { --hc:#a855f7; } #hold-tr-hq { --hc:#34d399; } #hold-tr-model { --hc:#ff3b41; }
|
| 70 |
#hold-tr-c, #hold-tr-b, #hold-tr-q, #hold-tr-hq, #hold-tr-model { border-color:var(--hc); color:var(--hc); }
|
| 71 |
#hold-tr-c.holding, #hold-tr-b.holding, #hold-tr-q.holding, #hold-tr-hq.holding, #hold-tr-model.holding { background:var(--hc); color:#fff; border-color:var(--hc); }
|
| 72 |
-
@media (max-width:760px){ .tr-fields{grid-template-columns:1fr;} }
|
| 73 |
</style>
|
| 74 |
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>👁️</text></svg>">
|
| 75 |
</head>
|
|
|
|
| 54 |
.tr-presets { display:flex; flex-wrap:wrap; gap:10px 18px; padding:10px 0; color:var(--text); }
|
| 55 |
.tr-presets input { accent-color:var(--red); }
|
| 56 |
.tr-log-tail { max-height:240px; overflow:auto; white-space:pre-wrap; background:rgba(255,255,255,.04); padding:12px; border-radius:10px; color:var(--muted); font:11px 'JetBrains Mono',monospace; }
|
| 57 |
+
.tr-chart-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:16px; }
|
| 58 |
+
.tr-chart-grid > div { min-width:0; min-height:290px; }
|
| 59 |
+
.tr-chart-grid .tr-chart-wide { grid-column:1/-1; }
|
| 60 |
+
.tr-sort { display:flex; align-items:center; gap:7px; color:var(--muted); font-size:12px; }
|
| 61 |
+
.tr-sort select { background:var(--surface); border:1px solid var(--border); border-radius:6px; color:var(--text); padding:5px; }
|
| 62 |
+
.tr-baseline-line { padding:9px 0 12px; color:var(--muted); font:12px 'JetBrains Mono',monospace; }
|
| 63 |
.tr-table-wrap { overflow-x:auto; }
|
| 64 |
.tr-table { width:100%; border-collapse:collapse; font:12px 'JetBrains Mono',monospace; }
|
| 65 |
.tr-table th, .tr-table td { padding:9px 10px; border-bottom:1px solid rgba(255,255,255,.08); text-align:right; white-space:nowrap; }
|
|
|
|
| 75 |
#hold-tr-c { --hc:#ff8a3d; } #hold-tr-b { --hc:#38bdf8; } #hold-tr-q { --hc:#a855f7; } #hold-tr-hq { --hc:#34d399; } #hold-tr-model { --hc:#ff3b41; }
|
| 76 |
#hold-tr-c, #hold-tr-b, #hold-tr-q, #hold-tr-hq, #hold-tr-model { border-color:var(--hc); color:var(--hc); }
|
| 77 |
#hold-tr-c.holding, #hold-tr-b.holding, #hold-tr-q.holding, #hold-tr-hq.holding, #hold-tr-model.holding { background:var(--hc); color:#fff; border-color:var(--hc); }
|
| 78 |
+
@media (max-width:760px){ .tr-fields{grid-template-columns:1fr;} .tr-chart-grid{grid-template-columns:1fr;} .tr-chart-grid .tr-chart-wide{grid-column:auto;} .tr-sort{margin-top:8px;} }
|
| 79 |
</style>
|
| 80 |
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>👁️</text></svg>">
|
| 81 |
</head>
|
static/train.js
CHANGED
|
@@ -67,17 +67,26 @@
|
|
| 67 |
<label class="tr-f"><span>Temperature</span><input id="tr-temp" type="number" step="0.05" value="0.9"></label>
|
| 68 |
<label class="tr-f"><span>Entropy weight</span><input id="tr-entropy" type="number" step="0.0005" value="0.003"></label>
|
| 69 |
<label class="tr-f"><span>KL weight</span><input id="tr-kl" type="number" step="0.001" value="0.015"></label>
|
|
|
|
| 70 |
</div>
|
| 71 |
<div class="tr-actions"><button id="tr-start" class="primary-btn">Start / resume</button><button id="tr-stop" class="hold-btn">Stop</button><button id="tr-refresh" class="hold-btn">Refresh</button><button id="tr-log" class="hold-btn">Download log</button><button id="tr-copy-results" class="hold-btn">Copy results</button><button id="tr-download-results" class="hold-btn">Download results</button><label class="tr-check tr-live"><input id="tr-pause" type="checkbox"> Pause live table/log updates</label></div>
|
| 72 |
</div>
|
| 73 |
<div class="params"><div class="params-head"><span class="field-label">Run status</span></div><div id="tr-status" class="tr-ck">Loading…</div><pre id="tr-log-tail" class="tr-log-tail"></pre></div>
|
| 74 |
-
<div class="params"><div class="params-head"><span class="field-label">
|
|
|
|
| 75 |
$("#tr-start").onclick = start;
|
| 76 |
$("#tr-stop").onclick = async () => { await api("/api/train/stop", { method: "POST" }); refresh(); };
|
| 77 |
$("#tr-refresh").onclick = refresh;
|
| 78 |
$("#tr-log").onclick = () => window.open("/api/train/log", "_blank");
|
| 79 |
$("#tr-copy-results").onclick = copyResults;
|
| 80 |
$("#tr-download-results").onclick = downloadResults;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
fillInitialModels();
|
| 82 |
}
|
| 83 |
|
|
@@ -109,6 +118,7 @@
|
|
| 109 |
init: $("#tr-init").value, output: $("#tr-output").value,
|
| 110 |
rate_weight: Number($("#tr-rate").value), speed_weight: Number($("#tr-speed").value),
|
| 111 |
temperature: Number($("#tr-temp").value), entropy_weight: Number($("#tr-entropy").value), kl_weight: Number($("#tr-kl").value),
|
|
|
|
| 112 |
};
|
| 113 |
try { await api("/api/train/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); refresh(); } catch (error) { $("#tr-status").textContent = error.message; }
|
| 114 |
}
|
|
@@ -116,23 +126,32 @@
|
|
| 116 |
function renderHistory(history) {
|
| 117 |
const root = $("#tr-history");
|
| 118 |
if (!root) return;
|
| 119 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
const v = row.validation || row;
|
| 121 |
-
return `<tr><td>${row.epoch ? `Epoch ${row.epoch}` : "Initial"}</td><td>${fmt(row.reward ?? v.reward, 5)}</td><td>${fmt(v.aggregate_score, 5)}</td><td>${fmt(v.mse, 3)}</td><td>${fmt(v.bpp, 6)}</td><td>${fmt(v.work, 0)}</td></tr>`;
|
| 122 |
});
|
| 123 |
-
|
|
|
|
| 124 |
}
|
| 125 |
|
| 126 |
function resultText() {
|
| 127 |
const history = latestStatus.history || [];
|
| 128 |
return history.map((row) => {
|
| 129 |
const v = row.validation || row;
|
| 130 |
-
return [row.epoch ? `Epoch ${row.epoch}` : "Initial", fmt(row.reward ?? v.reward, 5), fmt(v.aggregate_score, 5), fmt(v.mse, 3), fmt(v.bpp, 6), fmt(v.work, 0)].join("\t");
|
| 131 |
}).join("\n");
|
| 132 |
}
|
| 133 |
|
| 134 |
async function copyResults() {
|
| 135 |
-
const text = ["Checkpoint\
|
| 136 |
try { await navigator.clipboard.writeText(text); $("#tr-pause").checked = true; } catch { window.prompt("Copy training results", text); }
|
| 137 |
}
|
| 138 |
|
|
@@ -152,11 +171,49 @@
|
|
| 152 |
if (!$("#tr-pause")?.checked) {
|
| 153 |
$("#tr-log-tail").textContent = data.log_tail || "";
|
| 154 |
renderHistory(data.history || []);
|
|
|
|
| 155 |
}
|
| 156 |
if (data.running && !poll) poll = setInterval(refresh, 3000);
|
| 157 |
if (!data.running && poll) { clearInterval(poll); poll = null; loadCheckpoints(); }
|
| 158 |
}
|
| 159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
function renderCheckpoints() {
|
| 161 |
checkpointRoot.innerHTML = `<div class="params"><div class="params-head"><span class="field-label">Downloadable model checkpoints</span></div><p class="card-date">Download an exported <code>.npz</code> after reviewing its validation history, then upload it into the main project when you want to test or promote it.</p><input id="tr-ckpt-filter" class="tr-filter" placeholder="Filter checkpoints…"><div id="tr-checkpoint-list" class="tr-checkpoint-grid"></div></div>`;
|
| 162 |
$("#tr-ckpt-filter").oninput = renderCheckpointList;
|
|
|
|
| 67 |
<label class="tr-f"><span>Temperature</span><input id="tr-temp" type="number" step="0.05" value="0.9"></label>
|
| 68 |
<label class="tr-f"><span>Entropy weight</span><input id="tr-entropy" type="number" step="0.0005" value="0.003"></label>
|
| 69 |
<label class="tr-f"><span>KL weight</span><input id="tr-kl" type="number" step="0.001" value="0.015"></label>
|
| 70 |
+
<label class="tr-f"><span>Export checkpoint every N epochs</span><input id="tr-export-every" type="number" min="0" step="100" value="500"></label>
|
| 71 |
</div>
|
| 72 |
<div class="tr-actions"><button id="tr-start" class="primary-btn">Start / resume</button><button id="tr-stop" class="hold-btn">Stop</button><button id="tr-refresh" class="hold-btn">Refresh</button><button id="tr-log" class="hold-btn">Download log</button><button id="tr-copy-results" class="hold-btn">Copy results</button><button id="tr-download-results" class="hold-btn">Download results</button><label class="tr-check tr-live"><input id="tr-pause" type="checkbox"> Pause live table/log updates</label></div>
|
| 73 |
</div>
|
| 74 |
<div class="params"><div class="params-head"><span class="field-label">Run status</span></div><div id="tr-status" class="tr-ck">Loading…</div><pre id="tr-log-tail" class="tr-log-tail"></pre></div>
|
| 75 |
+
<div class="params"><div class="params-head"><span class="field-label">Learning graphics</span></div><div class="tr-chart-grid"><div id="tr-chart-reward"></div><div id="tr-chart-metrics"></div><div id="tr-chart-work"></div><div id="tr-chart-train"></div><div id="tr-chart-rd" class="tr-chart-wide"></div></div></div>
|
| 76 |
+
<div class="params"><div class="params-head"><span class="field-label">Validation history</span><label class="tr-sort">Sort by <select id="tr-sort"><option value="epoch">Epoch</option><option value="reward">Per-image reward</option><option value="aggregate_score">Aggregate score</option><option value="mse">Validation MSE</option><option value="bpp">Validation bpp</option><option value="work">Validation grid work</option><option value="train_reward">Training reward</option><option value="train_mse">Training MSE</option><option value="train_bpp">Training bpp</option></select><button id="tr-sort-dir" class="hold-btn sm">↓</button></label></div><div id="tr-history"></div></div>`;
|
| 77 |
$("#tr-start").onclick = start;
|
| 78 |
$("#tr-stop").onclick = async () => { await api("/api/train/stop", { method: "POST" }); refresh(); };
|
| 79 |
$("#tr-refresh").onclick = refresh;
|
| 80 |
$("#tr-log").onclick = () => window.open("/api/train/log", "_blank");
|
| 81 |
$("#tr-copy-results").onclick = copyResults;
|
| 82 |
$("#tr-download-results").onclick = downloadResults;
|
| 83 |
+
$("#tr-sort").onchange = () => renderHistory(latestStatus.history || []);
|
| 84 |
+
$("#tr-sort-dir").onclick = () => {
|
| 85 |
+
const button = $("#tr-sort-dir");
|
| 86 |
+
button.dataset.direction = button.dataset.direction === "1" ? "-1" : "1";
|
| 87 |
+
button.textContent = button.dataset.direction === "1" ? "↑" : "↓";
|
| 88 |
+
renderHistory(latestStatus.history || []);
|
| 89 |
+
};
|
| 90 |
fillInitialModels();
|
| 91 |
}
|
| 92 |
|
|
|
|
| 118 |
init: $("#tr-init").value, output: $("#tr-output").value,
|
| 119 |
rate_weight: Number($("#tr-rate").value), speed_weight: Number($("#tr-speed").value),
|
| 120 |
temperature: Number($("#tr-temp").value), entropy_weight: Number($("#tr-entropy").value), kl_weight: Number($("#tr-kl").value),
|
| 121 |
+
export_every: Number($("#tr-export-every").value),
|
| 122 |
};
|
| 123 |
try { await api("/api/train/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); refresh(); } catch (error) { $("#tr-status").textContent = error.message; }
|
| 124 |
}
|
|
|
|
| 126 |
function renderHistory(history) {
|
| 127 |
const root = $("#tr-history");
|
| 128 |
if (!root) return;
|
| 129 |
+
const key = $("#tr-sort")?.value || "epoch";
|
| 130 |
+
const direction = Number($("#tr-sort-dir")?.dataset.direction || (key === "epoch" ? "1" : "-1"));
|
| 131 |
+
const sorted = history.filter((row) => row.epoch || row.validation).slice().sort((a, b) => {
|
| 132 |
+
const value = (row) => key === "epoch" ? (row.epoch ?? -1) : (key.startsWith("train_") ? row : (row.validation || row))[key];
|
| 133 |
+
const av = value(a);
|
| 134 |
+
const bv = value(b);
|
| 135 |
+
return (Number(av ?? -Infinity) - Number(bv ?? -Infinity)) * direction;
|
| 136 |
+
});
|
| 137 |
+
const rows = sorted.map((row) => {
|
| 138 |
const v = row.validation || row;
|
| 139 |
+
return `<tr><td>${row.epoch ? `Epoch ${row.epoch}` : "Initial"}</td><td>${fmt(row.train_reward, 5)}</td><td>${fmt(row.reward ?? v.reward, 5)}</td><td>${fmt(v.aggregate_score, 5)}</td><td>${fmt(row.train_mse, 3)}</td><td>${fmt(v.mse, 3)}</td><td>${fmt(row.train_bpp, 6)}</td><td>${fmt(v.bpp, 6)}</td><td>${fmt(row.train_work, 0)}</td><td>${fmt(v.work, 0)}</td></tr>`;
|
| 140 |
});
|
| 141 |
+
const baseline = (history.find((r) => r.validation)?.validation || {});
|
| 142 |
+
root.innerHTML = rows.length ? `<div class="tr-baseline-line">Production baseline: MSE ${fmt(baseline.baseline_mse, 3)} · bpp ${fmt(baseline.baseline_bpp, 6)} · grid-work baseline is the starting policy, not PBC3.0.</div><div class="tr-table-wrap"><table class="tr-table"><thead><tr><th>Checkpoint</th><th>Train reward</th><th>Val reward</th><th>Aggregate score</th><th>Train MSE</th><th>Val MSE</th><th>Train bpp</th><th>Val bpp</th><th>Train work</th><th>Val work</th></tr></thead><tbody>${rows.join("")}</tbody></table></div>` : `<p class="card-date">No validation rows yet. The first baseline pass can take a few minutes.</p>`;
|
| 143 |
}
|
| 144 |
|
| 145 |
function resultText() {
|
| 146 |
const history = latestStatus.history || [];
|
| 147 |
return history.map((row) => {
|
| 148 |
const v = row.validation || row;
|
| 149 |
+
return [row.epoch ? `Epoch ${row.epoch}` : "Initial", fmt(row.train_reward, 5), fmt(row.reward ?? v.reward, 5), fmt(v.aggregate_score, 5), fmt(row.train_mse, 3), fmt(v.mse, 3), fmt(row.train_bpp, 6), fmt(v.bpp, 6), fmt(row.train_work, 0), fmt(v.work, 0)].join("\t");
|
| 150 |
}).join("\n");
|
| 151 |
}
|
| 152 |
|
| 153 |
async function copyResults() {
|
| 154 |
+
const text = ["Checkpoint\tTrain reward\tVal reward\tAggregate score\tTrain MSE\tVal MSE\tTrain bpp\tVal bpp\tTrain work\tVal work", resultText()].filter(Boolean).join("\n");
|
| 155 |
try { await navigator.clipboard.writeText(text); $("#tr-pause").checked = true; } catch { window.prompt("Copy training results", text); }
|
| 156 |
}
|
| 157 |
|
|
|
|
| 171 |
if (!$("#tr-pause")?.checked) {
|
| 172 |
$("#tr-log-tail").textContent = data.log_tail || "";
|
| 173 |
renderHistory(data.history || []);
|
| 174 |
+
drawCharts(data.history || []);
|
| 175 |
}
|
| 176 |
if (data.running && !poll) poll = setInterval(refresh, 3000);
|
| 177 |
if (!data.running && poll) { clearInterval(poll); poll = null; loadCheckpoints(); }
|
| 178 |
}
|
| 179 |
|
| 180 |
+
function chartLayout(title, yTitle, extra = {}) {
|
| 181 |
+
return Object.assign({ title, height: 290, margin: { l: 52, r: 48, t: 38, b: 42 }, paper_bgcolor: "rgba(0,0,0,0)", plot_bgcolor: "rgba(0,0,0,0)", font: { color: "#cfd3dc", size: 11 }, xaxis: { title: "Epoch", gridcolor: "rgba(255,255,255,.07)" }, yaxis: { title: yTitle, gridcolor: "rgba(255,255,255,.07)" }, legend: { orientation: "h" } }, extra);
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
function drawCharts(history) {
|
| 185 |
+
if (!window.Plotly) return;
|
| 186 |
+
const points = history.filter((row) => row.epoch || row.validation);
|
| 187 |
+
if (!points.length) return;
|
| 188 |
+
const x = points.map((row) => row.epoch ?? 0);
|
| 189 |
+
const values = points.map((row) => row.validation || row);
|
| 190 |
+
const color = points.map((_, i) => i);
|
| 191 |
+
Plotly.react($("#tr-chart-reward"), [
|
| 192 |
+
{ x, y: points.map((row) => row.train_reward), name: "train reward", mode: "lines", line: { color: "#60a5fa" } },
|
| 193 |
+
{ x, y: points.map((row) => row.reward ?? (row.validation || {}).reward), name: "validation reward", mode: "lines", line: { color: "#f59e0b" } },
|
| 194 |
+
{ x, y: values.map((row) => row.aggregate_score), name: "aggregate score", mode: "lines", line: { color: "#22c55e" } },
|
| 195 |
+
], chartLayout("Reward and aggregate score", "score"), { responsive: true, displayModeBar: false });
|
| 196 |
+
Plotly.react($("#tr-chart-metrics"), [
|
| 197 |
+
{ x, y: points.map((row) => row.train_mse), name: "train MSE", mode: "lines", line: { color: "#93c5fd" } },
|
| 198 |
+
{ x, y: values.map((row) => row.mse), name: "val MSE", mode: "lines", line: { color: "#ef4444" } },
|
| 199 |
+
{ x, y: values.map((row) => row.baseline_mse), name: "PBC3.0 MSE", mode: "lines", line: { color: "#fca5a5", dash: "dot" } },
|
| 200 |
+
], chartLayout("MSE", "MSE"), { responsive: true, displayModeBar: false });
|
| 201 |
+
Plotly.react($("#tr-chart-work"), [
|
| 202 |
+
{ x, y: points.map((row) => row.train_work), name: "train grid work", mode: "lines", line: { color: "#a78bfa" } },
|
| 203 |
+
{ x, y: values.map((row) => row.work), name: "val grid work", mode: "lines", line: { color: "#c084fc" } },
|
| 204 |
+
], chartLayout("Grid work", "coefficients"), { responsive: true, displayModeBar: false });
|
| 205 |
+
Plotly.react($("#tr-chart-train"), [
|
| 206 |
+
{ x, y: points.map((row) => row.train_bpp), name: "train bpp", mode: "lines", line: { color: "#93c5fd" } },
|
| 207 |
+
{ x, y: values.map((row) => row.bpp), name: "val bpp", mode: "lines", line: { color: "#38bdf8" } },
|
| 208 |
+
{ x, y: values.map((row) => row.baseline_bpp), name: "PBC3.0 bpp", mode: "lines", line: { color: "#7dd3fc", dash: "dot" } },
|
| 209 |
+
], chartLayout("Bitrate", "bits / pixel"), { responsive: true, displayModeBar: false });
|
| 210 |
+
const baseline = values[0];
|
| 211 |
+
Plotly.react($("#tr-chart-rd"), [
|
| 212 |
+
{ x: values.map((row) => row.bpp), y: values.map((row) => row.mse), text: x.map((epoch) => `Epoch ${epoch}`), mode: "markers", name: "validation RD", marker: { size: 9, color, colorscale: "YlOrRd", showscale: true, colorbar: { title: "newer" } } },
|
| 213 |
+
{ x: [baseline.baseline_bpp], y: [baseline.baseline_mse], text: ["PBC3.0 baseline"], mode: "markers+text", name: "PBC3.0 baseline", textposition: "top center", marker: { size: 13, color: "#fff", symbol: "diamond", line: { color: "#ef4444", width: 2 } } },
|
| 214 |
+
], chartLayout("Rate–distortion history", "MSE", { xaxis: { title: "bpp", gridcolor: "rgba(255,255,255,.07)" } }), { responsive: true, displayModeBar: false });
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
function renderCheckpoints() {
|
| 218 |
checkpointRoot.innerHTML = `<div class="params"><div class="params-head"><span class="field-label">Downloadable model checkpoints</span></div><p class="card-date">Download an exported <code>.npz</code> after reviewing its validation history, then upload it into the main project when you want to test or promote it.</p><input id="tr-ckpt-filter" class="tr-filter" placeholder="Filter checkpoints…"><div id="tr-checkpoint-list" class="tr-checkpoint-grid"></div></div>`;
|
| 219 |
$("#tr-ckpt-filter").oninput = renderCheckpointList;
|
train_api.py
CHANGED
|
@@ -68,7 +68,8 @@ def status():
|
|
| 68 |
if history_path and history_path.exists():
|
| 69 |
try:
|
| 70 |
saved = json.loads(history_path.read_text(encoding="utf-8"))
|
| 71 |
-
|
|
|
|
| 72 |
except Exception:
|
| 73 |
pass
|
| 74 |
if not history:
|
|
@@ -107,6 +108,7 @@ def start(spec):
|
|
| 107 |
sys.executable, "pbc3_rl_a20.py",
|
| 108 |
"--presets", ",".join(selected),
|
| 109 |
"--epochs", str(int(spec.get("epochs", 100))),
|
|
|
|
| 110 |
"--batch", str(int(spec.get("batch", 4))),
|
| 111 |
"--init", os.fspath(init),
|
| 112 |
"--out", os.fspath(output),
|
|
|
|
| 68 |
if history_path and history_path.exists():
|
| 69 |
try:
|
| 70 |
saved = json.loads(history_path.read_text(encoding="utf-8"))
|
| 71 |
+
initial = saved.get("initial")
|
| 72 |
+
history = ([{"validation": initial}] if initial else []) + saved.get("history", [])
|
| 73 |
except Exception:
|
| 74 |
pass
|
| 75 |
if not history:
|
|
|
|
| 108 |
sys.executable, "pbc3_rl_a20.py",
|
| 109 |
"--presets", ",".join(selected),
|
| 110 |
"--epochs", str(int(spec.get("epochs", 100))),
|
| 111 |
+
"--export-every", str(int(spec.get("export_every", 500))),
|
| 112 |
"--batch", str(int(spec.get("batch", 4))),
|
| 113 |
"--init", os.fspath(init),
|
| 114 |
"--out", os.fspath(output),
|