引言
本篇我们要讲解的模型是大名鼎鼎的支持向量机 SVM,这是曾经在机器学习界有着近乎「垄断」地位的模型,影响力持续了好多年。直至今日,即使深度学习神经网络的影响力逐渐增强,但 SVM 在中小型数据集上依旧有着可以和神经网络抗衡的极好效果和模型鲁棒性。
支持向量机学习方法,针对不同的情况,有由简至繁的不同模型:
- 线性可分支持向量机(linear support vector machine in linearly separable case):训练数据线性可分的情况下,通过硬间隔最大化(hard margin maximization),学习一个线性的分类器,即线性可分支持向量机(亦称作硬间隔支持向量机)。
- 线性支持向量机(linear support vector machine):训练数据近似线性可分的情况下,通过软间隔最大化(soft margin maximization),学习一个线性的分类器,称作线性支持向量机(又叫软间隔支持向量机)。
- 非线性支持向量机(non-linear support vector machine):训练数据线性不可分的情况下,通过使用核技巧(kernel trick)及软间隔最大化,学习非线性分类器,称作非线性支持向量机。
支持向量机可以借助核技巧完成复杂场景下的非线性分类,当输入空间为欧式空间或离散集合、特征空间为希尔贝特空间时,核函数(kernel function)表示将输入从输入空间映射到特征空间得到的特征向量之间的内积。
通过使用核函数可以学习非线性支持向量机,等价于隐式地在高维的特征空间中学习线性支持向量机。这样的方法称为核技巧。
1.最大间隔分类器
1)分类问题与线性模型
分类问题是监督学习的一个核心问题。在监督学习中,当输出变量取有限个离散值时,预测问题便成为分类问题。
实际生活中,有很多问题的本质都是分类,如识别某种模式:文本分类、分词、词性标注、图像内容识别和目标检测等。
分类问题的数学理解是空间划分(或者寻找不同类别的决策边界),如图所示是一个简单的线性分类器(这部分更详细的讲解参考ShowMeAI文章 图解机器学习 | 机器学习基础知识 和 图解机器学习 | 逻辑回归算法详解)。
2)最大间隔分类器
不同的模型在解决分类问题时,会有不同的处理方式,直观上看,我们会使用不同的决策边界对样本进行划分。
如图中「冰墩墩」与「雪容融」两类样本点,我们对其进行分类,可以完成该分类任务的决策边界有无数个。而我们这里介绍到的 SVM 模型,要求更高一些,它不仅仅希望把两类样本点区分开,还希望找到鲁棒性最高、稳定性最好的决策边界(对应图中的黑色直线)。
- 这个决策边界与两侧「最近」的数据点有着「最大」的距离,这意味着决策边界具有最强的容错性,不容易受到噪声数据的干扰。直观的理解就是,如果决策边界抖动,最不容易「撞上」样本点或者进而导致误判。
2.支持向量机详解
1)线性可分 SVM 与硬间隔最大化
我们希望这条直线满足「最大间隔」原则,也就是如下图的形态。图中位于红色和蓝色线上的图标就是所谓的支持向量(support vector)。
(1)几何间隔
(2)对偶算法
求解线性可分支持向量机的最优化问题,我们很多时候会将它转化为对偶问题(dual problem)来求解,也就是应用「拉格朗日对偶性」,通过求解「对偶问题(dual problem)」得到「原始问题(primal problem)」的最优解,即线性可分支持向量机的对偶算法(dual algorithm)。
这样做有一些优点:
- 对偶问题往往更容易求解。
- 引入自然核函数,进而可以推广到非线性分类问题。
2)线性 SVM 与软间隔最大化
我们前面提到的是线性可分的情况,但实际应用中完全线性可分的情况太少见了。如下图就是一个典型的线性不可分的分类图(我们没有办法找到一条直线,把空间划分为2个区域,一个区域只有黑点,一个区域只有白点)。
要对其进行切分,有2种方案:
方案1:用曲线去将其完全分开,即非线性的决策边界,这会和之后谈到的核函数关联。
方案2:还是使用直线,不过不追求完全可分,会适当包容一些分错的情况,在这个过程中我们会在模型中加入惩罚函数,尽量让分错的点不要太多太离谱。对分错点的惩罚函数就是这个点到其正确位置的距离,如下图所示:
图上黄色、蓝色的直线分别为支持向量所在的边界,黑色的线为决策函数,那些绿色的线表示分错的点到其相应的决策面的距离,这样我们可以在原函数上面加上一个惩罚函数,并且带上其限制条件为:
3)非线性 SVM 与核函数
如果我们要处理的分类问题更加复杂,甚至不能像上面一样近似线性可分呢,这种情况下找到的超平面分错的程度太高不可接受。
对于这样的问题,一种解决方案是将样本从原始空间映射到一个更高维的特征空间,使得样本在这个特征空间内线性可分,然后再运用 SVM 求解,如下图所示:
比如下图中的典型线性不可分的情况:
上述两个核函数分别为多项式核和高斯核,高斯核甚至是将原始空间映射为无穷维空间,另外核函数有一些比较好的性质,比如说不会比线性条件下增加多少额外的计算量,等等,此处我们不深入。对于一个问题,不同的核函数可能会带来不同的结果,我们需要做一些尝试来支撑选择(关于这个部分,大家可以看最下方的 python 实现部分)。
3. SVM 总结
1)模型总结
支持向量机(Support vector machines, SVM)是一种二分类模型,它的基本模型是定义在特征空间上的间隔最大的线性分类器,他的学习策略就是间隔最大化,同时该方法可以形式化为一个求解图二次规划。
SVM由简至繁可分为三类:线性可分支持向量机、硬间隔(hard-margin svm);线性支持向量机、软间隔(soft-margin svm);非线性支持向量机、Kernel SVM。
SVM中存在三宝:间隔、对偶、核技巧。
2)模型优缺点
(1)SVM模型优点
- SVM 是一个凸优化问题,求得的解一定是全局最优而不仅仅是局部最优。
- 不仅适用于线性问题,还适用于非线性问题(借助核技巧)。
- 模型鲁棒性好,决策边界只取决于支持向量而不是全部数据集。
- 中小样本量数据集上效果优异。
- 无需依赖整个数据。
- 泛化能力比较强。
(2)SVM模型缺点
- 二次规划问题求解将涉及n阶矩阵的计算(其中n为样本的个数),计算量随样本量上涨厉害,因此 SVM 不适用于超大数据集。
- 原始 SVM 仅适用于二分类问题。(当然, SVM 的推广SVR也适用于回归问题;我们也可以通过one-vs-one,one-vs-rest等思路组合 SVM 来解决多分类问题)。
- 对非线性问题没有通用解决方案,有时候很难找到一个合适的核函数。
- 对于核函数的高维映射解释力不强,尤其是径向基函数。
- SVM 对缺失数据敏感。
3) SVM的损失函数
Hinge Loss 是机器学习领域中的一种损失函数,可用于“最大间隔(max-margin)”分类,其最著名的应用是作为SVM的目标函数。
在二分类的情况下,公式如下:
L(y) = max(0 , 1 – t⋅y)
其中,y是预测值(-1到1之间),t为目标值(1或 -1)。其含义为,y的值在 -1到1之间即可,并不鼓励 |y|>1,即让某个样本能够正确分类就可以了,不鼓励分类器过度自信,当样本与分割线的距离超过1时并不会有任何奖励。目的在于使分类器更专注于整体的分类误差。
变种
在实际应用的时候,一方面,预测值y并不总是属于[-1,1],也可能属于其他的取值范围;另一方面,很多时候我们希望训练的是两个元素之间的相似关系,而非样本的类别得分。
下面公式可能会更为常用:
L( y, y′) = max( 0, margin – (y–y′) )
更多监督学习的算法模型总结可以查看ShowMeAI的文章AI知识技能速查 | 机器学习-监督学习。
4.基于Python的 SVM 代码实践
1)算法包说明
我们这里直接借助于python机器学习工具包sklearn来演示 SVM 的应用,sklearn中对 SVM 的算法实现都包在 sklearn.svm
下面,具体见 sklearn 官方文档,其中 SVC 类是用来进行进行分类的任务,SVR 是用来进行数值回归任务的。
不同的核函数需要指定不同的参数。
2)使用线性核函数
import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm, datasets
iris = datasets.load_iris()
X = iris.data[:, :2] #只取前两维特征,方便可视化
y = iris.target
svc = svm.SVC(kernel='linear', C=1).fit(X, y)
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
h = (x_max / x_min) / 100
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
plt.subplot(1, 1, 1)
Z = svc.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.contourf(xx, yy, Z, cmap=plt.cm.Paired, alpha=0.8)
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Paired)
plt.xlabel('Sepal length')
plt.ylabel('Sepal width')
plt.xlim(xx.min(), xx.max())
plt.title('SVC with linear kernel')
plt.show()
3)使用多项式核函数
初始化 SVM 对象的代码替换为下面这行
svc = svm.SVC(kernel='poly', degree=3).fit(X, y)
4)使用rbf核函数(高斯核函数)
初始化 SVM 对象的代码替换为下面这行
svc = svm.SVC(kernel='rbf', C=1).fit(X, y)
gamma 值越大,SVM 就会倾向于越准确的划分每一个训练集里的数据,这会导致泛化误差较大和过拟合问题。