Two-sided span counting: precision per prediction span, recall per annotation - #185
Draft
omri374 wants to merge 2 commits into
Draft
Two-sided span counting: precision per prediction span, recall per annotation#185omri374 wants to merge 2 commits into
omri374 wants to merge 2 commits into
Conversation
Replace per-annotation counting of predictions in SpanEvaluator with a
two-pass scheme. The recall pass keeps existing semantics: each
annotation independently checks whether same-type predictions cover it
at IoU >= threshold (pairwise for one span, combined for several) and
becomes TP or FN. The precision pass then counts every prediction span
exactly once: num_predicted equals the actual span count, and each span
is either credited (participated in a successful match) or an FP.
Previously a prediction overlapping several annotations was counted
once per annotation, and a group of same-type spans was counted once
per group, so num_predicted could drift above or below the real span
count depending on gold layout, one annotation could be counted as both
FN and TP, and a blob prediction could earn double credit at lenient
thresholds. Precision is now (num_predicted - false_positives) /
num_predicted; true_positives counts covered annotations and may differ
from the number of credited predictions.
Also:
- Single-span coverage uses exact pairwise Span.iou; the combined-IoU
path is reserved for genuine multi-span coverage.
- Each span appears in exactly one confusion-matrix cell: a wrong-type
detection at >= threshold is one (ann_type, pred_type) cell, with no
fallback (ann_type, "O") / ("O", pred_type) entries.
- Removed the superseded scenario-dispatch helpers and dead matching
machinery (~370 lines).
- Documented the counting contract in docs/span_evaluation.md and
docs/span_matching_strategies.md, added typed docstrings throughout,
and added contract tests for all six overlap scenarios.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ut adding new sections Keep the docs' original structure; only fix the statements the two-sided counting change made inaccurate (precision formula, prediction counting, multi-span FP accounting) and compress the new blob-overlap coverage to a short section. Drop the added counting-rules and confusion-matrix convention sections. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SpanEvaluatorcounted predictions through the annotations they overlapped: a prediction span overlapping two annotations enterednum_predictedtwice, and a group of same-type spans jointly covering one annotation entered it once.num_predictedtherefore drifted both above and below the model's actual output depending on gold layout, an annotation could be counted as both FN and TP, and a blob prediction could earn double TP credit at lenient thresholds (a sloppy span covering two golds scored P=1.0, R=1.0 by being counted twice on both sides).This PR replaces that with two-sided counting — each metric counted in its natural unit, each unit counted exactly once:
num_predicted= actual span count; every span is either credited (participated in ≥1 successful match) or an FP. Precision =(num_predicted − FP) / num_predicted.The two numerators may legitimately differ (one wide span covering two annotations = 2 recall hits, 1 credited prediction), which is why precision is no longer
TP / num_predicted.Counting contract (all covered by tests)
Invariants:
TP + FN == num_annotated;num_predicted== actual emitted span count.Also in this PR
Span.iou; the combined-IoU path (slightly inflated at boundaries) is reserved for genuine multi-span coverage. Borderline single-span matches can flip at exact threshold values (e.g. true IoU 0.4706 previously computed as 0.50 no longer passes τ=0.5).(ann_type, pred_type)cell representing both the gold and the prediction — no fallback(ann_type, "O")/("O", pred_type)entries._compare_single_overlaps,_compare_multiple_overlaps) collapsed into one uniform rule; dead helpers (_find_best_match,_check_if_matched_already,_handle_unmatched_predictions,_update_wrong_entities,_add_to_processed_predictions) deleted.docs/span_evaluation.mdanddocs/span_matching_strategies.mdcorrected where they described the old counting (precision formula, prediction counting, multi-span FP accounting) plus a short section on one prediction overlapping multiple annotations; typed docstrings added throughoutspan_evaluator.py.Verification
test_two_sided_counting_semantics(the 6-scenario contract table, incl. the P =(np−fp)/np≠tp/npregression case and both ledger invariants) andtest_single_prediction_overlapping_multiple_annotations_counted_once(the original double-counting repro). 681 unit tests pass; 8 pre-existing expectations updated, each a direct consequence of the rules above.num_predictednow equals the independently-counted actual span total everywhere (old code was wrong on 6 of 18 types, in both directions).Downstream impact
tp/predictedfromper_typecounts (orpii_true_positives / pii_predicted) must switch to(predicted − fp)/predicted—tp/predictedcan now exceed 1.end+1phantom character, shifted ranges for subsequent spans) — now isolated to the genuine multi-span path and a candidate for a follow-up fix.🤖 Generated with Claude Code