版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1435863
支持向量机学习笔记–原理篇(一)
前言
初步学习机器学习给我最大的感受是它背后需要强大的数学知识,理论推导往往能帮助我们理解其本质。而在我看来,单纯的求解数学问题还不够,我们需要有把这部分理论知识运用到实际应用中去的能力。支持向量机(support vector)是机器学习中用来解决监督分类问题的一种方法。
本文致力于把复杂的理论简化到简单的低维情况,配以图的方式对相关理论进行学习性解释。最后再用python语言实现它,以实践的方式,把数学公式应用到计算机编程领域。自知水平有限,大部分内容均是在学习过程中所参考的资料,本文不再重复上述所讲的任何观点以及重证理论公式,只在关键处加入自己的思考,以括号注释,呈现自己思考的过程。
ps:本文假设读者有了一定机器学习的基础,对基本概念均有所了解。
思考
- 感知机是什么?
- 支持向量机是什么?
- 支持向量机是为了解决什么问题而被提出来
感知机
感知机概念
感知机是二元分类中最简单的模型,我们先学习感知机,来慢慢加深对支持向量机的理解。
书本《统计学习方法》定义为:感知机是二类分类的线性分类模型,其输入为实例的特征向量,输出为实例的类别,取 1和-1二值。(注释:输出空间只取 1和-1,这在后续公式推导时有非常大的好处。)感知机对应于输入空间中将实例划分为正负两类的分离超平面,属于判别模型。
简单来说,对于一个二元分类问题,感知机能够根据给定的算法训练出一种线性模型来对数据进行分类的效果。首先我们来看这个函数:
f(x)=wx b
f(x) =wx b
初中我们就学过这样一条直线所代表的含义,即在X-Y轴上画出一条直线,那么在感知机中它是什么样的物理含义呢?咱们再来看看感知机输入空间到输出空间的映射函数:
f(x)=sign(wx b)
f(x)=sign(wx b)
其中ww和bb为感知机模型参数,w∈Rnwin R^n叫做权值(weight)或权值向量(weight verctor),b∈Rbin R叫作偏置(bias),w⋅xwcdot x表示ww和xx的内积。sign是符号函数,即
sign(x)={ 1,x≥0or−1,x<0
sign(x) = begin{cases} 1, & xge 0 or -1, & xlt 0 end{cases}
输入空间到输出空间的映射函数,是感知机的终极模型,因此算法也是建立在该模型上进行探讨的,数据的存在是为了训练模型所定义的参数,而这参数便是ww和bb,但仅定义这个模型显然是无法求解参数的,不急,咱们继续看看该函数在三维空间实际的几何图形是什么样的。
明明是二维!怎么是三维呢,其实f(x)f(x)函数所代表的维度为n 1n 1维,n是特征向量xx的维度,而 1维则代表了输出空间的维度。图中,二维空间向量可以用XX表示,其中Xi=(x1,x2)TX_i=(x_1,x_2)^T表示,第三维我们不以z轴来表示,而是以一种直观的形状来代表。假设负类为小方块,正类为小圆圈。
配上图我们便能理解该模型的物理含义了,大量的数据集表在我们面前,如果它们是线性可分的,那我们就能找到一条直线来区分正类和负类,但实际情况却总是出乎意料,现实世界的复杂程度超过了我们预测,往往数据都是非线性可分的,因此感知机升级到了支持向量机用来解决一些非线性问题,当然支持向量机还不是简单的为解决该问题所存在,后续还会提到。回到上图,当w⋅x b>0wcdot x bgt0时,代表该点在f(x)f(x)的上方,即sign(x)sign(x)为1。只要找到w,bw,b就能对数据进行二元分类。
感知机学习策略
假设训练数据集是线性可分的,感知机学习的目标是求得一个能够将训练集正实例点和负实例点完全正确分开的分离超平面。为了找出这样的超平面,即确定感知机模型参数w,bw,b需要确定一个学习策略,即定义经验损失函数并将损失函数极小化。
什么!经验损失函数。。。也就是在斯坦福大学机器学习课程中所定义的cost function。联想到实际物理情况中去,经验损失是我们根据已有数据进行拟合后所得出的在大脑中目前最好的“规则引擎”,当遇到类似情况时,我们找到该“规则引擎”对数据做预测or分类。但往往令人遗憾的是,我们涉世未深,所得到的数据只是我们漫长时间线上的很小一部分,该“规则引擎”显然还未拟合的很好,每当出现大量的预测错误时,我们便需要更改我们的规则引擎,怎么变?什么时候变?需要一个衡量标准,那好,把之前的数据拿出来,加上现有数据,我们重新计算一遍平均误差,选择误差最小的代表我们最新的规则引擎。
回归数学,损失函数的一个自然选择是误分类点的总数。但是,这样的损失函不是参数w,bw,b的连续可导函数,不易优化。损失函数的另一个选择是误分类点到超平面S的总距离,这是感知机所采用的,为此,首先写出输入空间RnR^n中任一点x0x_0到超平面S的距离:
1∥w∥|w⋅x0 b|
frac 1{Vert w Vert}vert w cdot x_0 bvert
这里,∥w∥Vert wVert是ww的L2L_2范数。
抛开高级范数定义,上述公式就是中学所学过的一个点到直线的距离。其次,对误分类的数据(xi,yi)(x_i,y_i)来说,
−yi(w⋅xi b)>0
-y_i(wcdot x_i b) gt0
成立。因为当w⋅xi b>0,yi=−1wcdot x_i b gt0,y_i =-1,而当w⋅xi b<0wcdot x_i b lt0时,yi= 1.y_i= 1.因此,误分类点xix_i到超平面S的距离是
−1∥w∥yi(w⋅xi b)
-frac 1{Vert wVert}y_i(wcdot x_i b)
这样,假设超平面S的误分类点集合为M,那么所有误分类点到超平面S的总距离为
−1∥w∥∑xi∈Myi(w⋅xi b)
-frac 1{Vert wVert}sum_{x_iin M}y_i(wcdot x_i b)
不考虑1∥w∥frac 1{Vert w Vert},就得到感知机学习的损失函数。
给定训练数据集
T={(x1,y1),(x2,y2),...,(xN,yN)}
T = { (x_1,y_1),(x_2,y_2),...,(x_N,y_N)}
其中,xi∈Rn,yi∈Y={ 1,−1},i=1,2,3,...N.x_iin R^n,y_iin Y={ 1,-1},i=1,2,3,...N.感知机sign(w⋅x b)sign(wcdot x b)学习的损失函数定义为
L(w,b)=−∑xi∈Myi(w⋅xi b)
L(w,b)=-sum_{x_iin M}y_i(wcdot x_i b)
其中M为误分类点的集合。这个损失函数是感知机学习的经验风险函数。
显然,损失函数L(w,b)L(w,b)是非负的。如果没有误分类点,损失函数值是0。(正确分类点不记录损失函数的点集合)而且,误分类点越少,误分类点离超平面越近,损失函数值就越小。一个特定的样本点的损失函数:在误分类时是参数w,bw,b的线性函数,在正确分类时是0。因此,给定训练数据集T,损失函数L(w,b)L(w,b)是w,bw,b的连续可导函数。
感知机学习算法
上节给出了具体的损失函数
L(w,b)=−∑xi∈Myi(w⋅xi b)
L(w,b)=-sum_{x_iin M}y_i(wcdot x_i b)
一个很直观的想法便是求解该损失函数的极小值,即可表示为
minL(w,b)=−∑xi∈Myi(w⋅xi b)
min{L(w,b)}= -sum_{x_iin M}y_i(wcdot x_i b)
感知机学习算法是误分类驱动的,具体采用随机梯度下降法。首先,任意选取一个超平面w0,b0w_0,b_0,然后用梯度下降法不断地极小化目标函数。极小化过程中不是一次使用M中所有误分类点的梯度下降,而是一次随机选取一个误分类点使其梯度下降。(有关梯度下降算法,看最近能否找到相关资料,重新开篇文章进行讲解,这里直接给出w,bw,b的更新公式,因为我只知道了该公式的形式化解释,随机梯度下降和所有误分类点的梯度下降在书中并没有找到相关联系)
随机选取一个误分类点(xi,yi)(x_i,y_i),对w,bw,b进行更新:
w←w ηyixi
w leftarrow w eta y_ix_i
b←b ηyi
b leftarrow b eta y_i
式中η(0<η≤1)eta(0lt eta le 1)是步长,在统计学习中又成为学习率。这样通过迭代可以期待损失函数L(w,b)L(w,b)不断减小,直到为0。综上所述,得到如下算法:
算法:(感知机学习算法的原始形式)
输入:训练数据集T={(x1,y1),,(x2,y2),...(xN,yN)}T ={(x_1,y_1),,(x_2,y_2),...(x_N,y_N)},其中xi∈X=Rn,yi∈Y={−1, 1},i=1,2,...,N;x_i in X=R^n,y_i in Y={-1, 1},i=1,2,...,N;学习率η(0<η≤1);eta(0lt eta le 1); 输出:w,b;w,b;感知机模型f(x)=sign(w⋅x b);f(x)=sign(wcdot x b); (1)选取初值w0,b0w_0,b_0 (2)在训练集中选取数据(xi,yi)(x_i,y_i) (3)如果yi(w⋅xi b)≤0y_i(wcdot x_i b) le 0 w←w ηyixi w leftarrow w eta y_ix_i b←b ηyi b leftarrow b eta y_iundefined (4) 转至(2),直至训练集中没有误分类点
这里采用了随机梯度下降算法,即在更新w,bw,b时,只单独计算了某个(xi,yi)(x_i,y_i)点。那为什么这样的更新算法便能找到L(w,b)L(w,b)的极小值呢?
步骤1、2可以忽略不看,重点关注步骤3。判别式yi(w⋅x b)≤0y_i(wcdot x b) le 0是用来删选所有误分类点,然后根据误分类点做w,bw,b的数值更新。
观察判别式yi(w⋅xi b)≤0y_i(wcdot x_i b) le 0,我们能发现什么几何含义呢?我们根据yiy_i进行分类,当yi= 1y_i= 1时,符合判别式的xix_i的情况为(w⋅xi b)≤0;(wcdot x_i b) le 0;同理,当yi=−1y_i=-1时,符合判别式的xix_i的情况为(w⋅xi b)≥0.(wcdot x_i b) ge 0.我们记margin=w⋅xi b.margin=wcdot x_i b.
在二维坐标轴上容易观察出,实际坐标距离为
realmargin=1∥w∥|w⋅xi b|.
real_{margin}=frac 1{Vert w Vert } vert wcdot x_i b vert.
因此忽略系数1∥w∥frac 1{Vert wVert},margin可以理解为实际的误分类点离我们所假设直线的误差距离。在直线上方,margin为正,在直线下方margin为负。
因此很容易想到,误差距离是所有误分类点形成的距离,那么根据每一个误差分类点,如果margin为正,就应该使margin变为负,如果margin为负,应该使margin为正。总结一句话,针对所有误分类点,更新w,bw,b,使得margin应该朝相反方向发生变化。即正变负,负变正。
好,有了这样简单的规律总结,我们便需要构造这样的更新式了,我们直接给出结论,证明它的确符合上述规律即可。式为:
w←w ηyixi
w leftarrow w eta y_ix_i
b←b ηyi
b leftarrow b eta y_i
w,bw,b只是根据原来的值,加上误分类点值做了更新。先来看bb的变化情况,ηeta在区间0,1之间,因此 b和yiy_i之间关系即为反向关系(你增我减,你减我增,跟你势不两立!),代入margin,得
marginnew=w⋅xi bold ηyi
margin^{new} =wcdot x_i b^{old} eta y_i
marginold=w⋅xi bold
margin^{old}=wcdot x_i b^{old}
marginnewmargin^{new}和marginoldmargin^{old}符合反向变化关系。同理,来看看ww的变化情况,代入magrin得
marginnew=wold⋅xi ηyixixTi b
margin^{new}=w^{old}cdot x_i eta y_ix_ix_i^T b
marginold=wold⋅xi b
margin^{old}=w^{old}cdot x_i b
同样的marginnewmargin^{new}和marginoldmargin^{old}符合反向变化关系。综上所述,对于所有的误分类点,如果margin不符合条件,我们便让它反向变化一次,当然针对同一个误分类点,我们可以让它变化n次,直到符合判别式条件。如果遇到线性不可分问题,那么该算法也将无解。
Python实现
参考《机器学习实战》中的代码,单独开一章来剖析SMO算法的具体实现。老规矩,看代码之前先看数据。
训练集:training.data
每个实例就是一个二维向量和一个±1的label:
代码语言:javascript复制3.542485 1.977398 -1
3.018896 2.556416 -1
7.551510 -1.580030 1
2.114999 -0.004466 -1
8.127113 1.274372 1
7.108772 -0.986906 1
8.610639 2.046708 1
2.326297 0.265213 -1
3.634009 1.730537 -1
0.341367 -0.894998 -1
初始化代码如下:
代码语言:javascript复制if __name__=="__main__":
"""
加载数据集
:param fileName:
:return:
"""
if len(sys.argv)!=4:
print "Usage:python perceptron.py n trainFile modelFile"
exit(0)
eta = float(sys.argv[1])
trainFile =file(sys.argv[2])
modelFile =file(sys.argv[3],'w')
lens =0
for line in trainFile:
chunk = line.strip().split(' ')
lens = len(chunk)-1 #feature X lens
tmp_all =[]
tmp =[]
for i in range(1,lens 1):
tmp.append(float(chunk[i]))
tmp_all.append(tmp)
tmp_all.append(float(chunk[0]))
training_set.append(tmp_all)
trainFile.close()
for i in range(lens):
w.append(0)
for i in range(1000):# iterator 1000
check()
print "The training_set is not linear separable."
w,bw,b更新:
代码语言:javascript复制def update(item):
global w,b,lens,eta
for i in range(lens):
w[i] =w[i] eta*item[1]*item[0][i]
b = b eta*item[1]
print w,b
L(w,b)=−∑xi∈Myi(w⋅xi b)L(w,b)=-sum_{x_iin M}y_i(wcdot x_i b)误差函数计算:
代码语言:javascript复制def cal(item):
global w,b
res =0
for i in range(len(item[0])):
res = item[0][i]*w[i]
res =b
res *=item[1]
return res
判别式yi(w⋅xi b)≤0y_i(wcdot x_i b) le 0判断:
代码语言:javascript复制def check():
flag =False
for item in training_set:
if cal(item) <=0:
flag = True
update(item)
if not flag: #False
print "RESULT:W:" str(w) "b:" str(b)
tmp=''
for keys in w:
tmp =str(keys) ' '
tmp = tmp.strip()
modelFile.write(tmp 'n')
modelFile.write(str(b) 'n')
modelFile.write(str(lens) 'n')
modelFile.write(str(eta) 'n')
modelFile.close()
os._exit(0)
flag =False
至此,感知机的基本原理和代码实现已基本讲解完成,在下篇中将继续学习感知学习算法的对偶形式,该对偶形式目测能够与支持向量机形成一些有趣的对比,待学习…
最后来一句废话吧,不积跬步,无以至千里;不积小流,无以成沧海。
参考文献及推荐阅读
- 感知机(python实现)
- 李航. 统计学习方法M. 北京:清华大学出版社,2012