AsianPlayer/TriDrive / code /eval /report_final_gzfc.py
AsianPlayer's picture
download
raw
13 kB
"""cwm3/eval/report_final_gzfc.py — results/tables/stage_final_gzfc.md: the gaze-chain APPENDIX variant of the joint model (2026-09-12; rule pre-registered in
docs/decisions.md BEFORE training) from results/paired_compare.csv. Composes eval/report_joint.py blocks (load / render / verdict / claim / provenance); nothing
is recomputed here; every number comes from the CSV rows written by eval/run_joint_evals_final_gzfc.sh.
python eval/report_final_gzfc.py [--seeds 0,1,2] [--out results/tables/stage_final_gzfc.md]
Blocks (tags): finalc_s<k>_allminusgzfc_vs_allminus (a) variant vs the FROZEN main model — the pre-registered comparison
finalc_s<k>_allminusgzfc_vs_indepgzfc (b) interaction effect on the gzfc bases (B2-style, descriptive)
finalc_s<k>_indepgzfc_vs_indep the U-base swap alone (assembled bases, no training)
finalcm_* masked protocol (64 v2 driver-box-suspect routes censored) — sensitivity
finalc/finalcm_s<k>_{allminusgzfc,indepgzfc}_vs_k0 driver cells vs k0 / persistence (B1 on the variant)
finalP_s<k>_allminusgzfc_vs_{allminus,indepgzfc} P cells (eval/pressure_eval.py)
gazec_s<k>_allminusgzfc_vs_indepgzfc gaze_off_on / gaze_off_off onset cells (eval/gaze_chain_eval.py)
PRE-REGISTERED RULE: the variant is reported as the appendix row "main + gaze chain (+face)" iff in (a) NO own endpoint (report_joint.OWN: veh / drv / D onset
PR-AUC@3s, both regimes) is WORSE (kill_checks.beats) AND drv_onset|head_turn_onset|3s BEATS in >= 1 regime; otherwise NEGATIVE RESULT. Never replaces the headline.
Also writes results/tables/stage_final_gzfc_decision.json.
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
import numpy as np
import pandas as pd
CWM3 = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(CWM3 / "eval"))
import report_joint as RJ # noqa: E402
HT = "drv_onset|head_turn_onset|3s"
EV = ("hand_leave", "hand_return", "head_turn_onset")
def block(stage, a, b, claim, title, seeds):
df = RJ.load(stage, a, b, seeds)
if not len(df):
return df, [f"### {title}: A = {a}, B = {b} — no rows yet (tags `{stage}_s<k>_{a}_vs_{b}`)", ""], {}, ("NO ROWS", [], [], [])
body, verdicts = RJ.render(df, sorted(df.seed.unique()), a, b, title)
return df, body, verdicts, RJ.claim(verdicts, claim)
def ms(v):
v = [float(x) for x in v if str(x) not in ("", "nan")]
return "—" if not v else (f"{np.mean(v):.3f} ± {np.std(v, ddof=1):.3f}" if len(v) > 1 else f"{v[0]:.3f}")
def fmt(ks):
return [f"{r}|{c}" + (f"|{m}" if m != "pr_auc" else "") for (r, c, m) in ks] or "none"
def own_table(verdicts):
"""every own endpoint of a block: mean Δ and verdict (the rule's inputs, listed explicitly)."""
lines = ["| regime | own endpoint | mean Δ | verdict |", "|---|---|---|---|"]
for (r, c, m), v in sorted(verdicts.items()):
if c in RJ.OWN and m == "pr_auc":
lines.append(f"| {r} | {c.replace('|', '/')} | {v['mean_delta']:+.4f} | {v['verdict']} |")
return lines
def b1_lines(seeds, dfs):
out, js = [], {}
for proto, stage in (("all rows", "finalc"), ("suspect routes masked", "finalcm")):
out += [f"### B1 on the variant — driver onset PR-AUC@3s (v2 causal targets) vs the k0 heuristic / persistence, protocol: {proto}", "",
"| variant | regime | event | model | k0 | persistence (base rate) | Δ per seed [CI] | verdict |", "|---|---|---|---|---|---|---|---|"]
for var in ("indepgzfc", "allminusgzfc"):
if proto != "all rows" and var == "indepgzfc":
continue
df = RJ.load(stage, var, "k0", seeds)
dfs.append(df)
if not len(df):
out.append(f"| {var} | — | — | no rows yet (`{stage}_s<k>_{var}_vs_k0`) | | | | |")
continue
verd = {}
for reg in ("manual", "engaged"):
for ev in EV:
cell = f"drv_onset|{ev}|3s"
g = df[(df.regime == reg) & (df.cell == cell) & (df.metric == "pr_auc")].sort_values("seed")
base = df[(df.regime == reg) & (df.cell == cell) & (df.metric == "base_rate")]
if not len(g):
continue
res = RJ.verdict(g)
verd[(reg, cell, "pr_auc")] = res
cells = " / ".join(f"{float(r.delta):+.3f} [{float(r.delta_ci_lo):+.3f},{float(r.delta_ci_hi):+.3f}]" for r in g.itertuples())
out.append(f"| {var} | {reg} | {ev} | {ms(g.value_a)} | {ms(g.value_b)} | {ms(base.value_a) if len(base) else '—'} | {cells} | {res['verdict']} |")
stb, _, _, _ = RJ.claim(verd, "B1")
n_beats = sum(v["verdict"] == "BEATS" for v in verd.values())
js[f"{var}|{proto}"] = {"verdict": stb, "beats": n_beats, "cells": {f"{r}|{c}": v["verdict"] for (r, c, _), v in verd.items()}}
out.append(f"| {var} | **B1 ({proto})** | | | | | {n_beats}/6 cells BEATS k0; rule (hand_leave AND head_turn_onset BEATS in both regimes) | **{stb}** |")
out.append("")
return out, js
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--seeds", default="0,1,2")
ap.add_argument("--out", default=str(CWM3 / "results" / "tables" / "stage_final_gzfc.md"))
a = ap.parse_args()
seeds = [int(s) for s in a.seeds.split(",")]
dfs = []
# (a) the pre-registered comparison
dA, bodyA, vA, (stA, worseA, bcA, boA) = block("finalc", "allminusgzfc", "allminus", "B2", "(a) variant vs the FROZEN main model (v2 causal kin targets)", seeds)
dB, bodyB, vB, (stB, worseB, bcB, boB) = block("finalc", "allminusgzfc", "indepgzfc", "B2", "(b) interaction effect on the gzfc bases: all_minus_road2drv_gzfc vs indep_gzfc (B2-style)", seeds)
dC, bodyC, vC, (stC, worseC, bcC, boC) = block("finalc", "indepgzfc", "indep", "B2", "U-base swap alone: indep_gzfc vs indep (assembled bases, no training; descriptive)", seeds)
dAm, bodyAm, vAm, (stAm, worseAm, bcAm, boAm) = block("finalcm", "allminusgzfc", "allminus", "B2", "Masked protocol (64 v2 suspect routes censored): variant vs FROZEN main", seeds)
dBm, bodyBm, vBm, (stBm, worseBm, bcBm, boBm) = block("finalcm", "allminusgzfc", "indepgzfc", "B2", "Masked protocol: all_minus_road2drv_gzfc vs indep_gzfc", seeds)
dP1, bodyP1, vP1, _ = block("finalP", "allminusgzfc", "allminus", "B3", "P cells (road-pressure onset PR-AUC@3s, eval/pressure_eval.py): variant vs FROZEN main", seeds)
dP2, bodyP2, vP2, _ = block("finalP", "allminusgzfc", "indepgzfc", "B3", "P cells: all_minus_road2drv_gzfc vs indep_gzfc", seeds)
dG, bodyG, vG, _ = block("gazec", "allminusgzfc", "indepgzfc", "B3", "gaze_off onset cells (eval/gaze_chain_eval.py, identical rows): all_minus_road2drv_gzfc vs indep_gzfc", seeds)
dfs += [dA, dB, dC, dAm, dBm, dP1, dP2, dG]
b1, b1_json = b1_lines(seeds, dfs)
# ---- the pre-registered rule
ht = {r: vA.get((r, HT, "pr_auc"), {}).get("verdict", "NO ROWS") for r in ("manual", "engaged")}
ht_beats = [r for r, v in ht.items() if v == "BEATS"]
insuff = any(v["verdict"] == "INSUFFICIENT" for (r, c, m), v in vA.items() if c in RJ.OWN and m == "pr_auc")
n_seeds = int(dA.seed.nunique()) if len(dA) else 0
if not len(dA):
decision, dtxt = "NOT DECIDABLE", "no finalc_s<k>_allminusgzfc_vs_allminus rows yet"
elif n_seeds < 3 or insuff:
decision, dtxt = "INSUFFICIENT", f"{n_seeds}/3 seeds paired (rule needs 3)"
elif not worseA and ht_beats:
decision, dtxt = "APPENDIX ROW: main + gaze chain (+face)", f"no own endpoint WORSE vs the frozen main; head_turn_onset BEATS in {ht_beats}"
else:
decision, dtxt = "NEGATIVE RESULT", (f"own endpoint(s) WORSE vs the frozen main: {fmt(worseA)}; " if worseA else "no own endpoint WORSE; ") + \
f"head_turn_onset verdicts manual {ht['manual']} / engaged {ht['engaged']} (needs BEATS in >= 1 regime)"
allrows = pd.concat([d for d in dfs if len(d)], ignore_index=True) if any(len(d) for d in dfs) else pd.DataFrame(columns=["a", "b", "regime", "run_stamp"])
prov, stale = RJ.provenance(allrows) if len(allrows) else ([], [])
void = f" — **VOID: stale rows for {stale}**" if stale else ""
L = ["# Stage FINAL — gaze-chain APPENDIX variant: the frozen joint recipe on the gaze U base (`U0_kin_can_noK_s<k>_kcv2_clip8_ln_gzfc`: gaze_off chain + causal face inputs), 3 seeds, test split, both regimes", "",
"Runs `I1_{indep,all_minus_road2drv}_s<k>_final_gzfc` = the FINAL command line with only the U base swapped (V0 `can4s_stats2`, E0 `pooled_P_v2`, seeds, epochs and the "
"selection rule unchanged; the gaze_off chain NLL enters the U loss and the selection objective as in the U base). The frozen main model `I1_all_minus_road2drv_s<k>_final` "
"and `I1_indep_s<k>_final` are read as B layouts only. Paired device-cluster bootstrap on identical rows (eval/paired_compare_joint.py, n_boot 1000; verdict = "
"kill_checks.beats: ≥ 2/3 seeds CI excl. 0, same sign, |mean Δ| > seed sd); driver populations from the v2 causal kin targets (`finalc_*`), masked protocol `finalcm_*`; "
"P cells by eval/pressure_eval.py (`finalP_*`), gaze cells by eval/gaze_chain_eval.py (`gazec_*`). Identity of the assembled `indep_gzfc` with its bases: "
"`results/tables/identity_final_gzfc.md`. Every number comes from `results/paired_compare.csv`.", "",
"**Pre-registered rule (docs/decisions.md, 2026-09-12, before training):** appendix row \"main + gaze chain (+face)\" iff in (a) NO own endpoint is WORSE vs the FROZEN main "
"AND head_turn_onset BEATS in ≥ 1 regime; otherwise a negative result. The variant never replaces the headline model.", "",
f"## VERDICT: **{decision}**{void}", "", f"- {dtxt}", f"- own endpoints WORSE vs frozen main: {fmt(worseA)}", f"- own endpoints BEATS vs frozen main: {fmt(boA)}",
f"- cross-leg endpoints BEATS vs frozen main: {fmt(bcA)}", f"- head_turn_onset verdicts (a): manual {ht['manual']}, engaged {ht['engaged']}",
f"- masked-protocol sensitivity (a): own WORSE {fmt(worseAm)}; head_turn_onset manual {vAm.get(('manual', HT, 'pr_auc'), {}).get('verdict', 'NO ROWS')} / engaged {vAm.get(('engaged', HT, 'pr_auc'), {}).get('verdict', 'NO ROWS')}", "",
"## Provenance of the A / B layouts (guard 2): layout mtime, source hash, model_id / code hash, completeness; rows older than the layout are STALE", "",
"| run | regime | layout mtime (UTC) | source sha256 | model_id / code | status |", "|---|---|---|---|---|---|", *prov, "",
*([f"**STALE ROWS DETECTED for {stale} — every verdict is VOID; recompute (eval/run_joint_evals_final_gzfc.sh).**", ""] if stale else []),
"## 1. (a) Variant vs the FROZEN main model — the pre-registered comparison", ""] + bodyA + ["", "### Own endpoints of (a) (the rule's inputs)", ""] + own_table(vA) + \
["", f"B2-style status of (a) for reference (needs a cross-leg BEATS): **{stA}**", "",
"## 2. (b) Interaction effect on the gzfc bases", ""] + bodyB + ["", f"B2-style verdict (b): **{stB}** — own WORSE {fmt(worseB)}; cross BEATS {fmt(bcB)}; own BEATS {fmt(boB)}", "",
"## 3. Attribution — the U-base swap alone (indep_gzfc vs indep, no training)", ""] + bodyC + ["", f"B2-style status: **{stC}** — own WORSE {fmt(worseC)}; own BEATS {fmt(boC)}; cross BEATS {fmt(bcC)}", "",
"## 4. Masked protocol (sensitivity to the 64 suspect routes)", ""] + bodyAm + [""] + bodyBm + ["",
"## 5. P cells (road-pressure chains, detached head)", ""] + bodyP1 + [""] + bodyP2 + ["",
"## 6. Gaze cells (gaze_off_on / gaze_off_off onset; descriptive)", ""] + bodyG + ["",
"## 7. B1 on the variant (driver cells vs k0 / persistence)", ""] + b1
out = Path(a.out); out.parent.mkdir(parents=True, exist_ok=True)
out.write_text("\n".join(L) + "\n")
dec = {"decision": decision, "detail": dtxt, "worse_own_vs_frozen_main": fmt(worseA), "beats_own_vs_frozen_main": fmt(boA), "beats_cross_vs_frozen_main": fmt(bcA),
"head_turn_onset": ht, "masked_worse_own": fmt(worseAm), "B2_style_a": stA, "B2_style_b": stB, "B1": b1_json, "stale": stale, "seeds": seeds, "n_seeds_paired_a": n_seeds,
"own_endpoints_a": {f"{r}|{c}": {"mean_delta": v["mean_delta"], "verdict": v["verdict"]} for (r, c, m), v in vA.items() if c in RJ.OWN and m == "pr_auc"}}
(out.parent / "stage_final_gzfc_decision.json").write_text(json.dumps(dec, indent=1, default=str))
print("\n".join(L[:12]))
print(f"\nVERDICT {decision}: {dtxt}\nwritten {out} and {out.parent / 'stage_final_gzfc_decision.json'}")
if __name__ == "__main__":
main()

Xet Storage Details

Size:
13 kB
·
Xet hash:
5bed069a2ead795266533c8000bc13830b69854efb9a17f025e490ca08349f97

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.