Skip to content

FEAT add routing to AdaBoost's fit #24026

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

Closed

Conversation

adrinjalali
Copy link
Member

@adrinjalali adrinjalali commented Jul 27, 2022

AdaBoost* ended up being a non-trivial one after-all.

The classifier passes sample_weight to the base_estimator, and always needs to do that, so the requirement in this PR is that the user should set that request for the base_estimator if they're passing it.

But the regressor doesn't pass weights and only uses it internally. So it's a consumer and never passes sample_weight to the base_estimator even if it's requested.

This PR doesn't introduce **params to decision_funcsion, predict, ... since that would really complicate things. I tried, and decided to leave that for a future release/PR.

TODO:

  • add tests

Comment on lines +167 to +171
# we then remove sample_weight from the routed parameters since we will
# pass it explicitly to the fit method of the base_estimator. It was
# included above only to have `process_routing` to do the validation.
if "sample_weight" in routed_params.base_estimator.fit:
del routed_params.base_estimator.fit["sample_weight"]
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure which one's best here?

  • we pass sample_weight to routing as is implemented here, and it would raise if the user hasn't requested sample weight for their base_estimator, even if they haven't provided sample_weight. While working with @glemaitre we thought this makes sense since AdaBoostClassifier always passes sample_weight to the base_estimator, which means the user should always request it explicitly.
  • we could only pass sample_weight to routing if it's explicitly provided by the user. This would make users' code not change in most cases since they mostly pass no explicit sample_weight, and they pass a simple consumer estimator as the base_estimator. Their code would still break if they pass a router as a base_estimator and the sub-estimators of that router are not requesting sample_weight.

Which one do you think makes more sense?

cc @thomasjpfan @jnothman

Copy link
Member

@thomasjpfan thomasjpfan Aug 29, 2022

Choose a reason for hiding this comment

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

If we want to be extra lenient, I say the AdaBoost will set the request of the base_estimator if the sample_weight request is not set yet. Assuming base_estimator is not requesting sample_weight, we can be consistent behavior to main:

  1. If base_estimator is a classifier, AdaBoostClassifer sets the metadata request to True.
  2. If base_estimator is a regressor, AdaBoostClasifer sets the meatadata request to False.

Edit: This suggestion does not work

Copy link
Member Author

Choose a reason for hiding this comment

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

We can't do that since routers don't expose a method to set requests. So that would only work if the user passes a simple consumer estimator.

Comment on lines +151 to +154
if is_regressor(self):
routed_params = process_routing(
obj=self, method="fit", other_params=fit_params
)
Copy link
Member Author

Choose a reason for hiding this comment

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

right now AdaBoostRegressor doesn't pass sample_weight to base_estimator even if the user explicitly passes them. Should this behavior be changed?

Copy link
Member

@thomasjpfan thomasjpfan Aug 29, 2022

Choose a reason for hiding this comment

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

If base_estimator is configured to require sample_weight, I think we pass it in. If it is not configured, we do not pass it in, which is consistent with the current behavior.

Copy link
Member

Choose a reason for hiding this comment

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

Thinking it over, I think want to go with a more lenient approach: #24026 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

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

But currently AdaBoostRegressor never passes sample_weight to the base_estimator_.

Copy link
Member

@thomasjpfan thomasjpfan Aug 30, 2022

Choose a reason for hiding this comment

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

AdaBoostRegressor currently does not pass in sample_weight, but if base_estimator requested it I think sample_weight should be passed in.

If AdaBoostRegressor does not pass in sample_weight to base_estimator when requested, then the API seems inconsistent. A router can ignore metadata requested by the base_estimator.

@@ -1089,7 +1201,7 @@ def _boost(self, iboost, X, y, sample_weight, random_state):
# for all samples in the training set
X_ = _safe_indexing(X, bootstrap_idx)
y_ = _safe_indexing(y, bootstrap_idx)
estimator.fit(X_, y_)
estimator.fit(X_, y_, **fit_params)
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 current behavior is that the regressor never passes sample_weights to the base_estimator.

Comment on lines +151 to +154
if is_regressor(self):
routed_params = process_routing(
obj=self, method="fit", other_params=fit_params
)
Copy link
Member Author

Choose a reason for hiding this comment

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

But currently AdaBoostRegressor never passes sample_weight to the base_estimator_.

Comment on lines +167 to +171
# we then remove sample_weight from the routed parameters since we will
# pass it explicitly to the fit method of the base_estimator. It was
# included above only to have `process_routing` to do the validation.
if "sample_weight" in routed_params.base_estimator.fit:
del routed_params.base_estimator.fit["sample_weight"]
Copy link
Member Author

Choose a reason for hiding this comment

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

We can't do that since routers don't expose a method to set requests. So that would only work if the user passes a simple consumer estimator.

@jnothman
Copy link
Member

AdaBoostClassifier is a bit unusual because the meta-estimator needs to send sample_weight to the base estimator, regardless of whether weights are passed in. As such, AdaBoostClassifier might be seen as a consumer of the sample_weight which it then modifies.

What it is doing with relation to its base estimator and sample_weight is certainly not routing, but rather producing. Thus a consumer should only have to set_fit_request(sample_weight=True) to be passed into AdaBoostClassifier in the case that it is wrapped in a Pipeline or similar... which makes sense! How to explain this apparent "exception" in the documentation? I'm not sure...

@thomasjpfan thomasjpfan deleted the branch scikit-learn:sample-props June 2, 2023 20:43
@thomasjpfan thomasjpfan closed this Jun 2, 2023
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.

3 participants