Skip to content

[MRG] Improve multi-metric scorer speed #10979

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

Closed
wants to merge 2 commits into from

Conversation

gamazeps
Copy link
Contributor

Reference Issues/PRs

Works on improving #10802

What does this implement/fix? Explain your changes.

Previously multi metric scoring called the predict method of an
estimator once for each scorer, this could lead to drastic increases in
costs.

This change avoids calling the scorers directly and instead allows to
call the scorer with the predicted results.

This is only done for _PredictScorer and _ProbaScorer generated with
make_scorer, this means that _ThresholdScorer and scorers not
generated with make_scorer do not benefit from this change

Any other comments?

This implements a solution proposed by @jimmywan and the demarch seemed ok to @jnothman another solution would have been to force the estimator to cache its predict, predict_proba and decision_function methods.

@gamazeps gamazeps changed the title Scorer [WIP] Improve multi-metric scorer speed Apr 14, 2018
@gamazeps gamazeps force-pushed the scorer branch 3 times, most recently from 65bbb14 to 9459bf9 Compare April 15, 2018 19:39
@gamazeps gamazeps changed the title [WIP] Improve multi-metric scorer speed [MRG] Improve multi-metric scorer speed Apr 15, 2018
@gamazeps
Copy link
Contributor Author

ping @jnothman ?

Copy link
Member

@jnothman jnothman left a comment

Choose a reason for hiding this comment

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

I'd tweak this a bit. Firstly let's rename score_predict to score_predictions or score_precomputed. Secondly, let's add a staticmethod to these scorers called precompute_predictions.

In _multimetric_score we can then do something like:

precomputed = {}
for name, scorer in scorers.items():
    if hasattr(scorer, 'score_precomputed'):
        func = scorer.precompute_predictions
        if func not in precomputed:
            precomputed[func] = func(X_test)
        score = scorer.score_precomputed(precomputed[func], y_test)
    else:
        score = scorer(X_test, y_test)
    
    ...

Do you think this would be much harder to understand? It still can call prediction functions more times than memoising.

def _is_predict(x):
return isinstance(x, _PredictScorer)

# We want to keep the memmap and score types in a single
Copy link
Member

Choose a reason for hiding this comment

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

I don't know what you mean by the memmap here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the loop at https://github.com/scikit-learn/scikit-learn/pull/10979/files#diff-60033c11a662f460e1567effd5faa6f0R625

The scores are checked for being scalars and are unwrapped if they are memmaped.

tmp_scores contains the scores before being processed this way (it avoids doing the checks for each type of scorers)

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I now see what you mean. I think this makes sense to you as the one changing the code, but it would be hard for the reader to follow what you're referring to in this comment. It's better without the comment.

@jnothman
Copy link
Member

And thanks for the ping

@glemaitre
Copy link
Member

@gamebusterz
Usually we use the signature func(y_true, y_pred) if you can just exchange and I see that the score_predict do not follow it. Could you change that.

@glemaitre
Copy link
Member

I'd tweak this a bit. Firstly let's rename score_predict to score_predictions or score_precomputed. Secondly, let's add a staticmethod to these scorers called precompute_predictions.

I agree with this statement of @jnothman. Changing the name will be more explicit.

Previously multi metric scoring called the `predict` method of an
estimator once for each scorer, this could lead to drastic increases in
costs.

This change avoids calling the scorers directly and instead allows to
call the scorer with the predicted results.

This is only done for `_PredictScorer` and `_ProbaScorer` generated with
`make_scorer`, this means that `_ThresholdScorer` and scorers not
generated with `make_scorer` do not benefit from this change.

Works on improving scikit-learn#10802
@gamazeps
Copy link
Contributor Author

@glemaitre Just did the changes (I forgot to push them last week...).

Regarding the static method, I am not convinced this is the best way to go.
It feels to me as a pretty roundabout fix for the lack of caching in the predict function of estimators,
it seems healthier (for the codebase), to invest time in putting real caching instead of working around it.
Indeed I fear that such a patch would stay a long time in the codebase, even after caching is merged, and this could be a mine waiting to explode.

In order to be constructive, I propose to attack the caching right away, and volunteer to do it (it may however be a big piece so I expect it to take a while).

@jnothman
Copy link
Member

jnothman commented Apr 30, 2018 via email

@gamazeps
Copy link
Contributor Author

@jnothman Hmm, I may have misunderstood you in the issue then ^^

If it's not too much of a hassle, why exactly wouldn't we want caching in the estimators (I'm not too familiar with the implementation of sklearn so I may miss something obvious) ?

@jnothman
Copy link
Member

Because the dataset being predicted might be large, and we don't want to force the storage of that data.

@gamazeps
Copy link
Contributor Author

@jnothman When you say it like that, there is indeed an obvious reason to avoid storage by default :)

Should I try the method proposed (above)[https://github.com//pull/10979#pullrequestreview-114281530] or is this good to go now that the changes in the names are applied ?

@glemaitre
Copy link
Member

@gamazeps I find the proposed method by @jnothman quite understandable. I would go for that one.

@glemaitre
Copy link
Member

@gamazeps Any news

@gamazeps
Copy link
Contributor Author

Wooopsy...

I git caught up with administrative issues and did not have much time to dedicate to open source in the last weeks, I expect to have more time in two weeks. Does that work for you ?

Cheers
Felix

@jnothman
Copy link
Member

@gamazeps are you able to work on this, or should we find someone else to complete it?

(Another alternative we may be considering would allow a single scorer function to compute multiple results, returning a dict.)

@NicolasHug
Copy link
Member

(Another alternative we may be considering would allow a single scorer function to compute multiple results, returning a dict.)

Along these lines, WDYT about introducing make_multi_scorer and a MultiScorer class?

@jnothman
Copy link
Member

jnothman commented Apr 6, 2019 via email

@marctorsoc
Copy link
Contributor

Do you need a hand to complete this? Happy to help @amueller @jnothman

@jnothman
Copy link
Member

jnothman commented Aug 1, 2019

#14484 is the current candidate, @marctorrellas. Review from a user perspective is very welcome.

@marctorsoc
Copy link
Contributor

maybe this can be closed then?

@NicolasHug
Copy link
Member

We usually don't close PR unless an equivalent implementation has been merged elsewhere. When/if #14484 gets merged, we will close this one

@amueller
Copy link
Member

fixed in #14593

@amueller amueller closed this Sep 11, 2019
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.

6 participants