最近在学习机器学习,看了一些算法,下面是整理的SVM的一些笔记吧,只写了关于线性可分支持向量。第一次写博客,一是希望把自己学的能够记录整理下来,方便以后查阅,二也是希望大家能够多多指教。有不对的地方还希望大家能够多多包涵和指点。
1.SVM设计到的一些概念
支持向量机(SVM)是一种二类分类模型(+1,-1),之所以用(+1,-1)表示而不用(1,0)表示是因为+1和-1仅仅相差一个符号,在数学上容易处理。SVM的基本模型是定义在特征空间上的间隔最大的线性分类器。SVM的学习策略就是间隔最大化,可形式化为求解一个凸二次规划的问题。下面通过图形和数学公式来一步步的解释SVM算法。
图B,C,D中,分割线(图中直线)可以将两组样本点分开,而且从图中我们可以看出,能将这两组样本点分开的分割线不止一条,结合SVM中的间隔最大化,其实指的就是,我们可以找出一条分割线,不仅能将两组样本点分割开来,而且要求每组样本点中距离分割线最近的点距离分割线的距离要尽可能得大。很容易理解,这样的分割线是存在的,比如图D中的分割线。而SVM算法就是找出这样的间隔线。这样的间隔线在SVM中称为分割超平面(在二维图中,分割超平面是一条直线,在N维图形中,间隔线是N-1维超平面)。这里的支持向量指的就是离分割超平面最近的点。这里需要注意的是,分割超平面受支持向量的影响,而不受那些远离分割超平面的点的影响,比如把距离分割超平面较远的点删除,分割超平面是不会改变的。 在这里,分割超平面可以用
来表示。每个点到该超平面的函数距离表示为
,所有样本点距离超平面最近的函数间隔表示为
而每个点到该超平面的几何距离表示为
所有样本点距离超平面最近的几何间隔表示为:
从函数距离和几何距离的定义,它们而这有如下的关系:
好了,有了上面这些概念和基本的距离公式后,下面就是一步步推导求解最大间隔超平面了。
2.间隔最大化
求间隔最大化,其实就是求几何间隔最大化的分离超平面,同时还必须满足所有的几何间隔都不能小于最小间隔,即我一开始提到的最小间隔的最大化,也就是支持向量的最大化。所以,求间隔最大化,就是求下面的关系式:
考虑到之前集合几何间隔和函数间隔之间的关系,上面的关系式还可以表示为:
通过第一个关系式,我们不难发现,函数间隔的取值并不影响最优化的解。假设,将w和b按比例改变为,这是函数间隔为,它对上面的最优化问题的不等式约束没有任何影响,对目标函数的优化也没有影响。所以,为了简化运算,我们在这里可以设=1,将其带入上面的最优化问题中,同时我们可以发现
是等价的,于是,上面的关系式可以写成:
这是图二次规划的求解问题。
所以,完整的最大间隔算法是:
还有一种求解最大间隔的方法是对偶算法,相较上一种算法,该算法有两个优点:一是对偶问题更容易求解,而是方便引入核函数,进而推广到非线性分类问题。对偶函数要构建拉格朗日函数。首先对不等式引入拉格朗日乘子
定义拉格朗日函数:
所以原始问题的对偶问题是极大极小问题:
首先求极小,将拉格朗日函数L分别对w,b求偏导并令其等于0:
结果为:
将上面两个式子带入拉格朗日函数L中得到:
最后就是对minL求对偶问题:
所以,线性可分支持向量机的学习算法为:
以上介绍的都是线性可分支持向量机,即存在分离超平面将样本点都正确分类。其实还有线性支持向量机和非线性支持向量机。而线性支持向量机的分类策略是软间隔最大化,非线性支持向量机会用到核技巧。它们都属于SVM的内容。其实不难理解,在实际的分类问题中,很少存在线性完全可分的情况,所以后两种方法也显得非常重要。
下面是我在学习《机器学习实战》SVM算法时,对SMO实现的理解。下面附上代码(呵呵,照着书上敲的,不过很多地方都做了注释)。
import numpy as np
class optStructK:#定义一个数据结构,来保存重要值
def __init__(self,dataSet,labels,C,toler):
self.dataMat=dataSet#数据集
self.labels=labels#类别
self.C=C#松弛变量,线性支持向量中定义的
self.toler=toler#误差
self.m=np.shape(dataSet)[0]
self.alphas=np.mat(np.zeros((self.m,1)))
self.cache=np.mat(np.zeros((self.m,2)))#误差缓存,是一个m行2列的矩阵,每一行存放的值分别为:第i个索引和第i个样本的误差
self.b=0#b变量
def calcEk(os,i):#该函数是求第i个样本的误差,用到了统计学习方法中7.104个公式
return float(np.multiply(os.alphas,os.labels).T*\
(os.dataMat*os.dataMat[i,:].T))+os.b-float(os.labels[i])
def selectJrand(i,m):#该函数用于选择第2个alpha
j=i
while j==i:
j=int(np.random.uniform(0,m))
return j
def selectJ(i,os,Ei):#该函数的功能:在找到了第一个alpha后,选择第二个alpha
os.cache[i]=[1,Ei]
maxK=-1
maxDeltaE=0
Ej=0
list=np.nonzero(os.cache[:,0])[0]#返回非0误差对应的alpha,然后选择与第一个alpha具有最大步长的第二个alpha
if len(list)>1:
for j in list:
if i==j:continue
Ej1=calcEk(os,j)
newDe=abs(Ej1-Ei)
if newDe>maxDeltaE:
maxDeltaE=newDe
Ej=Ej1
maxK=j
return maxK,Ej
else:#如果不存在非0误差对应的alpha,就随机选择一个与第一个alpha不同的第二个alpha
j=selectJrand(i,os.m)
Ej=calcEk(os, j)
return j,Ej#返回第二个alpha和其对应的误差
def clipAlpha(alphas, H, L):#该函数用于判断新求出的第二个alpha的值是否超出了C或者小于0
if alphas>H:#用到了统计学习方法中的7.108公式
alphas=H
elif alphas
os.toler) and (os.alphas[i]>0)):
j,Ej=selectJ(i,os,Ei)#求第二个样本,及其误差
alphaJOld=os.alphas[j].copy()
alphaIOld=os.alphas[i].copy()
if os.labels[i]!=os.labels[j]:
L=max(0,os.alphas[j]-os.alphas[i])
H=min(os.C,os.alphas[j]+os.C-os.alphas[i])
else:
L=max(0,os.alphas[i]+os.alphas[j]-os.C)
H=min(os.C,os.alphas[i]+os.alphas[j])
if L==H:return 0
#用到了统计学习方法中7.107公式
exopn=2.0*os.dataMat[i,:]*os.dataMat[j,:].T-\
os.dataMat[i,:]*os.dataMat[i,:].T-\
os.dataMat[j,:]*os.dataMat[j,:].T
if exopn>=0:return 0
#用到了统计学习方法中7.106公式
os.alphas[j]-=os.labels[j]*(Ei-Ej)/exopn
#用到了统计学习方法中7.108公式
os.alphas[j]=clipAlpha(os.alphas[j],H,L)
#更新误差
updateEk(os,j)
if (abs(os.alphas[j]-alphaJOld)<0.00001):return 0
#用到了统计学习方法中7.109公式
os.alphas[i]+=os.labels[i]*os.labels[j]*(alphaJOld-os.alphas[j])
#更新误差
updateEk(os,i)
#用到了统计学习方法中7.115公式
b1=os.b-Ei-os.labels[i]*os.dataMat[i,:]*os.dataMat[i,:].T*(os.alphas[i]-alphaIOld)-\
os.labels[j]*os.dataMat[j,:]*os.dataMat[i,:].T*(os.alphas[j]-alphaJOld)
#用到了统计学习方法中7.116公式
b2=os.b-Ej-os.labels[i]*os.dataMat[i,:]*os.dataMat[j,:].T*(os.alphas[i]-alphaIOld)-\
os.labels[j]*os.dataMat[j,:]*os.dataMat[j,:].T*(os.alphas[j]-alphaJOld)
#参考统计学习方法中7.116公式下面的第一段话
if os.alphas[i]>0 and os.alphas[i]
0 and os.alphas[j]
0):
alphaPairsChanged=0
if entireSet:
for i in range(os.m):
alphaPairsChanged+=innerl(os,i)
iter+=1
else:
nonBoundIs=np.nonzero((os.alphas.A>0)*(os.alphas.A