From 4cbfc843c2004b4b4f5280a2ebc28697e644e6e9 Mon Sep 17 00:00:00 2001 From: Claudio Salvatore Arcidiacono <22871978+ClaudioSalvatoreArcidiacono@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:02:18 +0100 Subject: [PATCH] Remove n_iter_no_change parameter --- python-package/lightgbm/dask.py | 3 -- python-package/lightgbm/sklearn.py | 48 +++++++++++++++-------- tests/python_package_test/test_sklearn.py | 1 - 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/python-package/lightgbm/dask.py b/python-package/lightgbm/dask.py index 43b4b1854967..cc73ef9e751a 100644 --- a/python-package/lightgbm/dask.py +++ b/python-package/lightgbm/dask.py @@ -1137,7 +1137,6 @@ def __init__( client: Optional[Client] = None, *, early_stopping: bool = False, - n_iter_no_change: int = 10, validation_fraction: Optional[float] = 0.1, **kwargs: Any, ): @@ -1344,7 +1343,6 @@ def __init__( client: Optional[Client] = None, *, early_stopping: bool = False, - n_iter_no_change: int = 10, validation_fraction: Optional[float] = 0.1, **kwargs: Any, ): @@ -1515,7 +1513,6 @@ def __init__( client: Optional[Client] = None, *, early_stopping: bool = False, - n_iter_no_change: int = 10, validation_fraction: Optional[float] = 0.1, **kwargs: Any, ): diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 8d7f0f71c206..ff65bf657c5c 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -508,8 +508,7 @@ def __init__( n_jobs: Optional[int] = None, importance_type: str = "split", *, - early_stopping: bool = False, - n_iter_no_change: int = 10, + early_stopping: Union[bool, int] = False, validation_fraction: Optional[float] = 0.1, **kwargs: Any, ): @@ -591,12 +590,15 @@ def __init__( The type of feature importance to be filled into ``feature_importances_``. If 'split', result contains numbers of times the feature is used in a model. If 'gain', result contains total gains of splits which use the feature. - early_stopping : bool, optional (default=False) - Whether to enable early stopping. If set to True, training will stop if the validation score does not improve - for a specified number of rounds (controlled by `n_iter_no_change`). - n_iter_no_change : int, optional (default=10) - If early stopping is enabled, this parameter specifies the number of iterations with no - improvement after which training will be stopped. + early_stopping : bool, optional (default=False) Whether to enable scikit-learn-style early + stopping. If set to ``True` and no other validation set is passed to ``fit()``, a new + validation set will be created by randomly sampling ``validation_fraction`` rows from + the training data ``X`` passed to ``fit()``. Training will stop if the validation score + does not improve for a specific number of rounds (controlled by ``n_iter_no_change``). + This parameter is here for compatibility with ``scikit-learn``'s + ``HistGradientBoosting`` estimators. it does not affect other ``lightgbm``-specific + early stopping mechanisms, like passing the ``lgb.early_stopping`` callback and + validation sets to the ``eval_set`` argument of `fit()`. validation_fraction : float or None, optional (default=0.1) Proportion of training data to set aside as validation data for early stopping. If None, early stopping is done on @@ -666,7 +668,6 @@ def __init__( self.n_jobs = n_jobs self.importance_type = importance_type self.early_stopping = early_stopping - self.n_iter_no_change = n_iter_no_change self.validation_fraction = validation_fraction self._Booster: Optional[Booster] = None self._evals_result: _EvalResultDict = {} @@ -834,18 +835,12 @@ def _process_params(self, stage: str) -> Dict[str, Any]: params.pop("n_estimators", None) params.pop("class_weight", None) params.pop("validation_fraction", None) - params.pop("early_stopping", None) - params.pop("n_iter_no_change", None) if isinstance(params["random_state"], np.random.RandomState): params["random_state"] = params["random_state"].randint(np.iinfo(np.int32).max) elif isinstance(params["random_state"], np.random.Generator): params["random_state"] = int(params["random_state"].integers(np.iinfo(np.int32).max)) - params = _choose_param_value("early_stopping_round", params, self.n_iter_no_change) - if self.early_stopping is not True: - params["early_stopping_round"] = None - if self._n_classes > 2: for alias in _ConfigAliases.get("num_class"): params.pop(alias, None) @@ -878,7 +873,28 @@ def _process_params(self, stage: str) -> Dict[str, Any]: params = _choose_param_value("num_threads", params, self.n_jobs) params["num_threads"] = self._process_n_jobs(params["num_threads"]) - return params + if not isinstance(self.early_stopping, bool) and isinstance(self.early_stopping, int): + _log_warning( + f"Found 'early_stopping={self.early_stopping}' passed through keyword arguments. " + "Future versions of 'lightgbm' will not allow this, as scikit-learn expects keyword argument " + "'early_stopping' to be a boolean indicating whether or not to perform early stopping with " + "a randomly-sampled validation set. To set the number of early stopping rounds, and suppress " + f"this warning, pass early_stopping_rounds={self.early_stopping} instead." + ) + params = _choose_param_value( + main_param_name="early_stopping_round", params=params, default_value=self.early_stopping + ) + + params.pop("early_stopping", None) + + if isinstance(self.early_stopping, bool) and self.early_stopping is True: + default_early_stopping_round = 10 + else: + default_early_stopping_round = None + + return _choose_param_value( + main_param_name="early_stopping_round", params=params, default_value=default_early_stopping_round + ) def _process_n_jobs(self, n_jobs: Optional[int]) -> int: """Convert special values of n_jobs to their actual values according to the formulas that apply. diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index 4366ba2c9b37..cfbb0e3d9f68 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -1274,7 +1274,6 @@ def fit_and_check(eval_set_names, metric_names, assumed_iteration, first_metric_ "verbose": -1, "seed": 123, "early_stopping_rounds": 5, - "early_stopping": True, } # early stop should be supported via global LightGBM parameter params_fit = {"X": X_train, "y": y_train}