From cceee43b0b5af83229526f238c25940048f3d5d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Est=C3=A8ve?= Date: Wed, 19 Nov 2025 13:59:01 +0100 Subject: [PATCH 1/3] MNT Deprecate n_jobs in LogisticRegression --- sklearn/linear_model/_logistic.py | 17 +++++++++++--- sklearn/linear_model/tests/test_logistic.py | 25 +++++++++------------ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/sklearn/linear_model/_logistic.py b/sklearn/linear_model/_logistic.py index 930177103b0a6..afd85e14adf61 100644 --- a/sklearn/linear_model/_logistic.py +++ b/sklearn/linear_model/_logistic.py @@ -894,7 +894,10 @@ class of problems. *warm_start* to support *lbfgs*, *newton-cg*, *sag*, *saga* solvers. n_jobs : int, default=None - Not used at the moment. + Does not have any effect. + + .. deprecated:: 1.8 + `n_jobs` is deprecated in version 1.8 and will be removed in 1.10. l1_ratio : float, default=None The Elastic-Net mixing parameter, with ``0 <= l1_ratio <= 1``. Only @@ -1090,6 +1093,13 @@ def fit(self, X, y, sample_weight=None): "(penalty={})".format(self.penalty) ) + msg = ( + "'n_jobs' has no effect since 1.8 and will be removed in 1.10. " + f"You provided 'n_jobs={self.n_jobs}', please leave it unspecified." + ) + if self.n_jobs is not None: + warnings.warn(msg, category=FutureWarning) + if self.penalty == "elasticnet" and self.l1_ratio is None: raise ValueError("l1_ratio must be specified when penalty is elasticnet.") @@ -1139,6 +1149,7 @@ def fit(self, X, y, sample_weight=None): "value > 1e30 results in a frozen fit. Please choose another " "solver or rescale the input X." ) + # TODO(lesteve) if effective_n_jobs(self.n_jobs) != 1: warnings.warn( "'n_jobs' > 1 does not have any effect when" @@ -1183,8 +1194,8 @@ def fit(self, X, y, sample_weight=None): warm_start_coef, self.intercept_[:, np.newaxis], axis=1 ) - # TODO: deprecate n_jobs since it's not used anymore and enable multi-threading - # if benchmarks show a positive effect. + # TODO: enable multi-threading if benchmarks show a positive effect, + # see https://github.com/scikit-learn/scikit-learn/issues/32162 n_threads = 1 coefs, _, n_iter = _logistic_regression_path( diff --git a/sklearn/linear_model/tests/test_logistic.py b/sklearn/linear_model/tests/test_logistic.py index 0a064658582fa..3c8b81fb62a15 100644 --- a/sklearn/linear_model/tests/test_logistic.py +++ b/sklearn/linear_model/tests/test_logistic.py @@ -35,7 +35,7 @@ from sklearn.preprocessing import LabelEncoder, StandardScaler, scale from sklearn.svm import l1_min_c from sklearn.utils import compute_class_weight, shuffle -from sklearn.utils._testing import ignore_warnings, skip_if_no_parallel +from sklearn.utils._testing import ignore_warnings from sklearn.utils.fixes import _IS_32BIT, COO_CONTAINERS, CSR_CONTAINERS pytestmark = pytest.mark.filterwarnings( @@ -119,20 +119,6 @@ def __call__(self, model, X, y, sample_weight=None): assert mock_scorer.calls == 1 -@skip_if_no_parallel -def test_lr_liblinear_warning(): - X, y = make_classification(random_state=0) - - lr = LogisticRegression(solver="liblinear", n_jobs=2) - warning_message = ( - "'n_jobs' > 1 does not have any effect when" - " 'solver' is set to 'liblinear'. Got 'n_jobs'" - " = 2." - ) - with pytest.warns(UserWarning, match=warning_message): - lr.fit(X, y) - - @pytest.mark.parametrize("csr_container", CSR_CONTAINERS) def test_predict_3_classes(csr_container): check_predictions(LogisticRegression(C=10), X, Y2) @@ -2619,3 +2605,12 @@ def test_logisticregressioncv_warns_with_use_legacy_attributes(): msg = "The default value of use_legacy_attributes will change from True" with pytest.warns(FutureWarning, match=msg): lr.fit(X, y) + + +# TODO(1.10): remove this test when n_jobs gets removed +def test_logisticregression_warns_with_n_jobs(): + X, y = make_classification(n_classes=3, n_samples=50, n_informative=6) + lr = LogisticRegression(n_jobs=1) + msg = "'n_jobs' has no effect" + with pytest.warns(FutureWarning, match=msg): + lr.fit(X, y) From 99affc9ece9ad5509304a9d891b64c8b2ba53fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Est=C3=A8ve?= Date: Wed, 19 Nov 2025 15:23:42 +0100 Subject: [PATCH 2/3] Remove warning that is not useful anymore since n_jobs never has an effect --- sklearn/linear_model/_logistic.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/sklearn/linear_model/_logistic.py b/sklearn/linear_model/_logistic.py index afd85e14adf61..d6bdc1c715af0 100644 --- a/sklearn/linear_model/_logistic.py +++ b/sklearn/linear_model/_logistic.py @@ -10,7 +10,6 @@ from numbers import Integral, Real import numpy as np -from joblib import effective_n_jobs from scipy import optimize from sklearn._loss.loss import HalfBinomialLoss, HalfMultinomialLoss @@ -1149,13 +1148,6 @@ def fit(self, X, y, sample_weight=None): "value > 1e30 results in a frozen fit. Please choose another " "solver or rescale the input X." ) - # TODO(lesteve) - if effective_n_jobs(self.n_jobs) != 1: - warnings.warn( - "'n_jobs' > 1 does not have any effect when" - " 'solver' is set to 'liblinear'. Got 'n_jobs'" - " = {}.".format(effective_n_jobs(self.n_jobs)) - ) self.coef_, self.intercept_, self.n_iter_ = _fit_liblinear( X, y, From ec021f4c2c987de52d68003876b34a536bcc6883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Est=C3=A8ve?= Date: Wed, 19 Nov 2025 15:23:55 +0100 Subject: [PATCH 3/3] [azure parallel] trigger CI