From 938665fee8e0780c38b2c5650456dc4af4728d9e Mon Sep 17 00:00:00 2001 From: Casey Brooks Date: Fri, 26 Dec 2025 04:03:13 +0000 Subject: [PATCH] fix(svm): guard sparse fit with empty sv --- sklearn/svm/base.py | 22 +++++++++++++--------- sklearn/svm/tests/test_sparse.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/sklearn/svm/base.py b/sklearn/svm/base.py index 64cebe837514a..5a846a5680161 100644 --- a/sklearn/svm/base.py +++ b/sklearn/svm/base.py @@ -286,12 +286,15 @@ def _sparse_fit(self, X, y, sample_weight, solver_type, kernel, n_class = 1 n_SV = self.support_vectors_.shape[0] - dual_coef_indices = np.tile(np.arange(n_SV), n_class) - dual_coef_indptr = np.arange(0, dual_coef_indices.size + 1, - dual_coef_indices.size / n_class) - self.dual_coef_ = sp.csr_matrix( - (dual_coef_data, dual_coef_indices, dual_coef_indptr), - (n_class, n_SV)) + if n_SV == 0 or dual_coef_data.size == 0: + self.dual_coef_ = sp.csr_matrix((n_class, 0), dtype=np.float64) + else: + dual_coef_indices = np.tile(np.arange(n_SV), n_class) + dual_coef_indptr = np.arange(0, dual_coef_indices.size + 1, + dual_coef_indices.size / n_class) + self.dual_coef_ = sp.csr_matrix( + (dual_coef_data, dual_coef_indices, dual_coef_indptr), + (n_class, n_SV)) def predict(self, X): """Perform regression on samples in X. @@ -907,9 +910,10 @@ def _fit_liblinear(X, y, C, fit_intercept, intercept_scaling, class_weight, bias = -1.0 if fit_intercept: if intercept_scaling <= 0: - raise ValueError("Intercept scaling is %r but needs to be greater than 0." - " To disable fitting an intercept," - " set fit_intercept=False." % intercept_scaling) + raise ValueError( + "Intercept scaling is %r but needs to be greater than 0." + " To disable fitting an intercept, set fit_intercept=False." + % intercept_scaling) else: bias = intercept_scaling diff --git a/sklearn/svm/tests/test_sparse.py b/sklearn/svm/tests/test_sparse.py index 7cf6e8af2acf2..3148aefe36dc1 100644 --- a/sklearn/svm/tests/test_sparse.py +++ b/sklearn/svm/tests/test_sparse.py @@ -40,6 +40,34 @@ iris.data = sparse.csr_matrix(iris.data) +def test_sparse_svr_empty_support_vectors(): + X = np.array([[0, 1, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0], + [0, 0, 0, 1]], dtype=np.float64) + y = np.array([0.04, 0.04, 0.10, 0.16], dtype=np.float64) + params = dict(kernel='linear', gamma=1.0, C=316.227766017, epsilon=0.1) + + dense_model = svm.SVR(**params).fit(X, y) + assert dense_model.support_vectors_.shape == (0, X.shape[1]) + + X_sparse = sparse.csr_matrix(X) + sparse_model = svm.SVR(**params).fit(X_sparse, y) + + assert sparse.isspmatrix_csr(sparse_model.support_vectors_) + assert sparse.isspmatrix_csr(sparse_model.dual_coef_) + assert sparse_model.support_vectors_.shape == (0, X.shape[1]) + assert sparse_model.dual_coef_.shape == (1, 0) + assert sparse_model.dual_coef_.dtype == np.float64 + assert sparse_model.support_.size == 0 + assert sparse_model.dual_coef_.nnz == 0 + + assert_array_equal(sparse_model._n_support, dense_model._n_support) + assert_array_equal(sparse_model.support_, dense_model.support_) + assert_array_almost_equal(sparse_model.predict(X_sparse), + dense_model.predict(X)) + + def check_svm_model_equal(dense_svm, sparse_svm, X_train, y_train, X_test): dense_svm.fit(X_train.toarray(), y_train) if sparse.isspmatrix(X_test): @@ -149,7 +177,7 @@ def test_svc_iris(): for k in ('linear', 'poly', 'rbf'): sp_clf = svm.SVC(kernel=k).fit(iris.data, iris.target) clf = svm.SVC(kernel=k).fit(iris.data.toarray(), - iris.target) + iris.target) assert_array_almost_equal(clf.support_vectors_, sp_clf.support_vectors_.toarray())