PIVOT / scripts /verify_reproducibility.py
bryan7264's picture
clean up scripts: drop banners, version markers, unused imports; add reproducibility check
fe43f4f verified
Raw
History Blame Contribute Delete
3.76 kB
# reproduce every model-dependent number in experiments/results/ from the saved
# checkpoints and diff against the committed json. any mismatch = a fake or a
# reproducibility break. loads the released weights, re-runs the exact eval.
import os, sys, json
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
import torch
from src.data.perturb_data import load_dataset, MATCH_STRATEGIES
from src.data.splits import load_split
from src.training.train import TrainConfig, make_model
from src.experiments.predictors import PivotPredictor
from src.experiments.forward_eval import evaluate_forward
from src.experiments.run_ablations import _fwd_inv
RES = "experiments/results"
GPU = "cuda:%d" % int(os.environ.get("PIVOT_GPU", "3"))
TOL = 1e-3
checks = [] # (label, ok, detail)
def load(path, data):
cfg = TrainConfig(**json.load(open(os.path.join(path, "config.json"))))
m = make_model(data, cfg, GPU)
m.load_state_dict(torch.load(os.path.join(path, "model.pt"), map_location=GPU))
m.eval()
return m
def cmp(label, got, exp):
keys = [k for k in exp if isinstance(exp[k], (int, float))
and isinstance(got.get(k), (int, float))]
diff = {k: (round(got[k], 4), round(exp[k], 4)) for k in keys if abs(got[k] - exp[k]) > TOL}
checks.append((label, not diff, diff))
def fwd_pivot(data, model, split, max_perts=80):
sp = load_split(data.dir, split)
test = list(sp["test_perts"]) if split != "cell" else [p for p in data.perturbations if len(data.parse(p)) == 1]
cp = sp["test_idx"][data.is_control[sp["test_idx"]]]
if len(cp) < 50:
cp = data.control_idx
return evaluate_forward(PivotPredictor(model, data, GPU), data, test, cp, max_perts=max_perts)
# ---- main forward tables (PIVOT row) ----
for ds, splits in [("norman", ["cell", "perturbation", "combination"]),
("replogle_k562", ["cell", "perturbation", "gene"])]:
data = load_dataset(ds)
for sp in splits:
j = json.load(open("%s/%s_forward_%s.json" % (RES, ds, sp)))["models"]["PIVOT"]
m = load("models/%s/%s" % (ds, sp), data)
cmp("forward %s/%s PIVOT" % (ds, sp), fwd_pivot(data, m, sp), j)
# ---- ablation tables (each row -> its checkpoint), norman/perturbation ----
data = load_dataset("norman")
A = "models/ablations/norman_perturbation"
comp = {"flow-map-only": "comp_map", "no-tangent": "comp_map_semi",
"no-semigroup": "comp_map_tan", "PIVOT-full": "default"}
rep = {"gene_op": "default", "op_only": "rep_op_only", "gene_only": "rep_gene_only",
"random_id": "rep_random_id", "gene_pathway_op": "rep_gene_pathway_op"}
frac = {"0.1": "frac_0.1", "0.25": "frac_0.25", "0.5": "frac_0.5", "0.75": "frac_0.75", "1.0": "default"}
match = {ms: ("default" if ms == "batch" else "match_%s" % ms) for ms in MATCH_STRATEGIES}
for jname, mapping in [("components", comp), ("representation", rep), ("datascale", frac), ("matching", match)]:
rows = json.load(open("%s/norman_ablation_%s.json" % (RES, jname)))["rows"]
for row, folder in mapping.items():
if row not in rows:
continue
m = load("%s/%s" % (A, folder), data)
r = _fwd_inv(data, m, "perturbation")
cmp("ablation %s[%s] forward" % (jname, row), r["forward"], rows[row]["forward"])
cmp("ablation %s[%s] inverse" % (jname, row), r["inverse"], rows[row]["inverse"])
# ---- summary ----
npass = sum(1 for _, ok, _ in checks if ok)
print()
for label, ok, diff in checks:
print(("PASS " if ok else "FAIL ") + label + ("" if ok else " mismatch=%s" % diff))
print("\n%d/%d checks reproduced within tol=%g" % (npass, len(checks), TOL))
print("RESULT:", "ALL REPRODUCED - no fakes" if npass == len(checks) else "MISMATCHES FOUND")