Skip to content

fix: compute multiclass F1 averages over all classes - #1482

Open
aidless wants to merge 1 commit into
huggingface:mainfrom
aidless:fix/multiclass-f1-micro
Open

aidless wants to merge 1 commit into
huggingface:mainfrom
aidless:fix/multiclass-f1-micro

Conversation

@aidless

@aidless aidless commented Nov 6, 2026

Copy link
Copy Markdown

What this changes (and what numbers move)

Before this change, CorpusLevelF1Score with num_classes > 2 computed one binary F1 per class with a one-vs-rest probe and averaged those numbers. That is not multiclass micro/macro/weighted F1: a one-vs-rest probe for a class that is never predicted scores 1.0 when nothing at all is predicted, which inflates the average.

After this change the requested averaging is delegated to sklearn.metrics.f1_score(..., average=..., labels=range(num_classes)).

Three-class corpus, golds -> preds:

case before after
perfect, [2,2,2] -> [2,2,2] 1.0 1.0
all correct, [1,0,0] -> [1,0,0] 1.0 1.0
no true positives, [0,1,1] -> [1,0,0] 0.3333 0.0
all predict class 0, [1,2,0] -> [0,0,0] 0.5556 0.3333

Existing results that change. Every mf1 score produced by Metrics.multi_f1_numeric (used by super_glue:cb, src/lighteval/tasks/tasks/glue.py:350) is affected, including scores already published in model cards, leaderboards or reports. The old numbers are inflated, worst when one class is never predicted. f1_score, f1_score_macro, f1_score_micro and loglikelihood_f1 are unchanged: the num_classes == 2 branch and the average=None path are untouched.

The multi_f1_numeric fixture encoded the old behaviour, so its two wrong expectations are corrected: 0.33 -> 0.0 (no true positives) and 0.55 -> 1/3 (one of three golds matched). The .gitattributes entry marks tests/unit/metrics/test_cases/*.json as -diff, so the fixture change does not render as a text diff here; those two lines are the only content change.

Tests

New tests/unit/metrics/test_corpus_f1.py (7 cases): multiclass corpus compared against sklearn for micro, macro and weighted; micro vs macro differ on the same corpus; all-zero predictions; single-class predictions; class absent from the corpus.

Local results:

  • tests/unit/metrics/test_corpus_f1.py: 7 passed
  • tests/unit/metrics/test_automated_metrics_pytest.py (incl. multi_f1_numeric): 45 passed, 1 failed (extractiveness, missing lighteval[multilingual] extra locally), 1 deselected (bleurt, hangs downloading its checkpoint)
  • tests/unit/metrics/test_normalizations.py: 4 passed
  • tests/unit/metrics/test_extractive_match.py: 233 passed

The extractiveness failure, the bleurt hang and the test_metric_requests.py hang in test_pmi_request all reproduce on the unmodified baseline in this environment and do not touch F1.

CorpusLevelF1Score computed one binary F1 per class and averaged those,
which is not the multiclass micro/macro/weighted F1 the caller asked for.
For golds [1, 2, 0] and preds [0, 0, 0] the old code returned 0.5556,
while the multiclass micro F1 is 0.3333.

The multi_f1_numeric (mf1) test fixture encoded the old values, so its
poor-performance and random-performance expectations are updated to the
correct 0.0 and 1/3. The average=None path and the num_classes == 2
branch are untouched.
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