【机器学习】支持向量机SVM - 对SVM与核函数的理解及sklearn参数详解

时间:2023-02-06 13:48:54

支持向量机是在深度学习流行开来之前,性能表现最好的一种机器学习方法。在看这篇blog之前,默认读者已经有了对支持向量机的基本概念的认识。


一、支持向量机的进一步理解

支持向量机的优化目标在逻辑回归优化目标基础上进一步产生的。具体优化目标不说了,参看各种svm的书籍和博客。

 

1、提升线性回归值的划分要求

具体来讲,逻辑回归的优化目标是使用sigmoid函数将线性回归 weight * x + b 进行非线性映射,然后得到大于0属于正类,小于0属于反类。然而这样的分类造成了一个问题,比如在测试一个样本时,我们得到了一个线性回归的值0.1,按照sigmoid函数进行映射,0.1对应的函数值是略微大于0.5,该样本应当属于正类。

 

但是这时有个问题,把判定样本是正类还是反类当做一个随机变量,观测到的值是0.1。我们只是根据当前观测到的值进行判定,得到了正类的结果,事实上是正还是负依然不确定,这属于概率学中的假设检验内容,对于0.1这个样本的线性回归值,我们有很大的置信度它属于反类,因为0.1这个值太小了,太靠近0。即在靠近0附近的线性回归取值造成了一种问题,越靠近0,逻辑回归的判定越不准确。

 

因此,SVM提出了必须使线性回归值大于1时才判定为正,小于-1时判定为反类,我们把判定的阈值线划在了1和-1,此时的判定当然要比在0处更可信,因为此时根据sigmoid函数判定错误的置信度会小很多。

 

进一步,我们知道两个向量的内积的算法以及向量夹角与内积值的关系。若要使两个向量的内积值最大,其夹角为0最好。weight * x就是这样,也就是说,权重和样本的属性向量的夹角为0时,即方向一致时,取值最大,这样,线性回归的取值就能够很方便地大于1或者小于-1。而根据空间几何知识,超平面 weight * x + b = 0 的法线方向就是 weight 的方向。也就是说,权重向量要在属性空间中与样本向量方向尽量一致或尽量相反。最不一致和最不相反的向量就是支持向量对应的样本。

 

2、最大间隔

在优化目标的条件确定之后,最大间隔就是要使 weight最大化,这样找到的超平面的法线方向可以使margin最大。使用欧氏距离的平方,这样可以保证凸函数,且求导简便。

 

3、核函数

核函数的作用很简单,就是强硬将不可分的数据提升一个维度,让其可分。最常用的就是高斯核,核函数难以理解在于它没有提供一个直观的理解。因为不论是不是核函数,但凡是函数,都是将数据从 x 维映射到了 y 维。我们可以假想一个核函数,正类的样本经过它,取值都是1,反类经过核函数,核函数的取值都是0,而核函数就恰好完成了这么一个工作。

 

核函数作为一个函数,它的定义域就是样本的属性空间,值域就是一个实数数轴。可以理解所有核函数都是在定义一种距离,定义属性空间中,两个样本之间的距离,大于某个距离判为正,小于某个距离判为反类。核函数的作用就是强行拉出距离来。

 

核函数就是将线性数据非线性化,让数据在高维可分。这种方法叫做核方法。可以推广至其它机器学习算法。但是,似乎在其它方法中的应用都没有在SVM上好。

 

二、代码

 

1、 使用sklearn中的svm库

Svm库中包含了如下几个svm类,svc,svr,以SVC为例来具体解释一下所有的参数的含义,以及方法和属性。

SVC参数解释 

(1)C: 目标函数的惩罚系数C,用来平衡分类间隔margin和错分样本的,default C = 1.0; 

(2)kernel:参数选择有RBF, Linear, Poly, Sigmoid,precomputed或者自定义一个核函数, 默认的是"RBF",即径向基核,也就是高斯核函数;而Linear指的是线性核函数,Poly指的是多项式核,Sigmoid指的是双曲正切函数tanh核。 

在这里需要具体讲一下各种核函数怎么使用:

RBF核:高斯核函数就是在属性空间中找到一些点,这些点可以是也可以不是样本点,把这些点当做base,以这些base为圆心向外扩展,扩展半径即为带宽,即可划分数据。换句话说,在属性空间中找到一些超圆,用这些超圆来判定正反类。

 

线性核和多项式核:这两种核的作用也是首先在属性空间中找到一些点,把这些点当做base,核函数的作用就是找与该点距离和角度满足某种关系的样本点。当样本点与该点的夹角近乎垂直时,两个样本的欧式长度必须非常长才能保证满足线性核函数大于0;而当样本点与base点的方向相同时,长度就不必很长;而当方向相反时,核函数值就是负的,被判为反类。即,它在空间上划分出一个梭形,按照梭形来进行正反类划分。

 

Sigmoid核:同样地是定义一些base,核函数就是将线性核函数经过一个tanh函数进行处理,把值域限制在了-1到1上。

 

总之,都是在定义距离,大于该距离,判为正,小于该距离,判为负。至于选择哪一种核函数,要根据具体的样本分布情况来确定。

 

(3)degree:degree决定了多项式的最高次幂; 

(4)gamma:核函数的系数('Poly', 'RBF' and 'Sigmoid'), 默认是gamma = 1 / n_features,即核函数的带宽,超圆的半径; 

(5)coef0:核函数中的独立项,'RBF' and 'Poly'有效,即加在这两种核函数后面的bias值,次幂为0的项,默认是0.0,即认为多项式的核的常数项为0; 

(6)probablity: 布尔取值,默认是false,指的是预测判定的时候是否采用概率估计; 

(7)shrinking:是否进行启发式; 

(8)tol(default = 1e - 3): svm结束标准的精度,即容忍1000分类里出现一个错误; 

(9)cache_size: 制定训练所需要的内存(以MB为单位); 

(10)class_weight: 正类和反类的样本数量是不一样的,这里就会出现类别不平衡问题,该参数就是指每个类所占据的权重,默认为1,即默认正类样本数量和反类一样多,也可以用一个字典dict指定每个类的权值,或者选择默认的参数balanced,指按照每个类中样本数量的比例自动分配权值。

(11)verbose: 在训练数据完成之后,会把训练的详细信息全部输出打印出来,可以看到训练了多少步,训练的目标值是多少;但是在多线程环境下,由于多个线程会导致线程变量通信有困难,因此verbose选项的值就是出错,所以多线程下不要使用该参数。 

(12)max_iter: 最大迭代次数,这个是硬限制,它的优先级要高于tol参数,不论训练的标准和精度达到要求没有,都要停止训练。默认值是-1,指没有最大次数限制; 

(13)decision_function_shape : 原始的SVM只适用于二分类问题,如果要将其扩展到多类分类,就要采取一定的融合策略,这里提供了三种选择。‘ovo’ 一对一,决策所使用的返回的是(样本数,类别数*(类别数-1)/2), ‘ovr’ 一对多,返回的是(样本数,类别数),或者None,就是不采用任何融合策略, 默认是ovr,因为此种效果要比oro略好一点。 

(14)random_state :在使用SVM训练数据时,要先将训练数据打乱顺序,用来提高分类精度,这里就用到了伪随机序列。如果该参数给定的是一个整数,则该整数就是伪随机序列的种子值;如果给定的就是一个随机实例,则采用给定的随机实例来进行打乱处理;如果啥都没给,则采用默认的 np.random实例来处理。

 

提供的属性:

(1)support_ : 是一个array类型,它指的是训练出的分类模型的支持向量的索引,即在所有的训练样本中,哪些样本成为了支持向量。

(2)support_vectors_: 支持向量的集合,即汇总了当前模型的所有的支持向量。

(3)n_support_ : 比如SVC将数据集分成了4类,该属性表示了每一类的支持向量的个数。

(4)dual_coef_ :array, shape = [n_class-1, n_SV]对偶系数

       支持向量在决策函数中的系数,在多分类问题中,这个会有所不同。

(5)coef_ : array,shape = [n_class-1, n_features]

       该参数仅在线性核时才有效,指的是每一个属性被分配的权值。

(6)intercept_ :array, shape = [n_class * (n_class-1) / 2]决策函数中的常数项bias。和coef_共同构成决策函数的参数值。


 
from sklearn import svmimport numpy as npimport pylab as pl# we create 40 separable pointsnp.random.seed(2)X = np.r_[np.random.randn(20, 2) - [2, 2], np.random.randn(20, 2) + [2, 2]]  # 一个是随机数加上均值为2,方差为2的正太分布,Y = [0] * 20 + [1] * 20print "++++++++++++++++++++++++++++++++++++++"clf = svm.SVC(kernel='linear', verbose=True)clf.fit(X, Y)print "对训练数据的准确性统计: ", clf.score(X, Y)# 获取分割超平面w = clf.coef_[0]  # 分割超平面的参数权值,由于属性只有两维,所以 weight 也只有 2 a = -w[0] / w[1]  # 超平面直线的斜率xx = np.linspace(-5, 5)  #  -5  5 上的数均分yy = a * xx - (clf.intercept_[0]) / w[1]# 画出经过支持向量的直线b = clf.support_vectors_[0]  # 第一个支持向量,一定属于正类yy_down = a * xx + (b[1] - a * b[0])  # 经过支持向量的点的直线b = clf.support_vectors_[-1]  # 最后一个支持向量,一定属于反类yy_up = a * xx + (b[1] - a * b[0])print "w: ", wprint "a: ", aprint "支持向量: ", clf.support_vectors_print "正类和负类的支持向量的索引: ", clf.support_print "每个类的支持向量的个数: ", clf.n_support_print "超平面的系数,仅在核函数为 RBF  Poly 时有效: ", clf.coef_pl.plot(xx, yy, 'k-')pl.plot(xx, yy_down, 'k--')  # 分界线pl.plot(xx, yy_up, 'k--')pl.scatter(clf.support_vectors_[:, 0],  # 正类的支持向量 clf.support_vectors_[:, 1],  # 反类的支持向量 s=80,  # 点的半径大小 facecolors='red')  # face colors,支持向量的样本点的颜色pl.scatter(X[:, 0],  # 样本的 x 轴数据 X[:, 1],  # 样本集的 y 轴数据 c=Y,  # 分类结果集 cmap=pl.cm.Paired)  # cmap 确定颜色pl.axis('tight')pl.show()
 
 

2、支持向量机在何时失效?

即在高维上仍然不可分,导致分类器失效。我们可以假设,如果核函数极为复杂,能够拟合任何函数,则支持向量机的精确度会是100%,然而正如下面sklearn包中提供的核函数,都是一些初等函数,根据高等代数泰勒公式,多项式函数随着次数上升,可以以任意精度逼近任何函数,那么在代码中规定最高次数为10,则精度上升,当然这个计算代价是非常大的。

比如上面的例子,将代码中的变量X改成

 
X = np.r_[np.random.randn(20, 2), np.random.randn(20, 2) + [2, 2]]
此时,X样本数据在属性空间无法做到线性可分,需要使用核函数映射到高维。这里,我们将SVC的参数改为多项式核函数,degree表示多项式核函数的次幂,我们可以多次调解degree的值,从2到10,观察预测数据的准确率。 

 
clf = svm.SVC(kernel='poly', degree=5, verbose=True)
 

得到的预测准确率就无法达到100%了,取值为0.925,随着degree的升高,准确率能够逐渐升高,此时,迭代次数也越来越多,使用verbose参数可以看到,迭代次数从7次即可收敛得到超平面,直到300次才能收敛,这就是多项式函数在拟合复杂曲面时候的劣势。

然而,继续升高多项式核函数的次幂,准确率的提升微乎其微,也就是说,SVM算法达到了自己的性能顶峰,即数据在属性空间上出现了杂糅,细致的核函数可以描述,但是代价巨大。