From 2643768414b3a708286fb09ee9ca1ff7e31617fb Mon Sep 17 00:00:00 2001 From: Ritzzer764 Date: Tue, 3 Jun 2025 13:33:06 +0530 Subject: [PATCH 1/8] fix: change limits of power_t param to [0, inf) --- sklearn/linear_model/_stochastic_gradient.py | 12 +++++----- sklearn/linear_model/tests/test_sgd.py | 24 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/sklearn/linear_model/_stochastic_gradient.py b/sklearn/linear_model/_stochastic_gradient.py index 8f7c814000614..bfb1418f91dd4 100644 --- a/sklearn/linear_model/_stochastic_gradient.py +++ b/sklearn/linear_model/_stochastic_gradient.py @@ -1082,7 +1082,7 @@ class SGDClassifier(BaseSGDClassifier): power_t : float, default=0.5 The exponent for inverse scaling learning rate. - Values must be in the range `(-inf, inf)`. + Values must be in the range `[0.0, inf)`. early_stopping : bool, default=False Whether to use early stopping to terminate training when validation @@ -1208,7 +1208,7 @@ class SGDClassifier(BaseSGDClassifier): "penalty": [StrOptions({"l2", "l1", "elasticnet"}), None], "alpha": [Interval(Real, 0, None, closed="left")], "l1_ratio": [Interval(Real, 0, 1, closed="both"), None], - "power_t": [Interval(Real, None, None, closed="neither")], + "power_t": [Interval(Real, 0, None, closed="left")], "epsilon": [Interval(Real, 0, None, closed="left")], "learning_rate": [ StrOptions({"constant", "optimal", "invscaling", "adaptive"}), @@ -1880,7 +1880,7 @@ class SGDRegressor(BaseSGDRegressor): power_t : float, default=0.25 The exponent for inverse scaling learning rate. - Values must be in the range `(-inf, inf)`. + Values must be in the range `[0, inf)`. early_stopping : bool, default=False Whether to use early stopping to terminate training when validation @@ -1994,7 +1994,7 @@ class SGDRegressor(BaseSGDRegressor): "penalty": [StrOptions({"l2", "l1", "elasticnet"}), None], "alpha": [Interval(Real, 0, None, closed="left")], "l1_ratio": [Interval(Real, 0, 1, closed="both"), None], - "power_t": [Interval(Real, None, None, closed="neither")], + "power_t": [Interval(Real, 0, None, closed="left")], "learning_rate": [ StrOptions({"constant", "optimal", "invscaling", "adaptive"}), Hidden(StrOptions({"pa1", "pa2"})), @@ -2118,7 +2118,7 @@ class SGDOneClassSVM(OutlierMixin, BaseSGD): power_t : float, default=0.5 The exponent for inverse scaling learning rate. - Values must be in the range `(-inf, inf)`. + Values must be in the range `[0, inf)`. warm_start : bool, default=False When set to True, reuse the solution of the previous call to fit as @@ -2201,7 +2201,7 @@ class SGDOneClassSVM(OutlierMixin, BaseSGD): Hidden(StrOptions({"pa1", "pa2"})), ], "eta0": [Interval(Real, 0, None, closed="left")], - "power_t": [Interval(Real, None, None, closed="neither")], + "power_t": [Interval(Real, 0, None, closed="left")], } def __init__( diff --git a/sklearn/linear_model/tests/test_sgd.py b/sklearn/linear_model/tests/test_sgd.py index 26d138ae3649b..d427d830c6188 100644 --- a/sklearn/linear_model/tests/test_sgd.py +++ b/sklearn/linear_model/tests/test_sgd.py @@ -506,6 +506,30 @@ def test_sgd_failing_penalty_validation(Estimator): ): clf.fit(X, Y) +@pytest.mark.parametrize( + "klass", + [ + SGDClassifier, + SparseSGDClassifier, + SGDRegressor, + SparseSGDRegressor, + SGDOneClassSVM, + SparseSGDOneClassSVM, + ], +) +def test_power_t_limits(klass): + """Check that power_t is limited to [0, inf)""" + clf = klass(power_t=0.0) + clf.fit(X, Y) + assert clf.power_t == 0.0 + + clf = klass(power_t=0.5) + clf.fit(X, Y) + assert clf.power_t == 0.5 + + clf = klass(power_t=-1.0) + with pytest.raises(ValueError, match=r"must be a float in the range \[0\.0, inf\)"): + clf.fit(X, Y) ############################################################################### # Classification Test Case From 17d4a68086a2d8b5165b3a204d4c48d7557f5857 Mon Sep 17 00:00:00 2001 From: Ritzzer764 Date: Tue, 3 Jun 2025 13:53:54 +0530 Subject: [PATCH 2/8] style: formatting --- sklearn/linear_model/tests/test_sgd.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sklearn/linear_model/tests/test_sgd.py b/sklearn/linear_model/tests/test_sgd.py index d427d830c6188..2f7a1a2ecaf53 100644 --- a/sklearn/linear_model/tests/test_sgd.py +++ b/sklearn/linear_model/tests/test_sgd.py @@ -506,6 +506,7 @@ def test_sgd_failing_penalty_validation(Estimator): ): clf.fit(X, Y) + @pytest.mark.parametrize( "klass", [ @@ -526,11 +527,12 @@ def test_power_t_limits(klass): clf = klass(power_t=0.5) clf.fit(X, Y) assert clf.power_t == 0.5 - + clf = klass(power_t=-1.0) with pytest.raises(ValueError, match=r"must be a float in the range \[0\.0, inf\)"): clf.fit(X, Y) + ############################################################################### # Classification Test Case From 523b38fda817b8a9dd56e0f6df0a2a631eff363d Mon Sep 17 00:00:00 2001 From: Ritzzer764 Date: Tue, 3 Jun 2025 13:56:52 +0530 Subject: [PATCH 3/8] fix: minor changes to docstring --- sklearn/linear_model/_stochastic_gradient.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sklearn/linear_model/_stochastic_gradient.py b/sklearn/linear_model/_stochastic_gradient.py index bfb1418f91dd4..0078993b0088f 100644 --- a/sklearn/linear_model/_stochastic_gradient.py +++ b/sklearn/linear_model/_stochastic_gradient.py @@ -1880,7 +1880,7 @@ class SGDRegressor(BaseSGDRegressor): power_t : float, default=0.25 The exponent for inverse scaling learning rate. - Values must be in the range `[0, inf)`. + Values must be in the range `[0.0, inf)`. early_stopping : bool, default=False Whether to use early stopping to terminate training when validation @@ -2118,7 +2118,7 @@ class SGDOneClassSVM(OutlierMixin, BaseSGD): power_t : float, default=0.5 The exponent for inverse scaling learning rate. - Values must be in the range `[0, inf)`. + Values must be in the range `[0.0, inf)`. warm_start : bool, default=False When set to True, reuse the solution of the previous call to fit as From ad996aaa81ce2a5f11658b73e57fb6ffcc40eccf Mon Sep 17 00:00:00 2001 From: Ritzzer764 Date: Tue, 3 Jun 2025 20:47:35 +0530 Subject: [PATCH 4/8] fix: handle the parameter limits using a deprecation cycle --- sklearn/linear_model/_stochastic_gradient.py | 43 +++++++++++++++++--- sklearn/linear_model/tests/test_sgd.py | 13 ++---- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/sklearn/linear_model/_stochastic_gradient.py b/sklearn/linear_model/_stochastic_gradient.py index 0078993b0088f..cac63f1f41be3 100644 --- a/sklearn/linear_model/_stochastic_gradient.py +++ b/sklearn/linear_model/_stochastic_gradient.py @@ -731,6 +731,14 @@ def _fit( ), ConvergenceWarning, ) + + if self.power_t < 0: + warnings.warn( + "Negative values for `power_t` are deprecated in version 1.8 and will raise an error in 1.10." + "Use values in the range [0.0, inf) instead.", + FutureWarning, + ) + return self def _fit_binary(self, X, y, alpha, C, sample_weight, learning_rate, max_iter): @@ -1082,7 +1090,10 @@ class SGDClassifier(BaseSGDClassifier): power_t : float, default=0.5 The exponent for inverse scaling learning rate. - Values must be in the range `[0.0, inf)`. + + .. deprecated:: 1.8 + Negative values for `power_t` are deprecated in version 1.8 and will raise an error in 1.10. + Values must be in the range [0.0, inf). early_stopping : bool, default=False Whether to use early stopping to terminate training when validation @@ -1208,7 +1219,7 @@ class SGDClassifier(BaseSGDClassifier): "penalty": [StrOptions({"l2", "l1", "elasticnet"}), None], "alpha": [Interval(Real, 0, None, closed="left")], "l1_ratio": [Interval(Real, 0, 1, closed="both"), None], - "power_t": [Interval(Real, 0, None, closed="left")], + "power_t": [Interval(Real, None, None, closed="neither")], "epsilon": [Interval(Real, 0, None, closed="left")], "learning_rate": [ StrOptions({"constant", "optimal", "invscaling", "adaptive"}), @@ -1585,6 +1596,13 @@ def _fit( ConvergenceWarning, ) + if self.power_t < 0: + warnings.warn( + "Negative values for `power_t` are deprecated in version 1.8 and will raise an error in 1.10." + "Use values in the range [0.0, inf) instead.", + FutureWarning, + ) + return self @_fit_context(prefer_skip_nested_validation=True) @@ -1880,7 +1898,10 @@ class SGDRegressor(BaseSGDRegressor): power_t : float, default=0.25 The exponent for inverse scaling learning rate. - Values must be in the range `[0.0, inf)`. + + .. deprecated:: 1.8 + Negative values for `power_t` are deprecated in version 1.8 and will raise an error in 1.10. + Values must be in the range [0.0, inf). early_stopping : bool, default=False Whether to use early stopping to terminate training when validation @@ -1994,7 +2015,7 @@ class SGDRegressor(BaseSGDRegressor): "penalty": [StrOptions({"l2", "l1", "elasticnet"}), None], "alpha": [Interval(Real, 0, None, closed="left")], "l1_ratio": [Interval(Real, 0, 1, closed="both"), None], - "power_t": [Interval(Real, 0, None, closed="left")], + "power_t": [Interval(Real, None, None, closed="neither")], "learning_rate": [ StrOptions({"constant", "optimal", "invscaling", "adaptive"}), Hidden(StrOptions({"pa1", "pa2"})), @@ -2118,7 +2139,10 @@ class SGDOneClassSVM(OutlierMixin, BaseSGD): power_t : float, default=0.5 The exponent for inverse scaling learning rate. - Values must be in the range `[0.0, inf)`. + + .. deprecated:: 1.8 + Negative values for `power_t` are deprecated in version 1.8 and will raise an error in 1.10. + Values must be in the range [0.0, inf). warm_start : bool, default=False When set to True, reuse the solution of the previous call to fit as @@ -2201,7 +2225,7 @@ class SGDOneClassSVM(OutlierMixin, BaseSGD): Hidden(StrOptions({"pa1", "pa2"})), ], "eta0": [Interval(Real, 0, None, closed="left")], - "power_t": [Interval(Real, 0, None, closed="left")], + "power_t": [Interval(Real, None, None, closed="neither")], } def __init__( @@ -2490,6 +2514,13 @@ def _fit( ConvergenceWarning, ) + if self.power_t < 0: + warnings.warn( + "Negative values for `power_t` are deprecated in version 1.8 and will raise an error in 1.10." + "Use values in the range [0.0, inf) instead.", + FutureWarning, + ) + return self @_fit_context(prefer_skip_nested_validation=True) diff --git a/sklearn/linear_model/tests/test_sgd.py b/sklearn/linear_model/tests/test_sgd.py index 2f7a1a2ecaf53..13437f8cc7f60 100644 --- a/sklearn/linear_model/tests/test_sgd.py +++ b/sklearn/linear_model/tests/test_sgd.py @@ -507,6 +507,7 @@ def test_sgd_failing_penalty_validation(Estimator): clf.fit(X, Y) +# TODO(1.10): remove this test @pytest.mark.parametrize( "klass", [ @@ -520,16 +521,10 @@ def test_sgd_failing_penalty_validation(Estimator): ) def test_power_t_limits(klass): """Check that power_t is limited to [0, inf)""" - clf = klass(power_t=0.0) - clf.fit(X, Y) - assert clf.power_t == 0.0 - - clf = klass(power_t=0.5) - clf.fit(X, Y) - assert clf.power_t == 0.5 - clf = klass(power_t=-1.0) - with pytest.raises(ValueError, match=r"must be a float in the range \[0\.0, inf\)"): + with pytest.warns( + FutureWarning, match="Negative values for `power_t` are deprecated" + ): clf.fit(X, Y) From 958bc39f0a4e5371dd4aba8f386c4ef7b2bed613 Mon Sep 17 00:00:00 2001 From: Ritzzer764 Date: Tue, 3 Jun 2025 20:57:49 +0530 Subject: [PATCH 5/8] style: fix formatting --- sklearn/linear_model/_stochastic_gradient.py | 21 +++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/sklearn/linear_model/_stochastic_gradient.py b/sklearn/linear_model/_stochastic_gradient.py index cac63f1f41be3..f1d6f8d70124e 100644 --- a/sklearn/linear_model/_stochastic_gradient.py +++ b/sklearn/linear_model/_stochastic_gradient.py @@ -734,7 +734,8 @@ def _fit( if self.power_t < 0: warnings.warn( - "Negative values for `power_t` are deprecated in version 1.8 and will raise an error in 1.10." + "Negative values for `power_t` are deprecated in version 1.8 " + "and will raise an error in 1.10. " "Use values in the range [0.0, inf) instead.", FutureWarning, ) @@ -1092,8 +1093,8 @@ class SGDClassifier(BaseSGDClassifier): The exponent for inverse scaling learning rate. .. deprecated:: 1.8 - Negative values for `power_t` are deprecated in version 1.8 and will raise an error in 1.10. - Values must be in the range [0.0, inf). + Negative values for `power_t` are deprecated in version 1.8 and will raise + an error in 1.10. Use values in the range [0.0, inf) instead. early_stopping : bool, default=False Whether to use early stopping to terminate training when validation @@ -1598,7 +1599,8 @@ def _fit( if self.power_t < 0: warnings.warn( - "Negative values for `power_t` are deprecated in version 1.8 and will raise an error in 1.10." + "Negative values for `power_t` are deprecated in version 1.8 " + "and will raise an error in 1.10. " "Use values in the range [0.0, inf) instead.", FutureWarning, ) @@ -1900,8 +1902,8 @@ class SGDRegressor(BaseSGDRegressor): The exponent for inverse scaling learning rate. .. deprecated:: 1.8 - Negative values for `power_t` are deprecated in version 1.8 and will raise an error in 1.10. - Values must be in the range [0.0, inf). + Negative values for `power_t` are deprecated in version 1.8 and will raise + an error in 1.10. Use values in the range [0.0, inf) instead. early_stopping : bool, default=False Whether to use early stopping to terminate training when validation @@ -2141,8 +2143,8 @@ class SGDOneClassSVM(OutlierMixin, BaseSGD): The exponent for inverse scaling learning rate. .. deprecated:: 1.8 - Negative values for `power_t` are deprecated in version 1.8 and will raise an error in 1.10. - Values must be in the range [0.0, inf). + Negative values for `power_t` are deprecated in version 1.8 and will raise + an error in 1.10. Use values in the range [0.0, inf) instead. warm_start : bool, default=False When set to True, reuse the solution of the previous call to fit as @@ -2516,7 +2518,8 @@ def _fit( if self.power_t < 0: warnings.warn( - "Negative values for `power_t` are deprecated in version 1.8 and will raise an error in 1.10." + "Negative values for `power_t` are deprecated in version 1.8 " + "and will raise an error in 1.10. " "Use values in the range [0.0, inf) instead.", FutureWarning, ) From 29f9b2176543897f03e723330d8b3dc6c22cdd28 Mon Sep 17 00:00:00 2001 From: Ritzzer764 Date: Tue, 3 Jun 2025 21:33:12 +0530 Subject: [PATCH 6/8] test: minor changes to test --- sklearn/linear_model/tests/test_sgd.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sklearn/linear_model/tests/test_sgd.py b/sklearn/linear_model/tests/test_sgd.py index 13437f8cc7f60..80b69adf99b99 100644 --- a/sklearn/linear_model/tests/test_sgd.py +++ b/sklearn/linear_model/tests/test_sgd.py @@ -1,4 +1,5 @@ import pickle +import warnings from unittest.mock import Mock import joblib @@ -520,13 +521,21 @@ def test_sgd_failing_penalty_validation(Estimator): ], ) def test_power_t_limits(klass): - """Check that power_t is limited to [0, inf)""" + """Check that a warning is raised when `power_t` is negative.""" + + # Check that negative values of `power_t` raise a warning clf = klass(power_t=-1.0) with pytest.warns( FutureWarning, match="Negative values for `power_t` are deprecated" ): clf.fit(X, Y) + # Check that values of 'power_t in range [0, inf) do not raise a warning + with warnings.catch_warnings(record=True) as w: + clf = klass(power_t=0.5) + clf.fit(X, Y) + assert len(w) == 0 + ############################################################################### # Classification Test Case From 614f15006f899b8d0f1ce016990b14295d2a0945 Mon Sep 17 00:00:00 2001 From: Ritzzer764 Date: Wed, 4 Jun 2025 16:06:59 +0530 Subject: [PATCH 7/8] fix: add changelog entry --- .../upcoming_changes/sklearn.linear_model/31474.api.rst | 6 ++++++ sklearn/linear_model/_stochastic_gradient.py | 3 +++ 2 files changed, 9 insertions(+) create mode 100644 doc/whats_new/upcoming_changes/sklearn.linear_model/31474.api.rst diff --git a/doc/whats_new/upcoming_changes/sklearn.linear_model/31474.api.rst b/doc/whats_new/upcoming_changes/sklearn.linear_model/31474.api.rst new file mode 100644 index 0000000000000..676adcbed5292 --- /dev/null +++ b/doc/whats_new/upcoming_changes/sklearn.linear_model/31474.api.rst @@ -0,0 +1,6 @@ +- :class:`linear_model.SGDClassifier`, :class:`linear_model.SGDRegressor`, and + :class:`linear_model.SGDOneClassSVM` now deprecate negative values for the + `power_t` parameter. Using a negative value will raise a warning in version 1.8 + and will raise an error in version 1.10. A value in the range [0, inf) must be used + instead. + By :user:`Ritvi Alagusankar ` \ No newline at end of file diff --git a/sklearn/linear_model/_stochastic_gradient.py b/sklearn/linear_model/_stochastic_gradient.py index f1d6f8d70124e..859e527fb3c3b 100644 --- a/sklearn/linear_model/_stochastic_gradient.py +++ b/sklearn/linear_model/_stochastic_gradient.py @@ -1091,6 +1091,7 @@ class SGDClassifier(BaseSGDClassifier): power_t : float, default=0.5 The exponent for inverse scaling learning rate. + Values must be in the range `[0.0, inf)`. .. deprecated:: 1.8 Negative values for `power_t` are deprecated in version 1.8 and will raise @@ -1900,6 +1901,7 @@ class SGDRegressor(BaseSGDRegressor): power_t : float, default=0.25 The exponent for inverse scaling learning rate. + Values must be in the range `[0.0, inf)`. .. deprecated:: 1.8 Negative values for `power_t` are deprecated in version 1.8 and will raise @@ -2141,6 +2143,7 @@ class SGDOneClassSVM(OutlierMixin, BaseSGD): power_t : float, default=0.5 The exponent for inverse scaling learning rate. + Values must be in the range `[0.0, inf)`. .. deprecated:: 1.8 Negative values for `power_t` are deprecated in version 1.8 and will raise From 28086a64585477abc8c2c814698303bcda120ab8 Mon Sep 17 00:00:00 2001 From: Ritzzer764 Date: Wed, 4 Jun 2025 16:08:48 +0530 Subject: [PATCH 8/8] fix: minor change to changelog --- .../upcoming_changes/sklearn.linear_model/31474.api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/whats_new/upcoming_changes/sklearn.linear_model/31474.api.rst b/doc/whats_new/upcoming_changes/sklearn.linear_model/31474.api.rst index 676adcbed5292..845b9b502b9f1 100644 --- a/doc/whats_new/upcoming_changes/sklearn.linear_model/31474.api.rst +++ b/doc/whats_new/upcoming_changes/sklearn.linear_model/31474.api.rst @@ -1,6 +1,6 @@ - :class:`linear_model.SGDClassifier`, :class:`linear_model.SGDRegressor`, and :class:`linear_model.SGDOneClassSVM` now deprecate negative values for the `power_t` parameter. Using a negative value will raise a warning in version 1.8 - and will raise an error in version 1.10. A value in the range [0, inf) must be used + and will raise an error in version 1.10. A value in the range [0.0, inf) must be used instead. By :user:`Ritvi Alagusankar ` \ No newline at end of file