机器学习 _SVM 支持向量机

1. 介绍

SVM 支持向量机属于广义的线性模型,先回忆一下线性模型:可依据平面(多维)或直线(一维/二维)来理解模型。简单地说,可用一条线将两类分开,如下图所示。

能将两类分开的直线不止一条(左图),我们希望找到离两组数据都最远的那条线(正中间那条线),以便更好地泛化。这就是右图中所示的极大边距分类器,一般把中间的直线称为决策面,把离决策面最近的那些点(训练实例)称为支持向量,也就是右图中紫圈中的点。

有时会遇到无法用直线分类的情况,如左图所示,但可以用圆环划分,右图展示了同样的数据,各个特征值取原始值的平方,变换到新的特征空间后,数据变成了线性可分。将数据从线性不可分转成线性可分的函数称为核函数,转换的方法称为核技巧(这里是取平方的操作,核函数一般将低维数据映射到高维)。由于需要把原始模型转换成线性模型,再进一步操作,我们叫它“广义线性模型”。

这就是一般意义上的支持向量机 SVM,它有两个重点:一个是考虑分类时的最大边距,另一个是使用核函数把线性不可分的变成线性可分的。

原理并不难,但具体实现涉及数学较多,如果嫌麻烦,请跳过“求解线性方程”部分。

2. 求解线性方程

用线性模型分类,就是求取能把数据正确分类的直线(或平面),使得界两边的各个点离界都最远。有很多点,我们只需要考虑离分割线最近的那些数据点(支持向量)。这是一个求点到直线距离的问题。

初中学过,点 (x0,y0) 到直线 Ax+By+C=0 的距离公式是:

在多维的情况下,我们一般使用 wx+b 描述决策面。其中 w 是权向量,它决定了决策面的方向,b 是偏置,它决定了决策面的位置。

求某点 x0 到决策面 wx+b 的距离,代入距离公式得到 d=|wx0+b|/||w||。

现在求什么样的 w 和 b 值能使距离 d 最小。可以看成在 (wx+b) 的条件下,求||w||的最小值。即求带条件的最小值,这用到拉格朗日乘子的方法。

拉格朗日乘子用于寻找多元变量在一个或者多个限制条件下的极值点(柱点)。比如求函数 f(x1,x2,...) 在 g(x1,x2,...)=0 的约束条件下的极值。其主要思想是将约束条件函数与原函数联系到一起,生成等式方程:\(L(x,λ)=f(x)+λ*g(x)\),λ为拉格朗日乘子,该公式把带条件的求极值化简成了不带条件的求极值。

在极值点处分别对 x 和λ求导。这里引入了λ,把求解 w 变成了求解λ。从而求出距离为最小值的情况下λ应该取什么值,知道了λ又能求 w,在不断迭代之后,最终确定了直线的参数。而其中λ不为 0 的点又正好是支持向量(只有不为 0 的点是该直线的限定条件)。这样同时把支持向量和分割线都求出来了。

3. 松弛变量

因为数据不是 100% 线性可分,因此引入了松弛变量,它是允许变量处于分隔面的错误一测的比例。

松弛变量一般用惩罚系数 C 设置,C 越大对误分类的惩罚越大(越不允许松弛),趋向于对训练集全分对的情况,这样对训练集测试时准确率很高,但泛化能力弱。C 值小,对误分类的惩罚减小,允许容错,将他们当成噪声点,泛化能力较强。

4. 维度变化与核函数

为什么低维不可分的数据变成高维就可分了呢?N 个点在 N-1 维一定是可分的,就好像决策树,只要叶子够多,一定能分开。我们之前看到的 PCA 降维和此处的核函数升维,都是对数据的映射,就像是换了一个视角,但数据内部的关系不变。

核函数的用途很广泛,SVM 只是其中的一种用法,SVM 中常用的核函数有:线性,多项式,径向基,S 型曲线等。

5. 用途

SVM 是空间几何属性的模型(靠距离远近判断相似性),一般处理数值型数据,常用作分类器。是一种有监督的学习。

从保留数据的角度看,有的算法要保留全部数据比如 KNN,有的完全不保留数据比如决策树,SVM 是保存一部分数据——支持向量(边界附近的点比其它点更重要),这样即能减少数据,又有明确的意义。

它的泛化错误率低,开销不大,易解释,综合了参数化和非参数化模型的优点。不过它对参数和核函数选择比较敏感。

在神经网络大规模应用之前,它是一种非常流行的算法,尤其在没有领域相关的先验知识的情况下,差不多是最好的不用修改就能很好工作的分类器了。

6. 例程

调用 sklearn 中的 svm 分类器示例

1
2
3
4
5
6
7
8
clf = svm.SVC(C=1.0, kernel='rbf') # 指定惩罚系数C,核函数rbf
clf.fit(X_train,y_train) # 训练
accuracy = clf.score(X_test,y_test) # 准确度
print("accuracy:",accuracy)
print(clf.support_vectors_) #支持向量列表,从中看到切分边界
print(clf.support_) # 支持向量索引
print(clf.n_support_) # 持持向量个数
print("predict ",clf.predict(np.array(item))) # 预测