SVM有很多实现,现在只关注其中最流行的一种实现,即序列最小优化(Sequential Minimal Optimization,SMO)算法,然后介绍如何使用一种核函数(kernel)的方式将SVM扩展到更多的数据集上。
1.基于最大间隔分隔数据
几个概念:
1.线性可分(linearly separable):对于图6-1中的圆形点和方形点,如果很容易就可以在图中画出一条直线将两组数据点分开,就称这组数据为线性可分数据
2.分隔超平面(separating hyperplane):将数据集分隔开来的直线称为分隔超平面
3.如果数据集是1024维的,那么就需要一个1023维的超平面来对数据进行分隔
4.间隔(margin):数据点到分隔面的距离称为间隔
5.支持向量(support vector):离分隔超平面最近的那些点
支持向量机的优点:泛化错误率低,计算开销不大,结果易解释
支持向量机的缺点:对参数调节和核函数的选择敏感,原始分类器不加修改仅适用于处理二类问题
适用数据类型:数值型和标称型数据
2.寻找最大间隔
如何求解数据集的最佳分隔直线?
分隔超平面的形式可以写成
其中 w = (w1,w2,w3...wd)为法向量,决定了超平面的方向,其中d等于数据的维度,
这很好理解,假设二维的(x1,x2)点可以被 ax+b=0 分隔,这里面直线 ax+b=0 是一维的,但是这里面a和x都是二维的
b为位移项,决定了超平面与原点之间的距离
对于图6-3中A点到分隔直线的距离为
表示向量的模,,w与w共轭的内积再开方
假设超平面(w,b)能将训练样本正确分类,即对于 ,
欲找到具有“最大间隔(maximum margin)”的划分超平面,也就是要找到能满足 中约束的参数w和b,使得最大,即
其中约束条件为 s.t. ,其实这个约束条件就是把两个不等式合并成了一个
显然,为了最大化间隔,仅需最大化 ,这等价于最小化 ,于是上式可重写为
其中约束条件为 s.t.
这就是支持向量机(Support Vector Machine,简称SVM)的基本型
对于这类带有不等式约束的最优化问题,可以使用拉格朗日乘子法(Lagrange Multiplier)对其进行求解。
首先先了解一下最优化问题的分类,最优化问题可以分为一下三类:
<1>无约束的优化问题,可以写成:
对于第<1>类的优化问题,常常使用的方法就是Fermat定理,即使用求取f(x)的导数,然后令其为零,可以求得候选最优值,再在这些候选值中验证;如果是凸函数,可以保证是最优解。
<2>有等式约束的优化问题,可以写成:
约束条件
对于第<2>类的优化问题,常常使用的方法就是拉格朗日乘子法(Lagrange Multiplier) ,即把等式约束 用一个系数与目标函数f(x)写为一个式子,称为拉格朗日函数,而系数称为拉格朗日乘子。拉格朗日函数的形式如下,u即拉格朗日乘子:
通过拉格朗日函数对各个变量求导,令其为零,可以求得候选值集合,然后验证求得最优值。
<3>有不等式约束的优化问题,可以写成:
约束条件
对于第<3>类的优化问题,常常使用的方法就是KKT条件。同样地,我们把所有的等式、不等式约束与f(x)写为一个式子,也叫拉格朗日函数,系数也称拉格朗日乘子,通过一些条件,可以求出最优值的必要条件,这个条件称为KKT条件。
拉格朗日乘子法(Lagrange Multiplier):
拉格朗日乘子法可以应用于有等式约束的优化问题和有不等式约束的优化问题,
对于有等式约束的优化问题, ,约束条件 ,其中 f(x) 被称为目标函数
<1>当没有约束条件的时候,我们通过求导的方法,来寻找最优点
设 是这个最优点,即此时有
此外,如果 f(x) 是一个实值函数, 是一个n维向量的话,那么 f(x) 对向量 的导数被定义为
<2>当有一个等式约束条件 的时候,举例设
从几何的角度看,可以看成是在一个曲面 上寻找函数 的最小值
设目标函数 ,当 z 取不同的值的时候,相当于可以投影在曲面 上,即形成等高线,如下图
当 取d1或者d2的时候,形成了虚线的等高线,此时我们要求的是最小的z
现在我们约束 的时候,在曲面 上形成一条曲线,即图中红色的曲线
假设形成的红色曲线与等高线相交,交点就是同时满足等式约束条件和目标函数的可行域的值,但肯定不是最优值,因为相交意味着肯定还存在其它的等高线在该条等高线的内部或者外部,使得新的等高线与目标函数的交点的值更大或者更小。
只有到等高线与目标函数的曲线相切的时候,可能取得最优值,如上图中的情况,即等高线和目标函数的曲线在该点的法向量必须有相同方向,所以最优值必须满足:f(x)的梯度 = * h(x)的梯度, 是常数,表示左右两边同向。这个等式就是拉格朗日函数 对参数x求导后,令其等于0的结果,即,最后求得最优解是 。
以上的约束条件只是 ,这时约束条件是一个等式。
<3>那么当约束条件是多个等式的时候,拉格朗日函数应该改写成
,是需要求其最小值的目标函数,是拉格朗日乘子,是约束条件
<4>如果约束条件是等式和不等式,且有多个的时候,就需要若干个KKT(Karush-Kuhn-Tucker)条件,即对于
约束条件
想取得最优解,要满足以下条件(KKT条件):
1. L(x, a, b)对x求导为零;
2. h(x) =0;
3. a*g(x) = 0;
一步步来说明,此时我们要求f(x)的最小值,可以构建拉格朗日函数
为什么这么构建呢?因为当 g(x)<=0,h(x)=0,a>=0 的时候,(注意此处的h(x)=0是KKT条件的第2个)
a*g(x)<=0,所以 在取得最大值的时候,即a*g(x)=0的时候,就等于f(x),(注意此处的a*g(x)=0是KKT条件的第3个)
所以需要求解的目标函数的最小值写成表达式是
上式的对偶表达式是
由于我们要求的最优化是满足强对偶的,即对偶式子的最优值等于原式子的最优解
设当 的时候有最优解,此时
即函数 在 处取得最小值
用fermat定理,对于函数 求取导数后令其等于0后的结果,(注意此处的导数=0是KKT条件的第1个)
即f(x)的梯度 + a*g(x)的梯度 + b*h(x)的梯度 = 0
所以对于多个不等式和等式混合的约束条件的时候,拉格朗日函数可以写成
约束条件
从而上面提到的支持向量机(Support Vector Machine,简称SVM)的基本型
就可以重新写成
其中 ,
接下来由第1个KKT条件(导数等于0),令 对 和 b 的偏导等于0,可得
把上面两个等式带入 中,得
的对偶表达式等于
在最后推出的公式中,y表示的是标签向量,u表示的是拉格朗日乘子,x表示的是待分类的点的坐标,也是一个向量
现在约束条件变成了等式,所以我们的任务变成了
以上的推导有一个假设条件,就是数据是线性可分的,而实际上,大多数情况是线性不可分的,对于这种情况:
我们把约束条件从 改成 ,允许出现例外
所以支持向量机(Support Vector Machine,简称SVM)的基本型就需要改写成,加上了松弛变量(这里面有泛函的数学原理,参考《神经网络与机器学习 第3版》)
通过和上面一样的对 w,b和求偏导的过程,进行简化之后,也是得到
现在可以应用上面的等式约束条件的方法,求出 u 后,在求出w与b即可得到模型
2.SMO高效优化算法
接下来分析SMO算法,因为有约束条件 ,所以一次不能只更新一个 ,因为如果一次只更新一个 的话,那么累加之后的结果就不能等于0,这就不满足约束条件了,所以SMO算法采取的是挑选一对 进行更新,这样就能满足约束条件。
图来自网上
当y1和y2异号的时候,即y1=1,y2=-1或者y1=-1,y2=1的时候,我们先固定一个 的值,然后求 的范围
<1>设y1=1,y2=-1
则如上图所示,横坐标轴是 ,纵坐标轴是 ,因为 ,且 是大于等于0的,
所以当 和 有可能满足红线的情况,即y1=1,y2=-1时,,那么 的范围是 ,即
所以当 和 也有可能满足蓝线的情况,即y1=1,y2=-1时,,那么 的范围是 ,即
由于不知道是红线还是蓝线,所以 的取值范围为:
<2>设y1=-1,y2=1
同样可能通过画图来说明,计算出来的 的取值范围仍然是:
综上所述,当y1和y2异号的时候,的取值范围为:
同理,当y1和y2同号的时候,的取值范围为:
以上内容的参考文献:
支持向量机入门系列-2:等式约束极小的最优性条件
深入理解拉格朗日乘子法(Lagrange Multiplier) 和KKT条件
《机器学习》——周志华
《神经网络与机器学习(第3版)》
《机器学习实战》
以下内容参考的网址:
支持向量机(SVM),SMO算法原理及源代码剖析
《机器学习实战》
我们需要求解的问题是
现在把求最大值转换成求最小值,其实是一样的,现在问题变成了
以下推导内容是CSDN上一篇博文的内容,直接贴图片,符号表示有些许区别
对最小化的目标函数和约束条件进行优化,以下是Python代码对应的伪代码流程
源代码及代码注释:
from numpy import *
from time import sleep
import time
def loadDataSet(fileName): #逐行解析,得到每行的类标签和整个数据矩阵
dataMat = []; labelMat = []
fr = open(fileName)
for line in fr.readlines():
lineArr = line.strip().split('\t')
dataMat.append([float(lineArr[0]), float(lineArr[1])])
labelMat.append(float(lineArr[2]))
return dataMat,labelMat
#辅助函数,用于在某一区间范围内随机选择一个整数
#i是第一个alpha的下标,m是所有alpha的数目,只要函数值不等于输入值i,函数就会进行随机选择
def selectJrand(i,m):
j=i #we want to select any J not equal to i
while (j==i):
j = int(random.uniform(0,m))
return j
def clipAlpha(aj,H,L): #辅助函数,数值太大或者太小时对其进行调整
if aj > H:
aj = H
if L > aj:
aj = L
return aj
#Platt SMO算法中的外循环确定要优化的最佳aplha集合,而简化版却会跳过这一部分
#首先在数据集上遍历每一个alpha,然后在剩下的alpha集合中随机选择另一个alpha,构建alpha对
#简化版SMO算法,数据集,类别标签,常数C,容错率,取消前最大的循环次数
def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
dataMatrix = mat(dataMatIn); labelMat = mat(classLabels).transpose() #把列表dataMatIn和classLabels转换成矩阵dataMatrix和labelMat
b = 0; m,n = shape(dataMatrix) #初始的b=0,m=100,n=2
alphas = mat(zeros((m,1))) #生成一个100×1的零矩阵
iter = 0
while (iter < maxIter): #在iter小于maxIter的时候循环
alphaPairsChanged = 0 #用于记录alpha是否已经进行优化
for i in range(m): #循环所有数据集,总共有100个
fXi = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b #fXi是预测的类别,i是alpha的下标,把第i个x代入公式计算
Ei = fXi - float(labelMat[i]) #Ei是第i数据向量的计算误差
#判断每一个alpha是否被优化过,如果误差很大,就对该alpha值进行优化,toler是容错率
if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
j = selectJrand(i,m) #随机选择另外一个数据向量j
print "随机选择的j下标是",j
fXj = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b #同时优化这两个向量,如果都不能被优化,退出内循环
Ej = fXj - float(labelMat[j]) #Ej是第j数据向量的计算误差
alphaIold = alphas[i].copy();alphaJold = alphas[j].copy();
#保证alpha在0与C之间
if (labelMat[i] != labelMat[j]): #当y1和y2异号,计算alpha的取值范围
L = max(0, alphas[j] - alphas[i])
H = min(C, C + alphas[j] - alphas[i])
else: #当y1和y2同号,计算alpha的取值范围
L = max(0, alphas[j] + alphas[i] - C)
H = min(C, alphas[j] + alphas[i])
#如果L和H相等,就不做任何改变,本次循环结束直接运行下一次for循环
if L==H: print "L==H"; continue
#eta是alpha[j]的最优修改量,eta=K11+K22-2*K12,也是f(x)的二阶导数,K表示内积
eta = 2.0 * dataMatrix[i,:]*dataMatrix[j,:].T - dataMatrix[i,:]*dataMatrix[i,:].T - dataMatrix[j,:]*dataMatrix[j,:].T
#如果二阶导数-eta <= 0,说明一阶导数没有最小值,就不做任何改变,本次循环结束直接运行下一次for循环
if eta >= 0: print "eta>=0"; continue
alphas[j] -= labelMat[j]*(Ei - Ej)/eta #利用公式更新alpha[j],alpha2new=alpha2-yj(Ei-Ej)/eta
alphas[j] = clipAlpha(alphas[j],H,L) #再判断一次alpha[j]的范围
#如果alphas[j]没有调整,就忽略下面语句,本次循环结束直接运行下一次for循环
if (abs(alphas[j] - alphaJold) < 0.00001): print "j改变的不够"; continue
alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j]) #调整alphas[i],修改量与j相同,但是方向相反
#已经计算出了alpha,接下来根据模型的公式计算b
b1 = b - Ei- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[i,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[i,:]*dataMatrix[j,:].T
b2 = b - Ej- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[j,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[j,:]*dataMatrix[j,:].T
#根据公式确定偏移量b,理论上可选取任意支持向量来求解,但是现实任务中通常使用所有支持向量求解的平均值,这样更加鲁棒
if (0 < alphas[i]) and (C > alphas[i]): b = b1
elif (0 < alphas[j]) and (C > alphas[j]): b = b2
else: b = (b1 + b2)/2.0
alphaPairsChanged += 1
print "总共100次迭代的次数: %d 当前i的下标:%d, 成对改变的alpha的数量 %d" % (iter,i,alphaPairsChanged)
#print "输出现在的alphas",alphas.T
#只有满足alphaPairsChanged为0的条件,即100个alpha的误差都在范围之内,才能进入下一个递归,不然只能在100个alpha再次循环优化
if (alphaPairsChanged == 0): iter += 1
else: iter = 0
print "iteration number: %d" % iter
plotBestFit(alphas,dataArr,labelArr,b)
return b,alphas
def calcWs(alphas,dataArr,classLabels):
X = mat(dataArr); labelMat = mat(classLabels).transpose()
m,n = shape(X)
w = zeros((n,1))
for i in range(m):
w += multiply(alphas[i]*labelMat[i],X[i,:].T)
return w
def plotBestFit(alphas,dataArr,labelArr,b): #画出数据集和超平面
import matplotlib.pyplot as plt
dataMat,labelMat=loadDataSet('testSet.txt') #数据矩阵和标签向量
dataArr = array(dataMat) #转换成数组
n = shape(dataArr)[0]
xcord1 = []; ycord1 = [] #声明两个不同颜色的点的坐标
xcord2 = []; ycord2 = []
for i in range(n):
if int(labelMat[i])== 1:
xcord1.append(dataArr[i,0]); ycord1.append(dataArr[i,1])
else:
xcord2.append(dataArr[i,0]); ycord2.append(dataArr[i,1])
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xcord1, ycord1, s=30, c='red')
ax.scatter(xcord2, ycord2, s=30, c='green', marker='s')
x = arange(2.0, 6.0, 0.1)
ws = calcWs(alphas,dataArr,labelArr)
#最佳拟合曲线,因为0是两个分类(0和1)的分界处(Sigmoid函数)
#图中y表示x2,x表示x1
y = [(i*ws[0]+array(b)[0])/-ws[1] for i in x]
ax.plot(x, y)
plt.xlabel('X1'); plt.ylabel('X2');
plt.show()
if __name__ == '__main__':
dataArr,labelArr = loadDataSet('testSet.txt')
b,alphas = smoSimple(dataArr,labelArr,0.6,0.001,40)
plotBestFit(alphas,dataArr,labelArr,b)
用超平面对数据集进行划分