Skip to content

Commit c64eeb5

Browse files
Copilotthinkall
andauthored
Document that final_estimator parameters in ensemble are not auto-tuned (#1499)
* Initial plan * Document final_estimator parameter behavior in ensemble configuration Co-authored-by: thinkall <3197038+thinkall@users.noreply.github.com> * Address code review feedback: fix syntax in examples and use float comparison Co-authored-by: thinkall <3197038+thinkall@users.noreply.github.com> * Run pre-commit to fix formatting issues Co-authored-by: thinkall <3197038+thinkall@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: thinkall <3197038+thinkall@users.noreply.github.com> Co-authored-by: Li Jiang <bnujli@gmail.com>
1 parent bf35f98 commit c64eeb5

File tree

3 files changed

+82
-3
lines changed

3 files changed

+82
-3
lines changed

flaml/automl/automl.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ def custom_metric(
176176
and 'final_estimator' to specify the passthrough and
177177
final_estimator in the stacker. The dict can also contain
178178
'n_jobs' as the key to specify the number of jobs for the stacker.
179+
Note: The hyperparameters of a custom 'final_estimator' are NOT
180+
automatically tuned. If you provide an estimator instance (e.g.,
181+
CatBoostClassifier()), it will use the parameters you specified
182+
or their defaults. If 'final_estimator' is not provided, the best
183+
model found during the search will be used as the final estimator.
179184
eval_method: A string of resampling strategy, one of
180185
['auto', 'cv', 'holdout'].
181186
split_ratio: A float of the valiation data percentage for holdout.
@@ -1827,6 +1832,11 @@ def custom_metric(
18271832
and 'final_estimator' to specify the passthrough and
18281833
final_estimator in the stacker. The dict can also contain
18291834
'n_jobs' as the key to specify the number of jobs for the stacker.
1835+
Note: The hyperparameters of a custom 'final_estimator' are NOT
1836+
automatically tuned. If you provide an estimator instance (e.g.,
1837+
CatBoostClassifier()), it will use the parameters you specified
1838+
or their defaults. If 'final_estimator' is not provided, the best
1839+
model found during the search will be used as the final estimator.
18301840
eval_method: A string of resampling strategy, one of
18311841
['auto', 'cv', 'holdout'].
18321842
split_ratio: A float of the valiation data percentage for holdout.
@@ -3147,6 +3157,10 @@ def _search(self):
31473157
# the total degree of parallelization = parallelization degree per estimator * parallelization degree of ensemble
31483158
)
31493159
if isinstance(self._ensemble, dict):
3160+
# Note: If a custom final_estimator is provided, it is used as-is without
3161+
# hyperparameter tuning. The user is responsible for setting appropriate
3162+
# parameters or using defaults. If not provided, the best model found
3163+
# during the search (self._trained_estimator) is used.
31503164
final_estimator = self._ensemble.get("final_estimator", self._trained_estimator)
31513165
passthrough = self._ensemble.get("passthrough", True)
31523166
ensemble_n_jobs = self._ensemble.get("n_jobs", ensemble_n_jobs)

test/automl/test_multiclass.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,49 @@ def test_ensemble(self):
181181
}
182182
automl.fit(X_train=X_train, y_train=y_train, **settings)
183183

184+
def test_ensemble_final_estimator_params_not_tuned(self):
185+
"""Test that final_estimator parameters in ensemble are not automatically tuned.
186+
187+
This test verifies that when a custom final_estimator is provided with specific
188+
parameters, those parameters are used as-is without any hyperparameter tuning.
189+
"""
190+
from sklearn.linear_model import LogisticRegression
191+
192+
automl = AutoML()
193+
X_train, y_train = load_wine(return_X_y=True)
194+
195+
# Create a LogisticRegression with specific non-default parameters
196+
custom_params = {
197+
"C": 0.5, # Non-default value
198+
"max_iter": 50, # Non-default value
199+
"random_state": 42,
200+
}
201+
final_est = LogisticRegression(**custom_params)
202+
203+
settings = {
204+
"time_budget": 5,
205+
"estimator_list": ["rf", "lgbm"],
206+
"task": "classification",
207+
"ensemble": {
208+
"final_estimator": final_est,
209+
"passthrough": False,
210+
},
211+
"n_jobs": 1,
212+
}
213+
automl.fit(X_train=X_train, y_train=y_train, **settings)
214+
215+
# Verify that the final estimator in the stacker uses the exact parameters we specified
216+
if hasattr(automl.model, "final_estimator_"):
217+
# The model is a StackingClassifier
218+
fitted_final_estimator = automl.model.final_estimator_
219+
assert (
220+
abs(fitted_final_estimator.C - custom_params["C"]) < 1e-9
221+
), f"Expected C={custom_params['C']}, but got {fitted_final_estimator.C}"
222+
assert (
223+
fitted_final_estimator.max_iter == custom_params["max_iter"]
224+
), f"Expected max_iter={custom_params['max_iter']}, but got {fitted_final_estimator.max_iter}"
225+
print("✓ Final estimator parameters were preserved (not tuned)")
226+
184227
def test_dataframe(self):
185228
self.test_classification(True)
186229

website/docs/Use-Cases/Task-Oriented-AutoML.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -469,18 +469,40 @@ To use stacked ensemble after the model search, set `ensemble=True` or a dict. W
469469
- "final_estimator": an instance of the final estimator in the stacker.
470470
- "passthrough": True (default) or False, whether to pass the original features to the stacker.
471471

472+
**Important Note:** The hyperparameters of a custom `final_estimator` are **NOT automatically tuned**. If you provide an estimator instance (e.g., `CatBoostClassifier()`), it will use the parameters you specified or their defaults. To use specific hyperparameters, you must set them when creating the estimator instance. If `final_estimator` is not provided, the best model found during the search will be used as the final estimator (recommended for best performance).
473+
472474
For example,
473475

474476
```python
475477
automl.fit(
476-
X_train, y_train, task="classification",
477-
"ensemble": {
478-
"final_estimator": LogisticRegression(),
478+
X_train,
479+
y_train,
480+
task="classification",
481+
ensemble={
482+
"final_estimator": LogisticRegression(), # Uses default LogisticRegression parameters
479483
"passthrough": False,
480484
},
481485
)
482486
```
483487

488+
Or with custom parameters:
489+
490+
```python
491+
from catboost import CatBoostClassifier
492+
493+
automl.fit(
494+
X_train,
495+
y_train,
496+
task="classification",
497+
ensemble={
498+
"final_estimator": CatBoostClassifier(
499+
iterations=100, depth=6, learning_rate=0.1
500+
),
501+
"passthrough": True,
502+
},
503+
)
504+
```
505+
484506
### Resampling strategy
485507

486508
By default, flaml decides the resampling automatically according to the data size and the time budget. If you would like to enforce a certain resampling strategy, you can set `eval_method` to be "holdout" or "cv" for holdout or cross-validation.

0 commit comments

Comments
 (0)