Reviewing Model Output¶
Model pre-labels, then human QC — check the least confident, correct what is wrong, ship. This is how most annotation pipelines run once there is a model worth running.
Potato had the parts and not the workflow. ai_prelabel writes predictions,
active learning ranks by uncertainty, curation slices the data, and
adjudication compares one human against another. Comparing a human against the
model had no home.
Quick start¶
assignment_strategy: model_review
pre_annotation:
enabled: true
field: predictions
Annotators are now served prelabelled items least-confident first. The report:
curl -H "X-API-Key: $KEY" http://localhost:8000/admin/model-review
{
"n_items": 12,
"n_prelabelled": 9,
"queue": ["r00", "r01", "r02", "..."],
"worst_first": ["r00", "r01"],
"empty_prediction_ids": ["r03", "r07", "r11"],
"n_empty": 3,
"metrics": { "precision": null, "recall": null, "..." : "..." }
}
Two separate review jobs¶
Worst-first review¶
An item's rank is its lowest prediction confidence. One bad box makes an item worth reviewing even beside nine good ones, and a mean would average that signal away.
Items whose predictions carry no confidence sort after the ones that do. A model that does not report confidence is not thereby unsure, and treating it as confidence zero fills the worst-first queue with items nobody has a reason to doubt.
The convention is to check the worst 10–20%. worst_first is the least
confident 20% of the queue, and never fewer than one item.
Items the model said nothing about¶
A wrong prediction is visible in a review UI. It is right there, wrong. A missing prediction is invisible: the reviewer sees an empty item and moves on.
A confidence-ordered queue cannot surface one either, since an item with no prediction has no confidence to be low. So missing detections survive review unless someone goes looking for them.
Potato ships a built-in slice for that, with no configuration:
curl -H "X-API-Key: $KEY" \
"http://localhost:8000/curation/api/slices/model-found-nothing/resolve"
It appears in /admin/catalog alongside your own slices, and can become a
review dataset through the normal slice-to-dataset path.
An empty prediction list is an answer
"predictions": {"boxes": []} means the model looked and found nothing.
That is what puts an item in the pool. An item with no predictions key at
all counts the same way.
Verdicts¶
A reviewer's judgement of a prelabel is stored separately from an annotation:
curl -X POST -H "Content-Type: application/json" \
-d '{"instance_id": "r00", "verdict": "accept", "schema_name": "tone"}' \
http://localhost:8000/api/model-review/verdict
| Verdict | Meaning |
|---|---|
accept |
The model was right |
correct |
Partly right; the human changed something |
reject |
Wrong |
"The model was right" and "a human independently chose this label" are different facts. Storing the first as the second makes model precision unmeasurable, because every accepted prelabel then looks like human work.
Re-reviewing replaces a verdict rather than appending, so an annotator who changes their mind is counted once. An unknown verdict is refused: a silently stored typo would make the precision denominator wrong with nothing to show for it.
Precision and recall¶
Precision is the accepted share of reviewed prelabels. A corrected prelabel does not count as a true positive — it was partly wrong, and counting corrections as correct lets a model that is always nearly-right score perfectly.
Recall requires someone to have opened the empty-prediction pool. Until they have, Potato reports:
"recall": null,
"recall_note": "The empty-prediction pool was not reviewed, so there is no
evidence about what the model missed and recall cannot be
computed. Sample it from the empty-prediction slice."
A recall computed over an unreviewed denominator would be an assumption reported as a measurement.
Once the pool is sampled, a verdict on an empty item can only mean the reviewer found something the model had not, so each is a confirmed false negative and recall becomes computable. Only items a human actually opened count; anything else moves recall by assumption.
Both sides are samples, so each is scaled to its own pool before recall is
computed: the share of reviewed prelabelled items the model got right stands for
every prelabelled item, and the share of reviewed empty items that held
something stands for every empty item. One miss in 2 sampled empty items out of
1,000 is about 500 misses, not 1. recall_note says what was sampled and
scaled.
Verdicts on empty items stay out of the precision denominator, so the model is not penalised for a prediction it never made. When two reviewers judge the same item, both verdicts count.
Assignment behaviour¶
assignment_strategy: model_review serves the queue. Items with no prediction
are excluded from assignment; review those through the slice instead.
The strategy is incompatible with search.annotator_claim, which lets
annotators reorder a queue whose order is what makes it useful.
Related¶
- AI Support — generating the prelabels
- Active Learning — a different use of the same uncertainty signal
- Near-Duplicate Detection — the other curation pass worth running before annotation starts
- AI Costs