From 79ad28b4e96710bb699c9d23a5848b0f93431578 Mon Sep 17 00:00:00 2001 From: Thomas J Fan Date: Wed, 6 Nov 2019 19:39:07 -0500 Subject: [PATCH 1/8] ENH Deprecated probA and probB --- sklearn/svm/_base.py | 22 +++++++++++++++------- sklearn/svm/_classes.py | 29 +++++++++++++++++++++++++++++ sklearn/svm/tests/test_svm.py | 15 +++++++++++++++ 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/sklearn/svm/_base.py b/sklearn/svm/_base.py index d327e0fef26e4..378555c6bdc9f 100644 --- a/sklearn/svm/_base.py +++ b/sklearn/svm/_base.py @@ -246,8 +246,8 @@ def _dense_fit(self, X, y, sample_weight, solver_type, kernel, # we don't pass **self.get_params() to allow subclasses to # add other parameters to __init__ self.support_, self.support_vectors_, self._n_support, \ - self.dual_coef_, self.intercept_, self.probA_, \ - self.probB_, self.fit_status_ = libsvm.fit( + self.dual_coef_, self.intercept_, self._probA, \ + self._probB, self.fit_status_ = libsvm.fit( X, y, svm_type=solver_type, sample_weight=sample_weight, class_weight=self.class_weight_, kernel=kernel, C=self.C, @@ -270,7 +270,7 @@ def _sparse_fit(self, X, y, sample_weight, solver_type, kernel, self.support_, self.support_vectors_, dual_coef_data, \ self.intercept_, self._n_support, \ - self.probA_, self.probB_, self.fit_status_ = \ + self._probA, self._probB, self.fit_status_ = \ libsvm_sparse.libsvm_sparse_train( X.shape[1], X.data, X.indices, X.indptr, y, solver_type, kernel_type, self.degree, self._gamma, self.coef0, self.tol, @@ -334,7 +334,7 @@ def _dense_predict(self, X): return libsvm.predict( X, self.support_, self.support_vectors_, self._n_support, self._dual_coef_, self._intercept_, - self.probA_, self.probB_, svm_type=svm_type, kernel=kernel, + self._probA, self._probB, svm_type=svm_type, kernel=kernel, degree=self.degree, coef0=self.coef0, gamma=self._gamma, cache_size=self.cache_size) @@ -359,7 +359,7 @@ def _sparse_predict(self, X): C, self.class_weight_, self.nu, self.epsilon, self.shrinking, self.probability, self._n_support, - self.probA_, self.probB_) + self._probA, self._probB) def _compute_kernel(self, X): """Return the data transformed by a callable kernel""" @@ -413,7 +413,7 @@ def _dense_decision_function(self, X): return libsvm.decision_function( X, self.support_, self.support_vectors_, self._n_support, self._dual_coef_, self._intercept_, - self.probA_, self.probB_, + self._probA, self._probB, svm_type=LIBSVM_IMPL.index(self._impl), kernel=kernel, degree=self.degree, cache_size=self.cache_size, coef0=self.coef0, gamma=self._gamma) @@ -438,7 +438,7 @@ def _sparse_decision_function(self, X): self.C, self.class_weight_, self.nu, self.epsilon, self.shrinking, self.probability, self._n_support, - self.probA_, self.probB_) + self._probA, self._probB) def _validate_for_predict(self, X): check_is_fitted(self) @@ -503,6 +503,14 @@ def n_support_(self): # _n_support has size 2, we make it size 1 return np.array([self._n_support[0]]) + @property + def probA_(self): + return self._probA + + @property + def probB_(self): + return self._probB + class BaseSVC(ClassifierMixin, BaseLibSVM, metaclass=ABCMeta): """ABC for LibSVM-based classifiers.""" diff --git a/sklearn/svm/_classes.py b/sklearn/svm/_classes.py index 50c2356142ae2..9949e14962690 100644 --- a/sklearn/svm/_classes.py +++ b/sklearn/svm/_classes.py @@ -8,6 +8,7 @@ from ..utils import check_X_y from ..utils.validation import _num_samples from ..utils.multiclass import check_classification_targets +from ..utils.deprecation import deprecated class LinearSVC(BaseEstimator, LinearClassifierMixin, @@ -991,6 +992,20 @@ def __init__(self, kernel='rbf', degree=3, gamma='scale', shrinking=shrinking, probability=False, cache_size=cache_size, class_weight=None, max_iter=max_iter, random_state=None) + @deprecated( + "The probA_ attribute is deprecated in version 0.22 and will be " + "removed in version 0.24.") + @property + def probA_(self): + return self._probA + + @deprecated( + "The probB_ attribute is deprecated in version 0.22 and will be " + "removed in version 0.24.") + @property + def probB_(self): + return self._probB + class NuSVR(RegressorMixin, BaseLibSVM): """Nu Support Vector Regression. @@ -1309,3 +1324,17 @@ def predict(self, X): """ y = super().predict(X) return np.asarray(y, dtype=np.intp) + + @deprecated( + "The probA_ attribute is deprecated in version 0.22 and will be " + "removed in version 0.24.") + @property + def probA_(self): + return self._probA + + @deprecated( + "The probB_ attribute is deprecated in version 0.22 and will be " + "removed in version 0.24.") + @property + def probB_(self): + return self._probB diff --git a/sklearn/svm/tests/test_svm.py b/sklearn/svm/tests/test_svm.py index b38a4697577a3..4f815b1cd0426 100644 --- a/sklearn/svm/tests/test_svm.py +++ b/sklearn/svm/tests/test_svm.py @@ -1266,3 +1266,18 @@ def test_n_support_oneclass_svr(): assert reg.n_support_ == reg.support_vectors_.shape[0] assert reg.n_support_.size == 1 assert reg.n_support_ == 4 + + +# TODO: Remove in 0.24 when probA_ and probB_ are deprecated +@pytest.mark.parametrize("SVMClass, data", [ + (svm.OneClassSVM, (X, )), + (svm.SVR, (X, Y)) +]) +@pytest.mark.parametrize("deprecated_prob", ["probA_", "probB_"]) +def test_svm_probA_proB_deprecated(SVMClass, data, deprecated_prob): + clf = SVMClass().fit(*data) + + msg = ("The {} attribute is deprecated in version 0.22 and will be " + "removed in version 0.24.").format(deprecated_prob) + with pytest.warns(FutureWarning, match=msg): + getattr(clf, deprecated_prob) From 538a08d5e980b58e23c6c576fd69bef2a23ea19f Mon Sep 17 00:00:00 2001 From: Thomas J Fan Date: Wed, 6 Nov 2019 19:39:14 -0500 Subject: [PATCH 2/8] DOC Adds whats new --- doc/whats_new/v0.22.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/whats_new/v0.22.rst b/doc/whats_new/v0.22.rst index 525ae77311064..d937e37c2f6c3 100644 --- a/doc/whats_new/v0.22.rst +++ b/doc/whats_new/v0.22.rst @@ -732,6 +732,9 @@ Changelog `kernel='precomputed'` and fit on non-square data. :pr:`14336` by :user:`Gregory Dexter `. +- |API| :class:`svm.SVR` and :class:`svm.OneClassSVM` attributes, `probA_` and + `probB_`, are now deprecated. :pr:`` by `Thomas Fan`_. + - |Fix| :class:`svm.SVC`, :class:`svm.SVR`, :class:`svm.NuSVR` and :class:`svm.OneClassSVM` when received values negative or zero for parameter ``sample_weight`` in method fit(), generated an From a511e79a7099f306400f5262e713c5872567dcef Mon Sep 17 00:00:00 2001 From: Thomas J Fan Date: Wed, 6 Nov 2019 21:26:03 -0500 Subject: [PATCH 3/8] DOC Adds pr number --- doc/whats_new/v0.22.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/whats_new/v0.22.rst b/doc/whats_new/v0.22.rst index d937e37c2f6c3..646d21a3c7227 100644 --- a/doc/whats_new/v0.22.rst +++ b/doc/whats_new/v0.22.rst @@ -733,7 +733,7 @@ Changelog :pr:`14336` by :user:`Gregory Dexter `. - |API| :class:`svm.SVR` and :class:`svm.OneClassSVM` attributes, `probA_` and - `probB_`, are now deprecated. :pr:`` by `Thomas Fan`_. + `probB_`, are now deprecated. :pr:`15558` by `Thomas Fan`_. - |Fix| :class:`svm.SVC`, :class:`svm.SVR`, :class:`svm.NuSVR` and :class:`svm.OneClassSVM` when received values negative or zero @@ -910,4 +910,4 @@ These changes mostly affect library developers. - |Fix| The estimators tags resolution now follows the regular MRO. They used to be overridable only once. :pr:`14884` by :user:`Andreas Müller - `. \ No newline at end of file + `. From 8bdfe82807215b9847b934a76c572d75d49a5a3e Mon Sep 17 00:00:00 2001 From: Thomas J Fan Date: Thu, 7 Nov 2019 09:51:16 -0500 Subject: [PATCH 4/8] CLN Move property accessor to BaseSVC --- sklearn/linear_model/tests/test_logistic.py | 6 ++++++ sklearn/svm/_base.py | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/sklearn/linear_model/tests/test_logistic.py b/sklearn/linear_model/tests/test_logistic.py index 894040c2053bd..9b7e65ce19a3e 100644 --- a/sklearn/linear_model/tests/test_logistic.py +++ b/sklearn/linear_model/tests/test_logistic.py @@ -1266,6 +1266,12 @@ def test_saga_vs_liblinear(): X_sparse, y_sparse = make_classification(n_samples=50, n_features=20, random_state=0) + + X_sparse = sparse.csr_matrix(X_sparse) + lr = LogisticRegression() + lr.fit(X_sparse, y_sparse) + assert False + X_sparse = sparse.csr_matrix(X_sparse) for (X, y) in ((X_bin, y_bin), (X_sparse, y_sparse)): diff --git a/sklearn/svm/_base.py b/sklearn/svm/_base.py index 378555c6bdc9f..53c672d8fdb6a 100644 --- a/sklearn/svm/_base.py +++ b/sklearn/svm/_base.py @@ -503,14 +503,6 @@ def n_support_(self): # _n_support has size 2, we make it size 1 return np.array([self._n_support[0]]) - @property - def probA_(self): - return self._probA - - @property - def probB_(self): - return self._probB - class BaseSVC(ClassifierMixin, BaseLibSVM, metaclass=ABCMeta): """ABC for LibSVM-based classifiers.""" @@ -697,7 +689,7 @@ def _dense_predict_proba(self, X): pprob = libsvm.predict_proba( X, self.support_, self.support_vectors_, self._n_support, self._dual_coef_, self._intercept_, - self.probA_, self.probB_, + self._probA, self._probB, svm_type=svm_type, kernel=kernel, degree=self.degree, cache_size=self.cache_size, coef0=self.coef0, gamma=self._gamma) @@ -723,7 +715,7 @@ def _sparse_predict_proba(self, X): self.C, self.class_weight_, self.nu, self.epsilon, self.shrinking, self.probability, self._n_support, - self.probA_, self.probB_) + self._probA, self._probB) def _get_coef(self): if self.dual_coef_.shape[0] == 1: @@ -740,6 +732,14 @@ def _get_coef(self): return coef + @property + def probA_(self): + return self._probA + + @property + def probB_(self): + return self._probB + def _get_liblinear_solver_type(multi_class, penalty, loss, dual): """Find the liblinear magic number for the solver. From 6e040ae34b3182b0c6c87e0d4d6464187eccdd0e Mon Sep 17 00:00:00 2001 From: Thomas J Fan Date: Thu, 7 Nov 2019 09:52:19 -0500 Subject: [PATCH 5/8] DOC Adds more details --- doc/whats_new/v0.22.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/whats_new/v0.22.rst b/doc/whats_new/v0.22.rst index 646d21a3c7227..aee4135fa6158 100644 --- a/doc/whats_new/v0.22.rst +++ b/doc/whats_new/v0.22.rst @@ -733,7 +733,8 @@ Changelog :pr:`14336` by :user:`Gregory Dexter `. - |API| :class:`svm.SVR` and :class:`svm.OneClassSVM` attributes, `probA_` and - `probB_`, are now deprecated. :pr:`15558` by `Thomas Fan`_. + `probB_`, are now deprecated as they were not useful. :pr:`15558` by + `Thomas Fan`_. - |Fix| :class:`svm.SVC`, :class:`svm.SVR`, :class:`svm.NuSVR` and :class:`svm.OneClassSVM` when received values negative or zero From 8196d017550ddc66d1bf419c4657d53aa24bef6f Mon Sep 17 00:00:00 2001 From: Thomas J Fan Date: Fri, 8 Nov 2019 14:59:44 -0500 Subject: [PATCH 6/8] REV --- sklearn/linear_model/tests/test_logistic.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sklearn/linear_model/tests/test_logistic.py b/sklearn/linear_model/tests/test_logistic.py index 9b7e65ce19a3e..a0a890ee2d46f 100644 --- a/sklearn/linear_model/tests/test_logistic.py +++ b/sklearn/linear_model/tests/test_logistic.py @@ -1267,11 +1267,6 @@ def test_saga_vs_liblinear(): X_sparse, y_sparse = make_classification(n_samples=50, n_features=20, random_state=0) - X_sparse = sparse.csr_matrix(X_sparse) - lr = LogisticRegression() - lr.fit(X_sparse, y_sparse) - assert False - X_sparse = sparse.csr_matrix(X_sparse) for (X, y) in ((X_bin, y_bin), (X_sparse, y_sparse)): From 80062b99d86f3283a44baea2480869dfaefaf7e9 Mon Sep 17 00:00:00 2001 From: Thomas J Fan Date: Fri, 8 Nov 2019 15:00:05 -0500 Subject: [PATCH 7/8] REV Removes diff --- sklearn/linear_model/tests/test_logistic.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sklearn/linear_model/tests/test_logistic.py b/sklearn/linear_model/tests/test_logistic.py index a0a890ee2d46f..894040c2053bd 100644 --- a/sklearn/linear_model/tests/test_logistic.py +++ b/sklearn/linear_model/tests/test_logistic.py @@ -1266,7 +1266,6 @@ def test_saga_vs_liblinear(): X_sparse, y_sparse = make_classification(n_samples=50, n_features=20, random_state=0) - X_sparse = sparse.csr_matrix(X_sparse) for (X, y) in ((X_bin, y_bin), (X_sparse, y_sparse)): From 843fb8ddf77a9bccfb2d9bea0d4a971c92ca9f8a Mon Sep 17 00:00:00 2001 From: Thomas J Fan Date: Mon, 2 Dec 2019 23:19:42 -0500 Subject: [PATCH 8/8] MNT Move to 0.23 --- doc/whats_new/v0.22.rst | 4 ---- doc/whats_new/v0.23.rst | 7 +++++++ sklearn/svm/_classes.py | 16 ++++++++-------- sklearn/svm/tests/test_svm.py | 6 +++--- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/doc/whats_new/v0.22.rst b/doc/whats_new/v0.22.rst index 24fce40b01fdf..f3f69a8299b0a 100644 --- a/doc/whats_new/v0.22.rst +++ b/doc/whats_new/v0.22.rst @@ -789,10 +789,6 @@ Changelog `kernel='precomputed'` and fit on non-square data. :pr:`14336` by :user:`Gregory Dexter `. -- |API| :class:`svm.SVR` and :class:`svm.OneClassSVM` attributes, `probA_` and - `probB_`, are now deprecated as they were not useful. :pr:`15558` by - `Thomas Fan`_. - - |Fix| :class:`svm.SVC`, :class:`svm.SVR`, :class:`svm.NuSVR` and :class:`svm.OneClassSVM` when received values negative or zero for parameter ``sample_weight`` in method fit(), generated an diff --git a/doc/whats_new/v0.23.rst b/doc/whats_new/v0.23.rst index c57855dd774b2..3eed9f657d848 100644 --- a/doc/whats_new/v0.23.rst +++ b/doc/whats_new/v0.23.rst @@ -50,3 +50,10 @@ Changelog - |Enhancement| :class:`cluster.AgglomerativeClustering` has a faster and more more memory efficient implementation of single linkage clustering. :pr:`11514` by :user:`Leland McInnes `. + +:mod:`sklearn.svm` +.................. + +- |API| :class:`svm.SVR` and :class:`svm.OneClassSVM` attributes, `probA_` and + `probB_`, are now deprecated as they were not useful. :pr:`15558` by + `Thomas Fan`_. diff --git a/sklearn/svm/_classes.py b/sklearn/svm/_classes.py index 9949e14962690..33ae41fc7d5fc 100644 --- a/sklearn/svm/_classes.py +++ b/sklearn/svm/_classes.py @@ -993,15 +993,15 @@ def __init__(self, kernel='rbf', degree=3, gamma='scale', class_weight=None, max_iter=max_iter, random_state=None) @deprecated( - "The probA_ attribute is deprecated in version 0.22 and will be " - "removed in version 0.24.") + "The probA_ attribute is deprecated in version 0.23 and will be " + "removed in version 0.25.") @property def probA_(self): return self._probA @deprecated( - "The probB_ attribute is deprecated in version 0.22 and will be " - "removed in version 0.24.") + "The probB_ attribute is deprecated in version 0.23 and will be " + "removed in version 0.25.") @property def probB_(self): return self._probB @@ -1326,15 +1326,15 @@ def predict(self, X): return np.asarray(y, dtype=np.intp) @deprecated( - "The probA_ attribute is deprecated in version 0.22 and will be " - "removed in version 0.24.") + "The probA_ attribute is deprecated in version 0.23 and will be " + "removed in version 0.25.") @property def probA_(self): return self._probA @deprecated( - "The probB_ attribute is deprecated in version 0.22 and will be " - "removed in version 0.24.") + "The probB_ attribute is deprecated in version 0.23 and will be " + "removed in version 0.25.") @property def probB_(self): return self._probB diff --git a/sklearn/svm/tests/test_svm.py b/sklearn/svm/tests/test_svm.py index 4f815b1cd0426..e46cc3c67176a 100644 --- a/sklearn/svm/tests/test_svm.py +++ b/sklearn/svm/tests/test_svm.py @@ -1268,7 +1268,7 @@ def test_n_support_oneclass_svr(): assert reg.n_support_ == 4 -# TODO: Remove in 0.24 when probA_ and probB_ are deprecated +# TODO: Remove in 0.25 when probA_ and probB_ are deprecated @pytest.mark.parametrize("SVMClass, data", [ (svm.OneClassSVM, (X, )), (svm.SVR, (X, Y)) @@ -1277,7 +1277,7 @@ def test_n_support_oneclass_svr(): def test_svm_probA_proB_deprecated(SVMClass, data, deprecated_prob): clf = SVMClass().fit(*data) - msg = ("The {} attribute is deprecated in version 0.22 and will be " - "removed in version 0.24.").format(deprecated_prob) + msg = ("The {} attribute is deprecated in version 0.23 and will be " + "removed in version 0.25.").format(deprecated_prob) with pytest.warns(FutureWarning, match=msg): getattr(clf, deprecated_prob)