Skip to content

Warn in the main process when a fit fails during a cross-validation #20619

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 19 commits into from
Aug 10, 2021

Conversation

lesteve
Copy link
Member

@lesteve lesteve commented Jul 27, 2021

Reference Issues/PRs

Fix #20475

What does this implement/fix? Explain your changes.

Removes a per-split warning and do a summary warning in the main process.

Any other comments?

Still polishing it for now, some test failures expected.

Some remaining things to discuss/do:

  • removing the per-split warning may affect BaseSearchCV derived classes outside scikit-learn. There will be no warnings when a fit fails. How acceptable is it?
  • agree on the warning message. This is how it looks right now. I could potentially shorten it when some of the errors are exactly the same:
/home/local/lesteve/dev/scikit-learn/sklearn/model_selection/_validation.py:366: FitFailedWarning: 
5 fits failed on the training sets over a total of 5 fits. The score on these train-test partitions for these parameters will be set to nan.
In case these failures are not expected, you can try to debug these failures by setting error_score='raise'.

Here are more details about the failures:
----------
Traceback (most recent call last):
  File "/home/local/lesteve/dev/scikit-learn/sklearn/model_selection/_validation.py", line 675, in _fit_and_score
    estimator.fit(X_train, y_train, **fit_params)
  File "/home/local/lesteve/dev/scikit-learn/sklearn/linear_model/_logistic.py", line 1458, in fit
    raise ValueError("Penalty term must be positive; got (C=%r)" % self.C)
ValueError: Penalty term must be positive; got (C='wrong-value')
----------
Traceback (most recent call last):
  File "/home/local/lesteve/dev/scikit-learn/sklearn/model_selection/_validation.py", line 675, in _fit_and_score
    estimator.fit(X_train, y_train, **fit_params)
  File "/home/local/lesteve/dev/scikit-learn/sklearn/linear_model/_logistic.py", line 1458, in fit
    raise ValueError("Penalty term must be positive; got (C=%r)" % self.C)
ValueError: Penalty term must be positive; got (C='wrong-value')
----------
Traceback (most recent call last):
  File "/home/local/lesteve/dev/scikit-learn/sklearn/model_selection/_validation.py", line 675, in _fit_and_score
    estimator.fit(X_train, y_train, **fit_params)
  File "/home/local/lesteve/dev/scikit-learn/sklearn/linear_model/_logistic.py", line 1458, in fit
    raise ValueError("Penalty term must be positive; got (C=%r)" % self.C)
ValueError: Penalty term must be positive; got (C='wrong-value')
----------
Traceback (most recent call last):
  File "/home/local/lesteve/dev/scikit-learn/sklearn/model_selection/_validation.py", line 675, in _fit_and_score
    estimator.fit(X_train, y_train, **fit_params)
  File "/home/local/lesteve/dev/scikit-learn/sklearn/linear_model/_logistic.py", line 1458, in fit
    raise ValueError("Penalty term must be positive; got (C=%r)" % self.C)
ValueError: Penalty term must be positive; got (C='wrong-value')
----------
Traceback (most recent call last):
  File "/home/local/lesteve/dev/scikit-learn/sklearn/model_selection/_validation.py", line 675, in _fit_and_score
    estimator.fit(X_train, y_train, **fit_params)
  File "/home/local/lesteve/dev/scikit-learn/sklearn/linear_model/_logistic.py", line 1458, in fit
    raise ValueError("Penalty term must be positive; got (C=%r)" % self.C)
ValueError: Penalty term must be positive; got (C='wrong-value')

  warnings.warn(some_fits_failed_message, FitFailedWarning)
  • add changelog
  • test_callable_multimetric_clf_all_fails failure: quirk that for multimetric you get an error if all the test fails (but not for single metric). Is there a good reason for this behaviour? I changed my PR to make this test pass, basically the warnings on fit failures happens before the insertion of error scores for the multimetric case.

@lesteve lesteve marked this pull request as draft July 27, 2021 15:34
@lesteve lesteve changed the title wip Warn in the main process when a fit fails during a cross-validation Jul 27, 2021
@lesteve lesteve marked this pull request as ready for review July 28, 2021 14:53
@lesteve
Copy link
Member Author

lesteve commented Jul 28, 2021

The tests pass, the CI red only because I have not added a changelog yet. Comments about the points noted in my top post or anything else in this PR, let me know!

@@ -2082,37 +2082,6 @@ def test_fit_and_score_failing():
y = np.ones(9)
fit_and_score_args = [failing_clf, X, None, dict(), None, None, 0, None, None]
# passing error score to trigger the warning message
fit_and_score_kwargs = {"error_score": 0}
Copy link
Member Author

Choose a reason for hiding this comment

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

The check on the warnings were moved below to test_cross_validate_failing_fits_warnings since _fit_and_score does not warn anymore

Copy link
Member

@thomasjpfan thomasjpfan left a comment

Choose a reason for hiding this comment

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

removing the per-split warning may affect BaseSearchCV derived classes outside scikit-learn.

The BaseSearchCV "public API" consist calling evaluate_candidates in _run_search. The warning will still appear when calling evaluate_candidates, just not as often. I think it is okay to change.

This is how it looks right now. I could potentially shorten it when some of the errors are exactly the same:

If error message is exactly the same, only show one of them?

quirk that for multimetric you get an error if all the test fails (but not for single metric)

With callable multimetric, at least one _fit_and_score has to succeed so that *SearchCV can create error_score dictionaries for the failed cases. In the single metric case, *SearchCV can still proceed because there are no keys to deal with.

@glemaitre
Copy link
Member

The BaseSearchCV "public API" consist calling evaluate_candidates in _run_search. The warning will still appear when calling evaluate_candidates, just not as often. I think it is okay to change.

To confirm this behaviour, I assume that we can try using HalvingSearchCV where the warning can be raised several times.

@glemaitre
Copy link
Member

glemaitre commented Jul 29, 2021

With callable multimetric, at least one _fit_and_score has to succeed so that *SearchCV can create error_score dictionaries for the failed cases. In the single metric case, *SearchCV can still proceed because it are no keys to deal with.

Interesting. On the user side, you don't think that we should anyway raise an error when everything is np.nan? I don't see an application where you can do anything with this result?

Indeed, I would not be surprised to raise an error in both multimeric and single metric. I would be a bit more lenient if some of the metrics fail in the multimetric case.

@thomasjpfan
Copy link
Member

On the user side, you don't think that we should anyway raise an error when everything is np.nan?

It does make sense to raise an error if everything failed. We can add this improvement in another PR.

@lesteve
Copy link
Member Author

lesteve commented Jul 30, 2021

I summarised errors in the warnings showing each error only once. This is how the warning looks with 10 fit failures with 2 different failures:

❯ python test.py                                                         
/home/lesteve/dev/scikit-learn/sklearn/model_selection/_validation.py:372: FitFailedWarning: 
10 fits failed on the training sets over a total of 25 fits.
The score on these train-test partitions for these parameters will be set to nan.
If these failures are not expected, you can try to debug them by setting error_score='raise'.

Below are more details about the failures:
--------------------------------------------------------------------------------
5 fits failed with the following error:
Traceback (most recent call last):
  File "/home/lesteve/dev/scikit-learn/sklearn/model_selection/_validation.py", line 681, in _fit_and_score
    estimator.fit(X_train, y_train, **fit_params)
  File "/home/lesteve/dev/scikit-learn/test.py", line 16, in fit
    raise ValueError(f"Failing classifier failed for parameter {self.parameter}")
ValueError: Failing classifier failed for parameter 2

--------------------------------------------------------------------------------
5 fits failed with the following error:
Traceback (most recent call last):
  File "/home/lesteve/dev/scikit-learn/sklearn/model_selection/_validation.py", line 681, in _fit_and_score
    estimator.fit(X_train, y_train, **fit_params)
  File "/home/lesteve/dev/scikit-learn/test.py", line 16, in fit
    raise ValueError(f"Failing classifier failed for parameter {self.parameter}")
ValueError: Failing classifier failed for parameter 3
test.py script used
import numpy as np

from sklearn.base import BaseEstimator


class FailingClassifier(BaseEstimator):
    """Classifier that raises a ValueError on fit()"""

    FAILING_PARAMETERS = [2, 3]

    def __init__(self, parameter=None):
        self.parameter = parameter

    def fit(self, X, y=None):
        if self.parameter in FailingClassifier.FAILING_PARAMETERS:
            raise ValueError(f"Failing classifier failed for parameter {self.parameter}")

    def predict(self, X):
        return np.zeros(X.shape[0])

    def score(self, X=None, Y=None):
        return 0.0


from sklearn.datasets import make_classification
X, y = make_classification()

from sklearn.model_selection import GridSearchCV
gs = GridSearchCV(FailingClassifier(), {'parameter': [1, 2, 3, 4, 5]})
gs.fit(X, y)

@ogrisel
Copy link
Member

ogrisel commented Jul 30, 2021

That looks great!

)

some_fits_failed_message = (
f"\n{num_failed_fits} fits failed on the training sets over a total of"
Copy link
Member

Choose a reason for hiding this comment

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

I find this language a bit strange... Should "over" be "out of"? Is "on the training sets" helpful?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the comment, I updated the wording:

  • now: "5 fits failed out of a total of 15"
  • before it was: "5 fits failed on the training sets over a total of 15 fits"

Let me know if the language is still a bit strange!

Copy link
Member

@ogrisel ogrisel left a comment

Choose a reason for hiding this comment

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

LGTM. I think this is a really nice usability improvement.

+1 for a follow-up PR to always raise an error if 100% of the fits fail (both in simple cross validation and in hyperparam search).

Co-authored-by: Guillaume Lemaitre <g.lemaitre58@gmail.com>
Copy link
Member

@thomasjpfan thomasjpfan left a comment

Choose a reason for hiding this comment

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

Small nit, otherwise LGTM!

Co-authored-by: Thomas J. Fan <thomasjpfan@gmail.com>
@ogrisel ogrisel merged commit 7317416 into scikit-learn:main Aug 10, 2021
@ogrisel
Copy link
Member

ogrisel commented Aug 10, 2021

Merged! Thank you very much for the improvement @lesteve!

@lesteve lesteve deleted the cross-val-nan-score branch September 13, 2021 13:21
samronsin pushed a commit to samronsin/scikit-learn that referenced this pull request Nov 30, 2021
…cikit-learn#20619)

Co-authored-by: Thomas J. Fan <thomasjpfan@gmail.com>
Co-authored-by: Guillaume Lemaitre <g.lemaitre58@gmail.com>
Co-authored-by: Olivier Grisel <olivier.grisel@ensta.org>
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.

error_score=nan issues hidden warnings in model selection utilities when n_jobs>1
5 participants