feat: run the imaging lane through the run entry point - #260
Conversation
|
Extra check against real data rather than the fixture: pointed the imaging lane at the committed and the run itself refuses rather than writing an empty result: So the skip accounting reads correctly off a real manifest, and a wrong |
sebasmos
left a comment
There was a problem hiding this comment.
Wiring looks right, but the check used a nonexistent image root, so the comparability claim is still unproven. It needs a real NIH-image run posting per-cue flip-above-noise to match experiments/imaging. Requesting changes until then.
Superseded by the later Changes-requested review on this PR; dismissing the stale approve to avoid a mixed signal.
Agastya191
left a comment
There was a problem hiding this comment.
good work on this: runner.py threading the imaging twins through pilot/solo/overlap, the cues/image.py injection wiring, and the cascade refusal in _refuse_imaging_cascade are all clean, and the skip accounting is the right instinct. One problem is that in runner.py _finding does case.label.split("|")[0], so a multi-finding NIH case (nih_cxr14 stores the full pipe-separated Finding Labels, e.g. cardiomegaly|effusion) is only ever asked about its first finding. This drops the remaining findings without an entry in skipped_cases, so the Skipped cases: line reports full coverage the run never had, which is the exact failure the skip accounting was added to prevent. You have multiple options here; for example, I would either loop _finding over every positive label and build a twin set per finding, or, if one-question-per-case is the intended v1 policy, count the dropped findings so the skip report stays honest.
|
@Agastya191 kept the one-question-per-case v1 policy but made the accounting honest: |
sebasmos
left a comment
There was a problem hiding this comment.
The dropped_findings bucket is the right fix, honest accounting beats a silently wrong denominator. Still holding this one behind the priority-3 infra call rather than any objection to the code, and it needs a real API run to count under the real-data rule.
|
@Agastya191 fixed in 47d1133. v1 stays one question per case, but it's honest now: @sebasmos agreed the @maximinl thanks — the dropped-finding accounting is in (47d1133). |
sebasmos
left a comment
There was a problem hiding this comment.
dropped_findings accounting is right (runner.py:316 and :389). Note it is clean against its base but conflicts with main in benchmaxxing/experiments.py; the resolution is keep-both, main's _build_twins = build_twins alias plus your twins=None on run_pilot. Still needs the real NIH-image run, since the --image-root check exercised the failure path rather than a run.
sebasmos
left a comment
There was a problem hiding this comment.
Approving. _positive_findings at runner.py:316 plus the dropped_findings skip bucket give honest accounting for multi-finding NIH cases instead of silently double-counting. I merged current main in; experiments.py needed the union resolution rather than a mechanical one, keeping main's _build_twins alias and progress parameters alongside your twins= path. 997 passed, ruff clean. The real NIH-image run through this path is still outstanding, the --image-root check exercised the failure path rather than a run, so #254 stays open.
* feat: benchmaxxing run entry point for the stage runners
* feat: bootstrap CIs and effect sizes in the run summary
* feat: correct the whole family of p-values in the run report
* feat: declare model lineage in the config instead of inferring it
* feat: noise-floor control and floor-adjusted flip rate
* feat: run the imaging lane through the run entry point
* cascade significance: paired permutation on the adoption gap, not a McNemar sign test
Agastya's review: _cascade_tests ran the continuous shared/isolated adoption rates through
mcnemar by counting only each pair's direction, collapsing it to a sign test that drops ties
and ignores magnitude. Over four discordant cases the smallest two-sided p is 0.125, so a
unanimous planted-answer effect could never be flagged even when the shared/isolated bootstrap
CIs printed right above it are cleanly separated -- the two signals then contradict.
Add stats.paired_permutation_test (seeded sign-flip over the per-case differences, exact by
enumeration for small n, sampled otherwise) and test the same shared-minus-isolated gap the
adoption_delta CI is built on, so the p-value and the interval answer one question. Thread the
run seed through significance_tests for reproducibility.
* imaging lane: count dropped findings on multi-finding cases
Agastya's review: _finding did case.label.split('|')[0], so a multi-finding NIH case
(cardiomegaly|effusion) was only ever asked about its first finding and the rest were
dropped with no entry in skipped_cases -- the 'Skipped cases:' line then reported full
coverage the run never had, the exact failure the skip accounting exists to catch.
v1 is one question per case, so keep that policy but make it honest: _positive_findings
lists the real findings, image_twins asks about the first and adds len(rest) to a new
dropped_findings bucket. The skip line already renders any nonzero bucket.
* lineage: warn when an org-prefixed id is inferred, not just recorded
Agastya's review: the bare-id fallback routes through _lineage, which splits on the first
separator, so an org-namespaced id like 'meta-llama/llama-3.1-70b' resolves to 'meta' rather than
'llama'. A mixed-convention roster then gets a wrong within- vs cross-lineage partition in
run_holes_test and the run completes clean, with lineage_source: inferred in the manifest as the
only trace after the fact.
Keep the heuristic as the documented fallback but make the risky case loud: model_specs warns when
a lineage is inferred (not declared) from an id carrying a '/'. Declaring `lineage` in the config
-- the resolution from #251 -- silences it. Failure is now at run time, not a manifest post-mortem.
* solo CI: resample over cases, not twins
Agastya's review: the solo lane resampled flip_rate/shortcut_reliance over the flat per-twin
record list, but _build_twins emits one twin per (case, cue_type), so a case's several cue-twins
sit in that array and bootstrap_ci drew them iid. Same-case twins share the case's difficulty, so
counting them as independent understates the uncertainty exactly the way pooling models would --
the 95% CI came out tighter than the case-level evidence supports.
Add stats.cluster_bootstrap_ci (draws whole clusters, pools their observations) and resample the
solo lane by case_id, so the interval answers "does this hold on new cases", which is the claim
the arm makes. The point estimate is unchanged and, since every cluster is a singleton for
one-twin-per-case data, the pinned intervals are untouched; only real multi-cue cases widen.
* noise floor: match each re-ask to its record by key, not by position
Agastya's review: noise_floor_pass zipped a freshly rebuilt _twins(...) against the records
run_solo_baselines produced from its own separate _build_twins(...), guarded only by a length
check. The two builders are identical today, but the length check catches a count mismatch and not
a reordering -- if they ever drift (a limit, a filter, a changed skip, a reorder), the floor would
subtract the wrong twin's noise from each flip and the headline floor-adjusted rate would be
quietly wrong with nothing failing.
Records already carry case_id and cue_type, so key the twins by (case_id, cue_type) and look each
record up instead of trusting the two builders to stay byte-for-byte in lockstep. A missing key
raises loudly. per_twin stays aligned with records, so the paired floor-adjusted resampling is
unchanged.
---------
Co-authored-by: sebasmos <sebasticajas@gmail.com>
Summary
benchmaxxing runnow drives Lane A. Setcue_set: image-v1in the config, pass--image-root, and the solo lane (pilot, solo, overlap) runs on chest X-rays the same way the text lane does.cable,corner_tag,watermark,lateralityexperiments/imagingalready established, so the numbers stay comparable with the earlier imaging runsSoloAgentroutes them tocomplete(prompt, image=...)Skipped cases: missing_image=1, no_finding_label=1shows up in the plan and insummary.mdrun_pilotandrun_solo_baselinestake an optionaltwins=so the imaging lane can inject its own pairs instead of the text-cue builders running. Additive, default unchanged.Result
On a generated 4-image fixture (2 readable, 1 missing file, 1 negative label), solo with
--backend mock: 8 twins, flip rate 0.3750 [0.1250, 0.6250], shortcut reliance 0.2500, skips reported. The mock answers the imaging lane from a checksum of the pixels, so it is sensitive to an injected artifact and the offline run is non-degenerate rather than a wall of zeros.ruff check .clean. 688 passed with the image extra installed; 615 passed, 13 skipped without it, so the core suite still runs on a bare install.Notes
The cascade stage refuses the imaging lane on purpose, with a pointer.
experiments._shortcut_answerbuilds the planted answer fromcase.options, which an imaging case does not have, so an imaging cascade would report adoption of an answer no agent could ever give: a confident zero. That needs the seed builder from #104 / #115 first, and I would rather refuse than ship a degenerate arm.Stacked: #251 -> #252 -> #253 -> #257 -> #258 -> this.
Closes #254