Skip to content
Browse files

Flip the sign when the user accesses coef_ or intercept_ in the 2-cla…

…ss case.
  • Loading branch information...
1 parent 0faf690 commit 16dc776cc83e57d9f3f564186a1bc4845dc0aa49 @mblondel mblondel committed
Showing with 30 additions and 8 deletions.
  1. +13 −4 scikits/learn/svm/base.py
  2. +2 −1 scikits/learn/svm/sparse/base.py
  3. +15 −3 scikits/learn/svm/tests/test_svm.py
View
17 scikits/learn/svm/base.py
@@ -435,7 +435,8 @@ def decision_function(self, X):
self._get_bias())
if len(self.label_) <= 2:
- # one class
+ # in the two-class case, the decision sign needs be flipped
+ # due to liblinear's design
return -dec_func
else:
return dec_func
@@ -451,14 +452,22 @@ def _check_n_features(self, X):
@property
def intercept_(self):
if self.fit_intercept:
- return self.intercept_scaling * self.raw_coef_[:, -1]
+ ret = self.intercept_scaling * self.raw_coef_[:, -1]
+ if len(self.label_) <= 2:
+ ret *= -1
+ return ret
return 0.0
@property
def coef_(self):
if self.fit_intercept:
- return self.raw_coef_[:, : -1]
- return self.raw_coef_
+ ret = self.raw_coef_[:, : -1]
+ else:
+ ret = self.raw_coef_
+ if len(self.label_) <= 2:
+ return -ret
+ else:
+ return ret
def predict_proba(self, T):
# only available for logistic regression
View
3 scikits/learn/svm/sparse/base.py
@@ -265,7 +265,8 @@ def decision_function(self, X):
self._get_bias())
if len(self.label_) <= 2:
- # one class
+ # in the two-class case, the decision sign needs be flipped
+ # due to liblinear's design
return -dec_func
else:
return dec_func
View
18 scikits/learn/svm/tests/test_svm.py
@@ -417,7 +417,7 @@ def test_dense_liblinear_intercept_handling(classifier=svm.LinearSVC):
clf.intercept_scaling = 100
clf.fit(X, y)
intercept1 = clf.intercept_
- assert intercept1 > 1
+ assert intercept1 < -1
# when intercept_scaling is sufficiently high, the intercept value
# doesn't depend on intercept_scaling value
@@ -435,14 +435,26 @@ def test_liblinear_predict():
returns the same as the one in libliblinear
"""
+ # multi-class case
clf = svm.LinearSVC().fit(iris.data, iris.target)
-
weights = clf.coef_.T
bias = clf.intercept_
H = np.dot(iris.data, weights) + bias
-
assert_array_equal(clf.predict(iris.data), H.argmax(axis=1))
+ # binary-class case
+ X = [[2, 1],
+ [3, 1],
+ [1, 3],
+ [2, 3]]
+ y = [0, 0, 1, 1]
+
+ clf = svm.LinearSVC().fit(X, y)
+ weights = np.ravel(clf.coef_)
+ bias = clf.intercept_
+ H = np.dot(X, weights) + bias
+ assert_array_equal(clf.predict(X), (H > 0).astype(int))
+
if __name__ == '__main__':
import nose
nose.runmodule()

3 comments on commit 16dc776

@fabianp
scikit-learn member

Thanks, probably a similar fix should be added to the libsvm part, since:

In [9]: clf = svm.SVC(kernel='linear').fit([[0], [1]], [0, 1])
In [10]: clf.intercept_
Out[10]: array([ 0.5])

and the decision function at 0 should match this, but:

In [11]: clf.decision_function([[0]])
Out[11]: array([[-0.5]])

Because we don't use properties in SVC, the fix should probably go at the C/Cython level.

@mblondel
scikit-learn member

I hit this problem today because I needed to do the dot products by myself rather than calling decision_function.

I think my fix makes the Python code harder to read. Ideally the fix should go to the C/Cython layer too.

@fabianp
scikit-learn member

Great, I opened issue #91

Please sign in to comment.
Something went wrong with that request. Please try again.