Fix: Feature Union: Checking if feautre union is fitted fails - #22953
Conversation
|
|
||
| def __sklearn_is_fitted__(self): | ||
| # Delegate whether feature union was fitted | ||
| check_is_fitted(self.transformer_list[0][1]) |
There was a problem hiding this comment.
I think we need to check all the transformers to make sure the whole FeatureUnion is fitted.
Also transformer_list can be configured with "passthrough" and "drop" as the estimator, so those needs to be avoided in this check. (In other words, transformer_list[0][1] can be string.)
There was a problem hiding this comment.
Thanks @thomasjpfan. Let me know if my fix works. Tx
|
|
||
|
|
||
| def test_feature_union_check_if_fitted(): | ||
| class Estimator(BaseEstimator): |
There was a problem hiding this comment.
We can use from sklearn.utils._testing import MinimalTransformer for a minimal transformer.
| union = clone(union) | ||
| with pytest.raises(NotFittedError): | ||
| check_is_fitted(union) | ||
|
|
||
| union.set_params(clf="drop") | ||
| check_is_fitted(union) | ||
|
|
||
| union.set_params(clf=Estimator()) | ||
| with pytest.raises(NotFittedError): | ||
| check_is_fitted(union) | ||
|
|
||
| union.fit(iris.data, iris.target) | ||
| check_is_fitted(union) |
There was a problem hiding this comment.
I think we can remove these tests. It's fundamentally testing that clone works. In other words, if clone works and the tests above pass, then these should pass as well.
There was a problem hiding this comment.
@thomasjpfan I just pushed out an update. Tx
thomasjpfan
left a comment
There was a problem hiding this comment.
Minor nits, otherwise LGTM
| - |Fix| Checking if :class:`pipeline.FeatureUnion` was fitted was broken. | ||
| :pr:`22953` by :user:`randomgeek78 <randomgeek78>` provides a fix. |
There was a problem hiding this comment.
We can be more explicit about the fix:
| - |Fix| Checking if :class:`pipeline.FeatureUnion` was fitted was broken. | |
| :pr:`22953` by :user:`randomgeek78 <randomgeek78>` provides a fix. | |
| - |Fix| Defines `__sklearn_is_fitted__` in | |
| :class:`pipeline.FeatureUnion` to return correct result | |
| with :func:`utils.validation.check_is_fitted`. | |
| :pr:`22953` by :user:`randomgeek78 <randomgeek78>` |
| check_is_fitted(pipeline) | ||
|
|
||
|
|
||
| def test_feature_union_check_if_fitted(): |
There was a problem hiding this comment.
| def test_feature_union_check_if_fitted(): | |
| def test_feature_union_check_if_fitted(): | |
| """Check __sklearn_is_fitted__ is defined correctly.""" |
There was a problem hiding this comment.
Great feedback. Just pushed an update. Tx
There was a problem hiding this comment.
@thomasjpfan I think linting is currently broken due to a black dependency.
There was a problem hiding this comment.
@thomasjpfan This PR does not build because of this message: Could not find a version that satisfies the requirement black==22.1.0. Do you know anything about this? Thanks.
|
@adrinjalali @ogrisel Could you help review this PR? It requires a second approval. Thanks! |
adrinjalali
left a comment
There was a problem hiding this comment.
we should also test how drop works here.
| for _, transformer, _ in self._iter(): | ||
| check_is_fitted(transformer) | ||
| return True |
There was a problem hiding this comment.
the issue with this implementation is that we allow all transformers to be None in FeatureUnion. fit's code is:
transformers = self._parallel_func(X, y, fit_params, _fit_one)
if not transformers:
# All transformers are None
return self
self._update_transformer_list(transformers)
return selfwouldn't it be better here to add a private _fitted attribute in fit, and in this method to check for that?
If we want to also support pre-fitting transformers and then putting them in a FeatureUnion, we could do something like:
if getattr(self, '_fitted', None) or all_transformers_fitted:
return TrueThere was a problem hiding this comment.
I think the current solution works because _iter does not yield dropped estimators:
scikit-learn/sklearn/pipeline.py
Lines 1052 to 1057 in 231dd7b
(Also FeatureUnion no longer supports None and uses `"drop" instead. )
There was a problem hiding this comment.
@adrinjalali If all the transformers are None, it is stateless and is by definition fitted (imho). This is the approach that is also taken by FunctionTransformer:
def __sklearn_is_fitted__(self):
"""Return True since FunctionTransfomer is stateless."""
return TrueInstead of all transformers being None, I am fine returning true even if all transformers are stateless (like FunctionTransformer). What do you think? If you feel strongly about introducing state to FeatureUnion through _fitted, I can push a PR with the required changes. Thanks for your feedback.
There was a problem hiding this comment.
Regarding drop, what would you like to test for it? As you know, transformers that are dropped are not yielded by _iter.
There was a problem hiding this comment.
@thomasjpfan Thanks. I didn't see your message while composing my responses.
…learn#22953) * Fix to checking if feature union was fitted * Updated test and linting * Added entry in whats new 1.1 * Expanded tests to all transformers and strings * Expanded test * Linting fix * Updated tests * Addressed additional feedback
…learn#22953) * Fix to checking if feature union was fitted * Updated test and linting * Added entry in whats new 1.1 * Expanded tests to all transformers and strings * Expanded test * Linting fix * Updated tests * Addressed additional feedback
Currently, checking if the feature union is fitted fails. This is because it does not have any attributes of its own that end with '_' and it does not have sklearn_is_fitted implemented.
This PR delegates checking if a feature union was fitted to one of its sub-transformers.
Reference Issues/PRs
What does this implement/fix? Explain your changes.
Any other comments?