FIX: guard _accept_sample_weight in _MultimetricScorer and add regression test (Fixes #31599) #32594
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.
Reference Issues/PRs
Fixes #31599
What does this implement/fix? Explain your changes.
Bug before this change
_MultimetricScorer.__call__iterates over the scorers inself._scorersand forwardssample_weightto each scorer if appropriate.Before this change, the code unconditionally did something like:
This assumes that every scorer in
self._scorersis a_BaseScorerinstance with an_accept_sample_weightattribute.However, users can (and do) pass a mix of scorer types, for example:
scorercreated viamake_scorer(i.e. a_BaseScorersubclass), andPlain callables do not define
_accept_sample_weight. In that case, calling_accept_sample_weight()raised:This means that evaluating multiple metrics at once with
sample_weight=...could crash at runtime if any of those metrics was provided as a bare function instead of a scorer object.Behavior after this change
We now guard access to
_accept_sample_weight().For each scorer in the multimetric scorer:
_accept_sample_weight(), we call it and (as before) forwardsample_weightonly if it returnsTrue._accept_sample_weight(e.g. it's just a plain callable), we treat it as not acceptingsample_weightinstead of raising.In code terms (simplified for illustration):
This preserves the existing behavior for real scorer objects, while preventing the
AttributeErrorfor plain callables. The result is that mixed-metric evaluation withsample_weightno longer crashes.Tests
This PR adds a non-regression test:
test_multimetric_scorer_mixed_callable_no_attribute_error_with_sample_weightin
sklearn/metrics/tests/test_score_objects.py.The test does the following:
_BaseScorer(and therefore supports_accept_sample_weight) and another scorer that is just a plain function with no_accept_sample_weight._MultimetricScorercombining both scorers.sample_weight=....The assertions are:
AttributeError(or other exception) is raised when calling withsample_weight.dictcontaining numeric scores for both metrics.The test also imports
_BaseScorerfromsklearn.metrics._scorerso we can explicitly build the mixed scorer setup.User impact
sample_weight.sample_weightis unchanged.sample_weightis also unchanged (they simply do not receivesample_weight), but we no longer crash in that case.This is therefore a robustness / correctness fix to
_MultimetricScorer.__call__, plus a regression test to prevent regressions in the future.Any other comments?
doc/whats_new/upcoming_changes/.