diff --git a/sklearn/linear_model/_glm/_newton_solver.py b/sklearn/linear_model/_glm/_newton_solver.py index c5c940bed6c39..24085f903882f 100644 --- a/sklearn/linear_model/_glm/_newton_solver.py +++ b/sklearn/linear_model/_glm/_newton_solver.py @@ -14,6 +14,7 @@ from ..._loss.loss import HalfSquaredError from ...exceptions import ConvergenceWarning +from ...utils.fixes import _get_additional_lbfgs_options_dict from ...utils.optimize import _check_optimize_result from .._linear_loss import LinearModelLoss @@ -187,9 +188,9 @@ def fallback_lbfgs_solve(self, X, y, sample_weight): options={ "maxiter": max_iter, "maxls": 50, # default is 20 - "iprint": self.verbose - 1, "gtol": self.tol, "ftol": 64 * np.finfo(np.float64).eps, + **_get_additional_lbfgs_options_dict("iprint", self.verbose - 1), }, args=(X, y, sample_weight, self.l2_reg_strength, self.n_threads), ) diff --git a/sklearn/linear_model/_glm/glm.py b/sklearn/linear_model/_glm/glm.py index 7f138f420dc36..8ba24878b95b2 100644 --- a/sklearn/linear_model/_glm/glm.py +++ b/sklearn/linear_model/_glm/glm.py @@ -21,6 +21,7 @@ from ...utils import check_array from ...utils._openmp_helpers import _openmp_effective_n_threads from ...utils._param_validation import Hidden, Interval, StrOptions +from ...utils.fixes import _get_additional_lbfgs_options_dict from ...utils.optimize import _check_optimize_result from ...utils.validation import _check_sample_weight, check_is_fitted, validate_data from .._linear_loss import LinearModelLoss @@ -273,12 +274,12 @@ def fit(self, X, y, sample_weight=None): options={ "maxiter": self.max_iter, "maxls": 50, # default is 20 - "iprint": self.verbose - 1, "gtol": self.tol, # The constant 64 was found empirically to pass the test suite. # The point is that ftol is very small, but a bit larger than # machine precision for float64, which is the dtype used by lbfgs. "ftol": 64 * np.finfo(float).eps, + **_get_additional_lbfgs_options_dict("iprint", self.verbose - 1), }, args=(X, y, sample_weight, l2_reg_strength, n_threads), ) diff --git a/sklearn/linear_model/_huber.py b/sklearn/linear_model/_huber.py index 51f24035a3c83..87e735ec998db 100644 --- a/sklearn/linear_model/_huber.py +++ b/sklearn/linear_model/_huber.py @@ -10,6 +10,7 @@ from ..utils._mask import axis0_safe_slice from ..utils._param_validation import Interval from ..utils.extmath import safe_sparse_dot +from ..utils.fixes import _get_additional_lbfgs_options_dict from ..utils.optimize import _check_optimize_result from ..utils.validation import _check_sample_weight, validate_data from ._base import LinearModel @@ -329,7 +330,11 @@ def fit(self, X, y, sample_weight=None): method="L-BFGS-B", jac=True, args=(X, y, self.epsilon, self.alpha, sample_weight), - options={"maxiter": self.max_iter, "gtol": self.tol, "iprint": -1}, + options={ + "maxiter": self.max_iter, + "gtol": self.tol, + **_get_additional_lbfgs_options_dict("iprint", -1), + }, bounds=bounds, ) diff --git a/sklearn/linear_model/_logistic.py b/sklearn/linear_model/_logistic.py index b85c01ee69f9e..2c564bb1a8b5a 100644 --- a/sklearn/linear_model/_logistic.py +++ b/sklearn/linear_model/_logistic.py @@ -30,6 +30,7 @@ ) from ..utils._param_validation import Hidden, Interval, StrOptions from ..utils.extmath import row_norms, softmax +from ..utils.fixes import _get_additional_lbfgs_options_dict from ..utils.metadata_routing import ( MetadataRouter, MethodMapping, @@ -464,9 +465,9 @@ def _logistic_regression_path( options={ "maxiter": max_iter, "maxls": 50, # default is 20 - "iprint": iprint, "gtol": tol, "ftol": 64 * np.finfo(float).eps, + **_get_additional_lbfgs_options_dict("iprint", iprint), }, ) n_iter_i = _check_optimize_result( diff --git a/sklearn/neighbors/_nca.py b/sklearn/neighbors/_nca.py index a4ef3c303b851..8383f95338932 100644 --- a/sklearn/neighbors/_nca.py +++ b/sklearn/neighbors/_nca.py @@ -25,6 +25,7 @@ from ..preprocessing import LabelEncoder from ..utils._param_validation import Interval, StrOptions from ..utils.extmath import softmax +from ..utils.fixes import _get_additional_lbfgs_options_dict from ..utils.multiclass import check_classification_targets from ..utils.random import check_random_state from ..utils.validation import check_array, check_is_fitted, validate_data @@ -312,7 +313,10 @@ def fit(self, X, y): "jac": True, "x0": transformation, "tol": self.tol, - "options": dict(maxiter=self.max_iter, disp=disp), + "options": dict( + maxiter=self.max_iter, + **_get_additional_lbfgs_options_dict("disp", disp), + ), "callback": self._callback, } diff --git a/sklearn/neural_network/_multilayer_perceptron.py b/sklearn/neural_network/_multilayer_perceptron.py index a8a00fe3b4ac5..e8260164202e6 100644 --- a/sklearn/neural_network/_multilayer_perceptron.py +++ b/sklearn/neural_network/_multilayer_perceptron.py @@ -31,6 +31,7 @@ ) from ..utils._param_validation import Interval, Options, StrOptions from ..utils.extmath import safe_sparse_dot +from ..utils.fixes import _get_additional_lbfgs_options_dict from ..utils.metaestimators import available_if from ..utils.multiclass import ( _check_partial_fit_first_call, @@ -585,8 +586,8 @@ def _fit_lbfgs( options={ "maxfun": self.max_fun, "maxiter": self.max_iter, - "iprint": iprint, "gtol": self.tol, + **_get_additional_lbfgs_options_dict("iprint", iprint), }, args=( X, diff --git a/sklearn/utils/fixes.py b/sklearn/utils/fixes.py index 624938d6f0a82..5ceb9930b993b 100644 --- a/sklearn/utils/fixes.py +++ b/sklearn/utils/fixes.py @@ -394,6 +394,16 @@ def _in_unstable_openblas_configuration(): return False +# TODO: Remove when Scipy 1.15 is the minimum supported version. In scipy 1.15, +# the internal info details (via 'iprint' and 'disp' options) were dropped, +# following the LBFGS rewrite from Fortran to C, see +# https://github.com/scipy/scipy/issues/23186#issuecomment-2987801035. For +# scipy 1.15, 'iprint' and 'disp' have no effect and for scipy >= 1.16 a +# DeprecationWarning is emitted. +def _get_additional_lbfgs_options_dict(key, value): + return {} if sp_version >= parse_version("1.15") else {key: value} + + # TODO(pyarrow): Remove when minimum pyarrow version is 17.0.0 PYARROW_VERSION_BELOW_17 = False try: