Buckets:
| """cwm3/eval/report_gaze_chain.py — results/tables/gaze_chain.md from results/paired_compare.csv (tags gaze_* / gazeon_*) and results/gaze_chain/gaze_gold.csv | |
| (tags gazegold_*); gaze/robustness agent 2026-09-12. Verdicts = cwm2/eval/kill_checks.beats over the per-seed paired rows (BEATS / WORSE / NOT_WORSE / | |
| INCONCLUSIVE; 3 seeds). Sections: (a) K4-style driver cells of the +chain / +chain+face U runs vs the survivor on identical rows (+ levels vs k0 / persistence); | |
| (b) gaze_off_on / gaze_off_off @3s vs the current-state rule and persistence (+ face effect); (c) gold clean-test rows: model vs DMS att@3s (comparator) on our | |
| gaze_off_on label; (d) the hand-off columns for the warning head. python eval/report_gaze_chain.py [--seeds "0 1 2"] [--out results/tables/gaze_chain.md] | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| import time | |
| from pathlib import Path | |
| import numpy as np | |
| import pandas as pd | |
| CWM3 = Path(__file__).resolve().parents[1] | |
| sys.path.insert(0, str(CWM3.parent / "cwm2" / "eval")) | |
| import kill_checks as KC # noqa: E402 | |
| DRV = ("hand_leave", "hand_return", "head_turn_onset", "head_return") | |
| GZ = ("gaze_off_on", "gaze_off_off") | |
| def beats(g): | |
| pr = pd.DataFrame({"seed": g.seed, "delta": g.delta.astype(float), "delta_ci_lo": g.delta_ci_lo.astype(float), "delta_ci_hi": g.delta_ci_hi.astype(float), "status": "secondary"}) | |
| return KC.beats(pr, pd.DataFrame({"value": g.value_a.astype(float)}), pd.DataFrame({"value": g.value_b.astype(float)}), min_seeds=3)["verdict"] | |
| def ms(v): | |
| v = np.asarray([x for x in v if x is not None and np.isfinite(x)], float) | |
| return "—" if not len(v) else (f"{v.mean():.3f} ± {v.std(ddof=1):.3f}" if len(v) > 1 else f"{v[0]:.3f}") | |
| def dcell(g, seeds): | |
| by = {int(s): r for s, r in zip(g.seed, g.itertuples())} | |
| return [f"{by[s].delta:+.3f} [{by[s].delta_ci_lo:+.3f},{by[s].delta_ci_hi:+.3f}]" if s in by else "—" for s in seeds] | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--seeds", default="0 1 2") | |
| ap.add_argument("--out", default=str(CWM3 / "results" / "tables" / "gaze_chain.md")) | |
| a = ap.parse_args() | |
| seeds = [int(s) for s in a.seeds.replace(",", " ").split()] | |
| d = pd.read_csv(CWM3 / "results" / "paired_compare.csv", low_memory=False) | |
| d = d[d.tag.str.startswith(("gaze_", "gazeon_"))].copy() | |
| d["seed"] = d.tag.str.extract(r"_s(\d+)_")[0].astype(int) | |
| d = d.sort_values("run_stamp").groupby(["tag", "regime", "cell", "metric"], as_index=False).tail(1) | |
| for c in ("value_a", "value_b", "delta", "delta_ci_lo", "delta_ci_hi", "n", "n_pos"): | |
| d[c] = pd.to_numeric(d[c], errors="coerce") | |
| gold_p = CWM3 / "results" / "gaze_chain" / "gaze_gold.csv" | |
| gold = pd.read_csv(gold_p) if gold_p.exists() else pd.DataFrame() | |
| if len(gold): | |
| gold = gold.sort_values("run_stamp").groupby(["tag", "regime", "column", "comparator", "metric"], as_index=False).tail(1) | |
| stats = json.loads((CWM3 / "results" / "targets_gaze_causal_routes_v2" / "build_stats_latest.json").read_text()) | |
| thr = stats["thresholds"] | |
| L = [f"# gaze_off as a driver target chain — U0 `+chain` (`_gz`) and `+chain+face` (`_gzfc`) vs the survivor recipe (seeds {seeds}, test, v2 causal targets; rendered {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())})", "", | |
| f"State: `gaze_off` = hysteresis on |lm_yaw_dev| of OUR RTMW HALPE-136 face landmarks (causal per-route reference; on / off = TRAIN moving-frame P90 / P75 = {thr['gaze_off_on']:.4f} / {thr['gaze_off_off']:.4f}), " | |
| f"re-derived on the 10 Hz route grid by `data/build_gaze_targets.py` (0 mismatches vs the face pack on {sum(v['n_checked_vs_face_F0'] for v in stats['splits'].values()):,} anchors) -> " | |
| f"`results/targets_gaze_causal_routes_v2/gaze_<regime>_<split>.npz` (S_g / S0_g / E_g / EM_g; s12 M censoring; uid/t asserted vs `kin_causal_*`). " | |
| f"Base rates (3 s, inactive anchors): manual test gaze_off_on {stats['splits']['manual_test']['events']['gaze_off_on']['base_rate@3s']:.3f} ({stats['splits']['manual_test']['events']['gaze_off_on']['pos@3s']:,}/{stats['splits']['manual_test']['events']['gaze_off_on']['n@3s']:,}), " | |
| f"engaged test {stats['splits']['engaged_test']['events']['gaze_off_on']['base_rate@3s']:.3f} ({stats['splits']['engaged_test']['events']['gaze_off_on']['pos@3s']:,}/{stats['splits']['engaged_test']['events']['gaze_off_on']['n@3s']:,}).", | |
| "Runs: `U0_kin_can_noK_s<k>_kcv2_clip8_ln_gz` (recipe `u_gaze_chain`: DriverBranch `gaze_head` -> a third 2-state chain, NLL `chain_gaze_off` in the U loss (lambda G = 1) and in the selection objective) and " | |
| "`..._gzfc` (+ `u_face`: the 9 causal `lm_*` channels + valid bit appended to every GRU input step); otherwise the frozen U recipe (causal v2 inputs, stats v2, kclip 8, input LN, chains only). " | |
| "Every row: identical rows of A and B, device-cluster bootstrap (n_boot 1000, seed 0), Δ = A − B; verdict = kill_checks.beats over the 3 seeds.", ""] | |
| # ---- (a) driver cells vs survivor | |
| L += ["## (a) K4-style driver cells (onset PR-AUC@3s, v2 causal targets) — A = gaze variant, B = survivor `U0_kin_can_noK_s<k>_kcv2_clip8_ln` (identical rows)", "", | |
| "Requirement: hand_leave / hand_return / head_turn_onset NOT WORSE (verdict not WORSE).", ""] | |
| for arm, label in (("gz", "+chain"), ("gzfc", "+chain+face")): | |
| L += [f"### {label} (`_{arm}`) vs survivor", "", "| regime | cell | metric | A (mean ± sd) | B (mean ± sd) | " + " | ".join(f"Δ s{s} [CI]" for s in seeds) + " | mean Δ | verdict |", "|---|---|---|---|---|" + "---|" * len(seeds) + "---|---|"] | |
| x = d[d.tag.str.match(rf"gaze_s\d+_{arm}_vs_clip8ln$")] | |
| for reg in ("manual", "engaged"): | |
| for ev in DRV: | |
| for metric in ("pr_auc", "nll"): | |
| cell = f"drv_onset|{ev}|3s" if metric == "pr_auc" else f"hazard_nll|{ev}" | |
| g = x[(x.regime == reg) & (x.cell == cell) & (x.metric == metric)].sort_values("seed") | |
| if not len(g): | |
| continue | |
| v = beats(g) if metric == "pr_auc" else beats(g.assign(delta=-g.delta, delta_ci_lo=-g.delta_ci_hi, delta_ci_hi=-g.delta_ci_lo)) | |
| if metric == "nll": | |
| v = {"BEATS": "BEATS (lower NLL)", "WORSE": "WORSE (higher NLL)"}.get(v, v) | |
| L.append(f"| {reg} | {cell} | {metric} | {ms(g.value_a)} | {ms(g.value_b)} | " + " | ".join(dcell(g, seeds)) + f" | {g.delta.mean():+.4f} | {v} |") | |
| L.append("") | |
| x = d[d.tag.str.match(r"gaze_s\d+_gzfc_vs_gz$")] | |
| if len(x): | |
| L += ["### face-input effect on the driver cells: A = `_gzfc`, B = `_gz`", "", "| regime | cell | A | B | " + " | ".join(f"Δ s{s} [CI]" for s in seeds) + " | verdict |", "|---|---|---|---|" + "---|" * len(seeds) + "---|"] | |
| for reg in ("manual", "engaged"): | |
| for ev in DRV: | |
| g = x[(x.regime == reg) & (x.cell == f"drv_onset|{ev}|3s") & (x.metric == "pr_auc")].sort_values("seed") | |
| if len(g): | |
| L.append(f"| {reg} | drv_onset/{ev}/3s | {ms(g.value_a)} | {ms(g.value_b)} | " + " | ".join(dcell(g, seeds)) + f" | {beats(g)} |") | |
| L.append("") | |
| # levels vs k0 / persistence | |
| L += ["### Levels vs the label-free references (k0 heuristic from the v2 causal K0; persistence = base rate)", "", "| regime | event | survivor (k4_v2_summary) | +chain | +chain+face | k0 | persistence | +chain vs k0 | +chain+face vs k0 |", "|---|---|---|---|---|---|---|---|---|"] | |
| k4 = pd.read_csv(CWM3 / "results" / "paired_compare.csv", low_memory=False) | |
| k4 = k4[k4.tag.str.match(r"K4v2c_s\d+_clip8ln_vs_k0$")].copy() | |
| k4["seed"] = k4.tag.str.extract(r"_s(\d+)_")[0].astype(int) | |
| k4 = k4.sort_values("run_stamp").groupby(["tag", "regime", "cell", "metric"], as_index=False).tail(1) | |
| for reg in ("manual", "engaged"): | |
| for ev in ("hand_leave", "hand_return", "head_turn_onset"): | |
| cell = f"drv_onset|{ev}|3s" | |
| gz_ = d[d.tag.str.match(r"gaze_s\d+_gz_vs_k0$") & (d.regime == reg) & (d.cell == cell) & (d.metric == "pr_auc")].sort_values("seed") | |
| fc_ = d[d.tag.str.match(r"gaze_s\d+_gzfc_vs_k0$") & (d.regime == reg) & (d.cell == cell) & (d.metric == "pr_auc")].sort_values("seed") | |
| sv_ = k4[(k4.regime == reg) & (k4.cell == cell) & (k4.metric == "pr_auc")] | |
| pr_ = d[d.tag.str.match(r"gaze_s\d+_gz_vs_k0$") & (d.regime == reg) & (d.cell == cell) & (d.metric == "base_rate")] | |
| if not len(gz_): | |
| continue | |
| L.append(f"| {reg} | {ev} | {ms(pd.to_numeric(sv_.value_a, errors='coerce'))} | {ms(gz_.value_a)} | {ms(fc_.value_a)} | {ms(gz_.value_b)} | {ms(pd.to_numeric(pr_.value_a, errors='coerce'))} | {beats(gz_) if len(gz_) >= 3 else '—'} | {beats(fc_) if len(fc_) >= 3 else '—'} |") | |
| L.append("") | |
| # ---- (b) gaze onset vs rule / persistence | |
| L += ["## (b) The new chain: gaze_off_on@3s (and gaze_off_off@3s) — model vs the current-state rule |lm_yaw_dev|(t) and vs persistence (test grid, first-passage populations, identical rows)", "", | |
| "Rule = the continuous quantity behind the hysteresis at the anchor (higher = closer to the on threshold; sign-flipped for the off event) — the gaze analogue of the k0 heuristic. Persistence = constant score (PR-AUC = base rate).", "", | |
| "| regime | event | arm | metric | model (mean ± sd) | rule | persistence (base rate) | " + " | ".join(f"Δ model−rule s{s} [CI]" for s in seeds) + " | verdict vs rule |", "|---|---|---|---|---|---|---|" + "---|" * len(seeds) + "---|"] | |
| for reg in ("manual", "engaged"): | |
| for ev in GZ: | |
| cell = f"gaze_onset|{ev}|3s" | |
| pr_ = d[d.tag.str.match(r"gazeon_s\d+_gz_persist$") & (d.regime == reg) & (d.cell == cell) & (d.metric == "base_rate")] | |
| for arm in ("gz", "gzfc"): | |
| for metric in ("pr_auc", "auroc"): | |
| g = d[d.tag.str.match(rf"gazeon_s\d+_{arm}_vs_rule$") & (d.regime == reg) & (d.cell == cell) & (d.metric == metric)].sort_values("seed") | |
| if not len(g): | |
| continue | |
| L.append(f"| {reg} | {ev} | {arm} | {metric} | {ms(g.value_a)} | {ms(g.value_b)} | {ms(pd.to_numeric(pr_.value_a, errors='coerce')) if metric == 'pr_auc' else '0.5'} | " + " | ".join(dcell(g, seeds)) + f" | {beats(g) if len(g) >= 3 else '—'} |") | |
| L.append("") | |
| x = d[d.tag.str.match(r"gazeon_s\d+_gzfc_vs_gz$")] | |
| if len(x): | |
| L += ["### face-input effect on the gaze chain: A = `_gzfc`, B = `_gz`", "", "| regime | event | metric | A | B | " + " | ".join(f"Δ s{s} [CI]" for s in seeds) + " | verdict |", "|---|---|---|---|---|" + "---|" * len(seeds) + "---|"] | |
| for reg in ("manual", "engaged"): | |
| for ev in GZ: | |
| for metric in ("pr_auc", "auroc"): | |
| g = x[(x.regime == reg) & (x.cell == f"gaze_onset|{ev}|3s") & (x.metric == metric)].sort_values("seed") | |
| if len(g): | |
| L.append(f"| {reg} | {ev} | {metric} | {ms(g.value_a)} | {ms(g.value_b)} | " + " | ".join(dcell(g, seeds)) + f" | {beats(g) if len(g) >= 3 else '—'} |") | |
| L.append("") | |
| # ---- (c) gold rows vs DMS | |
| L += ["## (c) Gold clean-test rows (cwm2/eval/warning_gold.py anchors, legacy_first, seed-matched legacy CSV): LABEL = our gaze_off_on onset within 3 s (anchor inactive); model vs the DMS `att@3s` column (COMPARATOR only) vs the rule vs persistence", ""] | |
| if len(gold): | |
| n_note = gold.groupby(["seed", "regime"]).agg(n=("n", "first"), n_pos=("n_pos", "first"), n_dev=("n_dev", "first")).reset_index() | |
| L += ["Rows used per seed / stratum (rows whose anchor is inactive & valid for the gaze chain and carry every score): " + "; ".join(f"s{int(r.seed)} {r.regime} n={int(r.n)} pos={int(r.n_pos)} dev={int(r.n_dev)}" for r in n_note.itertuples()), "", | |
| "| stratum | arm | column | comparator | metric | value (mean ± sd over seeds) | comparator value | " + " | ".join(f"Δ s{s} [CI]" for s in seeds) + " | verdict |", "|---|---|---|---|---|---|---|" + "---|" * len(seeds) + "---|"] | |
| gold["arm"] = gold.tag.str.extract(r"gazegold_s\d+_(\w+)$")[0] | |
| for strat in ("pooled", "manual", "engaged"): | |
| for arm in ("gz", "gzfc"): | |
| for colkey, coln in (("model", "model_gaze_off_onset_p@3s"), ("dms", "dms_att@3s"), ("rule", "rule_absyawdev_t")): | |
| for comp in ("dms_att@3s", "rule_absyawdev_t", "persistence"): | |
| for metric in ("auroc", "pr_auc"): | |
| g = gold[(gold.regime == strat) & (gold.arm == arm) & (gold.comparator == comp) & (gold.metric == metric) & (gold["column"].str.endswith(coln))].sort_values("seed") | |
| if not len(g) or (colkey != "model" and arm != "gz"): | |
| continue | |
| gg = g.rename(columns={"value": "value_a"}) | |
| v = beats(gg) if len(gg) >= 3 else "—" | |
| L.append(f"| {strat} | {arm if colkey == 'model' else '—'} | {coln} | {comp} | {metric} | {ms(g.value)} | {ms(g.value_b)} | " + " | ".join(dcell(gg, seeds)) + f" | {v} |") | |
| L.append("") | |
| else: | |
| L += ["(no gold rows yet)", ""] | |
| # ---- (d) hand-off | |
| L += ["## (d) Hand-off to the warning head", "", | |
| "Every `U0_..._gz*` layout (`pred_layout_<regime>_<split>.npz`, test + val) carries EXTRA keys beside the unchanged 14-column contract: `gaze_off_onset_p[N,4]` " | |
| "(P(first 0->1 within tau | z0), taus (5, 10, 15, 25) in `gaze_off_taus`), `gaze_off_pi[N,25]` (forward marginal P(gaze_off = 1 at t+k)), `gaze_off_h_on` / `gaze_off_h_off[N,25]`, " | |
| "`gaze_off_z0[N]` (state at t) and `gaze_off_z0valid[N]`; NaN where the branch did not predict (no causal driver box) or the anchor state is undefined. " | |
| "Meta key `gaze_block` documents the layout; `roi_streams` gains `Sg` (+ `Fh` for `_gzfc`). A warning head reads them exactly like the `p_*` road-pressure keys " | |
| "(train/train_warning_head.py `--extra-features`); the joint model (`models/cwm3.py`) is untouched, so the columns come from the U0 base runs, not from `I1_*_final`. " | |
| "The warning head was NOT retrained here.", ""] | |
| Path(a.out).write_text("\n".join(L) + "\n") | |
| print(f"written {a.out} ({len(d)} paired rows, {len(gold)} gold rows)") | |
| if __name__ == "__main__": | |
| main() | |
Xet Storage Details
- Size:
- 14.6 kB
- Xet hash:
- 805ab94178716f5ce820196ecdca2e0ed5bc61bab03ca8961423debe48f888f6
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.