test_passive_aggressive.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import numpy as np
  2. import pytest
  3. import scipy.sparse as sp
  4. from sklearn.base import ClassifierMixin
  5. from sklearn.datasets import load_iris
  6. from sklearn.linear_model import PassiveAggressiveClassifier, PassiveAggressiveRegressor
  7. from sklearn.utils import check_random_state
  8. from sklearn.utils._testing import (
  9. assert_almost_equal,
  10. assert_array_almost_equal,
  11. assert_array_equal,
  12. )
  13. iris = load_iris()
  14. random_state = check_random_state(12)
  15. indices = np.arange(iris.data.shape[0])
  16. random_state.shuffle(indices)
  17. X = iris.data[indices]
  18. y = iris.target[indices]
  19. X_csr = sp.csr_matrix(X)
  20. class MyPassiveAggressive(ClassifierMixin):
  21. def __init__(
  22. self,
  23. C=1.0,
  24. epsilon=0.01,
  25. loss="hinge",
  26. fit_intercept=True,
  27. n_iter=1,
  28. random_state=None,
  29. ):
  30. self.C = C
  31. self.epsilon = epsilon
  32. self.loss = loss
  33. self.fit_intercept = fit_intercept
  34. self.n_iter = n_iter
  35. def fit(self, X, y):
  36. n_samples, n_features = X.shape
  37. self.w = np.zeros(n_features, dtype=np.float64)
  38. self.b = 0.0
  39. for t in range(self.n_iter):
  40. for i in range(n_samples):
  41. p = self.project(X[i])
  42. if self.loss in ("hinge", "squared_hinge"):
  43. loss = max(1 - y[i] * p, 0)
  44. else:
  45. loss = max(np.abs(p - y[i]) - self.epsilon, 0)
  46. sqnorm = np.dot(X[i], X[i])
  47. if self.loss in ("hinge", "epsilon_insensitive"):
  48. step = min(self.C, loss / sqnorm)
  49. elif self.loss in ("squared_hinge", "squared_epsilon_insensitive"):
  50. step = loss / (sqnorm + 1.0 / (2 * self.C))
  51. if self.loss in ("hinge", "squared_hinge"):
  52. step *= y[i]
  53. else:
  54. step *= np.sign(y[i] - p)
  55. self.w += step * X[i]
  56. if self.fit_intercept:
  57. self.b += step
  58. def project(self, X):
  59. return np.dot(X, self.w) + self.b
  60. def test_classifier_accuracy():
  61. for data in (X, X_csr):
  62. for fit_intercept in (True, False):
  63. for average in (False, True):
  64. clf = PassiveAggressiveClassifier(
  65. C=1.0,
  66. max_iter=30,
  67. fit_intercept=fit_intercept,
  68. random_state=1,
  69. average=average,
  70. tol=None,
  71. )
  72. clf.fit(data, y)
  73. score = clf.score(data, y)
  74. assert score > 0.79
  75. if average:
  76. assert hasattr(clf, "_average_coef")
  77. assert hasattr(clf, "_average_intercept")
  78. assert hasattr(clf, "_standard_intercept")
  79. assert hasattr(clf, "_standard_coef")
  80. def test_classifier_partial_fit():
  81. classes = np.unique(y)
  82. for data in (X, X_csr):
  83. for average in (False, True):
  84. clf = PassiveAggressiveClassifier(
  85. random_state=0, average=average, max_iter=5
  86. )
  87. for t in range(30):
  88. clf.partial_fit(data, y, classes)
  89. score = clf.score(data, y)
  90. assert score > 0.79
  91. if average:
  92. assert hasattr(clf, "_average_coef")
  93. assert hasattr(clf, "_average_intercept")
  94. assert hasattr(clf, "_standard_intercept")
  95. assert hasattr(clf, "_standard_coef")
  96. def test_classifier_refit():
  97. # Classifier can be retrained on different labels and features.
  98. clf = PassiveAggressiveClassifier(max_iter=5).fit(X, y)
  99. assert_array_equal(clf.classes_, np.unique(y))
  100. clf.fit(X[:, :-1], iris.target_names[y])
  101. assert_array_equal(clf.classes_, iris.target_names)
  102. @pytest.mark.parametrize("loss", ("hinge", "squared_hinge"))
  103. def test_classifier_correctness(loss):
  104. y_bin = y.copy()
  105. y_bin[y != 1] = -1
  106. clf1 = MyPassiveAggressive(loss=loss, n_iter=2)
  107. clf1.fit(X, y_bin)
  108. for data in (X, X_csr):
  109. clf2 = PassiveAggressiveClassifier(
  110. loss=loss, max_iter=2, shuffle=False, tol=None
  111. )
  112. clf2.fit(data, y_bin)
  113. assert_array_almost_equal(clf1.w, clf2.coef_.ravel(), decimal=2)
  114. @pytest.mark.parametrize(
  115. "response_method", ["predict_proba", "predict_log_proba", "transform"]
  116. )
  117. def test_classifier_undefined_methods(response_method):
  118. clf = PassiveAggressiveClassifier(max_iter=100)
  119. with pytest.raises(AttributeError):
  120. getattr(clf, response_method)
  121. def test_class_weights():
  122. # Test class weights.
  123. X2 = np.array([[-1.0, -1.0], [-1.0, 0], [-0.8, -1.0], [1.0, 1.0], [1.0, 0.0]])
  124. y2 = [1, 1, 1, -1, -1]
  125. clf = PassiveAggressiveClassifier(
  126. C=0.1, max_iter=100, class_weight=None, random_state=100
  127. )
  128. clf.fit(X2, y2)
  129. assert_array_equal(clf.predict([[0.2, -1.0]]), np.array([1]))
  130. # we give a small weights to class 1
  131. clf = PassiveAggressiveClassifier(
  132. C=0.1, max_iter=100, class_weight={1: 0.001}, random_state=100
  133. )
  134. clf.fit(X2, y2)
  135. # now the hyperplane should rotate clock-wise and
  136. # the prediction on this point should shift
  137. assert_array_equal(clf.predict([[0.2, -1.0]]), np.array([-1]))
  138. def test_partial_fit_weight_class_balanced():
  139. # partial_fit with class_weight='balanced' not supported
  140. clf = PassiveAggressiveClassifier(class_weight="balanced", max_iter=100)
  141. with pytest.raises(ValueError):
  142. clf.partial_fit(X, y, classes=np.unique(y))
  143. def test_equal_class_weight():
  144. X2 = [[1, 0], [1, 0], [0, 1], [0, 1]]
  145. y2 = [0, 0, 1, 1]
  146. clf = PassiveAggressiveClassifier(C=0.1, tol=None, class_weight=None)
  147. clf.fit(X2, y2)
  148. # Already balanced, so "balanced" weights should have no effect
  149. clf_balanced = PassiveAggressiveClassifier(C=0.1, tol=None, class_weight="balanced")
  150. clf_balanced.fit(X2, y2)
  151. clf_weighted = PassiveAggressiveClassifier(
  152. C=0.1, tol=None, class_weight={0: 0.5, 1: 0.5}
  153. )
  154. clf_weighted.fit(X2, y2)
  155. # should be similar up to some epsilon due to learning rate schedule
  156. assert_almost_equal(clf.coef_, clf_weighted.coef_, decimal=2)
  157. assert_almost_equal(clf.coef_, clf_balanced.coef_, decimal=2)
  158. def test_wrong_class_weight_label():
  159. # ValueError due to wrong class_weight label.
  160. X2 = np.array([[-1.0, -1.0], [-1.0, 0], [-0.8, -1.0], [1.0, 1.0], [1.0, 0.0]])
  161. y2 = [1, 1, 1, -1, -1]
  162. clf = PassiveAggressiveClassifier(class_weight={0: 0.5}, max_iter=100)
  163. with pytest.raises(ValueError):
  164. clf.fit(X2, y2)
  165. def test_regressor_mse():
  166. y_bin = y.copy()
  167. y_bin[y != 1] = -1
  168. for data in (X, X_csr):
  169. for fit_intercept in (True, False):
  170. for average in (False, True):
  171. reg = PassiveAggressiveRegressor(
  172. C=1.0,
  173. fit_intercept=fit_intercept,
  174. random_state=0,
  175. average=average,
  176. max_iter=5,
  177. )
  178. reg.fit(data, y_bin)
  179. pred = reg.predict(data)
  180. assert np.mean((pred - y_bin) ** 2) < 1.7
  181. if average:
  182. assert hasattr(reg, "_average_coef")
  183. assert hasattr(reg, "_average_intercept")
  184. assert hasattr(reg, "_standard_intercept")
  185. assert hasattr(reg, "_standard_coef")
  186. def test_regressor_partial_fit():
  187. y_bin = y.copy()
  188. y_bin[y != 1] = -1
  189. for data in (X, X_csr):
  190. for average in (False, True):
  191. reg = PassiveAggressiveRegressor(
  192. random_state=0, average=average, max_iter=100
  193. )
  194. for t in range(50):
  195. reg.partial_fit(data, y_bin)
  196. pred = reg.predict(data)
  197. assert np.mean((pred - y_bin) ** 2) < 1.7
  198. if average:
  199. assert hasattr(reg, "_average_coef")
  200. assert hasattr(reg, "_average_intercept")
  201. assert hasattr(reg, "_standard_intercept")
  202. assert hasattr(reg, "_standard_coef")
  203. @pytest.mark.parametrize("loss", ("epsilon_insensitive", "squared_epsilon_insensitive"))
  204. def test_regressor_correctness(loss):
  205. y_bin = y.copy()
  206. y_bin[y != 1] = -1
  207. reg1 = MyPassiveAggressive(loss=loss, n_iter=2)
  208. reg1.fit(X, y_bin)
  209. for data in (X, X_csr):
  210. reg2 = PassiveAggressiveRegressor(
  211. tol=None, loss=loss, max_iter=2, shuffle=False
  212. )
  213. reg2.fit(data, y_bin)
  214. assert_array_almost_equal(reg1.w, reg2.coef_.ravel(), decimal=2)
  215. def test_regressor_undefined_methods():
  216. reg = PassiveAggressiveRegressor(max_iter=100)
  217. with pytest.raises(AttributeError):
  218. reg.transform(X)