forecasting-models / code /reproduce /appendix /fid_sample_stability.py
coarse-grained-forecasting-benchmark's picture
Add anonymous reproduction code
41dd7e0 verified
Raw History Blame Contribute Delete
5.93 kB
"""Appendix B.3: finite-sample stability of cFID and csFID (the ordering of the models and their relative gaps at N = 10,000).
python reproduce/appendix/fid_sample_stability.py --system euler --samples direct_si=DIR --samples edm=DIR \\
--samples cascaded_si=DIR --out DIR [--floor-ns 1000,2500,5000] [--model-ns 2000,5000,10000] [--device cuda]
floor rows FID(A_N, B_N), A and B disjoint halves of the truth ensemble (N <= 5,000: the bank holds 10,000 members)
model rows FID(gen_N, truth_N) for each Table 2 model
Members are the first N in the stored order, so every N is a nested subset; features are extracted once at the largest
N. Writes OUT/sample_stability.{csv,json}.
"""
from __future__ import annotations
import argparse
import csv
import math
import sys
from collections import defaultdict
from datetime import datetime, timezone
from pathlib import Path
import numpy as np
REPO = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO))
from reproduce.appendix.common import SystemInputs, add_system_args, both_features, extractors, write_json # noqa: E402
from bifurcation.fid import backend as BE # noqa: E402
from bifurcation.fid.evaluate import bank_members # noqa: E402
def main(argv=None) -> int:
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
add_system_args(ap)
ap.add_argument("--floor-ns", default="1000,2500,5000")
ap.add_argument("--model-ns", default="2000,5000,10000")
a = ap.parse_args(argv)
inp = SystemInputs(a)
outdir = a.out.resolve(); outdir.mkdir(parents=True, exist_ok=True)
ext, dev, layer_rec = extractors(inp.fid_cfg, a.device)
batch = int(inp.fid_cfg.get("feature_batch", 200))
adapter, gids, t_idx = inp.adapter, inp.gids, inp.t_idx
models = inp.models()
floor_ns = [int(x) for x in a.floor_ns.split(",")]
model_ns = [int(x) for x in a.model_ns.split(",")]
nmax = max(model_ns + [2 * max(floor_ns)])
print(f"{a.system}: {len(gids)} test conditions, features at N = {nmax}")
print("models: " + "; ".join(f"{m['label']} ({m['key']}, n* = {m['n_steps']})" for m in models))
print(f"floor N {floor_ns} (at most 5000: an independent floor at N needs 2N of the 10,000 members); model N {model_ns}\n")
acc = defaultdict(lambda: defaultdict(list)) # acc[(row, N)][metric] -> per-condition values
for g in gids:
tv = bank_members(inp.bank, g, t_idx, nmax)
imgs, _ = adapter.to_images(tv)
tc, ts = both_features(imgs, ext, dev, batch)
for N in floor_ns:
acc[("floor (truth vs truth)", N)]["cfid"].append(BE.fid_from_features(tc[:N], tc[N:2 * N]))
acc[("floor (truth vs truth)", N)]["sfid"].append(BE.fid_from_features(ts[:N], ts[N:2 * N]))
line = (f" gid {g:4d} floor({floor_ns[-1]}) c{acc[('floor (truth vs truth)', floor_ns[-1])]['cfid'][-1]:6.2f}"
f"/s{acc[('floor (truth vs truth)', floor_ns[-1])]['sfid'][-1]:7.2f}")
for m in models:
gi, _ = adapter.to_images(inp.generated(m["methods"][0], g, nmax))
gc, gs = both_features(gi, ext, dev, batch)
for N in model_ns:
acc[(m["label"], N)]["cfid"].append(BE.fid_from_features(gc[:N], tc[:N]))
acc[(m["label"], N)]["sfid"].append(BE.fid_from_features(gs[:N], ts[:N]))
line += f" | {m['label'][:20]} c{acc[(m['label'], model_ns[-1])]['cfid'][-1]:6.2f}/s{acc[(m['label'], model_ns[-1])]['sfid'][-1]:7.2f}"
print(line, flush=True)
def ms(v):
x = np.asarray(v, float); return x.mean(), x.std(ddof=1) / math.sqrt(len(x))
rows = []
print(f"\n{'row':28s} {'N':>6s} {'cFID':>17s} {'csFID':>18s}")
for (label, N), d in sorted(acc.items(), key=lambda kv: (kv[0][0], kv[0][1])):
mc, sc = ms(d["cfid"]); msf, ssf = ms(d["sfid"])
rows.append({"row": label, "N": N, "n_ic": len(d["cfid"]), "cfid": mc, "cfid_sem": sc, "sfid": msf, "sfid_sem": ssf,
"per_gid_cfid": {int(g): float(v) for g, v in zip(gids, d["cfid"])},
"per_gid_sfid": {int(g): float(v) for g, v in zip(gids, d["sfid"])}})
print(f"{label:28s} {N:6d} {mc:8.3f} +- {sc:6.3f} {msf:9.3f} +- {ssf:6.3f}")
print("\nordering and relative gaps across N (model rows only)")
summary = {}
for metric, key in (("cFID", "cfid"), ("csFID", "sfid")):
summary[metric] = {}
for N in model_ns:
vals = {r["row"]: r[key] for r in rows if r["N"] == N and not r["row"].startswith("floor")}
order = sorted(vals, key=vals.get)
best = vals[order[0]]
summary[metric][N] = {"order": order, "values": vals, "ratio_to_best": {k: vals[k] / best for k in order}}
print(f" {metric} N={N:6d} order: {' < '.join(order)} ratios to best: "
+ ", ".join(f"{k}={v:.2f}" for k, v in summary[metric][N]["ratio_to_best"].items()))
write_json(outdir / "sample_stability.json", {"at": datetime.now(timezone.utc).isoformat(timespec="seconds"), "system": a.system,
"gids": gids, "t_idx": t_idx, "floor_ns": floor_ns, "model_ns": model_ns,
"models": models, "adapter": adapter.params, "layer": layer_rec, "pins": BE.pins(),
"rows": rows, "stability": summary})
with open(outdir / "sample_stability.csv", "w", newline="") as fh:
w = csv.writer(fh); w.writerow(["row", "N", "n_ic", "cfid", "cfid_sem", "sfid", "sfid_sem"])
for r in rows:
w.writerow([r["row"], r["N"], r["n_ic"], r["cfid"], r["cfid_sem"], r["sfid"], r["sfid_sem"]])
print(f"\nwrote {outdir / 'sample_stability.csv'}\n {outdir / 'sample_stability.json'}")
return 0
if __name__ == "__main__":
raise SystemExit(main())