umm / scripts /compare_cvbench_results.py
Orangerl's picture
Update Stage-2 code, evaluations, handoff, and deployment skill
bca45b0 verified
Raw History Blame Contribute Delete
4.66 kB
#!/usr/bin/env python3
"""Compare multiple audited CV-Bench runs at the paired sample level."""
from __future__ import annotations
import argparse
import json
import math
from itertools import combinations
from pathlib import Path
def _json(path: Path) -> dict:
return json.loads(path.read_text(encoding="utf-8"))
def _jsonl_by_id(path: Path) -> dict[str, dict]:
rows = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line]
result = {row["sample_id"]: row for row in rows}
if len(result) != len(rows):
raise AssertionError(f"Duplicate sample IDs: {path}")
return result
def _mcnemar_exact_p(improved: int, regressed: int) -> float:
discordant = improved + regressed
if discordant == 0:
return 1.0
tail = sum(math.comb(discordant, k) for k in range(min(improved, regressed) + 1))
return min(1.0, 2.0 * tail / (2**discordant))
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--eval-root", type=Path, required=True)
parser.add_argument("--experiment", default="stage2_covt_sam_vggt_covt4_v2")
parser.add_argument("--steps", type=int, nargs="+", required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
runs: dict[int, dict] = {}
results: dict[int, dict[str, dict]] = {}
for step in args.steps:
directory = args.eval_root / f"{args.experiment}-checkpoint-{step}"
validation = _json(directory / "validation_report.json")
if validation.get("assessment") != "ready_to_share":
raise AssertionError(f"Run has not passed validation: checkpoint-{step}")
metrics = _json(directory / "metrics.json")["metrics"]
swanlab = _json(directory / "swanlab.json")
results[step] = _jsonl_by_id(directory / "results.jsonl")
runs[step] = {
"correct": int(validation["correct_total"]),
"overall_accuracy": metrics["eval/cvbench_accuracy"],
"task_macro_accuracy": metrics["eval/cvbench_task_macro_accuracy"],
"2d_accuracy": metrics["eval/cvbench_2d_accuracy"],
"2d_count_accuracy": metrics["eval/cvbench_2d_count_accuracy"],
"2d_relation_accuracy": metrics["eval/cvbench_2d_relation_accuracy"],
"3d_accuracy": metrics["eval/cvbench_3d_accuracy"],
"3d_depth_accuracy": metrics["eval/cvbench_3d_depth_accuracy"],
"3d_distance_accuracy": metrics["eval/cvbench_3d_distance_accuracy"],
"parse_rate": metrics["eval/cvbench_parse_rate"],
"errors": int(metrics["eval/cvbench_errors"]),
"swanlab_run_id": swanlab["run_id"],
"swanlab_run_url": swanlab["run_url"],
}
reference_ids = set(results[args.steps[0]])
if len(reference_ids) != 500 or any(set(results[step]) != reference_ids for step in args.steps):
raise AssertionError("Checkpoint runs do not cover the same 500 samples")
pairwise = []
for earlier, later in combinations(args.steps, 2):
improved = sum(
not results[earlier][sample_id]["correct"] and results[later][sample_id]["correct"]
for sample_id in reference_ids
)
regressed = sum(
results[earlier][sample_id]["correct"] and not results[later][sample_id]["correct"]
for sample_id in reference_ids
)
pairwise.append(
{
"earlier_step": earlier,
"later_step": later,
"accuracy_delta_percentage_points": 100
* (runs[later]["overall_accuracy"] - runs[earlier]["overall_accuracy"]),
"improved_samples": improved,
"regressed_samples": regressed,
"net_correct_delta": improved - regressed,
"mcnemar_exact_p": _mcnemar_exact_p(improved, regressed),
}
)
payload = {
"assessment": "ready_to_share_with_subset_and_sample_size_caveat",
"population": "fixed CV-Bench-500: 250 2D + 250 3D",
"scoring": "greedy option exact-match; no LLM judge",
"runs": {str(step): runs[step] for step in args.steps},
"pairwise": pairwise,
"best_overall_step": max(args.steps, key=lambda step: runs[step]["overall_accuracy"]),
}
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(
json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
print(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True))
if __name__ == "__main__":
main()