Download scripts/collect_final_eval.py from Anonymous1611/Lattice_Spike: direct link, hf CLI and curl.
- Browser
- Download file 4.55 kB
-
https://huggingface.co/Anonymous1611/Lattice_Spike/resolve/main/scripts/collect_final_eval.py
- Command line
-
hf download hf://Anonymous1611/Lattice_Spike/scripts/collect_final_eval.py
-
curl -L -o collect_final_eval.py https://huggingface.co/Anonymous1611/Lattice_Spike/resolve/main/scripts/collect_final_eval.py
4.55 kB
| #!/usr/bin/env python3 | |
| """Collect a final-eval directory into one mean +/- std table across seeds. | |
| Reads every `*/summary.json` under the run root, pulls the held-out metrics for | |
| each method, and aggregates across seeds. LatticeSpike and the baselines store | |
| their metrics differently -- LatticeSpike as a dict under `eval`, the baseline | |
| runner as a list of per-method dicts -- so both shapes are handled here rather | |
| than at the call site. | |
| Two reporting rules, both because AP is base-rate dependent: | |
| * the random-AP floor (positive prevalence) is printed alongside, since an AP is | |
| meaningless without it and is never comparable across datasets; | |
| * `n_metric_tokens` is printed per method, because a table is only a comparison | |
| if every row scored the same tokens. A mismatch is flagged loudly. | |
| """ | |
| from __future__ import annotations | |
| import glob | |
| import json | |
| import os | |
| import statistics | |
| import sys | |
| def load(root): | |
| """method -> seed -> metrics, from both summary shapes.""" | |
| out = {} | |
| prevalence = {} | |
| for path in sorted(glob.glob(os.path.join(root, "*", "summary.json"))): | |
| name = os.path.basename(os.path.dirname(path)) | |
| seed = name.rsplit("_s", 1)[-1] | |
| try: | |
| summary = json.load(open(path)) | |
| except Exception: | |
| continue | |
| evaluation = summary.get("eval") | |
| rows = [] | |
| if isinstance(evaluation, dict): # LatticeSpike | |
| rows = [dict(evaluation, method="LatticeSpike (ours)")] | |
| elif isinstance(evaluation, list): # baseline runner | |
| rows = evaluation | |
| for row in rows: | |
| method = row.get("method") | |
| if not method: | |
| continue | |
| out.setdefault(method, {})[seed] = row | |
| if row.get("positive_prevalence") is not None: | |
| prevalence[method] = row["positive_prevalence"] | |
| return out, prevalence | |
| def agg(values): | |
| values = [v for v in values if isinstance(v, (int, float))] | |
| if not values: | |
| return None, None | |
| if len(values) == 1: | |
| return values[0], None | |
| return statistics.mean(values), statistics.stdev(values) | |
| def fmt(mean, sd): | |
| if mean is None: | |
| return "--" | |
| return f"{mean:.4f}" + (f" ±{sd:.4f}" if sd is not None else "") | |
| def main(argv): | |
| if len(argv) < 2: | |
| raise SystemExit("usage: collect_final_eval.py <run-root>") | |
| root = argv[1] | |
| data, prevalence = load(root) | |
| if not data: | |
| print("(no summaries found)") | |
| return 0 | |
| supports = { | |
| m: {r.get("n_metric_tokens") for r in seeds.values()} | |
| for m, seeds in data.items() | |
| } | |
| all_supports = {s for values in supports.values() for s in values if s} | |
| ordered = sorted( | |
| data.items(), | |
| key=lambda kv: -(agg([r.get("auprc") for r in kv[1].values()])[0] or 0), | |
| ) | |
| print(f"# Final evaluation — {os.path.basename(root)}\n") | |
| print(f"{'method':<30}{'AP':>18}{'AUROC':>18}{'P':>10}{'R':>10}{'F1':>10}{'seeds':>7}") | |
| print("-" * 103) | |
| for method, seeds in ordered: | |
| rows = list(seeds.values()) | |
| cells = [] | |
| for key in ("auprc", "auroc", "precision", "recall", "f1"): | |
| mean, sd = agg([r.get(key) for r in rows]) | |
| cells.append((mean, sd)) | |
| star = "*" if method.startswith("LatticeSpike") else " " | |
| print( | |
| f"{star}{method:<29}" | |
| f"{fmt(*cells[0]):>18}{fmt(*cells[1]):>18}" | |
| f"{cells[2][0] if cells[2][0] is None else f'{cells[2][0]:.4f}':>10}" | |
| f"{cells[3][0] if cells[3][0] is None else f'{cells[3][0]:.4f}':>10}" | |
| f"{cells[4][0] if cells[4][0] is None else f'{cells[4][0]:.4f}':>10}" | |
| f"{len(rows):>7}" | |
| ) | |
| floor = statistics.median(prevalence.values()) if prevalence else None | |
| print() | |
| if floor is not None: | |
| print(f"random-AP floor (positive prevalence): {floor:.4f}") | |
| if len(all_supports) == 1: | |
| print(f"support: all methods scored {all_supports.pop():,} metric tokens — comparable") | |
| else: | |
| print(f"!! SUPPORT MISMATCH across methods: {sorted(all_supports)}") | |
| print("!! rows scored on different token sets are NOT directly comparable") | |
| seed_counts = {len(s) for s in data.values()} | |
| if len(seed_counts) > 1: | |
| print(f"!! uneven seed counts across methods: {sorted(seed_counts)} — a mean over") | |
| print("!! more seeds is not comparable to a single draw; equalise before reporting") | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main(sys.argv)) | |