写在前面
有一个多月没有更新博客了,整个三月份都在忙项目的事,忙着各种扫尾解决一些“历史遗留问题”。终于到了清明节假期,可以写一写博客了。老实讲,一共有好几篇可以写,不过想来想去还是先写这篇SVM相关的吧!主要是再不写估计算法的推导细节就快忘完了,其他的坑慢慢再填:(哭。
OK,言归正传,先简单介绍一下什么是序列最小优化算法(以下简称SMO算法)。SMO算法是一种解决二次优化问题的算法,其最经典的应用就是在解决SVM问题上。SVM推导到最后,特别是使用了拉格朗日因子法求解之后便不难发现其最终等效为一个二次规划问题。二次规划问题有很多成熟的解法,在SMO算法出现之前这些解法就已经应用到了SVM问题的求解上。但是这些解法无论效果如何都有一个共同的缺点即是计算量太大,在小样本的情况下尚堪使用,数据量一大就变得难以奏效。1996年,John Platt发布了一个称为SMO的强大算法,用于训练SVM分类器。其基本思路就是一次迭代只优化两个变量而固定剩余的变量。直观地讲就是将一个大的优化问题分解为若干个小的优化问题,这些小的优化问题往往是易于求解的。要想搞清楚SMO算法首先要简要介绍一下SVM。
什么是SVM
SVM是Support Vector Machine(支持向量机)的英文缩写,是上世纪九十年代兴起的一种机器学习算法,在目前神经网络大行其道的情况下依然保持着生命力。有人说现在是神经网络深度学习的时代了,AI从业者可以不用了解像SVM这样的古董了。姑且不说SVM是否真的已经没有前途了,仅仅是SVM在数学上优美的推导就值得后来者好好欣赏一番,这也是笔者迄今为止见过机器学习领域最优美的数学推导。
和大多数二分类算法一样,SVM算法也是致力于在正例和反例之间找出一个超平面来将它们区分开来,如下图所示:
如图所示,正例用“+”号表示,反例用“-”号表示。从图中可以看出,正例和反例是线性可分的。学习器的目的就是要学出一条如图所示的红色超平面将正例和反例区分开来。这也是其他非SVM分类器的共同目标,即:
而SVM与其它分类器所不同的是它引入了“支持向量”这一概念,反映到图中也就是红色的小点所代表的向量。(注:由于笔者作图时采用的SVM是软间隔的版本,因此支持向量不像是大多数教科书上采用硬间隔的SVM那样)由SVM的优化目标我们可以知道:样本空间中任意一个点x到该超平面的的距离可写为:
假设超平面可以完全正确地将所有样本分类,则对于任意一个样本(xi,yi)来说都有如下性质(注:样本的标签用+1代表正例,-1代表反例):
训练样本中使上式成立的样本称为支持向量,两个异类支持向量到超平面距离之和为:
上式被称为“间隔”。SVM的优化目标是为了找到这样一个划分超平面,该超平面能使间隔最大化,则SVM的优化目标可以表示为如下形式:
这就是SVM的基本数学表达,接下来就要对SVM问题进行求解。从上面的数学形式可以看出这是一个优化问题,可以使用拉格朗日乘子法求解其对偶问题。由于本文不是专门介绍SVM的,因此忽略掉具体的推导,直接给出SVM的对偶问题表达:
由于采用了拉格朗日乘子法,因此该对偶问题还有一个KKT条件约束,即要求:
以上,就是SVM的一些相关介绍。需要特别说明的是,以上的推导都是建立在“硬间隔”的基础上,“硬间隔”要求样本集中每一个样本都满足约束条件。在现实中往往很难确定合适的核函数使得训练样本在特征空间中是线性可分的,缓解该问题的一个办法是允许支持向量机在一些样本上出错,为此引入“软间隔”的概念。具体来说,“硬间隔”要求所有参与训练的样本都必须满足SVM的约束条件,而“软间隔”允许有部分样本不满足这样的约束。由于本文不是专门论述SVM的,因此就不展开讲“软间隔”所带来的一些新的问题,只说一下“软间隔”条件下新的优化目标:
KKT条件为:
其中,C为容忍度因子,可以理解为SVM对“软间隔”的支持度。若C为无穷大,则所有的训练样本均必须满足SVM的约束条件,C值越小就允许越多的样本不满足约束条件。
SMO算法思想
通过观察SVM的优化目标我们可以发现其最终的目的是要计算出一组最优的alpha和常数项b的值。SMO算法的中心思想就是每次选出两个alpha进行优化(之所以是两个是因为alpha的约束条件决定了其与标签乘积的累加等于0,因此必须一次同时优化两个,否则就会破坏约束条件),然后固定其他的alpha值。重复此过程,直到达到某个终止条件程序退出并得到我们需要的优化结果。接下来,就具体推导一下SMO算法的细节。
算法数学推导
由于SVM中有核函数的概念,因此我们用Kij来表示在核函数K下向量i和向量j的计算值。现在假定我们已经选出alpha1和alpha2两个待优化项,然后将原优化目标函数展开为与alpha1和alpha2有关的部分和无关的部分:
其中c是与alpha1和alpha2无关的部分,在本次优化中当做常数项处理。由SVM优化目标函数的约束条件:
可以得到:
将优化目标中所有的alpha1都替换为用alpha2表示的形式,得到如下式子:
此时,优化目标中仅含有alpha2一个待优化变量了,我们现在将待优化函数对alpha2求偏导得到如下结果:
已知:
将以上三个条件带入偏导式子中,得到如下结果:
化简后得:
记:
若n<=0则退出本次优化,若n>0则可得到alpha2的更新公式:
此时,我们已经得到了alpha2的更新公式。不过我们此时还需要考虑alpha2的取值范围问题。因为alpha2的取值范围应该是在0到C之间,但是在这里并不能简单地把取值范围限定在0至C之间,因为alpha2的取值不仅仅与其本身的范围有关,也与alpha1,y1和y2有关。设alpha1*y1+alpha2*y2=k,画出其约束,在这里要分两种情况,即y1是否等于y2。我们在这里先来考虑y1!=y2的情况:在这种情况下alpha1-alpha2=k:
可以看出此时alpha2的取值范围为:
当y1=y2时,alpha1+alpha2=k:
可以看出此时alpha2的取值范围为:
以上,可以总结出alpha2的取值上下界的规律:
故可得到alpha2的取值范围:
由alpha_old1y1+alpha_old2y2=alpha_new1y1+alpha_new2y2可得alpha1的更新公式:
接下来,需要确定常数b的更新公式,在这里首先需要根据“软间隔”下SVM优化目标函数的KKT条件推导出新的KKT条件,得到结果如下:
由于现在alpha的取值范围已经限定在0至C之间,也就是上面KKT条件的第三种情况。接下来我们将第三种KKT条件推广到任意核函数的情境下:
由此我们可以得到常数b的更新公式:
其中Ei是SVM的预测误差,计算式为:
以上,笔者就已经把SMO算法的大部分细节推导出来了。接下来,我们可以根据这些推导对SMO算法进行实现,并且用我们的算法训练一个SVM分类器。
SMO的算法实现
SMO算法的实现还是比较复杂的,有很多细节,我们不用一一关注,只用抓住其中两个特别重要的点就行了:1、如何选取每次迭代的alpha对;2、如何确定SVM优化程序的退出条件。笔者将就这两个主要问题进行论述。首先关注第一个问题:如何选取alpha对。
我们可以简化一下第一个问题:假定现在已经选取了第一个待优化的alpha,如何选取另一个alpha?在SMO算法中为了让迭代次数尽量少,收敛速度尽量快,算法要求我们选取的两个alpha有尽可能大的“差异”。在算法的实现中我们用预测的误差值来表征一个alpha的效果。那么两个aplha尽可能不同反映在算法上就是两个alpha所对应的预测误差值之差的绝对值最大,该思想用代码表现出来如下图所示:
上面的代码反映出了这样一种思想:首先传入第一个alpha所对应的索引值“i”,然后搜索误差列表eCache,在其中找到与“i”所对应的误差值相差绝对值最大的那个索引值“j”,则另一个alpha的索引值就定为“j”。若误差值列表eCache还没有初始化则从所有的索引值中随机选取一个索引值作为另一个alpha的索引值。
那么第一个alpha如何选取呢?这个问题与另外两个问题是相关的:第一个问题是选取的alpha值是否违反KKT条件,如果该alpha值违反了KKT条件则该alpha是不能够作为优化对象的;第二个问题与SMO优化算法的优化终止条件有关,通常SMO算法会在一个死循环中反复执行两个过程,第一个过程是遍历所有的alpha值,每扫描到一个alpha就将其作为alpha对的第一个值传入内循环,同时根据上一段提出的选取准则选择另一个alpha。遍历完一次alpha值之后若alpha值被优化器改变的次数不为0则本次循环结束同时修改一些标志量然后进入下一次循环。下一次循环执行第二个过程:遍历alpha值列表中所有不为0的值,每扫描到一个不为0的alpha值之后就将其传入内循环,然后执行和上面相同的过程。重复执行上述过程,最终,当某次循环遍历优化所有alpha列表后却没有一个alpha值被优化器改变则程序认为优化任务完成了,程序退出。代码如下:
以上,就是SMO算法在实现时的一些细节。完整的SMO实现还是比较复杂的,有很多的小细节需要注意,在这里就不一一论述了。如果读者想了解更多可以访问我的git仓库,上面有我的详细代码,仓库地址在文章的末尾给出。
后记
SVM算法其实笔者很早就用过,最早是大学时用来做垃圾邮件的分类,但那个时候一直是在调用算法包,对SVM算法的种种细节不是很了解。今年寒假的时候数据挖掘这门课刚好有一个大作业用到了这个算法,这才算好好了解了一下这种算法实现的内部细节,自己用python好好实现了一下。
自从上了研究生以后就一直在了解学习机器学习的种种算法,真心感觉这个领域非常有意思,对数学的要求很高。虽然自己学习的算法现在还是停留在像支持向量机,随机森林,决策树,逻辑回归这些经典的算法上,可以说进步缓慢,但是我还是很有信心,总是要先把基础打好嘛!嗯~目前为止这些经典的算法几乎都快被我扫清了,数据挖掘领域的十大算法自己已经差不多实现了六七个,接下来想想看就该进入概率图模型,马尔科夫随机场,神经网络深度学习什么的了,刚好这学期有一门神经网络的课。估计这篇文章就是传统机器学习算法的最后一篇博客了,以后再写就应该是神经网络和分布式系统方面的了,什么TensorFlow,什么CNN,RNN都写一写。哈哈!请大家关注我的github,我会在上面把我自己的机器学习笔记代码都实时公开的,博客也会有时间就写。
https://github.com/yhswjtuILMARE/Machine-Learning-Study-Notes
2018年4月8日