支持向量机(support vector machine),简称SVM,最早在1963年,由 Vladimir N. Vapnik 和 Alexey Ya. Chervonenkis 提出。目前的版本(soft margin)是由Corinna Cortes 和 Vapnik在1993年提出,并在1995年发表。
背景
深度学习(2012)出现之前,SVM被认为机器学习中近十几年来最成功,表现最好的算法。SVM本质上是一种二分类器,其基本模型定义为特征空间上的间隔最大的线性分类器,其学习策略便是间隔最大化,最终可转化为一个凸二次规划问题的求解。
看下图
在这个二维的平面中,用一条直线将上面的点分成两类,显然 H1 无法将点区分开,而H2 和 H3 都可以,但这两条哪个更好呢?作为分界线,H3 是更合适的,因为分界线其两边有尽可能大的间隙,这样的好处之一就是增强分类模型的泛化能力,能较为正确的分类预测新的实例。
上面的例子是在二维平面,我们找的是一条直线。假如在三维空间,我们要找的就是一个平面。总的来说就是要寻找区分两类的超平面(hyper plane
),使边界(margin
)最大。在一个 n 维空间中,超平面的方程定义为:
$a_1x_1+a_2x_2+…+a_nx_n = b$
总共可以有多少个可能的超平面?无数条。我们要寻找的超平面是到边界一测最近点的距离等于到另一侧最近点的距离,在这个边界中,边界两侧的超平面平行。
点到超平面的距离
在二维平面中,计算点 (x0, y0
) 到线ax + by + c = 0
的距离是:
在 n 维空间中,点到超平面的距离:
将点的坐标和系数都向量化表示,距离公式可以视为:
其中 w = {w0, w1, w2,...,wn}
我们寻找超平面时,先寻找各分类到超平面的距离最小,再寻找距离之和最大的超平面。共N 个训练点,点的坐标记为xi
,结果分类为yi
,构成 (xi, yi)
:
常见的SVM是 二分类模型,因此一般yi
有两种取值为 1 和 -1,取这两个值可以简化求解过程。
超平面推导
在 (xi, yi)
中,我们用 xi
表示了点的坐标,yi
表示了分类结果。超平面表示如下:
$$
w^Tx + b = 0
$$
在超平面的上方的点满足:
$$
w^T + b > 0
$$
在超平面的下方的点满足:
$$
w^T + b < 0
$$
因为 yi
只有两种取值 1 和 -1。因此就满足:
整合这两个等式(左右都乘以 yi,当yi是负值时,不等号要改方向)得:
$$
y_i(w^T + b)\geq1,\forall{i}
$$
支持向量
所有坐落在边界的边缘上的点被称作是 “支持向量”。分界边缘上的任意一点到超平面的距离为:$\left|\frac{1}{w}\right|$。
其中,||w||
是向量的范数(norm
),或者说是向量的模。它的计算方式为:
$$
\left|{w}\right| = \sqrt{w * w} = \sqrt{w_1^2 + w_2^2 +…+ w_n^2}
$$
所以最大边界距离为$\left|\frac{2}{w}\right|$。
找出最大边界的超平面
利用一些数学推导,把
yi(w^T*x + b) >= 1
变为有限制的凸优化问题(convex quadratic optimization
);利用
Karush-Kuhn-Tucker
( KKT ) 条件和拉格朗日公式,可以推出 MMH 可以被表示为以下的“决定边界(decision boundary
)”$$
d(X^T) = \sum_{i=1}^{l}y_ia_iX_iX^T + b_0
$$
上述公式中各个符号含义如下:
l
:表示 支持向量点 的个数;yi
: 第i个支持向量点;Xi
:支持向量的类别标记( class label );ai
与b0
:都是单一数值型参数。
对于测试实例,代入以上公式,按得出结果的正负号来进行分类。
SVM 算法有下面几个特性:
- 训练好的模型的算法复杂度是由支持向量的个数决定的,而不是由数据的维度决定的。所以 SVM 不太容易产生过拟合(
overfitting
)的情况;- SVM 训练出来的模型完全依赖于支持向量,即使训练集里所有非支持向量的点都被去除,重新训练,结果仍然会得到完全一样的模型;
- 一个 SVM 如果训练得出的支持向量个数比较小,那训练出的模型比较容易被泛化
线性不可分
在有些情况下,我们无法用一条直线对实例进行划分。
其实在很多情况下,数据集在空间中对应的向量不可被一个超平面区分开。这种时候我们要采取以下2个步骤:
- 利用一个非线性的映射把原数据集中的向量点转化到一个更高维度的空间中;
- 在这个高维度的空间中找一个线性的超平面来进行可区分处理。
如上图表示的,将其从一维转为二维空间,然后在二维空间进行求解。动态演示
高纬空间(核方法)
我们如何将原始数据转化到高纬空间呢,举个例子,在三维空间中的向量 X = (x1, x2, x3)
转化到六维空间 Z 中去,令:
新的决策超平面为:
其中,W
和Z
都是向量,这个超平面是线性的,解出 W
和b
后,带回原方程:
上面转换的求解过程中,需要计算内积,而内积的复杂度非常高,这就需要使用到核方法
。
核方法
在处理非线性的情况中,SVM 选择一个核函数 ( kernel trick )
,通过函数 k(.,.)
将数据映射到高维空间。支持向量机首先在低维空间中完成计算,然后通过核函数将输入空间映射到高维特征空间,最终在高维空间中构造出最优分离超平面。
核函数例子: 假设两个向量:x = (a1, a2, a3) y = (b1, b2, b3)
,定义方程:1
f(x) = (x1x1, x1x2, x1x3, x2x1, x2x2, x2x3, x3x1, x3x2, x3x3)
假设核函数为:K(x, y) = (<x, y>)^2
, 且设x = (1, 2, 3) y=(4, 5, 6)
则有:1
2
3f(x) = (1, 2, 3, 2, 4, 6, 3, 6, 9)
f(y) = (16, 20, 24, 20, 25, 36, 24, 30, 36)
<f(x), f(y)> = 16 + 40 + 72 + 40 + 100 + 180 + 72 + 180 + 324 = 1024
核函数计算为: K(x, y) = (4 + 10 + 18 ) ^2 = 32^2 = 1024
,相同的结果,使用核函数计算会快很多。
核函数能很好的避开直接在高维空间中进行计算,而结果却是等价的。
常见的几个核函数 (kernel functions
)有
- h度多项式核函数(polynomial kernel of degree h)
- 高斯径向基核函数(Gaussian radial basis function kernel)
- S型核函数(Sigmoid function kernel)
如何选择使用哪个核函数? 根据先验知识,比如图像分类,使用RBF
,尝试不同的核函数,根据结果准确度而定。
多分类
SVM 常见的是二分类模型,如果空间中有多个分类,比方有猫,狗,鸟。那么SVM就需要对每个类别做一次模型,是猫还是不是猫?是狗还是不是狗?是鸟还是不是鸟?从中选出可能性最大的。也可以两两做一次SVM:是猫还是狗?是猫还是鸟?是狗还是鸟?最后三个分类器投票决定。因此,多分类情况有两种做法:
- 对于每个类,有一个当前类和其他类的二类分类器(
one-versus-the-rest approach
)- 两两做一次二类分类器(
one-versus-one approace
)
代码实例
几个点的分类
在上面的二维空间中,有三个点:(1, 1) (2, 0) (2, 3)
。前两个点属于一类,第三个点属于另一类,我们使用这个例子来简单说明 sklearn 中 SVM 的初步用法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from sklearn import svm
x = [[2,0],[1,1],[2,3]]
y = [0,0,1]
classify = svm.SVC(kernel='linear')
classify.fit(x,y)
print(classify)
#打印支持向量 output:[[1. 1.],[2. 3.]]
print(classify.support_vectors_)
#打印支持向量在数据实例中的索引 output:[1 2]
print(classify.support_)
#打印每一类中支持向量的个数 output:[1 1]
print(classify.n_support_)
#预测新的实例 output:[1]
print(classify.predict([[2,3]]))
上面的例子中有两个点是支持向量:(1, 1) (2, 3)
,通过clf.support_vectors_
可以得到具体的点。这些支持向量点在数据集中的索引可以通过 clf.support_
得到。在这个例子中,分界线的两侧各有一个支持向量,可以通过clf.n_support_
得到,结果为 [1, 1]
。
多个点的分类
我们增加点的个数,并将超平面画出来,进行可视化展示。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52import numpy as np
from sklearn import svm
import pylab as pl
np.random.seed(0) #设置相同的seed,每次产生的随机数相同
'''
生成2个(20*2)的二维数组,然后按列将它们连接组成(40*2)的二维数组
生成的点服从正态分布,-[2,2],+[2,2]会让点靠左下方和右上方
'''
X = np.r_[np.random.randn(20,2)-[2,2],np.random.randn(20,2)+[2,2]]
Y = [0]*20 + [1]*20 #给这40个点分类,标记为0和1
#print(X.shape)
#print(Y)
classify = svm.SVC(kernel='linear') #构造SVM分类模型
classify.fit(X,Y)
'''
利用上面的40个点来画一个二维的分类平面图,
假设超平面方程为w0x + w1y + b = 0 转为点斜式就是: y = -(w0/w1)x - (b/w1) :
调用classify,获取w的值
'''
w = classify.coef_[0]
a = -w[0]/w[1]
xx = np.linspace(-5,5) #在(-5,5)区间内生成一些连续值,方便作图
yy = a*xx - (classify.intercept_[0])/w[1]
#print(w)
#print(a)
'''
绘制经过支持向量,并与超平面平行的上下2条直线
由于平行,因此斜率相同,只是截距不同
'''
b = classify.support_vectors_[0] #第一分类中的第一个支持向量点
yy_down = a*xx + (b[1]-a*b[0])
b = classify.support_vectors_[-1] #第二分类中的最后一个支持向量点
yy_up = a*xx + (b[1]-a*b[0])
#print(classify.support_vectors_)
'''
将所有的点、超平面和2条分界线绘制出来
k-为实线,即超平面线
k--为虚线,2条边界线
'''
pl.plot(xx, yy, 'k-')
pl.plot(xx, yy_down, 'k--')
pl.plot(xx, yy_up, 'k--')
#单独标记出支持向量点
pl.scatter(classify.support_vectors_[:, 0], classify.support_vectors_[:, 1], s=80, facecolors='none')
pl.scatter(X[:, 0], X[:, 1], c=Y,cmap=pl.cm.Paired )
pl.axis('tight')
pl.show()
可视化结果如下:
SVM人脸分类
我们构建SVM模型分类人脸并进行可视化展示,数据集(lfw
)默认存储在~/scikit_learn_data
文件夹中。代码讲解见注释。
1 | from __future__ import print_function |
程序运行结果如下
1 | ===== 数据集中信息 ===== |
可视化结果展示如下