Skip to content

feat(benchmarks): dish-ambiguity-v1 — taxonomy, manifests, and evaluated classifier pins - #57

Draft
obvious-autobuild[bot] wants to merge 6 commits into
alphafrom
obv/fl-l2-dish-ambiguity
Draft

obvious-autobuild[bot] wants to merge 6 commits into
alphafrom
obv/fl-l2-dish-ambiguity

Conversation

@obvious-autobuild

@obvious-autobuild obvious-autobuild Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Port onto alpha (2026-10-15, head e6fa5b3)

The original base feat/routing-eval-harness is stale: alpha (the current integration branch) is 64 commits ahead of it. The change is ported onto alpha by merging origin/alpha @ 0309dd1 into obv/fl-l2-dish-ambiguity (merge commit e6fa5b3, no conflicts; the PR base is retargeted to alpha). The merge brings in alpha's iOS/CI/skills changes only - it does not touch benchmarks/dish-ambiguity-v1.

Evidence preservation, verified on the merged head:

  • git diff a6c921a..e6fa5b3 -- benchmarks/dish-ambiguity-v1 is empty: the 21-file study diff, all committed predictions/scored/report artifacts, and the pinned weight/output hashes are byte-identical to the reviewed head a6c921a.
  • check_manifest.py → exit 0 on the merged tree.
  • python3 -m pytest benchmarks/dish-ambiguity-v1/tests -q → 18 passed.
  • python3 benchmarks/dish-ambiguity-v1/score.py --verify-report → verify OK (scored.json and report.md byte-identical to committed).
  • Frozen manifest hashes re-hashed on the merged head, unchanged: test manifest.json 85ae3fb06242bf542df026ba03e9e2e53faae690fe50b53eba7e12a984a12ec2, development dev_manifest.json 90dd25dbc785a22a90b5efd6b98608ef25b38b62d36311f9a96b6701d71615c6 (both match results/run-metadata.json).
  • Hosted macOS CI on the merged head (run 38738162582): build-and-test pass (6m40s) and dead-code-advisory pass (3m10s).

Everything below (the original dish-ambiguity experiment) is unchanged.

What changed and why

benchmarks/dish-ambiguity-v1 answers the routing question: when a fridge photo is classified into one of Food-101's 101 dish labels, when can the app safely show a specific bundled recipe? Food-101 classes and the 166-recipe bundled catalog overlap only partially, and some labels are genuinely ambiguous (e.g. apple_pie vs cheesecake-family renders), so a blind "show recipe for the predicted dish" policy can render the wrong recipe with high confidence.

Three commits, one per milestone, then review-fix commits:

  1. Taxonomy freeze (f3fe481): every Food-101 class is mapped to the bundled catalog with status exact (one recipe answer), coarse (an equivalence class of recipes), ambiguous (multiple non-equivalent renders; no answer claimable), or unsupported (nothing close in the catalog). 11 exact / 3 coarse / 2 ambiguous / 85 unsupported. check_manifest.py validates coverage, recipe existence, uniqueness, and no-overlap rules.
  2. Manifests (05c6ef0): seeded stratified samples from the official Food-101 splits - 25 test images/class (2,525) and 6 dev images/class (606), with per-image sha256 + dHash; 5 dev near-duplicates of test images excluded and refilled. Rebuild is byte-identical.
  3. Evaluation (12c1f23): both pinned classifiers run over both manifests on CPU; scorer computes accuracy + a dev-selected abstention threshold (suggest only exact-status predictions above tau, targeting 0.90 suggestion precision on dev, coverage-maximizing), and the committed report/scored JSON are re-verifiable byte-for-byte via score.py --verify-report.
  4. Review fixes (9d84084): emitted paths canonicalized (repo-relative inside the checkout, bare names otherwise; no /home/user under the committed results), --verify-report confirmed from a different working directory, and the report/README lead with unrestricted suggestion precision.
  5. Review fix follow-up (a6c921a): the README Handoff section (the one reported for 9d84084 had not landed in that commit) with the frozen manifest hashes.

Headline results

  • nateraw/vit-base-food101 @ 55859a2 is the only viable pin: top1 0.8075, top5 0.9620 (2,525 test images). At the dev-selected tau (0.1), unrestricted suggestion precision is 175/183 = 0.9563 (coverage 0.0725) across all test images. Restricted to the 350 eligible test images (true class exact/coarse), precision is 175/175 = 1.000 at 0.500 coverage. Recommendation: adopt it for recipe suggestion with this abstention policy; abstain otherwise.
  • rajkr/mobilenet-v2-food101 @ 0dea82e is broken: top1 0.0614 via a confident collapse - five labels absorb 2,196/2,525 predictions, often with top1 probability ~1.0, so no threshold can separate right from wrong. Cross-checked against the canonical HF image-classification pipeline at the same revision (identical outputs), so the checkpoint is at fault, not our runner.
  • Practical caveat: even the good ViT is underconfident (most correct predictions sit far below 0.5), so the threshold is dev-selected, not hand-picked, and its test-side precision (0.9563 unrestricted) is reported alongside dev (0.9111).

How I checked it

Re-verified in this review round at head a6c921a, from a fresh independent worktree of origin/obv/fl-l2-dish-ambiguity:

  • cwd-independent report verification: cd /tmp && python3 /home/user/work/fridgeluck-57-review/benchmarks/dish-ambiguity-v1/score.py --verify-report → score: verify OK - scored.json and report.md byte-identical to committed (exit 0). The same command from the checkout root gives the identical output and exit 0.
  • Path canonicalization: grep -rn "/home/user" benchmarks/dish-ambiguity-v1/results benchmarks/dish-ambiguity-v1/*.py benchmarks/dish-ambiguity-v1/README.md → no matches. The report emits Results: benchmarks/dish-ambiguity-v1/results (repo-relative). git diff 12c1f23..a6c921a shows the only changes to prediction payloads and run-metadata.json are path strings (/home/user/... → repo-relative/bare); model repo/revision/weights-sha256 pins, dataset tarball sha256, and manifest sha256s are byte-identical.
  • Report emphasis: the report's per-model blocks list unrestricted suggestion precision first with its own denominator (ViT: precision 175/183 = 0.9563, coverage 0.0725 over 2,525), and the eligible-only statistic on a separate line with a separate denominator (175/175 = 1.0, coverage 0.5 over the 350 eligible images); the README Results section leads with unrestricted 175/183 for the recommended pin. The two claims are never blended.
  • python3 -m pytest benchmarks/dish-ambiguity-v1/tests -q → 18 passed (validator planted failures + scoring hand controls); re-run at head.
  • python3 check_manifest.py → exit 0 (check_manifest: OK); re-run at head.
  • Manifest rebuild determinism → md5sum -c OK, byte-identical (at evaluation time).
  • Full run: both models × both manifests, 0 recorded failures; weights verified by sha256 against the pinned HF revisions (hashes in results/run-metadata.json).
  • Runner vs canonical pipeline cross-check on 30 images → identical label behavior (this is how the MobileNet checkpoint was confirmed broken).
  • Food-101 archive verified: sha256 d97d15e4…4053a4, 101 classes, no cheeseburger class in this distribution.

Handoff

For whoever wires recipe suggestion into the app:

  • Pin: nateraw/vit-base-food101 @ 55859a2a1349 (weights sha256 61f707d9d423461b..., recorded in results/run-metadata.json). Do not pin rajkr/mobilenet-v2-food101 @ 0dea82e - the checkpoint is broken (see Headline results); if a small mobile model is wanted, re-run this benchmark over a replacement candidate before trusting it.
  • Policy: suggest the mapped recipe only when (a) the predicted class has status exact in mapping.food101-v1.json, and (b) its top1 probability >= 0.1. Expected test behavior: unrestricted suggestion precision 175/183 = 0.9563 (coverage 0.0725), eligible-only precision 175/175 = 1.000 at 0.500 coverage on the 350 eligible images - keep the two denominators separate when quoting. Show nothing otherwise; unsupported/ambiguous classes should render dish identity only.
  • Re-verify after any change to model, mapping, or thresholds: python3 benchmarks/dish-ambiguity-v1/score.py --verify-report (works from any directory; byte-compares the committed report - confirmed from /tmp at head a6c921a). Full reproduction: run.py --dataset-root <food-101> --out benchmarks/dish-ambiguity-v1/results, then score.py; validate first with check_manifest.py, tests with python3 -m pytest benchmarks/dish-ambiguity-v1/tests -q.
  • Frozen manifest hashes (sha256, at head a6c921a, must match results/run-metadata.json): test manifest.json (2,525 images) 85ae3fb06242bf542df026ba03e9e2e53faae690fe50b53eba7e12a984a12ec2; development dev_manifest.json (606 images) 90dd25dbc785a22a90b5efd6b98608ef25b38b62d36311f9a96b6701d71615c6. Both are committed; changing either is a reproducibility break, not a tweak - rebuild with build_manifest.py and re-pin deliberately. A rebuild that hashes differently is a different sample - do not rescore the committed results against it.
  • Resume from a clean checkout: python3 benchmarks/dish-ambiguity-v1/check_manifest.py (exit 0 = taxonomy/manifest consistency; re-verified at head), python3 -m pytest benchmarks/dish-ambiguity-v1/tests -q (18 passed at head), then score.py --verify-report above.
  • Open items: per-class thresholds instead of one global tau; a tau transfer check on future data (dev precision 0.9111 vs test 0.9563 is one sample); macOS CI has not run on this PR - the benchmark is Linux-runnable Python only and touches no Swift code.

What I could not check

  • No Xcode/macOS in this environment: nothing here builds or runs the iOS app; the benchmark is Python-only and does not touch Swift code. Hosted macOS CI has not run on this PR yet.
  • The ViT checkpoint claims ~85-90% accuracy in its model card; I measured 0.8075 top1 on my 2,525-image official-split sample (this distribution's test split). I did not investigate the delta beyond confirming the pipeline agreement.
  • tau transfer risk: dev (606 images) selects tau; test precision (0.9563 unrestricted) is a point estimate from one sample, with a Wilson-style uncertainty not computed. The 1.000 precision over 175 eligible-image suggestions has a non-trivial confidence interval; treat "never wrong" as "0 wrong in this sample".

Assumptions

  • Class-level mapping decisions (exact/coarse/ambiguous/unsupported) were curated from bundled recipe titles and dish identity, not ingredient overlap (imported ingredient mappings are unreliable).
  • This distribution's meta/classes.txt lacks cheeseburger; the burger family is represented by hamburger alone, so the frozen class list is authoritative.
  • The abstention policy (single global probability threshold on exact-status predictions only) is the minimal deployable policy; per-class thresholds are future work.
  • Food-101 dataset and model weights are not committed (hashes only), per the benchmark's reproducibility contract.

Human author: Sam Gu (sgu07966@gmail.com)

🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact

…pping

Freeze the Food-101 -> bundled-recipe mapping for the dish-ambiguity
benchmark: 101 classes -> 11 exact, 3 coarse, 2 ambiguous, 85
unsupported against the 166-recipe bundled catalog, each entry with
catalog-evidenced reason and near-miss notes.

- check_manifest.py validates class coverage, recipe existence,
  status-shape rules, exact/coarse uniqueness, ambiguous/no-exact
  overlap, stratification and >=2000 manifest requirements, and
  dHash near-duplicate discipline between dev and test manifests
- build_manifest.py does seeded stratified sampling (25 official test
  images/class; dev from train with near-duplicate exclusion)
- tests/ plants duplicate-group, unsupported-exact-recipe, and
  ambiguous double-booking conflicts and asserts the validator exits 1
  on each; the real committed mapping must pass end-to-end (12 passed)
- taxonomy-summary.json: 25/166 recipes claimed; 14 eligible classes
  -> 350/2525 sampled test images suggestion-eligible

Note: this Food-101 distribution's meta/classes.txt has no
'cheeseburger' class (verified against the official tarball), so the
burger family is represented by 'hamburger' alone.

Checks: python3 check_manifest.py -> OK (exit 0); pytest 12 passed;
planted duplicate and unsupported-exact demos both exit 1 with the
expected ERROR lines.
Build the sampling manifests from the official Food-101 splits:

- manifest.json: 25 images per class from the official test split
  (2,525 total), seeded (default_rng(20261010)), sorted-order
  deterministic, with per-image sha256 and 64-bit dHash
- dev_manifest.json: 6 per class from the official train split
  (606 total); near-duplicates of any test image (dHash Hamming <= 10,
  development-only threshold) are excluded AND refilled from the same
  seeded shuffle so stratification stays exact at 6/class
- dev-near-duplicates-dropped.json records the 5 exclusions with
  their minimum Hamming distances

Also fixes build_manifest.py image paths to include the class
directory (images/<class>/<id>.jpg) and reads this distribution's
split files at meta/{split}.txt (not *_list.txt).

Rebuild determinism verified byte-for-byte (md5sum -c OK for both
manifests). check_manifest.py passes; pytest 12 passed.
Run both pinned Food-101 classifiers over the committed test + dev
manifests on CPU and score the dish-ambiguity question end to end:

- run.py: verifies pinned weight hashes, runs each model over both
  manifests, records per-image top-5 predictions with latency,
  retains per-image failures, emits run metadata (weight sha256,
  manifest hashes, peak RSS); 0 failures recorded across all runs
- score.py: per-class stratified top1/top5 accuracy; abstention
  policy (suggest only exact-status predicted classes above a dev
  selected tau targeting 0.90 suggestion precision); deterministic
  report whose committed bytes are re-verified with --verify-report
- results/: predictions, run metadata, scored.json, report.md

Findings: nateraw/vit-base-food101 @ 55859a2 reaches top1 0.8075 /
top5 0.9620 and, at tau 0.1, emits specific recipe suggestions at
1.000 precision with 0.500 coverage over the 350 eligible test
images. rajkr/mobilenet-v2-food101 @ 0dea82e7 is broken: top1
0.0614 with a confident collapse onto five labels (no probability
threshold separates right from wrong); its output was verified
identical to the canonical HF pipeline, so the checkpoint is at
fault. Dev cannot reach the precision target for it, so its tau is
reported as unvalidated.

Checks: pytest 18 passed; score.py --verify-report passes twice
consecutively; canonical-pipeline cross-check on 30 images.
@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fe5414da-fe59-497d-9cf6-ed47f1c4c5bf

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

SamGu-NRX and others added 3 commits October 10, 2026 21:31
…icted precision

Review fixes for the dish-ambiguity-v1 evaluation (PR #57):

- run.py/score.py: emitted paths are repo-relative when inside the
  checkout and bare names otherwise (previously absolute /home/user
  paths leaked into prediction metadata, run-metadata.json, and the
  report header); committed artifacts re-pointed without touching
  any pinned weight/output hash or prediction payload
- score.py --verify-report confirmed from a different working
  directory (cwd-independent: both scripts anchor on __file__)
- report now leads with unrestricted suggestion precision
  (175/183 = 0.9563, coverage 0.0725) and keeps the eligible-only
  statistic (175/175 = 1.0 precision at 0.500 coverage) clearly
  separate; suggestion_stats records the correct count
- README: findings reordered to match, plus a Handoff section for
  wiring the policy into the app (pin, threshold, re-verification,
  open items)

Checks: pytest 18 passed; score.py --verify-report OK from /tmp;
no /home/user paths remain under benchmarks/dish-ambiguity-v1/results.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…hashes

The Handoff section reported earlier did not survive into commit
9d84084 (only the findings reorder landed). Adding it for real:

- pin: nateraw/vit-base-food101 @ 55859a2 (weights sha256 recorded
  in results/run-metadata.json); MobileNet explicitly not pinnable
- policy: exact-status classes only, top1 >= tau 0.1, quoting
  unrestricted precision (175/183 = 0.9563) and eligible-only
  precision (175/175 = 1.000 at 0.500 coverage) with separate
  denominators; unsupported/ambiguous classes render dish identity,
  never a native bundled recipe
- exact frozen manifest hashes (verified against the committed
  files and results/run-metadata.json): test manifest.json
  85ae3fb0...2ec2, dev_manifest.json 90dd25db...15c6; a rebuild
  that hashes differently must not be rescored against
- resume/verification commands (check_manifest, run, verify-report,
  pytest) and open items (per-class thresholds, tau transfer,
  macOS CI pending)

No code changes; pinned weights/outputs and the 21-file diff are
untouched.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
feat/routing-eval-harness is stale against the current integration
branch (alpha is 64 commits ahead), so PR #57 is ported onto alpha:
merging origin/alpha (0309dd1) into obv/fl-l2-dish-ambiguity. No
conflicts - alpha does not touch benchmarks/dish-ambiguity-v1 - and
the study diff, pinned weights/outputs, frozen manifest hashes, and
committed evidence are preserved byte-identically
(git diff a6c921a..HEAD -- benchmarks/dish-ambiguity-v1 is empty).
Re-verification runs on the merged head: check_manifest.py,
pytest (18), score.py --verify-report.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
@obvious-autobuild
obvious-autobuild Bot changed the base branch from feat/routing-eval-harness to alpha October 15, 2026 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant