Skip to content

MAINT Refactor: use _get_response_values in CalibratedClassifierCV #27796

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 7 commits into from
Nov 24, 2023

Conversation

lucyleeow
Copy link
Member

What does this implement/fix? Explain your changes.

Refactors CalibratedClassifierCV to use _get_response_values, removing 2 similar functions in calibration.py.

Realised when working on #27700 that _get_response_values could also be used in CalibratedClassifierCV

Any other comments?

I think pos_label in _get_response_values for binary cases is not needed to ensure order as we are always passing self.classes around.

@glemaitre you may have suggestion for improvements, I am happy to change.

Copy link

github-actions bot commented Nov 17, 2023

✔️ Linting Passed

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

Generated for commit: fe00537. Link to the linter CI: here

@glemaitre glemaitre self-requested a review November 17, 2023 10:21
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.

It makes sense to me.

It would not bother adding a function for the reshaping but directly add the if statement in the code.

@@ -788,9 +743,8 @@ def predict_proba(self, X):
proba : array, shape (n_samples, n_classes)
The predicted probabilities. Can be exact zeros.
"""
predictions = _get_response_and_reshape(self.estimator, X)
Copy link
Member

Choose a reason for hiding this comment

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

I assume that I would remove the function and directly call _get_response_values and just add

if predictions.ndim == 1:
    predictions = prediction[:, np.newaxis]

Copy link
Member

Choose a reason for hiding this comment

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

Apparently using .ndim will also solve the last failure. It seems that we have 1D array in other cases than binary for some reason.

Copy link
Member Author

Choose a reason for hiding this comment

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

So that last failure (test_setting_request_on_sub_estimator_removes_error) fails because we use ConsumingClassifier, which will always output shape (n_samples,) (no matter the target type of y):

def decision_function(self, X, sample_weight="default", metadata="default"):
record_metadata_not_default(
self, "predict_proba", sample_weight=sample_weight, metadata=metadata
)
return np.zeros(shape=(len(X),))

I think this isn't a problem for any other metaestimator because only CalibratedClassifierCV will call a 'predict' method during fit. Do you think we need to do anything about this @glemaitre ?

Copy link
Member Author

Choose a reason for hiding this comment

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

How do you decide between reshape and newaxis ? Having a quick look at: https://stackoverflow.com/questions/28385666/numpy-use-reshape-or-newaxis-to-add-dimensions I would lean towards reshape?

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 np.newaxis more explicit that the -1 and 1 meaning that might not be trivial to understand at first. However, if you are more comfortable with reshape, go with it. This is just a cosmetic and personal preference.

n_classes = len(classes)
pred_method, method_name = _get_prediction_method(estimator)
predictions = _compute_predictions(pred_method, method_name, X_test, n_classes)
predictions = _get_response_and_reshape(estimator, X_test)
Copy link
Member

Choose a reason for hiding this comment

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

Here as well, the if statement is quite minimal.

@glemaitre glemaitre self-requested a review November 22, 2023 10:29
@lucyleeow
Copy link
Member Author

So codecov highlights that line 369 in calibration.py is not really tested. This is because test_calibration_prefit uses MultinomialNB which only has a predict_proba and always returns predictions with ndim > 1. I am happy to try and improve coverage here, even though this PR shouldn't have really changed it, or I can leave for a separate PR.

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.

I checked codecov and the line that are reported partially covered. I run the test and this is only a false positive.

Comment on lines 463 to 464
# Reshape in the binary case
if len(self.classes_) == 2:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# Reshape in the binary case
if len(self.classes_) == 2:
if len(self.classes_) == 2:
# reshape from (n_samples,) to (n_samples, 1) for binary case

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm actually maybe I should use predictions.ndim here for consistency.

Comment on lines 750 to 751
# Reshape binary output from `(n_samples,)` to `(n_samples, 1)`
if predictions.ndim == 1:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# Reshape binary output from `(n_samples,)` to `(n_samples, 1)`
if predictions.ndim == 1:
if predictions.ndim == 1:
# Reshape binary output from `(n_samples,)` to `(n_samples, 1)`

@glemaitre
Copy link
Member

So codecov highlights that line 369 in calibration.py is not really tested.

This is weird because with a `print is see that the following tests pass by this line:

  • test_calibration_prefit
  • test_calibration_dict_pipeline
  • test_calibration_attributes
  • test_calibration_votingclassifier

@lucyleeow
Copy link
Member Author

lucyleeow commented Nov 22, 2023

Really? I tried with a print and I didn't get any tests in test_calibration.py?

Ah nevermind, i tested wrong. Will ignore then!

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 on my side. Thanks @lucyleeow

@lucyleeow lucyleeow added the Waiting for Second Reviewer First reviewer is done, need a second one! label Nov 23, 2023
@glemaitre
Copy link
Member

Uhm actually there is an error that should be linked to the shape where we are missing a reshape. Since the tests were passing before, I would check the last commit done.

@lucyleeow
Copy link
Member Author

lucyleeow commented Nov 24, 2023

So when we use cross_val_predict to get the predictions, we can't use if predictions.ndim == 1 because predict_proba will give ndim = 2. Reverted this one back to if len(self.classes_) == 2:

@glemaitre
Copy link
Member

glemaitre commented Nov 24, 2023

So when we use cross_val_predict to get the predictions, we can't use if predictions.ndim == 1 because predict_proba will give ndim = 2. Reverted this one back to if len(self.classes_) == 2:

OK this make sense. This is a viable solution then.

LGTM to me. Adding a flag to get a second review.

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.

LGTM

@thomasjpfan thomasjpfan merged commit 3287570 into scikit-learn:main Nov 24, 2023
@lucyleeow lucyleeow deleted the refact_calbclass branch November 24, 2023 22:57
@lucyleeow
Copy link
Member Author

Thanks for the review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
No Changelog Needed Refactor Code refactor Waiting for Second Reviewer First reviewer is done, need a second one!
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants