Skip to content

ENH Add chain_method to ClassifierChain #27700

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 20 commits into from
Feb 23, 2024

Conversation

lucyleeow
Copy link
Member

@lucyleeow lucyleeow commented Nov 1, 2023

Reference Issues/PRs

Fixes #9247
Closes #9316 (supersedes)

What does this implement/fix? Explain your changes.

Add chain_method to ClassifierChain. Supports {'predict', 'predict_proba', 'predict_log_proba', 'decision_function'} (as suggested in #9316 (review))

Any other comments?

  • Was not sure about naming to distinguish feature input prediction vs output prediction variables, happy to change.

Copy link

github-actions bot commented Nov 1, 2023

✔️ Linting Passed

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

Generated for commit: 969d889. Link to the linter CI: here

@glemaitre glemaitre self-requested a review November 1, 2023 11:36
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.

A couple of comments regarding the code.

@@ -844,6 +889,10 @@ class labels for each estimator in the chain.
order_ : list
The order of labels in the classifier chain.

chain_method_ : str
Copy link
Member

Choose a reason for hiding this comment

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

This would be provided when using _return_response_method_used in _get_response_values.

Comment on lines 673 to 676
if sp.issparse(X):
X_aug = sp.hstack((X, previous_predictions))
else:
X_aug = np.hstack((X, previous_predictions))
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
if sp.issparse(X):
X_aug = sp.hstack((X, previous_predictions))
else:
X_aug = np.hstack((X, previous_predictions))
hstack = sp.hstack if sp.issparse(X) else np.hstack
X_aug = hstack([X, previous_prediction])

Is it possible to get a previous_prediction that is dense and X being sparse and thus the stacking will make something not expected?

Copy link
Member Author

Choose a reason for hiding this comment

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

AFAICT it seems stacking a sparse with dense via sp.hstack gives you a sparse array (even though sp.hstack is not documented to support dense):

In [34]: from scipy.sparse import coo_matrix, hstack
    ...: 
    ...: A = coo_matrix([[1, 2], [3, 4]])

In [35]: B = np.zeros((2,2))

In [36]: hstack([A,B])
Out[36]: 
<2x4 sparse matrix of type '<class 'numpy.float64'>'
        with 4 stored elements in COOrdinate format>

Maybe from here: https://github.com/scipy/scipy/blob/f990b1d2471748c79bc4260baf8923db0a5248af/scipy/sparse/_construct.py#L654 ?

Copy link
Member

Choose a reason for hiding this comment

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

Interesting. Probably this is the np.hstack that did not behave properly then.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes you may be right. The documentation does not mention that it would support sparse + dense.

I can't find any reference about whether this is intentional. This scipy PR mentions that the stack functions converts everything to COO format (but we knew this from the code). Also, I found this stackoverflow answer saying that you can stack sparse + dense.

Happy to add previous_prediction sparse conversion ? (Though this would be difficult to test since sp.hstack allows sparse + dense)

Copy link
Member Author

Choose a reason for hiding this comment

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

Also a quick look at our code, I could not find any other cases where it would be possible to be stacking dense + sparse, if that helps the decision in whether we need to covert to sparse.

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

lucyleeow commented Nov 2, 2023

@glemaitre tests are currently failing because _get_response_values does not properly support 'predict_log_proba'. Maybe we add could support this in another PR? (or alternatively do not support 'predict_log_proba' in chain_method in this PR) ?

@glemaitre
Copy link
Member

I did not think about predict_log_prob. I will open a PR to add the support. I think it should behave exactly like predict_proba (with an additional log when reverting the proba :))

@glemaitre
Copy link
Member

The funny part about predict_log_proba is that ClassifierChain does not support it. So it is a bit fun to request it as a chain_method but not being able to get it a predict method :). I added the support in #27720 because it is straightforward.

@lucyleeow
Copy link
Member Author

Yes I noticed that too, I mostly added it because it was suggested by Joel here: #9316 (review)

I can add support for predict_log_proba here? Maybe that was intended?

@glemaitre
Copy link
Member

I can add support for predict_log_proba here? Maybe that was intended?

I open #27720 already.

@lucyleeow
Copy link
Member Author

Ah amazing, thanks!

@lucyleeow
Copy link
Member Author

Sorry I missed that in your comment!

@glemaitre
Copy link
Member

Since we merged the blocking PR, I can give a review to this one once this is ready. Ping me @lucyleeow.

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.

Another round of review but it looks good on my side.

@lucyleeow
Copy link
Member Author

Thanks @glemaitre , changes made. I think we did not decide if should convert to sparse: #27700 (comment)

I am fine with changing this in a separate PR as well, since this is not strictly related to this PR

@glemaitre
Copy link
Member

I am fine with changing this in a separate PR as well, since this is not strictly related to this PR

Let's keep the same behaviour here.

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 @lucyleeow

thomasjpfan
thomasjpfan previously approved these changes Feb 23, 2024
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.

Minor comments on moving to v1.5, otherwise LGTM

:mod:`sklearn.multioutput`
..........................

- |Enhancement| `chain_method` parameter added to `:class:``multioutput.ClassifierChain`.
Copy link
Member

Choose a reason for hiding this comment

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

This needs to be moved to 1.5

preference. The method used corresponds to the first method in
the list that is implemented by `base_estimator`.

.. versionadded:: 1.4
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
.. versionadded:: 1.4
.. versionadded:: 1.5

@thomasjpfan thomasjpfan dismissed their stale review February 23, 2024 00:25

Looks like the CI is failing. It has been a while, so I do not know if it is related.

@lucyleeow
Copy link
Member Author

Updated, thanks @thomasjpfan !

@thomasjpfan thomasjpfan merged commit 4e82537 into scikit-learn:main Feb 23, 2024
@lucyleeow lucyleeow deleted the chain_method branch February 24, 2024 05:21
@lucyleeow
Copy link
Member Author

Thanks @glemaitre and @thomasjpfan for the reviews!

@lesteve
Copy link
Member

lesteve commented Feb 24, 2024

It seems like PR broke main somehow see https://dev.azure.com/scikit-learn/scikit-learn/_build/results?buildId=64417&view=results.

The error does reminded me of some issues that were seen in #27576.

NotImplementedError: We have not yet implemented 1D sparse slices; please index using explicit indices, e.g. `x[:, [0]]`

Maybe @StefanieSenger has some insights into this.

@StefanieSenger
Copy link
Contributor

Yes, I do: :) Since merging this PR estimator_checks also check for sparse arrays, and for RegressorChain we had to convert the format of X into sparse coo array to circumvent a bug in scipy.

See this diff for more info.

Can I fix that?

@StefanieSenger
Copy link
Contributor

I made #28524, please have a look @lesteve.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
module:multioutput 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.

Chain on decision_function or predict_proba in ClassifierChain
5 participants