支持向量机的基本原理
支持向量机分类器,是在数据空间中找出一个超平面作为决策边界,利用这个决策边界来对数据进行分类,并使分 类误差尽量小的模型。
以二维数据为例,图中的数据集有两个特征,标签有两类,一类为紫色,一类为红色。对于这 组数据,我们找出的决策边界被表达为 ,决策边界把平面分成了上下两部分,决策边界以上的样本 都分为一类,决策边界以下的样本被分为另一类。以我们的图像为例,绿色实线上部分为一类(全部都是紫色 点),下部分为另一类(全都是红色点)。
支持向量机分类器,就是以找出最大化的边际d为目标来求解损失函数,以求解出参数w和b,以构建决策边界,然后用决策边界来分类的分类器。
二分类SVC中的样本不均衡问题:重要参数class_weight
分类模型天生会倾向于多数的类,让多数类更容易被判断正确,少数类被牺牲掉
模型评估指标会失去意义
标签的值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()
SVC的模型评估指标
目标是希望尽量捕获少数类
寻找捕获少数类的能力和将多数类判错后需要付出的成本的平衡
如果一个模型在能够尽量捕获少 数类的情况下,还能够尽量对多数类判断正确,则这个模型就非常优秀了。为了评估这样的能力,我们将引入新的模型评估指标:混淆矩阵和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中的混淆矩阵
sklearn当中提供了大量的类来帮助我们了解和使用混淆矩阵。
ROC曲线
全称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实现概率预测
在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()
sklearn中的ROC曲线和AUC面积
帮助我们计算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()
利用ROC曲线找出最佳阈值
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()