Download scripts/analyze_localization_transition_decomp.py from Dhruv1000/tracegrpo: direct link, hf CLI and curl.
- Browser
- Download file 6.01 kB
-
https://huggingface.co/Dhruv1000/tracegrpo/resolve/main/scripts/analyze_localization_transition_decomp.py
- Command line
-
hf download hf://Dhruv1000/tracegrpo/scripts/analyze_localization_transition_decomp.py
-
curl -L -o analyze_localization_transition_decomp.py https://huggingface.co/Dhruv1000/tracegrpo/resolve/main/scripts/analyze_localization_transition_decomp.py
6.01 kB
| #!/usr/bin/env python3 | |
| """Decompose paired VALID IoU changes by SFT-to-GRPO label transition. | |
| Run from the repository root. This is descriptive analysis of the frozen | |
| chronological VALID outputs; it does not access the TEST split. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import hashlib | |
| import json | |
| import random | |
| from collections import defaultdict | |
| from pathlib import Path | |
| from src.rewards.checker import statement_iou | |
| def load_rows(path: Path) -> dict[str, dict]: | |
| rows = {} | |
| with path.open() as handle: | |
| for line in handle: | |
| if not line.strip(): | |
| continue | |
| row = json.loads(line) | |
| sid = row["sample_id"] | |
| if sid in rows: | |
| raise ValueError(f"duplicate sample_id: {sid}") | |
| rows[sid] = row | |
| return rows | |
| def predicted_label(row: dict) -> str: | |
| return row.get("pred_label") if row.get("format_ok") else "INVALID" | |
| def row_iou(row: dict) -> float: | |
| # Match validation evaluation: invalid/unparsed rows contribute zero; | |
| # parsed rows are scored regardless of their predicted class. | |
| if not row.get("format_ok"): | |
| return 0.0 | |
| return statement_iou(set(row.get("pred_lines") or []), | |
| set(row.get("gold_lines") or [])) | |
| def sha256(path: Path) -> str: | |
| h = hashlib.sha256() | |
| with path.open("rb") as handle: | |
| for block in iter(lambda: handle.read(1 << 20), b""): | |
| h.update(block) | |
| return h.hexdigest() | |
| def bootstrap_mean(values: list[float], *, seed: int, reps: int = 20_000) -> list[float] | None: | |
| if not values: | |
| return None | |
| rng = random.Random(seed) | |
| n = len(values) | |
| means = sorted(sum(rng.choice(values) for _ in range(n)) / n | |
| for _ in range(reps)) | |
| return [means[int(0.025 * reps)], means[int(0.975 * reps) - 1]] | |
| def analyze(sft_path: Path, grpo_path: Path, *, seed: int = 20261008) -> dict: | |
| sft, grpo = load_rows(sft_path), load_rows(grpo_path) | |
| if sft.keys() != grpo.keys(): | |
| raise ValueError("SFT and GRPO sample_id sets differ") | |
| grouped: dict[tuple[str, str], list[tuple[float, float]]] = defaultdict(list) | |
| overall_sft, overall_grpo = [], [] | |
| class_parity = True | |
| for sid, a in sft.items(): | |
| b = grpo[sid] | |
| if int(a["target"]) != int(b["target"]): | |
| class_parity = False | |
| raise ValueError(f"target mismatch for {sid}") | |
| if set(a.get("gold_lines") or []) != set(b.get("gold_lines") or []): | |
| raise ValueError(f"gold line mismatch for {sid}") | |
| if int(a["target"]) != 1: | |
| continue | |
| sa, gb = row_iou(a), row_iou(b) | |
| overall_sft.append(sa) | |
| overall_grpo.append(gb) | |
| grouped[(predicted_label(a), predicted_label(b))].append((sa, gb)) | |
| n = len(overall_sft) | |
| transitions = {} | |
| for (sft_label, grpo_label), values in sorted(grouped.items()): | |
| ds = [b - a for a, b in values] | |
| transitions[f"{sft_label}->{grpo_label}"] = { | |
| "n": len(values), | |
| "sft_mean_iou": sum(a for a, _ in values) / len(values), | |
| "grpo_mean_iou": sum(b for _, b in values) / len(values), | |
| "paired_mean_delta": sum(ds) / len(ds), | |
| "contribution_to_overall_mean_delta": sum(ds) / n, | |
| } | |
| both_vulnerable = grouped.get(("VULNERABLE", "VULNERABLE"), []) | |
| common_deltas = [b - a for a, b in both_vulnerable] | |
| overall_deltas = [b - a for a, b in zip(overall_sft, overall_grpo)] | |
| return { | |
| "analysis": "gold-vulnerable VALID localization by paired label transition", | |
| "n_valid_rows_paired": len(sft), | |
| "n_gold_vulnerable": n, | |
| "gold_target_and_lines_parity_verified": class_parity, | |
| "test_touched": False, | |
| "overall": { | |
| "sft_mean_iou": sum(overall_sft) / n, | |
| "grpo_step200_mean_iou": sum(overall_grpo) / n, | |
| "paired_mean_delta": sum(overall_deltas) / n, | |
| "paired_row_bootstrap_95_ci": bootstrap_mean(overall_deltas, seed=seed), | |
| }, | |
| "transitions": transitions, | |
| "both_predicted_vulnerable": { | |
| "n": len(common_deltas), | |
| "sft_mean_iou": transitions.get("VULNERABLE->VULNERABLE", {}).get("sft_mean_iou"), | |
| "grpo_mean_iou": transitions.get("VULNERABLE->VULNERABLE", {}).get("grpo_mean_iou"), | |
| "paired_mean_delta": (sum(common_deltas) / len(common_deltas) | |
| if common_deltas else None), | |
| "paired_row_bootstrap_95_ci": bootstrap_mean(common_deltas, seed=seed + 1), | |
| }, | |
| "bootstrap": {"unit": "gold-vulnerable sample (one per pair)", | |
| "replicates": 20_000, "seed": seed}, | |
| "input_sha256": {str(sft_path): sha256(sft_path), | |
| str(grpo_path): sha256(grpo_path)}, | |
| "interpretation_caveat": ( | |
| "Transitions condition on model predictions and are descriptive, not causal. " | |
| "An overall IoU increase can reflect more vulnerable rows receiving a " | |
| "VULNERABLE prediction and nonempty localization; it does not alone show " | |
| "better localization precision on rows both models detect." | |
| ), | |
| } | |
| def main() -> None: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--sft", type=Path, | |
| default=Path("results/phase4/contract_v2/sft_val_step_500.jsonl")) | |
| parser.add_argument("--grpo", type=Path, | |
| default=Path("results/phase6/scale_20261007_v5/valid_predictions.jsonl")) | |
| parser.add_argument("--output", type=Path, | |
| default=Path("results/phase6/scale_20261007_v5/localization_transition_decomposition_step200.json")) | |
| args = parser.parse_args() | |
| result = analyze(args.sft, args.grpo) | |
| args.output.parent.mkdir(parents=True, exist_ok=True) | |
| args.output.write_text(json.dumps(result, indent=2) + "\n") | |
| print(json.dumps(result, indent=2)) | |
| if __name__ == "__main__": | |
| main() | |