test_sparse.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. import numpy as np
  2. import pytest
  3. from numpy.testing import assert_array_almost_equal, assert_array_equal
  4. from scipy import sparse
  5. from sklearn import base, datasets, linear_model, svm
  6. from sklearn.datasets import load_digits, make_blobs, make_classification
  7. from sklearn.exceptions import ConvergenceWarning
  8. from sklearn.svm.tests import test_svm
  9. from sklearn.utils._testing import ignore_warnings, skip_if_32bit
  10. from sklearn.utils.extmath import safe_sparse_dot
  11. # test sample 1
  12. X = np.array([[-2, -1], [-1, -1], [-1, -2], [1, 1], [1, 2], [2, 1]])
  13. X_sp = sparse.lil_matrix(X)
  14. Y = [1, 1, 1, 2, 2, 2]
  15. T = np.array([[-1, -1], [2, 2], [3, 2]])
  16. true_result = [1, 2, 2]
  17. # test sample 2
  18. X2 = np.array(
  19. [
  20. [0, 0, 0],
  21. [1, 1, 1],
  22. [2, 0, 0],
  23. [0, 0, 2],
  24. [3, 3, 3],
  25. ]
  26. )
  27. X2_sp = sparse.dok_matrix(X2)
  28. Y2 = [1, 2, 2, 2, 3]
  29. T2 = np.array([[-1, -1, -1], [1, 1, 1], [2, 2, 2]])
  30. true_result2 = [1, 2, 3]
  31. iris = datasets.load_iris()
  32. # permute
  33. rng = np.random.RandomState(0)
  34. perm = rng.permutation(iris.target.size)
  35. iris.data = iris.data[perm]
  36. iris.target = iris.target[perm]
  37. # sparsify
  38. iris.data = sparse.csr_matrix(iris.data)
  39. def check_svm_model_equal(dense_svm, sparse_svm, X_train, y_train, X_test):
  40. dense_svm.fit(X_train.toarray(), y_train)
  41. if sparse.issparse(X_test):
  42. X_test_dense = X_test.toarray()
  43. else:
  44. X_test_dense = X_test
  45. sparse_svm.fit(X_train, y_train)
  46. assert sparse.issparse(sparse_svm.support_vectors_)
  47. assert sparse.issparse(sparse_svm.dual_coef_)
  48. assert_array_almost_equal(
  49. dense_svm.support_vectors_, sparse_svm.support_vectors_.toarray()
  50. )
  51. assert_array_almost_equal(dense_svm.dual_coef_, sparse_svm.dual_coef_.toarray())
  52. if dense_svm.kernel == "linear":
  53. assert sparse.issparse(sparse_svm.coef_)
  54. assert_array_almost_equal(dense_svm.coef_, sparse_svm.coef_.toarray())
  55. assert_array_almost_equal(dense_svm.support_, sparse_svm.support_)
  56. assert_array_almost_equal(
  57. dense_svm.predict(X_test_dense), sparse_svm.predict(X_test)
  58. )
  59. assert_array_almost_equal(
  60. dense_svm.decision_function(X_test_dense), sparse_svm.decision_function(X_test)
  61. )
  62. assert_array_almost_equal(
  63. dense_svm.decision_function(X_test_dense),
  64. sparse_svm.decision_function(X_test_dense),
  65. )
  66. if isinstance(dense_svm, svm.OneClassSVM):
  67. msg = "cannot use sparse input in 'OneClassSVM' trained on dense data"
  68. else:
  69. assert_array_almost_equal(
  70. dense_svm.predict_proba(X_test_dense), sparse_svm.predict_proba(X_test), 4
  71. )
  72. msg = "cannot use sparse input in 'SVC' trained on dense data"
  73. if sparse.issparse(X_test):
  74. with pytest.raises(ValueError, match=msg):
  75. dense_svm.predict(X_test)
  76. @skip_if_32bit
  77. def test_svc():
  78. """Check that sparse SVC gives the same result as SVC"""
  79. # many class dataset:
  80. X_blobs, y_blobs = make_blobs(n_samples=100, centers=10, random_state=0)
  81. X_blobs = sparse.csr_matrix(X_blobs)
  82. datasets = [
  83. [X_sp, Y, T],
  84. [X2_sp, Y2, T2],
  85. [X_blobs[:80], y_blobs[:80], X_blobs[80:]],
  86. [iris.data, iris.target, iris.data],
  87. ]
  88. kernels = ["linear", "poly", "rbf", "sigmoid"]
  89. for dataset in datasets:
  90. for kernel in kernels:
  91. clf = svm.SVC(
  92. gamma=1,
  93. kernel=kernel,
  94. probability=True,
  95. random_state=0,
  96. decision_function_shape="ovo",
  97. )
  98. sp_clf = svm.SVC(
  99. gamma=1,
  100. kernel=kernel,
  101. probability=True,
  102. random_state=0,
  103. decision_function_shape="ovo",
  104. )
  105. check_svm_model_equal(clf, sp_clf, *dataset)
  106. def test_unsorted_indices():
  107. # test that the result with sorted and unsorted indices in csr is the same
  108. # we use a subset of digits as iris, blobs or make_classification didn't
  109. # show the problem
  110. X, y = load_digits(return_X_y=True)
  111. X_test = sparse.csr_matrix(X[50:100])
  112. X, y = X[:50], y[:50]
  113. X_sparse = sparse.csr_matrix(X)
  114. coef_dense = (
  115. svm.SVC(kernel="linear", probability=True, random_state=0).fit(X, y).coef_
  116. )
  117. sparse_svc = svm.SVC(kernel="linear", probability=True, random_state=0).fit(
  118. X_sparse, y
  119. )
  120. coef_sorted = sparse_svc.coef_
  121. # make sure dense and sparse SVM give the same result
  122. assert_array_almost_equal(coef_dense, coef_sorted.toarray())
  123. # reverse each row's indices
  124. def scramble_indices(X):
  125. new_data = []
  126. new_indices = []
  127. for i in range(1, len(X.indptr)):
  128. row_slice = slice(*X.indptr[i - 1 : i + 1])
  129. new_data.extend(X.data[row_slice][::-1])
  130. new_indices.extend(X.indices[row_slice][::-1])
  131. return sparse.csr_matrix((new_data, new_indices, X.indptr), shape=X.shape)
  132. X_sparse_unsorted = scramble_indices(X_sparse)
  133. X_test_unsorted = scramble_indices(X_test)
  134. assert not X_sparse_unsorted.has_sorted_indices
  135. assert not X_test_unsorted.has_sorted_indices
  136. unsorted_svc = svm.SVC(kernel="linear", probability=True, random_state=0).fit(
  137. X_sparse_unsorted, y
  138. )
  139. coef_unsorted = unsorted_svc.coef_
  140. # make sure unsorted indices give same result
  141. assert_array_almost_equal(coef_unsorted.toarray(), coef_sorted.toarray())
  142. assert_array_almost_equal(
  143. sparse_svc.predict_proba(X_test_unsorted), sparse_svc.predict_proba(X_test)
  144. )
  145. def test_svc_with_custom_kernel():
  146. def kfunc(x, y):
  147. return safe_sparse_dot(x, y.T)
  148. clf_lin = svm.SVC(kernel="linear").fit(X_sp, Y)
  149. clf_mylin = svm.SVC(kernel=kfunc).fit(X_sp, Y)
  150. assert_array_equal(clf_lin.predict(X_sp), clf_mylin.predict(X_sp))
  151. @skip_if_32bit
  152. def test_svc_iris():
  153. # Test the sparse SVC with the iris dataset
  154. for k in ("linear", "poly", "rbf"):
  155. sp_clf = svm.SVC(kernel=k).fit(iris.data, iris.target)
  156. clf = svm.SVC(kernel=k).fit(iris.data.toarray(), iris.target)
  157. assert_array_almost_equal(
  158. clf.support_vectors_, sp_clf.support_vectors_.toarray()
  159. )
  160. assert_array_almost_equal(clf.dual_coef_, sp_clf.dual_coef_.toarray())
  161. assert_array_almost_equal(
  162. clf.predict(iris.data.toarray()), sp_clf.predict(iris.data)
  163. )
  164. if k == "linear":
  165. assert_array_almost_equal(clf.coef_, sp_clf.coef_.toarray())
  166. def test_sparse_decision_function():
  167. # Test decision_function
  168. # Sanity check, test that decision_function implemented in python
  169. # returns the same as the one in libsvm
  170. # multi class:
  171. svc = svm.SVC(kernel="linear", C=0.1, decision_function_shape="ovo")
  172. clf = svc.fit(iris.data, iris.target)
  173. dec = safe_sparse_dot(iris.data, clf.coef_.T) + clf.intercept_
  174. assert_array_almost_equal(dec, clf.decision_function(iris.data))
  175. # binary:
  176. clf.fit(X, Y)
  177. dec = np.dot(X, clf.coef_.T) + clf.intercept_
  178. prediction = clf.predict(X)
  179. assert_array_almost_equal(dec.ravel(), clf.decision_function(X))
  180. assert_array_almost_equal(
  181. prediction, clf.classes_[(clf.decision_function(X) > 0).astype(int).ravel()]
  182. )
  183. expected = np.array([-1.0, -0.66, -1.0, 0.66, 1.0, 1.0])
  184. assert_array_almost_equal(clf.decision_function(X), expected, 2)
  185. def test_error():
  186. # Test that it gives proper exception on deficient input
  187. clf = svm.SVC()
  188. Y2 = Y[:-1] # wrong dimensions for labels
  189. with pytest.raises(ValueError):
  190. clf.fit(X_sp, Y2)
  191. clf.fit(X_sp, Y)
  192. assert_array_equal(clf.predict(T), true_result)
  193. def test_linearsvc():
  194. # Similar to test_SVC
  195. clf = svm.LinearSVC(dual="auto", random_state=0).fit(X, Y)
  196. sp_clf = svm.LinearSVC(dual="auto", random_state=0).fit(X_sp, Y)
  197. assert sp_clf.fit_intercept
  198. assert_array_almost_equal(clf.coef_, sp_clf.coef_, decimal=4)
  199. assert_array_almost_equal(clf.intercept_, sp_clf.intercept_, decimal=4)
  200. assert_array_almost_equal(clf.predict(X), sp_clf.predict(X_sp))
  201. clf.fit(X2, Y2)
  202. sp_clf.fit(X2_sp, Y2)
  203. assert_array_almost_equal(clf.coef_, sp_clf.coef_, decimal=4)
  204. assert_array_almost_equal(clf.intercept_, sp_clf.intercept_, decimal=4)
  205. def test_linearsvc_iris():
  206. # Test the sparse LinearSVC with the iris dataset
  207. sp_clf = svm.LinearSVC(dual="auto", random_state=0).fit(iris.data, iris.target)
  208. clf = svm.LinearSVC(dual="auto", random_state=0).fit(
  209. iris.data.toarray(), iris.target
  210. )
  211. assert clf.fit_intercept == sp_clf.fit_intercept
  212. assert_array_almost_equal(clf.coef_, sp_clf.coef_, decimal=1)
  213. assert_array_almost_equal(clf.intercept_, sp_clf.intercept_, decimal=1)
  214. assert_array_almost_equal(
  215. clf.predict(iris.data.toarray()), sp_clf.predict(iris.data)
  216. )
  217. # check decision_function
  218. pred = np.argmax(sp_clf.decision_function(iris.data), 1)
  219. assert_array_almost_equal(pred, clf.predict(iris.data.toarray()))
  220. # sparsify the coefficients on both models and check that they still
  221. # produce the same results
  222. clf.sparsify()
  223. assert_array_equal(pred, clf.predict(iris.data))
  224. sp_clf.sparsify()
  225. assert_array_equal(pred, sp_clf.predict(iris.data))
  226. def test_weight():
  227. # Test class weights
  228. X_, y_ = make_classification(
  229. n_samples=200, n_features=100, weights=[0.833, 0.167], random_state=0
  230. )
  231. X_ = sparse.csr_matrix(X_)
  232. for clf in (
  233. linear_model.LogisticRegression(),
  234. svm.LinearSVC(dual="auto", random_state=0),
  235. svm.SVC(),
  236. ):
  237. clf.set_params(class_weight={0: 5})
  238. clf.fit(X_[:180], y_[:180])
  239. y_pred = clf.predict(X_[180:])
  240. assert np.sum(y_pred == y_[180:]) >= 11
  241. def test_sample_weights():
  242. # Test weights on individual samples
  243. clf = svm.SVC()
  244. clf.fit(X_sp, Y)
  245. assert_array_equal(clf.predict([X[2]]), [1.0])
  246. sample_weight = [0.1] * 3 + [10] * 3
  247. clf.fit(X_sp, Y, sample_weight=sample_weight)
  248. assert_array_equal(clf.predict([X[2]]), [2.0])
  249. def test_sparse_liblinear_intercept_handling():
  250. # Test that sparse liblinear honours intercept_scaling param
  251. test_svm.test_dense_liblinear_intercept_handling(svm.LinearSVC)
  252. @pytest.mark.parametrize("datasets_index", range(4))
  253. @pytest.mark.parametrize("kernel", ["linear", "poly", "rbf", "sigmoid"])
  254. @skip_if_32bit
  255. def test_sparse_oneclasssvm(datasets_index, kernel):
  256. # Check that sparse OneClassSVM gives the same result as dense OneClassSVM
  257. # many class dataset:
  258. X_blobs, _ = make_blobs(n_samples=100, centers=10, random_state=0)
  259. X_blobs = sparse.csr_matrix(X_blobs)
  260. datasets = [
  261. [X_sp, None, T],
  262. [X2_sp, None, T2],
  263. [X_blobs[:80], None, X_blobs[80:]],
  264. [iris.data, None, iris.data],
  265. ]
  266. dataset = datasets[datasets_index]
  267. clf = svm.OneClassSVM(gamma=1, kernel=kernel)
  268. sp_clf = svm.OneClassSVM(gamma=1, kernel=kernel)
  269. check_svm_model_equal(clf, sp_clf, *dataset)
  270. def test_sparse_realdata():
  271. # Test on a subset from the 20newsgroups dataset.
  272. # This catches some bugs if input is not correctly converted into
  273. # sparse format or weights are not correctly initialized.
  274. data = np.array([0.03771744, 0.1003567, 0.01174647, 0.027069])
  275. indices = np.array([6, 5, 35, 31])
  276. indptr = np.array(
  277. [
  278. 0,
  279. 0,
  280. 0,
  281. 0,
  282. 0,
  283. 0,
  284. 0,
  285. 0,
  286. 1,
  287. 1,
  288. 1,
  289. 1,
  290. 1,
  291. 1,
  292. 1,
  293. 1,
  294. 1,
  295. 1,
  296. 1,
  297. 1,
  298. 1,
  299. 1,
  300. 1,
  301. 1,
  302. 1,
  303. 1,
  304. 1,
  305. 1,
  306. 1,
  307. 1,
  308. 1,
  309. 1,
  310. 1,
  311. 1,
  312. 1,
  313. 1,
  314. 1,
  315. 1,
  316. 1,
  317. 1,
  318. 2,
  319. 2,
  320. 2,
  321. 2,
  322. 2,
  323. 2,
  324. 2,
  325. 2,
  326. 2,
  327. 2,
  328. 2,
  329. 2,
  330. 2,
  331. 2,
  332. 2,
  333. 2,
  334. 2,
  335. 2,
  336. 2,
  337. 2,
  338. 2,
  339. 2,
  340. 2,
  341. 2,
  342. 2,
  343. 2,
  344. 2,
  345. 2,
  346. 2,
  347. 2,
  348. 2,
  349. 2,
  350. 2,
  351. 2,
  352. 2,
  353. 2,
  354. 2,
  355. 2,
  356. 4,
  357. 4,
  358. 4,
  359. ]
  360. )
  361. X = sparse.csr_matrix((data, indices, indptr))
  362. y = np.array(
  363. [
  364. 1.0,
  365. 0.0,
  366. 2.0,
  367. 2.0,
  368. 1.0,
  369. 1.0,
  370. 1.0,
  371. 2.0,
  372. 2.0,
  373. 0.0,
  374. 1.0,
  375. 2.0,
  376. 2.0,
  377. 0.0,
  378. 2.0,
  379. 0.0,
  380. 3.0,
  381. 0.0,
  382. 3.0,
  383. 0.0,
  384. 1.0,
  385. 1.0,
  386. 3.0,
  387. 2.0,
  388. 3.0,
  389. 2.0,
  390. 0.0,
  391. 3.0,
  392. 1.0,
  393. 0.0,
  394. 2.0,
  395. 1.0,
  396. 2.0,
  397. 0.0,
  398. 1.0,
  399. 0.0,
  400. 2.0,
  401. 3.0,
  402. 1.0,
  403. 3.0,
  404. 0.0,
  405. 1.0,
  406. 0.0,
  407. 0.0,
  408. 2.0,
  409. 0.0,
  410. 1.0,
  411. 2.0,
  412. 2.0,
  413. 2.0,
  414. 3.0,
  415. 2.0,
  416. 0.0,
  417. 3.0,
  418. 2.0,
  419. 1.0,
  420. 2.0,
  421. 3.0,
  422. 2.0,
  423. 2.0,
  424. 0.0,
  425. 1.0,
  426. 0.0,
  427. 1.0,
  428. 2.0,
  429. 3.0,
  430. 0.0,
  431. 0.0,
  432. 2.0,
  433. 2.0,
  434. 1.0,
  435. 3.0,
  436. 1.0,
  437. 1.0,
  438. 0.0,
  439. 1.0,
  440. 2.0,
  441. 1.0,
  442. 1.0,
  443. 3.0,
  444. ]
  445. )
  446. clf = svm.SVC(kernel="linear").fit(X.toarray(), y)
  447. sp_clf = svm.SVC(kernel="linear").fit(sparse.coo_matrix(X), y)
  448. assert_array_equal(clf.support_vectors_, sp_clf.support_vectors_.toarray())
  449. assert_array_equal(clf.dual_coef_, sp_clf.dual_coef_.toarray())
  450. def test_sparse_svc_clone_with_callable_kernel():
  451. # Test that the "dense_fit" is called even though we use sparse input
  452. # meaning that everything works fine.
  453. a = svm.SVC(C=1, kernel=lambda x, y: x * y.T, probability=True, random_state=0)
  454. b = base.clone(a)
  455. b.fit(X_sp, Y)
  456. pred = b.predict(X_sp)
  457. b.predict_proba(X_sp)
  458. dense_svm = svm.SVC(
  459. C=1, kernel=lambda x, y: np.dot(x, y.T), probability=True, random_state=0
  460. )
  461. pred_dense = dense_svm.fit(X, Y).predict(X)
  462. assert_array_equal(pred_dense, pred)
  463. # b.decision_function(X_sp) # XXX : should be supported
  464. def test_timeout():
  465. sp = svm.SVC(
  466. C=1, kernel=lambda x, y: x * y.T, probability=True, random_state=0, max_iter=1
  467. )
  468. warning_msg = (
  469. r"Solver terminated early \(max_iter=1\). Consider pre-processing "
  470. r"your data with StandardScaler or MinMaxScaler."
  471. )
  472. with pytest.warns(ConvergenceWarning, match=warning_msg):
  473. sp.fit(X_sp, Y)
  474. def test_consistent_proba():
  475. a = svm.SVC(probability=True, max_iter=1, random_state=0)
  476. with ignore_warnings(category=ConvergenceWarning):
  477. proba_1 = a.fit(X, Y).predict_proba(X)
  478. a = svm.SVC(probability=True, max_iter=1, random_state=0)
  479. with ignore_warnings(category=ConvergenceWarning):
  480. proba_2 = a.fit(X, Y).predict_proba(X)
  481. assert_array_almost_equal(proba_1, proba_2)