软间隔让决定两条虚线超平面的支持向量可能是来自于同一个类别的样本点,而硬间隔的时候两条虚线超平面必须是来自两个不同类别的支持向量决定的。
C值会决定我们究竟是依赖红色点作为支持向量,还是要依赖软间隔中混在在红色中的紫色点来作为支持向量。如果C值设定较大,那SVC可能会选择边际较小的,能够更好地分类所有训练点的决策边界,训练时间也会变长。如果C的设定值较小,那SVC会尽量最大化边界,尽量将掉落在决策边界另一方的样本点预测正确,这样会影响训练的决策准确度。此时,所有可能影响超平面的样本都会被定义为支持向量,而不仅仅是压在虚线超平面上的点,是哪些混在彼此类别中的点。
观察不同数据集分类的支持向量
数据导入以及绘制linear下各个数据集的支持向量
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn import svm
from sklearn.datasets import make_circles, make_moons,make_blobs,make_classification
n_samples =100
datasets = [
make_moons(n_samples=n_samples, noise=0.2, random_state=0),
make_circles(n_samples=n_samples, noise=0.2, factor=0.5, random_state=1),
make_blobs(n_samples=n_samples, centers=2, random_state=5),
make_classification(n_samples=n_samples,n_features =
2,n_informative=2,n_redundant=0, random_state=5)
]
Kernel = ["linear"]
for X,Y in datasets:
plt.figure(figsize=(5,4))
plt.scatter(X[:,0],X[:,1],c=Y,s=50,cmap="rainbow")
nrows=len(datasets)
ncols=len(Kernel)+1
fig, axes = plt.subplots(nrows,ncols,figsize=(10,16))
for ds_cnt, (X,Y) in enumerate(datasets):
ax = axes[ds_cnt,0]
if ds_cnt ==0:
ax.set_title("Input data")
ax.scatter(X[:,0],X[:,1],c=Y,zorder=10,cmap=plt.cm.Paired,edgecolors='k')
ax.set_xticks(())
ax.set_yticks(())
for est_idx, kernel in enumerate(Kernel):
ax = axes[ds_cnt,est_idx+1]
clf = svm.SVC(kernel=kernel,gamma=2).fit(X,y)
score =clf.score(X,Y)
ax.scatter(X[:,0],X[:,1],c=Y
,zorder=10
,cmap=plt.cm.Paired,edgecolors='k')
ax.scatter(clf.support_vectors_[:,0],clf.support_vectors_[:,1],s=100,
facecolors='none',zorder=10,edgecolors='white')
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.mgrid[x_min:x_max:200j, y_min:y_max:200j]
#np.c_,类似于np.vstack的功能
Z = clf.decision_function(np.c_[XX.ravel(), YY.ravel()]).reshape(XX.shape)
ax.pcolormesh(XX,YY,Z>0,cmap=plt.cm.Paired)
ax.contour(XX,YY,Z,colors=['k','k','k'],linestyles=['--','-','--'],
levels=[-1,0,1])
ax.set_xticks(())
ax.set_yticks(())
if ds_cnt==0:
ax.set_title(kernel)
ax.text(0.95,0.06,('%.2f'% score).lstrip('0')
,size=15
,bbox=dict(boxstyle='round',alpha=0.8,facecolor='white')
,transform=ax.transAxes
,horizontalalignment='right')
plt.tight_layout()
plt.show()
其中白色圈出来的就是我们的支持向量,可以看到所有在两条虚线超平面之间的点,和虚线超平面外的,但属于另一个类别的点,都被认为是支持向量,并不是因为这些点都在超平面上,而是因为超平面由所有的这些点来决定,可以调节C来移动超平面,让超平面超过任何一个白色点。
分类模型天生倾向于多数的类,即让多数类判断正确,少数类被牺牲掉。为了让算法意识到数据的标签是不均衡的,通过一些惩罚或者改变样本本身,让模型向着捕获少数类的方向建模。SVC中一半调整SVC类中的class_weight和接口fit中可以设定的sample_weight参数
逻辑回归中的上采样下采样方法,会增加样本点的总数,对于SVC这种算法来说。样本量对计算速度的影响很大,而且决策边界仅仅受到参数C和支持向量的影响,单纯增加样本数量可能增加无数对决策边界无影响的点。
SVC的参数:class_weight
默认None,表示自动认为标签比例为1:1,所有类都被假设为占有相同的权重1,模型会根据数据原本的状况去训练。可以添加字典来进行调整。或者输入‘balanced’,使使用y的值自动调整为与输入数据中的类频率成反比的权重n_samples/(n_classes * np.bincount(y))
SVC的接口fit的参数:sample_weight
数组,结构为 (n_samples, ),必须对应输入fit中的特征矩阵的每个样本。每个样本在fit时的权重,让权重 * 每个样本对应的C值来迫使分类器强调设定的权重更大的样本。通常,较大的权重加在少数类的样本上,以迫使模型向着少数类的方向建模
通常设置两个参数中的一个即可
分别绘制进行了数据平衡与未数据平衡数据的决策边界
步骤一:导入库和创建不平衡的数据集
import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn.datasets import make_blobs
class_1 = 500
class_2 = 50
centers = [[0.0,0.0],[2.0,2.0]]
clusters_std = [1.5,0.5]
X, y =make_blobs(n_samples=[class_1,class_2],
centers=centers,
cluster_std=clusters_std,
random_state=0,shuffle=False)
plt.scatter(X[:,0],X[:,1],c=y,cmap="rainbow",s=10)
clf = svm.SVC(kernel='linear',C=1.0)
clf.fit(X,y)
wclf = svm.SVC(kernel='linear',class_weight={1:10})
wclf.fit(X,y)
clf.score(X,y)
wclf.score(X,y)
#获取数据分布以及网格
plt.figure(figsize=(6,5))
plt.scatter(X[:,0],X[:,1],c=y,cmap="rainbow",s=10)
ax=plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xx = np.linspace(xlim[0],xlim[1],30)
yy = np.linspace(ylim[0],ylim[1],30)
YY,XX =np.meshgrid(yy,xx)
xy = np.vstack([XX.ravel(),YY.ravel()]).T
#找出样本点到决策边界的距离
Z_clf = clf.decision_function(xy).reshape(XX.shape)
a = ax.contour(XX,YY,Z_clf,colors='black',levels=[0],alpha=0.5,linestyles=['-'])
Z_wclf = wclf.decision_function(xy).reshape(XX.shape)
b = ax.contour(XX,YY,Z_wclf,colors='red',levels=[0],alpha=0.5,linestyles=['-'])
#画图例
plt.legend([a.collections[0],b.collections[0]],["non weighted","weighted"],
loc="upper right")
plt.show()
从准确率的角度来看,不做样本平衡的准确率反而更高。这是因为做了样本平衡之后,为了更加有效的捕捉出少数类,模型多数类被分错的数量>少数类被分类正确的数量,如果此时我们追求的是模型整体的准确率,那么就要拒绝样本平衡。当然实际生活中,我们往往追求的是捕捉少数类,因为它们判错的代价比较大。
(y[y == clf.predict(X)] == 1).sum()/(clf.predict(X) ==1).sum()
(y[y == wclf.predict(X)] == 1).sum()/(wclf.predict(X) ==1).sum()
它可以帮我们判断是否每一次对少数类的预测都精确,所以又被称为“查准率”。如果代价是不惜一切捕捉少数类,则不必在意。
(y[y == clf.predict(X)] ==1).sum()/(y==1).sum()
(y[y == wclf.predict(X)] ==1).sum()/(y==1).sum()
假负率 False Negative Rate:1-Recall
F1 measure:同时兼顾精确度和召回率
F − m e a s u r e = 2 1 P r e c i s i o n + 1 R e c a l l = 2 ∗ P r e c i s i o n ∗ R e c a l l P r e c i s i o n + R e c a l l F-measure=\frac{2}{\frac{1}{Precision}+\frac{1}{Recall}}=\frac{2*Precision*Recall}{Precision+Recall} F−measure=Precision1+Recall12=Precision+Recall2∗Precision∗Recall
两个数的调和平均倾向于靠近两个数中比较小的那一个数,高的F1值能保证精确度和召回率都较高
(y[y == clf.predict(X)] == 0).sum()/(y==0).sum()
(y[y == wclf.predict(X)] == 0).sum()/(y==0).sum()
sklearn中的混淆矩阵
类 | 含义 |
---|---|
sklearn.metrics.confusion_matrix | 混淆矩阵 |
sklearn.metrics.accuracy_score | 准确率accuracy |
sklearn.metrics.precision_score | 精确度precision |
sklearn.metrics.recall_score | 召回率recall |
sklearn.metrics.precision_recall_curve | 精确度-召回率平衡曲线 |
sklearn.metrics.f1_score | F1 measure |
ROC曲线
全称The Receiver Operating Characteristic Curve,是一条以不同阈值下的假正率FPR为横坐标,不同阈值下的召回率为纵坐标的曲线
概率probability和阈值threshold
步骤一:数据创建
class_1_ = 7
class_2_ = 4
centers_ = [[0.0, 0.0], [1,1]]
clusters_std = [0.5, 1]
X_, y_ = make_blobs(n_samples=[class_1_, class_2_],
centers=centers_,
cluster_std=clusters_std,
random_state=0, shuffle=False)
plt.scatter(X_[:, 0], X_[:, 1], c=y_, cmap="rainbow",s=30)
plt.show()
from sklearn.linear_model import LogisticRegression as LogiR
clf_lo = LogiR().fit(X_,y_)
prob = clf_lo.predict_proba(X_)
import pandas as pd
prob = pd.DataFrame(prob)
for i in range(prob.shape[0]):
if prob.loc[i,"1"] > 0.5:
prob.loc[i,"pred"] =1
else:
prob.loc[i,"pred"]=0
prob["y_true"]=y_
prob = prob.sort_values(by="1",ascending=False)
from sklearn.metrics import confusion_matrix as CM,precision_score as P ,recall_score as R
CM(prob.loc[:,"y_true"],prob.loc[:,"pred"],labels=[1,0])
P(prob.loc[:,"y_true"],prob.loc[:,"pred"],labels=[1,0])
R(prob.loc[:,"y_true"],prob.loc[:,"pred"],labels=[1,0])
for i in range(prob.shape[0]):
if prob.loc[i,"1"] > 0.4:
prob.loc[i,"pred"] =1
else:
prob.loc[i,"pred"]=0
CM(prob.loc[:,"y_true"],prob.loc[:,"pred"],labels=[1,0])
P(prob.loc[:,"y_true"],prob.loc[:,"pred"],labels=[1,0])
R(prob.loc[:,"y_true"],prob.loc[:,"pred"],labels=[1,0])
可以看到,不同阈值下模型的评估指标也会发生变化。升高或者降低阈值并不能确保Recall的改变,一切要根据数据的实际变化来进行判断。如果有概率需求,通常优先考虑逻辑回归和朴素贝叶斯。
decision_function
返输入的特征矩阵中每个样本到划分数据集的超平面的距离,也被称为SVM的置信度(confidence)
SVC参数probability
参数 | 含义 |
---|---|
probability | 布尔值,可不填,默认False ,是否启用概率估计 进行必须在调用fit之前启用它,启用此功能会减慢SVM的运算速度 |
设置为True则会启动,启用之后,SVC的接口predict_proba和predict_log_proba将生效。在二分类情况下,SVC将使用Platt缩放来生成概率,即在decision_function生成的距离上进行Sigmoid压缩,并附加训练数据的交叉验证拟合,来生成类逻辑回归的SVM分数
概率预测实现
步骤一:创建数据
class_1 = 500
class_2 = 50
centers =[[0.0,0.0],[2.0,2.0]]
clusters_std = [1.5,0.5]
X, y =make_blobs(n_samples=[class_1,class_2],
centers=centers,
cluster_std=clusters_std,
random_state=0,shuffle=False)
plt.scatter(X[:,0],X[:,1],c=y,cmap="rainbow",s=10)
步骤二:代入模型输出结果即可
clf_proba = svm.SVC(kernel="linear",C=1.0,probability=True).fit(X,y)
clf_proba.predict_proba(X)
clf_proba.predict_proba(X).shape
clf_proba.decision_function(X)
clf_proba.decision_function(X).shape
由于是距离,所以decision_function只会生成一列距离。注意,Platt缩放中涉及的交叉验证对于大型数据集来说非常昂贵,计算会非常缓慢。另外,由于Platt缩放的理论原因,在二分类过程中,有可能出现predict_proba返回的概率小于0.5,但样本依旧被标记为正类的情况出现,毕竟支持向量机本身并不依赖于概率来完成自己的分类。如果需要置信度分数,但不一定非要是概率形式的话,那建议可以将probability设置为False,使用decision_function这个接口而不是predict_proba
手动绘制SVM的ROV曲线
ROC是一条以不同阈值下假正率FPR为横坐标,不同阈值下的召回率为纵坐标的曲线,因此需要不断调整阈值求解混淆矩阵。
recall = []
FPR = []
probrange = np.linspace(clf_proba.predict_proba(X)[:,1].min(),
clf_proba.predict_proba(X)[:,1].max(),num=50,
endpoint=False)
from sklearn.metrics import confusion_matrix as CM,recall_score as R
import matplotlib.pyplot as plot
for i in probrange:
y_predict = []
for j in range(X.shape[0]):
if clf_proba.predict_proba(X)[j,1]>i:
y_predict.append(1)
else:
y_predict.append(0)
cm = CM(y,y_predict,labels=[1,0])
recall.append(cm[0,0]/cm[0,:].sum())
FPR.append(cm[1,0]/cm[1,:].sum())
recall.sort()
FPR.sort()
plt.plot(FPR,recall,c="red")
plt.plot(probrange+0.05,probrange+0.05,c="black",linestyle="--")
plt.show()
建立ROC曲线的根本目的是找寻Recall和FPR之间的平衡,能够在尽量在捕捉少数类的时候,误伤多数类的情况会如何变化。横坐标是FPR,代表着模型将多数类判断错误的能力,纵坐标Recall,代表着模型捕捉少数类的能力,所以ROC曲线代表着,随着Recall的不断增加,FPR如何增加。
对于一条凸型ROC曲线来说,曲线越靠近左上角越好,越往下越糟糕,曲线如果在虚线的下方,则证明模型完全无法使用。但是它也有可能是一条凹形的ROC曲线。对于一条凹型ROC曲线来说,应该越靠近右下角越好,凹形曲线代表模型的预测结果与真实情况完全相反,那也不算非常糟糕,只要手动将模型的结果逆转,就可以得到一条左上方的弧线了。最糟糕的就是,无论曲线是凹形还是凸型,曲线位于图像中间,和虚线非常靠近。
from sklearn.metrics import roc_curve
FPR,recall,thresholds = roc_curve(y,clf_proba.decision_function(X),pos_label=1)
from sklearn.metrics import roc_auc_score as AUC
area = AUC(y,clf_proba.decision_function(X))
plt.figure()
plt.plot(FPR,recall,color='red',
label='ROC curve (area=%0.2f)'%area)
plt.plot([0,1],[0,1],color='black',linestyle='--')
plt.xlim([-0.05, 1.05])
plt.ylim([-0.05, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('Recall')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()
maxindex = (recall - FPR).tolist().index(max(recall - FPR))
thresholds[maxindex]
plt.figure()
plt.plot(FPR,recall,color='red',
label='ROC curve (area=%0.2f)'%area)
plt.plot([0,1],[0,1],color='black',linestyle='--')
plt.scatter(FPR[maxindex],recall[maxindex],c='black',s=30)
plt.xlim([-0.05, 1.05])
plt.ylim([-0.05, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('Recall')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()