fix(scorer): exclude unscored samples from accuracy() and mean() denominator#4458
Conversation
54a91ac to
897b55f
Compare
|
@dragonstyle, this is ready for maintainer review. I rebased it onto current |
…minator Score.unscored() explicitly says samples with NaN value are 'excluded from metrics and reducers.' The reducer layer already honours this contract, but accuracy() and mean() passed every sample through value_to_float() without checking for NaN, so a single unscored sample would force the entire metric to NaN or silently include NaN in numpy operations. Changes: - accuracy(): skip NaN samples, compute ratio on scored subset only, log coverage when < 100%, clamp result to [0, 1] with a warning - mean(): skip NaN samples, compute on scored subset only - Both metrics return 0.0 when no samples are scored Closes UKGovernmentBEIS#4286 in the sense that unscored samples no longer corrupt the headline metric. Full three-bucket accounting (scored / inconclusive / errored) is left as follow-up for a richer Score status enum. Signed-off-by: Göktuğ Özkan, MD <232098340+goktugozkanmd@users.noreply.github.com>
897b55f to
d2ed040
Compare
|
Thanks for looking at this area; metric handling of unscored samples is exactly where I've been spending time lately, so I want to flag something about the premise before this takes up a maintainer review cycle. As far as I can tell, the NaN corruption described in the summary can't occur through the eval pipeline. NaN scores are filtered out before metrics are computed, in That's consistent with the tests here: they invoke Given that, I think the options are:
On the broader visibility concern from #4286 (unscored/errored samples silently narrowing the effective denominator): that's now tracked as a presentation change in #4481, which proposes surfacing |
|
Thanks, this is a fair distinction.
I do not have an end-to-end reproduction where a normal eval run feeds `NaN` through to `accuracy()` / `mean()` and produces a `NaN` metric. The reproduction I had in mind is the broader coverage problem from #4286: samples can leave the effective denominator (`errored` or `unscored`) while the headline metric still looks clean/high. That is a real user-facing reporting problem, but it is not the same as the metric-level `NaN` corruption described in this PR.
Given your explanation of `_eval/task/results.py`, I agree this PR should not be framed as fixing a user-reachable pipeline bug.
I think the cleaner path is:
1. Treat this PR, if kept, only as defense-in-depth for direct callers of metric functions, and tighten it accordingly — including not importing a private `_is_unscored` helper from the reducer module.
2. Move the main user-facing work to #4481, where the actual issue is clearer: headline metrics should surface `scored / unscored / errored` coverage so readers can see what the metric was computed over.
So I will stop presenting #4458 as the fix for #4286's main user-facing problem. If maintainers prefer, I am also fine with closing this PR in favor of #4481 and handling the direct-metric guard separately.
|
|
Closing this myself in favor of #4481. Matt's point is correct: I do not have an end-to-end reproduction where a normal eval run lets NaN propagate into If a direct-metric defense-in-depth guard is still useful later, it should be reopened as a narrower PR with the helper promoted to a shared public location first. |
Summary
Score.unscored()is documented as "excluded from metrics and reducers" — the reducer layer honors this by skipping NaN-at-root values. However,accuracy()andmean()pass every sample throughvalue_to_float()without checking for NaN, so a single unscored sample forces the entire metric to NaN.This is part of the work tracked in #4286 (scoring metrics silently dropping / mishandling inconclusive and errored samples).
Changes
accuracy()Score.valueis NaN (theScore.unscored()sentinel)scored / total) at INFO level when below 100%, so the effective evaluation scope is visible alongside the headline[0, 1]with a WARNING if out of range (prevents silently reading1.2or-0.3as accuracy)mean()np.mean()Compatibility
Score.unscored()or rawfloat(\"nan\")values are present — these are now correctly excludedRelated
Follow-up
Full three-bucket accounting (scored / inconclusive / errored) and a formal
ScoreStatusenum are left as future work — this PR only fixes the immediate metric-level NaN corruption.