Repository navigation
Verifier (#258): candidate set, leakage bound, and the production validator on every candidate - #269
jonfroehlich wants to merge 7 commits into
Conversation
…FP/ignored All 10,504 op_cache peaks (score >= 0.05) on the 14 pinned splits, labelled by the shared matcher in score order, so one label holds at every threshold; asserted against score_pano at 0.05/0.30/0.55. Second hits on a claimed ramp are fp + redundant. --check re-derives the files byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0 of the 14 benchmark splits' panos is in, or within 500 m of, a current CurbRamp label in the validator's 19 training cities (284,494 labels; the training images themselves were never published). docs/verifier_258.md holds step 1, the leakage result, and the step 2 design (serving-path crops). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…serving crop path validator_score_258.py reproduces sidewalk-ai-api @745e0c5 /process: 8192x4096 resize, 90-degree 2048 perspective (copied verbatim), DA-V2 metric vitb/vkitti, crop half int(6100/depth), 640 long side, validator @969ad5a3, and records P(correct). Resumes per pano for klone ckpt preemption. The .slurm verifies every split's imagery manifest before scoring. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… pano bootstrap Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ond only Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…than RampNet's score Klone job 42152480 (one L40S, 0.51 GPU-h, $0) scored all 10,504 candidates on the sidewalk-ai-api serving crop path. Validator P(correct) AUC is below RampNet's own score on 13 of 14 splits (open pool -0.054 [-0.077, -0.033]); only laurens_mapillary is above (+0.055). Below the 0.30 operating point the validator ranks better on the pooled verdict splits (+0.070 open, +0.092 reference) but not on manual_gold (-0.004, crops upsampled 2x). The untuned two-pass rule lowers F1 on 12 of 14 splits and raises laurens_mapillary 0.660 -> 0.739. Scores, read-out, sacct dump, job log and the compute_log row are committed. Vectorized the tie ranks in the read-out (same values, 40 s instead of minutes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jonfroehlich
left a comment
There was a problem hiding this comment.
Review of the step 2 read-out (docs/verifier_258.md, validator_auc_258.py)
The scoring run, the leakage bound and the candidate set are solid and well pinned. The conclusion is drawn on the wrong stratum, and once it is read against the design actually posted on #258 the verdict flips from "stop" to "step 3 as planned". Findings in severity order.
1. The primary AUC answers a question the design did not ask, and the "stop rule met" rests on it
The posted design (#258, comment 6316627866, step 1) defines the candidate set as RampNet peaks with score in [0.05, 0.55), and step 2's stop rule is the AUC on that set. Step 1 here widened the set to every peak ≥ 0.05 ("a two-pass pipeline filters everything above its lowered threshold"). That is a defensible pipeline choice, but it changes the primary metric: over the full range RampNet's own score is being asked to separate its confident TPs from its 0.05-floor FPs, a separation it wins by construction because the candidate set was selected by that same score (incorporation bias: the ranker under test defined the sample). The 13-of-14 "worse" result is mostly that.
The below_op table ([0.05, 0.30), the band a lowered threshold actually adds) is the closest thing in the PR to the pre-registered band, and there the pre-registered stop rule is not met: open pooled +0.070 [0.024, 0.117], reference pooled +0.092 [0.036, 0.148]. The doc labels below_op as the post-hoc deviation and the all-peaks AUC as primary. It is the other way round. Please:
- State in the Step 1 section that widening to all peaks departs from the posted step 1, and why.
- Add the literal pre-registered band, [0.05, 0.55), as a third stratum (it is a one-line change to
below_op; keep the 0.30 one too since the operating point moved to 0.30 after the plan's wording). - Make the pre-registered band the primary row of the Result paragraph, and move the all-peaks AUC to a secondary table with the selection caveat beside it.
- Rewrite the headline: the stop condition is not met on the open pool; it is met on manual_gold and not met on laurens_mapillary; bayonne is flat.
2. Multiplicity around the post-hoc stratum is handled, but say what richmond showed when it was added
The disclosure is good. Add the sentence that makes it checkable: richmond's below_op difference was +0.084 [−0.039, 0.222] (not significant) at commit b8287cc, so the stratum was not added because the first split looked favourable. With 14 splits × 3 variants × 2 subsets, say explicitly that the pooled rows are the inferential ones and the per-split rows are descriptive.
3. The "untuned two-pass rule" is not the rule the design proposed, and the doc reads it as evidence
Step 3 of the posted design says: fuse the two scores, fit on sao_paulo only, report recall at matched precision against the threshold-only control in cascade_cost_35.py. The table here instead hard-gates at the validator's production cut (P > 0.5), which the doc itself notes passes 80–87% of candidates, then concludes the rule "lowers F1 on 12 of 14 splits". That is a straw man and should not sit under a "Secondary" heading as if it were a pre-planned test. Either run step 3 as written (sao_paulo dev, everything else held out; the design already fixed the dev split, so leave-one-split-out is not needed) or move this table to an appendix titled "the production cut as a filter, for reference".
4. The manual_gold 2× upsample confound has a cheap direct test
Both readings of the manual_gold row are left open pending #21. They can be separated now: downsample one GSV verdict split (richmond, native 8192 panos on klone) to 4096, re-run the same .slurm, and compare richmond's below_op AUC native vs upsampled. One L40S job, well under the 30 min the full run took, $0. If upsampling costs richmond the same ~0.08 that separates manual_gold from the verdict pool, reading 2 wins and the manual_gold row stops being the counter-evidence.
5. "Open pool" contains a GSV split
The open pool is taken from open_rig_misses_258.py and includes laurens_gsv, which is GSV. For a rig-transfer question the open pool should be the four non-GSV splits (laurens_mapillary, bayonne, budapest_district5, juneau), with laurens_gsv reported beside laurens_mapillary as the same-town control, as #265 does. Recompute the pooled rows that way; keep the current definition only if it is needed to match #265's tables, and then say so.
6. The gain is laurens_mapillary, not "open rigs"
Within the GoPro Max pair, laurens_mapillary is +0.218 [0.135, 0.309] below the operating point and bayonne is −0.005 [−0.094, 0.082]. #35 also gained only on laurens_mapillary. The write-up should say the evidence is one town, flag that bayonne (the other GoPro Max split, Panoramax) does not show it, and name the candidate explanations (Laurens is where RampNet's own ranking is weakest, 0.847, and where sub-threshold misses dominate per #265).
7. Ledger: the compute row exists twice
The doc says the 42152480 row is in analysis_out/compute_log.jsonl. It is committed in this branch, and the same row is also sitting uncommitted in the main checkout's working tree (D:\Git\RampNet on cluster-review-224), which is where the ledger appends by default. Whoever commits that checkout will duplicate it. Add a note in the PR body that the main-checkout copy is to be discarded, or drop it now.
8. Smaller points
- The AUC bootstrap resamples panos but the two-pass F1 cells carry no interval; add the same paired bootstrap to the F1 differences if the table stays.
- "Status" line and the Result paragraph both say "stops here"; both need the rewrite from finding 1.
- The design's step 2 said "if it does not beat RampNet's score on the open-rig splits, stop". Record in the doc that the run scored all 14 splits rather than the five named, and that nothing was fitted on them, so the held-out splits remain usable for step 3.
- Step 1 caveat "TP labels on sub-threshold peaks are a lower bound" is right; add the consequence for the AUC comparison in one line: an unmarked real ramp is scored high by the validator and low by RampNet, so the bias penalises the validator, which makes the below-op gain conservative.
What I would merge
After 1, 3 and 5 are addressed the PR is a positive result for the pre-registered band on the open pool, driven by one split, with step 3 (fusion fitted on sao_paulo, recall at matched precision) as the committed next step and the richmond downsample check as the cheap confound test. That is a different document from the one here, so this is request-changes in substance even though I am posting it as a comment on a draft that another session owns.
🤖 Generated with Claude Code — Fable 5.1, claude-fable-5-1
Steps 1 and 2 of the #258 verifier plan (#258 (comment)), plus the leakage check. Write-up:
docs/verifier_258.md.Result: the production CurbRamp validator ranks RampNet's candidates worse than RampNet's own score does on 13 of 14 splits, so the design's stop condition is met. Below the 0.30 operating point it ranks better on the pooled verdict splits, but not on manual_gold. Draft because the one follow-up (a combined rule, below) needs Jon's call.
Step 2: the validator on every candidate
scripts/analysis/validator_score_258.py(GPU) cuts the crop production would cut, following the sidewalk-ai-api745e0c5serving path with the perspective function copied verbatim:int(6100/depth), resized to 640;969ad5a3…, recording P(correct).It resumes per pano.
validator_score_258.slurmchecks every split'simagery_manifest.jsonbefore scoring.scripts/analysis/validator_auc_258.py(CPU,--check) computes the AUC of P(correct) against RampNet's score, with a paired pano bootstrap, plus the untuned two-pass rule.Run: klone job 42152480, one L40S, 0.51 GPU-h, $0, no preemption. The
compute_log.jsonlrow, the sacct dump, the job log and all 14 score files are committed.b8287cc), before any other split's scores were read. It was not in the posted design.Instrument differences, recorded in the doc:
Proposed, not run: keep RampNet ≥ 0.30 and admit a sub-threshold candidate only above a high P(correct) bar, with the bar fitted leave-one-split-out.
Step 1 and the leakage check (unchanged)
verifier_candidates_258.pylabels all 10,504 RampNet peaks ≥ 0.05 on 14 splits, once each. The label holds at every threshold, and the script asserts this againstscore_pano. Recall ceiling: laurens_mapillary 0.78, bayonne 0.72.validator_leakage_258.pyfinds 0 pano-id overlaps and 0 benchmark panos within 500 m of any CurbRamp label in the validator's 19 training cities, on all 14 splits. The nearest is 2.3 km away.Tests
tests/test_validator_auc_258.py(new, 12) andtests/test_verifier_candidates_258.pypass, as doestests/test_docs_index.py.validator_auc_258.py --checkandverifier_candidates_258.py --checkare current. The full suite is left to CI.🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5