写点什么

支持向量机 - 线性 SVM 决策过程的可视化

  • 2022-11-23
    山东
  • 本文字数:4845 字

    阅读完需:约 16 分钟

我们可以使用 sklearn 中的式子来为可视化我们的决策边界,支持向量,以及决策边界平行的两个超平面。


from sklearn.datasets import make_blobsfrom sklearn.svm import SVCimport matplotlib.pyplot as pltimport numpy as np
复制代码


单独说一下 make_blobs

make_blobs(
 ['n_samples=100', 'n_features=2', 'centers=None', 'cluster_std=1.0', 'center_box=(-10.0, 10.0)', 'shuffle=True', 'random_state=None'],
)
# n_samples:待生成的样本的总数
# n_features:每个样本的特征数。
# centers:中心数,就是分成几类
# luster_std:表示每个类别的方差。如果是一个数字,则所有类的方差都是这个数;如要要不同类方差不一致,可以使用列表
# random_state:随机数生成器
# 注意返回X和y两个array

一会可视化会更好理解


X, y = make_blobs(n_samples=50,centers=2,random_state=0,cluster_std=0.6)
X.shape # 因为我们指定要50个样本,方法本身默认两个维度,因此样本矩阵的shape为(50,2)---(50, 2)
y.shape---(50,)
set(y) # 生成不同类样本的标签默认0,1,2……排列,这里指定两个中心,也就是两类,一次y的取值为1,2---{0, 1}
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap='rainbow')# 这里告诉我们,color是支持非负正数array作为参数值的plt.xticks([])plt.yticks([])plt.show()
复制代码



画决策边界:理解函数 contourContour 是我们专门用来绘制等高线的函数。等高线,本质上是在二维图像上表现三维图像的一种形式,其中两维 X 和 Y 是两条坐标轴上的取值,而 Z 表示高度。Contour 就是将由 X 和 Y 构成平面上的所有点中,高度一致的点连接成线段的函数,在同一条等高线上的点一定具有相同的 Z 值。我们可以利用这个性质来绘制我们的决策边界。


contour([X, Y,] Z, [levels], **kwargs)---# X, Y:选填。两维平面上所有的点的横纵坐标取值,一般要求是二维结构并且形状需要与Z相同(因为坐标要与Z值意义对应),往往通过numpy.meshgrid()这样的函数来创建。# 这一段可以先不看,不太好理解(其实我根本就没理解这一段)。如果X和Y都是一维,则Z的结构必须为(len(Y), len(X))。如果不填写,则默认X = range(Z.shape[1]),Y = range(Z.shape[0])。# Z:必填。平面上所有的点所对应的高度。# levels:可不填,不填默认显示所有的等高线,填写用于确定等高线的数量和位置。如果填写整数n,则显示n个数据区间,即绘制n+1条等高线。水平高度自动选择。如果填写的是数组或列表,则在指定的高度级别绘制等高线。列表或数组中的值必须按递增顺序排列。
复制代码


我们的决策边界是,并在决策边界的两边找出两个超平面,使得超平面到决策边界的相对距离为 1。那其实,我们只需要在我们的样本构成的平面上,把所有到决策边界的距离为 0 的点相连,就是我们的决策边界,而把所有到决策边界的相对距离为 1 的点相连,就是我们的两个平行于决策边界的超平面了。此时,我们的 Z 就是平面上的任意点到达超平面的距离。


上面这一段加粗的就是 contour 核心的东西


那首先,我们需要获取样本构成的平面,作为一个对象。


# 首先要有散点图,因为我们要获得axis对象来获得xlim,ylimplt.scatter(X[:,0],X[:,1],c=y,s=50,cmap='rainbow')ax = plt.gca() # 获取当前的子图,如果不存在,就创建新的子图# 这里有图,就不再重复了,和上面的散点图一样
ax # axes对象,就是那个框---<matplotlib.axes._subplots.AxesSubplot at 0x2886e2fe6d8>
# 获取平面上两条坐标轴的最大值和最小值xlim = ax.get_xlim()ylim = ax.get_ylim()xlim # 所有数据点x的最大值和最小值---(-0.7541740493109252, 3.3838081780236036)
ylim # 所有数据点y的最大值和最小值---(-0.43660961990940284, 5.772756283035797)
# 在最大值和最小值(含两端)之间形成30个规律的数据# 这也就决定着我们meshgrid的结果应该是一个30*30的矩阵axisx = np.linspace(xlim[0],xlim[1],30)axisy = np.linspace(ylim[0],ylim[1],30)
axisx# arrax里的元素,从小到大排列,最小值即为数据点x的最小值,最大值即为数据点x的最大值# 这个可以认为是一个列向量---array([-0.75417405, -0.61148501, -0.46879596, -0.32610692, -0.18341788,……, 3.38380818]) # 中间截掉了
axisx.shape---(30,)
axisy, axisx = np.meshgrid(axisy,axisx) # 注意对应关系,别把x,y弄反了,要换一起换# meshgrid函数将两个一维向量转换为特征矩阵# 核心是两个特征向量的广播,以便获取y.shape * x.shape数量个坐标点的横坐标和纵坐标
axisx.shape# 对于axisx来说,是一个30*30的矩阵,第i行是之前array的第i-1个元素(因为有第0个元素,但没有第0行)# 对于x来说,同一行的元素相同---(30, 30)
axisx[0]---array([-0.75417405, -0.75417405, -0.75417405, -0.75417405, -0.75417405,…… , -0.75417405])
axisy[:,0] # 对y来说,同一列的元素相同---array([-0.43660962, -0.43660962, -0.43660962, -0.43660962, -0.43660962,……, -0.43660962])
复制代码


单独说一下 vstack

np.vstack([[1,2,3],[2,3,4]]) # 纵向堆叠,认为是矩阵加行
# vstack能够将多个结构一致的一维数据按行堆叠起来
---
array([[1, 2, 3],
    [2, 3, 4]])


xy = np.vstack([axisx.ravel(), axisy.ravel()]).T# xy就是已经形成的网格,它是遍布在整个画布上密集的点
xy.shape# 两列也就是两个维度,第一列是x坐标,第二列是y坐标# 900说明有900个点---(900, 2)
xy # ravel的时候x在前面,并且x每一行是同一个数值,因此这里的xy,第一列对应的就是之前的x,且每30个元素聚在一起都是一样的,然后后面y从小到大的取值不断重复与x进行匹配---array([[-0.75417405, -0.43660962], [-0.75417405, -0.22249355], [-0.75417405, -0.00837749], ..., [ 3.38380818, 5.34452415], [ 3.38380818, 5.55864022], [ 3.38380818, 5.77275628]])
plt.scatter(xy[:,0],xy[:,1],s=1,cmap='rainbow')# 可视化来看xy里每一行就是一个点,并且这个点的顺序是从最左边的x开始,y从最下边开始,y逐渐向上增大,到最大后,x向右移动一个值,y回到最小值,重复上述操作,直到x,y都到最大值
复制代码



有了网格后,我们需要计算网格所代表的“平面上所有的点”到我们的决策边界的距离。所以我们需要我们的模型和决策边界。


clf = SVC(kernel="linear").fit(X,y)# 实例化,实际上就是计算得到决策边界# X是数据矩阵,y是样本标签Z = clf.decision_function(xy).reshape(axisx.shape)# 重要接口decision_function,返回每个输入样本所对应的到决策边界的距离# 这里输入样本就是上面的矩阵散点,距离指的是几何距离# 这里reshape要注意一点,由于我们这xy里点的顺序是先从下到上,然后从左到右,也就是scatter可视化的时候说的顺序# 因此clf.decision_function(xy).shape得到的距离array每个元素对应的点顺序也是从下到上,从左到右,同scatter可视化的时候说的顺序,只不过现在只有一列# reshape这个array之后得到的Z矩阵元素第一行从左到右实际上是上面散点矩阵第一列从下到上点到决策边界的距离,同scatter可视化的时候说的顺序,只不过这里变成了30*30,只要注意顺序就行# 一个小知识点,reshape可以传入元组
clf.decision_function(xy).shape# xy有900个点,因此返回距离也有900个---(900,)
Z.shape---(30, 30)
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap='rainbow')ax = plt.gca() # 获取当前的子图,如果不存在,就创建新的子图# 这两行必须写上,不然画不出图,因为没有plt.scatter我们就没有画布,我们只是操作了ax对象ax.contour(axisx,axisy,Z ,colors='k' ,levels=[-1,0,1] # 画三条等高线,分别是Z为-1,0,1的三条线 ,alpha=0.5 ,linestyles=['--','-','--'] )# 关于上面的一句话# reshape这个array之后得到的Z矩阵元素第一行从左到右实际上是上面散点矩阵第一列从下到上点到决策边界的距离# 其实也很好理解# 我们取axisx的第1行第1列的元素,为-0.75417405,axisy的第1行第1列的元素,为-0.43660962,Z的第1行第1列的元素,为该点到决策边界的距离# 同一第1行第2列的元素同理,由axisx,axisy得到的坐标应该是(-0.75417405,-0.22249355),对应Z第1行第2列的元素也是该点到决策边界的距离# 这也就是说,axisx,axisy,Z三个矩阵,对应元素组合起来是有意义的ax.set_xlim(xlim)ax.set_ylim(ylim)# 这里需要注意的是,上面的虚线超平面对应相对距离为-1,下面的虚线超平面对应相对距离为1
复制代码



Z[0] # 对应我们Z的第一行,也就是横坐标为-0.75417405,纵坐标从小变大点到决策边界的距离# 显然两头数值大,离决策边界远,中间接近,甚至有0值,上面的可视化就能验证---array([ 3.61796573, 3.32300315, 3.02804058, 2.73307801, 2.43811544, 2.14315287, 1.8481903 , 1.55322772, 1.25826515, 0.96330258, 0.66834001, 0.37337744, 0.07841487, -0.21654771, -0.51151028, -0.80647285, -1.10143542, -1.39639799, -1.69136056, -1.98632313, -2.28128571, -2.57624828, -2.87121085, -3.16617342, -3.46113599, -3.75609856, -4.05106114, -4.34602371, -4.64098628, -4.93594885])
复制代码


进一步理解,我们要画过某点平行于决策边界的超平面


distance = clf.decision_function(X[10].reshape(1,-1))# 由于decision_function接口只接受二维数据,而X[10]返回array([1.71444449, 5.02521524])为一维数据,因此考虑reshape(1,-1)plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")plt.scatter(X[10,0],X[10,1],c='black',s=50)ax = plt.gca()ax.contour(axisx,axisy,Z          ,colors='k'          ,levels=[distance]          ,alpha=0.5          ,linestyles=['--']          )
复制代码



关于 reshape(-1,1),reshape(1,-1),这里的负数不是行或列的元素数为负数,负数代表不指定元素个数,例如 reshape(-1,1),就是我们只要求有一列,行上我们不要求,让计算机自己计算。因此我们也可以有 reshape(-1,2)等操作


我们再回看上面绘制决策边界的过程,发现我们只需要模型和散点图的边框就可以得到可视化的决策边界。因此,我们可视化决策边界的过程进行包装


def plot_svc_decision_function(model,ax=None):    if ax is None:        ax = plt.gca()        xlim = ax.get_xlim()    ylim = ax.get_ylim()    x = np.linspace(xlim[0],xlim[1],30)    y = np.linspace(ylim[0],ylim[1],30)    Y,X = np.meshgrid(y,x)    xy = np.vstack([X.ravel(),Y.ravel()]).T    P = model.decision_function(xy).reshape(X.shape)    ax.contour(X,Y,P,colors='k',levels=[-1,0,1],alpha=0.5,linestyles=['--','-','--'])    ax.set_xlim(xlim)    ax.set_ylim(ylim)    clf = SVC(kernel="linear").fit(X,y)plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")# 这两行和吉祥物一样plot_svc_decision_function(clf)plt.contour()
复制代码



探索建好的模型


clf.predict(X)# 根据决策边界,对X中的样本进行分类,返回结构为n_samples---array([1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0])
clf.score(X,y)# 返回给定测试集数据和标签的平均准确度# 因为这里我们建立线性SVM的时候用的就是可分的样本集,再用样本集进行测试,因此score一定为1---1.0
clf.support_vectors_# 返回支持向量,不一定只有两个---array([[0.44359863, 3.11530945], [2.33812285, 3.43116792], [2.06156753, 1.96918596]])
clf.n_support_# 返回每个类中支持向量的个数---array([2, 1])
复制代码


视频作者:菜菜TsaiTsai链接:【技术干货】菜菜的机器学习sklearn【全85集】Python进阶_哔哩哔哩_bilibili


发布于: 刚刚阅读数: 4
用户头像

跃入人海 2022-09-14 加入

还未添加个人简介

评论

发布
暂无评论
支持向量机-线性SVM决策过程的可视化_Python_烧灯续昼2002_InfoQ写作社区