支持向量机分类器,是在数据空间中找出一个超平面作为决策边界,利用这个决策边界来对数据进行分类,并使分 类误差尽量小的模型。
以二维数据为例,图中的数据集有两个特征,标签有两类,一类为紫色,一类为红色。对于这 组数据,我们找出的决策边界被表达为 ,决策边界把平面分成了上下两部分,决策边界以上的样本 都分为一类,决策边界以下的样本被分为另一类。以我们的图像为例,绿色实线上部分为一类(全部都是紫色 点),下部分为另一类(全都是红色点)。

支持向量机分类器,就是以找出最大化的边际d为目标来求解损失函数,以求解出参数w和b,以构建决策边界,然后用决策边界来分类的分类器。
分类模型天生会倾向于多数的类,让多数类更容易被判断正确,少数类被牺牲掉
模型评估指标会失去意义
标签的值1的C:权重1 * C,标签的值2的C:权重2*C 或者,可以使用“balanced”模式,这个模式使用y的值自动调整与输入数据中的类频率成反比的权重为 n_samples/(n_classes * np.bincount(y))
如何使用这个参数:我们来自建一组样本不平衡的数据集。我们在这组数据集上建两个SVC模型,一个设置有class_weight参 数,一个不设置class_weight参数。我们对两个模型分别进行评估并画出他们的决策边界,以此来观察 class_weight带来的效果
- import numpy as np
- import matplotlib.pyplot as plt
- from sklearn import svm
- from sklearn.datasets import make_blobs
-
- #创建不均衡数据集
- class_1 = 500 #类别1有500个样本
- class_2 = 50 #类别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)
- #其中红色点是少数类,紫色点是多数类
-
- #在数据集上分别建模
- #不设定class_weight
- clf = svm.SVC(kernel='linear', C=1.0)
- clf.fit(X, y)
- #设定class_weight
- wclf = svm.SVC(kernel='linear', class_weight={1: 10})
- wclf.fit(X, y)
- #给两个模型分别打分看看,这个分数是accuracy准确度
- 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()
目标是希望尽量捕获少数类
寻找捕获少数类的能力和将多数类判错后需要付出的成本的平衡
如果一个模型在能够尽量捕获少 数类的情况下,还能够尽量对多数类判断正确,则这个模型就非常优秀了。为了评估这样的能力,我们将引入新的模型评估指标:混淆矩阵和ROC曲线来帮助我们。
在混淆矩阵中,我们将少数类认为是正 例,多数类认为是负例。在决策树,随机森林这些普通的分类算法里,即是说少数类是1,多数类是0。在SVM里, 就是说少数类是1,多数类是-1。普通的混淆矩阵,一般使用{0,1}来表示。
- #所有判断正确并确实为1的样本 / 所有被判断为1的样本
- #对于没有class_weight,没有做样本平衡的灰色决策边界来说:
- (y[y == clf.predict(X)] == 1).sum()/(clf.predict(X) == 1).sum()
- #对于有class_weight,做了样本平衡的红色决策边界来说:
- (y[y == wclf.predict(X)] == 1).sum()/(wclf.predict(X) == 1).sum()
又被称为敏感度(sensitivity),真正率,查全率,表示所有真实为1的样本中,被我们预测正确的样 本所占的比例
在支持向量机中,召回率可以被表示为,决策边界上方的所有红色点占全部样本中的红色点的比 例。召回率越高,代表我们尽量捕捉出了越多的少数类,召回率越低,代表我们没有捕捉出足够的少数类。
- #所有predict为1的点 / 全部为1的点的比例
- #对于没有class_weight,没有做样本平衡的灰色决策边界来说:
- (y[y == clf.predict(X)] == 1).sum()/(y == 1).sum()
- #对于有class_weight,做了样本平衡的红色决策边界来说:
- (y[y == wclf.predict(X)] == 1).sum()/(y == 1).sum()
表示所有真实为0的样本中,被正确预测为0的样本所占的比例。在支持向量机中,可以形象地 表示为,决策边界下方的点占所有紫色点的比例
- #所有被正确预测为0的样本 / 所有的0样本
- #对于没有class_weight,没有做样本平衡的灰色决策边界来说:
- (y[y == clf.predict(X)] == 0).sum()/(y == 0).sum()
- #对于有class_weight,做了样本平衡的红色决策边界来说:
- (y[y == wclf.predict(X)] == 0).sum()/(y == 0).sum()
特异度衡量了一个模型将多数类判断正确的能力,而1 - specificity就是一个模型将多数类判断错误的能力,这种 能力被计算如下,并叫做假正率(False Positive Rate)
sklearn当中提供了大量的类来帮助我们了解和使用混淆矩阵。

全称The Receiver Operating Characteristic Curve,译为受试者操作特性曲线。这是一条以不同阈值下的假正率FPR为横坐标,不同阈值下的召回率Recall为纵坐标的曲线。让我们先从概率和阈值开始讲起。
1 概率(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)
-
- #建模,调用概率
- from sklearn.linear_model import LogisticRegression as LogiR
- clf_lo = LogiR().fit(X_,y_)
- prob = clf_lo.predict_proba(X_)
- #将样本和概率放到一个DataFrame中
- import pandas as pd
- prob = pd.DataFrame(prob)
- prob.columns = ["0","1"]
- prob
-
- #使用阈值0.5,大于0.5的样本被预测为1,小于0.5的样本被预测为0
- #手动调节阈值,来改变我们的模型效果
- 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)
- prob
-
- #使用混淆矩阵查看结果
- 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])
- #试试看手动计算Precision和Recall?
- P(prob.loc[:,"y_true"],prob.loc[:,"pred"],labels=[1,0])
- R(prob.loc[:,"y_true"],prob.loc[:,"pred"],labels=[1,0])
-
- #假如我们使用0.4作为阈值呢?
- 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
- prob
- 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])
- #注意,降低或者升高阈值并不一定能够让模型的效果变好,一切都基于我们要追求怎样的模型效果
在SVM中利用超平面来判断我们的样本,本质上来说,当两个点的距离 是相同的符号的时候,越远离超平面的样本点归属于某个标签类的概率就很大。
比如说,一个距离超平面0.1的 点,和一个距离超平面100的点,明显是距离为0.1的点更有可能是负类别的点混入了边界。同理,一个距离超平面 距离为-0.1的点,和一个离超平面距离为-100的点,明显是-100的点的标签更有可能是负类。
所以,到超平面的距 离一定程度上反应了样本归属于某个标签类的可能性。
接口decision_function返回的值也因此被我们认为是SVM 中的置信度(confidence)。
- #使用最初的X和y,样本不均衡的这个模型
- class_1 = 500 #类别1有500个样本
- class_2 = 50 #类别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
绘制SVM的ROC曲线
- #首先来看看如何从混淆矩阵中获取FPR和Recall
- cm = CM(prob.loc[:,"y_true"],prob.loc[:,"pred"],labels=[1,0])
- cm
- #FPR
- cm[1,0]/cm[1,:].sum()
- #Recall
- cm[0,0]/cm[0,:].sum()
- #开始绘图
- 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曲线的横坐标假正率FPR,纵坐标Recall和对应的阈值的类 sklearn.metrics.roc_curve
帮助我们计算AUC面积的类sklearn.metrics.roc_auc_score
- from sklearn.metrics import roc_curve
- FPR, recall, thresholds = roc_curve(y,clf_proba.decision_function(X), pos_label=1)
- FPR
- recall
- thresholds #此时的threshold就不是一个概率值,而是距离值中的阈值了,所以它可以大于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()
Recall和FPR差距最大的点。又叫做约登指数
- maxindex = (recall - FPR).tolist().index(max(recall - FPR))
- thresholds[maxindex]
- #我们可以在图像上来看看这个点在哪里
- plt.scatter(FPR[maxindex],recall[maxindex],c="black",s=30)
- #把上述代码放入这段代码中:
- 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()