8000 ENH Adds multimetric support to check_scoring by thomasjpfan · Pull Request #28360 · scikit-learn/scikit-learn · GitHub
[go: up one dir, main page]

Skip to content

ENH Adds multimetric support to check_scoring #28360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Feb 19, 2024

Conversation

thomasjpfan
Copy link
Member

Reference Issues/PRs

Fixes #28299

What does this implement/fix? Explain your changes.

This PR adds multi-metric support to check_scoring. This gives a public inference for returning a multiple metric scoring that uses the caching from scoring. With this PR, one can write the following to get a multi-metric scorer:

mutli_scoring = check_scoring(scoring=["r2", "roc_auc", "accuracy"])

Any other comments?

There are more places that can use this, but it requires #28359 to be merged in first.

Copy link
github-actions bot commented Feb 4, 2024

✔️ Linting Passed

All linting checks passed. Your pull request is in excellent shape! ☀️

Generated for commit: 867cc10. Link to the linter CI: here

Copy link
Contributor
@eddiebergman eddiebergman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. One place that also seems to do something similiar to create multiple scorers is BaseSearchCV

def _get_scorers(self, convert_multimetric):
"""Get the scorer(s) to be used.
This is used in ``fit`` and ``get_metadata_routing``.
Parameters
----------
convert_multimetric : bool
Whether to convert a dict of scorers to a _MultimetricScorer. This
is used in ``get_metadata_routing`` to include the routing info for
multiple scorers.
Returns
-------
scorers, refit_metric
"""
refit_metric = "score"
if callable(self.scoring):
scorers = self.scoring
elif self.scoring is None or isinstance(self.scoring, str):
scorers = check_scoring(self.estimator, self.scoring)
else:
scorers = _check_multimetric_scoring(self.estimator, self.scoring)
self._check_refit_for_multimetric(scorers)
refit_metric = self.refit
if convert_multimetric and isinstance(scorers, dict):
scorers = _MultimetricScorer(
scorers=scorers, raise_exc=(self.error_score == "raise")
)
return scorers, refit_metric

@@ -942,6 +959,9 @@ def check_scoring(estimator, scoring=None, *, allow_none=False):
"to a scorer." % scoring
)
return get_scorer(scoring)
if isinstance(scoring, (list, tuple, set, dict)):
scorers = _check_multimetric_scoring(estimator, scoring=scoring)
return _MultimetricScorer(scorers=scorers)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing that is occluded is the raise_exc: bool = True argument of _MultimetricScorer. I have personally never used it so I do not mind. Is the decision not to support passing it in?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not want to increase the scope of this PR. Usually increasing the scope makes it harder to merge.

If we want to add raise_exc, then it can be done in a follow up PR.


- a list or tuple of unique strings;
- a callable returning a dictionary where the keys are the metric
names and the values are the metric scores;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
names and the values are the metric scores;
names and the values are the metric scorers;

@thomasjpfan
Copy link
Member Author
thomasjpfan commented Feb 5, 2024

One place that also seems to do something similiar to create multiple scorers is BaseSearchCV

Changing that part would increase the scope of this PR. #28359 is a step toward in refactoring _get_scorers, independent of this PR.

Once #28359 is merged, then we can have a follow up PR to refactor _get_scorers.

Copy link
Member
@adrinjalali adrinjalali left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should make _MultimetricScorer public when we're returning it in a public function. Otheriwise lgtm.

@thomasjpfan
Copy link
Member Author

I think we should make _MultimetricScorer public when we're returning it in a public function. Otheriwise lgtm.

For the public API, check_scoring returns a callable and _MultimetricScorer is an implementation detail. We already do this with make_scorer, which also returns a private scorer class, but the public API only promises a callable.

@glemaitre glemaitre self-requested a review February 8, 2024 18:32
@glemaitre
Copy link
Member

For the public API, check_scoring returns a callable and _MultimetricScorer is an implementation detail. We already do this with make_scorer, which also returns a private scorer class, but the public API only promises a callable.

I agree with this statement. However, I would implement two additional things.

First, having a nicer __repr__ to be less surprising while manipulating a private object. This is currently the difference between the _Scorer and _MultiScorer:

In [5]: check_scoring(estimator=LogisticRegression(), scoring=scoring)
Out[5]: <sklearn.metrics._scorer._MultimetricScorer at 0x11c316830>

In [6]: check_scoring(estimator=LogisticRegression(), scoring=scoring["accuracy"])
Out[6]: make_scorer(accuracy_score, response_method='predict')

Then, I think that we should have an entry point in the user guide: https://scikit-learn.org/dev/modules/model_evaluation.html#using-multiple-metric-evaluation. We could have a call to check_scoring there and a small description.

@eddiebergman
Copy link
Contributor

@glemaitre side question, is there any scope to make multimetric scorers publicly available through the get_scorer function? It's already the defacto standard for getting a scorer and check_scoring doesn't seem like an intuitive name to get a multi metric scorer.

@glemaitre
Copy link
Member

It's already the defacto standard for getting a scorer and check_scoring doesn't seem like an intuitive name to get a multi metric scorer.

Yep check_scoring is more a third-party library way to validate a container of scoring. And actually my remark regarding the documentation should be more towards this intent of documenting a developer tool.

I think that we need to make sure that we can pass multiple scorer in the scoring parameter everywhere in scikit-learn before to expose it through get_scorer. But I don't see a reason why not at a first glance.

Copy link
Member
@adrinjalali adrinjalali left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Copy link
Member
@glemaitre glemaitre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks @thomasjpfan

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[API] A public API for creating and using multiple scorers in the sklearn-ecosystem
4 participants
0