前一篇文章中,对
prob = pd.DataFrame(clf.decision_function(Xtest))
prob.loc[prob.iloc[:,0] >= 0.13157937002761821,"y_pred"] = 1
prob.loc[prob.iloc[:,0] < 0.13157937002761821,"y_pred"] = 0
有疑问,为什么比阈值大的标签就是1,反之就是0,本篇文章就是来探讨这个问题,并对decision_function会有更详细的解释
首先我们先看二分类的数据,为了便于展示,我们使用二维数据
from sklearn.svm import SVC
import numpy as np
from sklearn.datasets import make_classification
import matplotlib.pyplot as plt
import pandas as pd
X,y = make_classification(n_samples=10
,n_features=2
,n_informative=2
,n_redundant=0
,n_classes=2
,n_clusters_per_class=1
,random_state=1)
plt.scatter(X[:,0],X[:,1],c=y)
这里为decision_function更好理解,所以我们要画出分离超平面和决策边界
X_min,X_max = X[:,0].min()-.5,X[:,0].max()+.5
Y_min,Y_max = X[:,1].min()-.5,X[:,1].max()+.5
xx,yy = np.meshgrid(np.linspace(X_min,X_max,30),np.linspace(Y_min,Y_max,30))
xy = np.c_[xx.ravel(),yy.ravel()]
clf = SVC(kernel="linear",probability=True).fit(X,y)
Z = clf.decision_function(xy).reshape(xx.shape)
plt.scatter(clf.support_vectors_[:,0],clf.support_vectors_[:,1],edgecolors='red',s=70)
plt.scatter(X[:,0],X[:,1],c=y)
plt.contour(xx,yy,Z,levels=[-1,0,1],colors='k',linestyles=['--','-','--'])
predict_proba
我们直接调用predict_proba会返回一个array
clf.predict_proba(X)
---
array([[0.23591956, 0.76408044],
[0.52321909, 0.47678091],
[0.23591958, 0.76408042],
……
[0.09537723, 0.90462277]])
但是我们不知道第一列代表什么,也就是第一列的标签是什么我们并不知道。其实在classes_中给我们指定了
clf.classes_
---
array([0, 1])
这就告诉我们predict_proba返回的array第一列的标签为0,第二列的标签为0。这个同样适用于多分类
因此predict_proba的返回值可以解释为每个位置的概率分别对应classes_中对应位置的类别标签
decision_function
再看decision_function
clf.decision_function(X)
---
array([ 1.00018728, -0.16081953, 1.00018718, 1.83842516, -2.25386413,
-1.59970291, -1.16204388, -1.00037424, 1.65798574, 1.98238421])
返回了一个array,但是这个是一维的,官方的解释是样本到分隔超平面的有符号距离来度量预测结果的置信度。可以大概用上面的可视化看一看(按照我们的约定,分离超平面上的样本点距离为0,决策边界上的样本点距离为 ± 1 \pm 1 ±1),显然是正确的,也很好理解
需要注意的是二分类情况下classes_中的第一个标签到超平面的距离为负,第二个标签到超平面的距离为正
实际上predict_proba和decision_function对于默认情况下预测标签的作用是相同的
X_prob = pd.DataFrame(clf.predict_proba(X))
X_prob.loc[X_prob.loc[:,0] >= 0.5,"y_pred"] = 0
X_prob.loc[X_prob.loc[:,0] < 0.5,"y_pred"] = 1
# predict_proba默认阈值为0.5
X_dec = pd.DataFrame(clf.decision_function(X))
X_dec.loc[X_dec.loc[:,0] > 0,"y_pred"] = 1
X_dec.loc[X_dec.loc[:,0] <= 0,"y_pred"] = 0
# decision_function默认阈值为0
(X_prob.loc[:,"y_pred"] == X_dec.loc[:,"y_pred"]).value_counts()
---
True 10
Name: y_pred, dtype: int64
综上很好的说明了之前
prob = pd.DataFrame(clf.decision_function(Xtest))
prob.loc[prob.iloc[:,0] >= 0.13157937002761821,"y_pred"] = 1
prob.loc[prob.iloc[:,0] < 0.13157937002761821,"y_pred"] = 0
的原因,因为0就是默认为负的,而我们改变的阈值,但是符号是不变的(至于等号在哪边我觉得不需要太关注)
对于多分类
这里重点关注decision_function
X,y = make_classification(n_samples=20
,n_features=2
,n_informative=2
,n_redundant=0
,n_classes=4
,n_clusters_per_class=1
,random_state=1)
set(y)
---
{0, 1, 2, 3}
plt.scatter(X[:,0],X[:,1],c=y)
OVR
这里decision_function
输出的shape受到decision_function_shape
的影响,decision_function_shape
有两个取值ovo,ovr,可以看看逻辑回归的multi_classes
,二者是相同的
这里我们先说decision_function_shape='ovr'
的情况,因为这里有4中标签,因此此时的SVM会有 C 4 1 = 4 C_{4}^{1}=4 C41=4个分类器,一个分类器在decision_function就是一个维度,从左到右依次是0vr、1vr、2vr、3vr,因此哪一列的数值最大,那么这个样本的预测标签就是分类器对应的标签
clf = SVC(kernel="linear",probability=True,decision_function_shape='ovr').fit(X,y)
X_prob = clf.predict_proba(X)
X_dec = clf.decision_function(X)
X_pre = clf.predict(X)
clf.classes_
---
array([0, 1, 2, 3])
X_dec
---
array([[ 3.28784057, 2.2352511 , 1.14201238, -0.30149012],
[ 0.73528603, -0.2446702 , 2.26653209, 3.24143854],
[ 1.96880983, 0.84197947, 3.24635749, -0.21545976],
……
X_pre # 我们可以自行验证一下
---
array([0, 3, 2, 2, 1, 0, 2, 1, 3, 2, 0, 1, 0, 2, 0, 1, 3, 3, 1, 1])
此时predict_proba返回的依旧是每个标签的概率,哪一列的数值最大,那么这个样本的预测标签就是分类器对应的标签。因为是概率所以加起来等于1。对应predict可以验证一下
X_prob
---
array([[0.80906775, 0.08899449, 0.07938091, 0.02255684],
[0.06470735, 0.09689169, 0.28614348, 0.55225747],
[0.20518961, 0.18070128, 0.46535195, 0.14875717],
……
OVO
clf = SVC(kernel="linear",probability=True,decision_function_shape='ovo').fit(X,y)
X_prob = clf.predict_proba(X)
X_dec = clf.decision_function(X)
X_pre = clf.predict(X)
decision_function这里是 C 4 2 = 6 C_{4}^{2}=6 C42=6,也就是01、02、03、12、13、23组合6个分类器,前面的数字表示正类,后面的表示负类
np.set_printoptions(suppress=True)
X_dec # 以第一行为例,输出应该是,0,0,0,1,1,2,因此结果为0
---
array([[ 1.49694976, 2.67767293, 2.15255016, 0.24857641, 3.64688225,
3.66852234],
[ 0.0028548 , -2.86082208, -0.99974773, -1.69537724, -1.06131628,
-0.5662723 ],
[ 0.56618621, -0.83219337, 0.16277738, -1.00000001, 0.66482295,
1.00028824],
……
X_pre # 自行验证
---
array([0, 3, 2, 2, 1, 0, 2, 1, 3, 2, 0, 1, 0, 2, 0, 1, 3, 3, 1, 1])
predict_proba同ovr,需要的话自行验证
X_prob
---
array([[0.78288768, 0.09813264, 0.09142072, 0.02755896],
[0.05720746, 0.11158587, 0.36170213, 0.46950454],
[0.18260654, 0.17594623, 0.47444007, 0.16700716],
……