From d3c4c0f9f93c475bd929645898c80da7583efd19 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 16 Mar 2022 16:18:58 -0400 Subject: [PATCH 1/4] Add check_scalar in Bayes. Update Bayes tests. --- sklearn/linear_model/_bayes.py | 9 +++------ sklearn/linear_model/tests/test_bayes.py | 6 +++++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/sklearn/linear_model/_bayes.py b/sklearn/linear_model/_bayes.py index 0ced5c796ae39..3dec58f48ff6a 100644 --- a/sklearn/linear_model/_bayes.py +++ b/sklearn/linear_model/_bayes.py @@ -6,6 +6,7 @@ # License: BSD 3 clause from math import log +import numbers import numpy as np from scipy import linalg @@ -13,6 +14,7 @@ from ..base import RegressorMixin from ._base import _deprecate_normalize from ..utils.extmath import fast_logdet +from ..utils import check_scalar from scipy.linalg import pinvh from ..utils.validation import _check_sample_weight @@ -230,12 +232,7 @@ def fit(self, X, y, sample_weight=None): self.normalize, default=False, estimator_name=self.__class__.__name__ ) - if self.n_iter < 1: - raise ValueError( - "n_iter should be greater than or equal to 1. Got {!r}.".format( - self.n_iter - ) - ) + check_scalar(self.n_iter, "n_iter", numbers.Integral, min_val=1) X, y = self._validate_data(X, y, dtype=[np.float64, np.float32], y_numeric=True) diff --git a/sklearn/linear_model/tests/test_bayes.py b/sklearn/linear_model/tests/test_bayes.py index 9fe6ae711797e..e4a7ba3541127 100644 --- a/sklearn/linear_model/tests/test_bayes.py +++ b/sklearn/linear_model/tests/test_bayes.py @@ -27,9 +27,13 @@ def test_n_iter(): X = np.array([[1], [2], [6], [8], [10]]) y = np.array([1, 2, 6, 8, 10]) clf = BayesianRidge(n_iter=0) - msg = "n_iter should be greater than or equal to 1." + msg = "n_iter == 0, must be >= 1." with pytest.raises(ValueError, match=msg): clf.fit(X, y) + clf = BayesianRidge(n_iter=2.5) + msg = "n_iter must be an instance of int, not float." + with pytest.raises(TypeError, match=msg): + clf.fit(X, y) def test_bayesian_ridge_scores(): From 74752485ad6f2df3d73a67b43eba655f40e53be5 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 16 Mar 2022 17:35:20 -0400 Subject: [PATCH 2/4] Add validation for scalar params of BayesianRidge. Add tests for validation. --- sklearn/linear_model/_bayes.py | 103 +++++++++++++++++++++-- sklearn/linear_model/tests/test_bayes.py | 85 +++++++++++++++++-- 2 files changed, 174 insertions(+), 14 deletions(-) diff --git a/sklearn/linear_model/_bayes.py b/sklearn/linear_model/_bayes.py index 3dec58f48ff6a..bf910f9c44853 100644 --- a/sklearn/linear_model/_bayes.py +++ b/sklearn/linear_model/_bayes.py @@ -207,6 +207,103 @@ def __init__( self.copy_X = copy_X self.verbose = verbose + def _check_params(self): + """Check validity of parameters and raise ValueError + or TypeError if not valid.""" + + check_scalar( + self.n_iter, + name="n_iter", + target_type=numbers.Integral, + min_val=1, + ) + + check_scalar( + self.tol, + name="tol", + target_type=numbers.Real, + min_val=0.0, + include_boundaries="neither", + ) + + check_scalar( + self.alpha_1, + name="alpha_1", + target_type=numbers.Real, + include_boundaries="neither", + ) + + check_scalar( + self.alpha_2, + name="alpha_2", + target_type=numbers.Real, + include_boundaries="neither", + ) + + check_scalar( + self.lambda_1, + name="lambda_1", + target_type=numbers.Real, + include_boundaries="neither", + ) + + check_scalar( + self.lambda_2, + name="lambda_2", + target_type=numbers.Real, + include_boundaries="neither", + ) + + check_scalar( + self.alpha_init, + name="alpha_init", + target_type=(numbers.Real, type(None)), + include_boundaries="neither", + ) + + check_scalar( + self.lambda_init, + name="lambda_init", + target_type=(numbers.Real, type(None)), + include_boundaries="neither", + ) + + check_scalar( + self.compute_score, + name="compute_score", + target_type=(numbers.Integral, np.bool_, bool), + min_val=0, + max_val=1, + ) + + check_scalar( + self.fit_intercept, + name="fit_intercept", + target_type=(numbers.Integral, np.bool_, bool), + min_val=0, + max_val=1, + ) + + self._normalize = _deprecate_normalize( + self.normalize, default=False, estimator_name=self.__class__.__name__ + ) + + check_scalar( + self.copy_X, + name="copy_X", + target_type=(numbers.Integral, np.bool_, bool), + min_val=0, + max_val=1, + ) + + check_scalar( + self.verbose, + name="verbose", + target_type=(numbers.Integral, np.bool_, bool), + min_val=0, + max_val=1, + ) + def fit(self, X, y, sample_weight=None): """Fit the model. @@ -228,11 +325,7 @@ def fit(self, X, y, sample_weight=None): self : object Returns the instance itself. """ - self._normalize = _deprecate_normalize( - self.normalize, default=False, estimator_name=self.__class__.__name__ - ) - - check_scalar(self.n_iter, "n_iter", numbers.Integral, min_val=1) + self._check_params() X, y = self._validate_data(X, y, dtype=[np.float64, np.float32], y_numeric=True) diff --git a/sklearn/linear_model/tests/test_bayes.py b/sklearn/linear_model/tests/test_bayes.py index e4a7ba3541127..f33e279311faf 100644 --- a/sklearn/linear_model/tests/test_bayes.py +++ b/sklearn/linear_model/tests/test_bayes.py @@ -22,17 +22,84 @@ diabetes = datasets.load_diabetes() -def test_n_iter(): - """Check value of n_iter.""" +@pytest.mark.parametrize( + "params, err_type, err_msg", + [ + ({"n_iter": 0}, ValueError, "n_iter == 0, must be >= 1."), + ({"n_iter": 2.5}, TypeError, "n_iter must be an instance of int, not float."), + ({"tol": -1}, ValueError, "tol == -1, must be > 0"), + ({"tol": "-1"}, TypeError, "tol must be an instance of float, not str."), + ( + {"alpha_1": "-1"}, + TypeError, + "alpha_1 must be an instance of float, not str.", + ), + ( + {"alpha_2": "-1"}, + TypeError, + "alpha_2 must be an instance of float, not str.", + ), + ( + {"lambda_1": "-1"}, + TypeError, + "lambda_1 must be an instance of float, not str.", + ), + ( + {"lambda_2": "-1"}, + TypeError, + "lambda_2 must be an instance of float, not str.", + ), + ( + {"alpha_init": "-1"}, + TypeError, + "alpha_init must be an instance of {float, NoneType}, not str.", + ), + ( + {"lambda_init": "-1"}, + TypeError, + "lambda_init must be an instance of {float, NoneType}, not str.", + ), + ({"compute_score": -1}, ValueError, "compute_score == -1, must be >= 0"), + ({"compute_score": 2}, ValueError, "compute_score == 2, must be <= 1"), + ( + {"compute_score": 0.5}, + TypeError, + "compute_score must be an instance of {int, numpy.bool_, bool}, not float.", + ), + ({"fit_intercept": -1}, ValueError, "fit_intercept == -1, must be >= 0"), + ({"fit_intercept": 2}, ValueError, "fit_intercept == 2, must be <= 1"), + ( + {"fit_intercept": 0.5}, + TypeError, + "fit_intercept must be an instance of {int, numpy.bool_, bool}, not float.", + ), + ( + {"normalize": -1}, + ValueError, + "Leave 'normalize' to its default value or set it to True or False", + ), + ({"copy_X": -1}, ValueError, "copy_X == -1, must be >= 0"), + ({"copy_X": 2}, ValueError, "copy_X == 2, must be <= 1"), + ( + {"copy_X": 0.5}, + TypeError, + "copy_X must be an instance of {int, numpy.bool_, bool}, not float.", + ), + ({"verbose": -1}, ValueError, "verbose == -1, must be >= 0"), + ({"verbose": 2}, ValueError, "verbose == 2, must be <= 1"), + ( + {"verbose": 0.5}, + TypeError, + "verbose must be an instance of {int, numpy.bool_, bool}, not float.", + ), + ], +) +def test_bayesian_ridge_scalar_params_validation(params, err_type, err_msg): + """Check the scalar parameters of BayesianRidge.""" X = np.array([[1], [2], [6], [8], [10]]) y = np.array([1, 2, 6, 8, 10]) - clf = BayesianRidge(n_iter=0) - msg = "n_iter == 0, must be >= 1." - with pytest.raises(ValueError, match=msg): - clf.fit(X, y) - clf = BayesianRidge(n_iter=2.5) - msg = "n_iter must be an instance of int, not float." - with pytest.raises(TypeError, match=msg): + clf = BayesianRidge(**params) + with pytest.raises(err_type, match=err_msg): clf.fit(X, y) From bfdb15a5a246d52c712bf4c5a5359379c7f06a1a Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 12 May 2022 13:15:15 -0700 Subject: [PATCH 3/4] Remove undocumented number parameters. Fix None check. --- sklearn/linear_model/_bayes.py | 50 +++++++++++++++++----------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/sklearn/linear_model/_bayes.py b/sklearn/linear_model/_bayes.py index bf910f9c44853..372c088cc1f85 100644 --- a/sklearn/linear_model/_bayes.py +++ b/sklearn/linear_model/_bayes.py @@ -230,58 +230,60 @@ def _check_params(self): self.alpha_1, name="alpha_1", target_type=numbers.Real, - include_boundaries="neither", + min_val=0.0, + include_boundaries="left", ) check_scalar( self.alpha_2, name="alpha_2", target_type=numbers.Real, - include_boundaries="neither", + min_val=0.0, + include_boundaries="left", ) check_scalar( self.lambda_1, name="lambda_1", target_type=numbers.Real, - include_boundaries="neither", + min_val=0.0, + include_boundaries="left", ) check_scalar( self.lambda_2, name="lambda_2", target_type=numbers.Real, - include_boundaries="neither", + min_val=0.0, + include_boundaries="left", ) - check_scalar( - self.alpha_init, - name="alpha_init", - target_type=(numbers.Real, type(None)), - include_boundaries="neither", - ) + if self.alpha_init is not None: + check_scalar( + self.alpha_init, + name="alpha_init", + target_type=numbers.Real, + include_boundaries="neither", + ) - check_scalar( - self.lambda_init, - name="lambda_init", - target_type=(numbers.Real, type(None)), - include_boundaries="neither", - ) + if self.lambda_init is not None: + check_scalar( + self.lambda_init, + name="lambda_init", + target_type=numbers.Real, + include_boundaries="neither", + ) check_scalar( self.compute_score, name="compute_score", - target_type=(numbers.Integral, np.bool_, bool), - min_val=0, - max_val=1, + target_type=(np.bool_, bool), ) check_scalar( self.fit_intercept, name="fit_intercept", - target_type=(numbers.Integral, np.bool_, bool), - min_val=0, - max_val=1, + target_type=(np.bool_, bool), ) self._normalize = _deprecate_normalize( @@ -291,9 +293,7 @@ def _check_params(self): check_scalar( self.copy_X, name="copy_X", - target_type=(numbers.Integral, np.bool_, bool), - min_val=0, - max_val=1, + target_type=(np.bool_, bool), ) check_scalar( From 18edbb6ce18c2d756108c5d20be069350dff3b19 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Tue, 17 May 2022 11:54:08 +0200 Subject: [PATCH 4/4] TST correct the params validation regex --- sklearn/linear_model/tests/test_bayes.py | 31 +++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/sklearn/linear_model/tests/test_bayes.py b/sklearn/linear_model/tests/test_bayes.py index a9cca1643e731..8c14869e7052d 100644 --- a/sklearn/linear_model/tests/test_bayes.py +++ b/sklearn/linear_model/tests/test_bayes.py @@ -51,38 +51,47 @@ ( {"alpha_init": "-1"}, TypeError, - "alpha_init must be an instance of {float, NoneType}, not str.", + "alpha_init must be an instance of float, not str.", ), ( {"lambda_init": "-1"}, TypeError, - "lambda_init must be an instance of {float, NoneType}, not str.", + "lambda_init must be an instance of float, not str.", + ), + ( + {"compute_score": 2}, + TypeError, + "compute_score must be an instance of {numpy.bool_, bool}, not int.", ), - ({"compute_score": -1}, ValueError, "compute_score == -1, must be >= 0"), - ({"compute_score": 2}, ValueError, "compute_score == 2, must be <= 1"), ( {"compute_score": 0.5}, TypeError, - "compute_score must be an instance of {int, numpy.bool_, bool}, not float.", + "compute_score must be an instance of {numpy.bool_, bool}, not float.", + ), + ( + {"fit_intercept": 2}, + TypeError, + "fit_intercept must be an instance of {numpy.bool_, bool}, not int.", ), - ({"fit_intercept": -1}, ValueError, "fit_intercept == -1, must be >= 0"), - ({"fit_intercept": 2}, ValueError, "fit_intercept == 2, must be <= 1"), ( {"fit_intercept": 0.5}, TypeError, - "fit_intercept must be an instance of {int, numpy.bool_, bool}, not float.", + "fit_intercept must be an instance of {numpy.bool_, bool}, not float.", ), ( {"normalize": -1}, ValueError, "Leave 'normalize' to its default value or set it to True or False", ), - ({"copy_X": -1}, ValueError, "copy_X == -1, must be >= 0"), - ({"copy_X": 2}, ValueError, "copy_X == 2, must be <= 1"), + ( + {"copy_X": 2}, + TypeError, + "copy_X must be an instance of {numpy.bool_, bool}, not int.", + ), ( {"copy_X": 0.5}, TypeError, - "copy_X must be an instance of {int, numpy.bool_, bool}, not float.", + "copy_X must be an instance of {numpy.bool_, bool}, not float.", ), ({"verbose": -1}, ValueError, "verbose == -1, must be >= 0"), ({"verbose": 2}, ValueError, "verbose == 2, must be <= 1"),