Fix Issue 2114: Accept estimators that don't inherit from sklearn.BaseEstimator in tabular_pipeline - #2225
Fix Issue 2114: Accept estimators that don't inherit from sklearn.BaseEstimator in tabular_pipeline#2225ljdyer wants to merge 12 commits into
Conversation
jeromedockes
left a comment
There was a problem hiding this comment.
thank you very much @ljdyer !! this looks great.
- I think we can remove the parameters that override the kind of estimator for now, because we would need to discuss more what are the classes of estimator and the parameter names
- the tests can be simplified slightly
looks great otherwise, thanks :)
| means 1 unless in a joblib ``parallel_backend`` context. ``-1`` means using all | ||
| processors. | ||
|
|
||
| is_tree_ensemble_estimator : bool, default=None |
There was a problem hiding this comment.
sorry that wasn't clear from the issue discussion, but let's keep things simple for now and not add those extra parameters
| " or 'classifier' as its first argument." | ||
| "tabular_pipeline expects a scikit-learn compatible estimator as its first" | ||
| " argument. The estimator object must have 'get_params' and 'set_params'" | ||
| " attributes." |
There was a problem hiding this comment.
can we add 'fit' and 'predict' to the list? thanks!
There was a problem hiding this comment.
No problem, will do.
Just want to point out that this makes the requirements a bit more stringent that previously with BaseEstimator inheritance. For example AgglomerativeClustering inherits from BaseEstimator and has get_params/set_params/fit but does not have have predict. But perhaps we want to exclude it or are not concerned because it is neither a regressor or a classifier?
Another example that has the first three but does not have predict is PCA, though this does not inherit from BaseEstimator.
| @@ -285,7 +311,7 @@ def tabular_pipeline(estimator, *, n_jobs=None): | |||
| if not is_estimator_from_tabicl: | |||
| if not get_tags(estimator).input_tags.allow_nan: | |||
There was a problem hiding this comment.
here we could be a bit more lenient on input estimators that don't implement the full scikit-learn api with something like
try:
allow_nan = get_tags(estimator).input_tags.allow_nan
except AttributeError:
allow_nan = False # assume we need imputation
if not allow_nan:
steps.append(SimpleImputer(add_indicator=True))
There was a problem hiding this comment.
Looks good - implementing more or less as you wrote
| ) | ||
|
|
||
|
|
||
| def fake_get_tags(_): |
There was a problem hiding this comment.
with the catching tag attribute error suggested below we can remove this
| tabular_pipeline(sklearn_incompatible_learner) | ||
|
|
||
|
|
||
| def test_sklearn_compatible_learner_succeeds(monkeypatch): |
There was a problem hiding this comment.
can we create a real estimator instead,
class Regressor:
def fit(self, X, y=None): return self
def predict(self, X): return np.zeros(X.shape[0])
def get_params(self): return {}
def set_params(self, **params): return self
then make a pipeline and check we can call fit and predict on toy data and get the expected prediction? thanksThere was a problem hiding this comment.
I was able to add the fit test with a pandas DataFrame, but with predict we run into issues as check_is_fitted gets called. Could monkeypatch, but in the interest of test simplicity could we skip the test with toy data?
| def test_tree_ensemble_treatment_for_any_random_forest(monkeypatch): | ||
| """Test that special treatment for tree ensemble models is applied when | ||
| substring 'RandomForest' appears in estimator class name""" | ||
| IAmARandomForestEstimator = type( |
There was a problem hiding this comment.
in those instead of calling type we can subclass the dummy regressor defined earlier eg
class UserRandomForest(Regressor): pass
There was a problem hiding this comment.
Great advice - makes the tests much simpler
| if not isinstance(estimator, BaseEstimator): | ||
|
|
||
| is_scikit_learn_compatible = hasattr(estimator, "get_params") and hasattr( | ||
| estimator, "set_params" |
There was a problem hiding this comment.
This accepts non-callable attributes, so an object with get_params = 1 and set_params = 1 passes the guard but the returned pipeline raises TypeError as soon as get_params() or clone() touches it. Should these checks use callable(getattr(..., None)) instead?
There was a problem hiding this comment.
Hi Sanjay, I agree that it's fine to check for callable( as well. Extending this to fit and predict too. Only caveat is what I wrote in my response to Jerome's comment above about estimators that don't implement predict.
|
Thanks @jeromedockes for the feedback! I'll be working on this again in the next few days. Cheers |
|
Thank you @jeromedockes for the review. I have resubmitted so please review again when convenient. |
|
thanks a lot for making the changes @ljdyer ! I will look at it again in the next few days :) |
Fix Issue 2114: Accept estimators that don't inherit from sklearn.BaseEstimator in tabular_pipeline
Description
All suggestions in the issue comments made sense, so I implemented them as specified. There were three main changes:
is_scikit_learn_compatiblevariableisinstance(estimator, type)check carried out beforeis_scikit_learn_compatiblecheck since the validity check no longer makes sense for types. Class name no longer included in error message, since it may be displayed even for invalid types. Still serves its purpose of helping the user to spot the mistake.test_sklearn_incompatible_learner_failsandtest_sklearn_incompatible_learner_succeeds) with dummy classes that defineget_params/set_params.isinstancechecks with broader substring match checks to determine need to HGBT/tree ensemble treatment (HistGradientBoosting,RandomForest)XGBandLGBMincluded in substrings for tree ensemble check, for compatibility with xgboost and lightgbm libraries_is_hgbt_estimator,_is_tree_ensemble_estimator)RandomForest/XGBin their namesis_hgbt_estimator/is_tree_ensemble_estimatorparameters, per the suggestion in the issue commentsis_tree_ensemble_estimatorresults in tree ensemble treatment in a case where this would not be triggered by the default heuristicAddresses #2114
Checklist
How Has This Been Tested?
All existing and new tests in test_tabular_pipeline.py
(+ entire test suite)
AI Disclosure