zoukankan      html  css  js  c++  java
  • 支持向量机(SVM)

    断断续续看了好多天,赶紧补上坑。

    感谢july的 http://blog.csdn.net/v_july_v/article/details/7624837/

    以及CSDN上淘的比较正规的SMO C++ 模板代码。~LINK~

    1995年提出的支持向量机(SVM)模型,是浅层学习中较新代表,当然Adaboost更新一点。

    按照Andrew NG的说法: "SVM的效果大概相当于调整最好的神经网络。"于是,SVM被各种神化,被誉为"未来人类的希望,世界人民的终极武器"。

    甚至是IEEE选举的数据挖掘十大算法中唯一一个神经网络,对,没错,SVM就是个MLP。

    SVM忽悠大法第一条“我有核函数!":

    傻子都知道RBF径向基核函数在SVM发明的7年前就已经被用于RBF神经网络,RBF网络本质就是个把激活函数从Sigmoid替换成RBF的MLP。

    SVM处理线性不可分的能力大致来自于两个部分,非线性映射(重点)、高维空间(核函数)。

    SVM的非线性映射藏的比较隐秘,来自其伪·隐层结构:支持向量神经元层。至于核函数?就是个激活函数罢了。

    实际上Logistic-Sigmoid函数也是核函数之一,Logistic回归不能处理线性不可分问题,并不是因为没有核函数,其实它有。

    缺乏非线性映射变换的隐层结构,才是只能画线,不能画圆的关键,在这点上,不从神经网络角度理解SVM,是很容易产生误解的。

    RBF较之于Sigmoid,确实有更快的收敛、更精确的逼近,但是,径向范围$sigma$的选取并非易事,过大过小都会影响映射,至于SVM中默认

    取定值的做法?其实并不完美。

    SVM忽悠大法第二条“我分类效果好":

    SVM的最优间隔理论,从数学上来讲,very good,其效果也比直接用Gradient Descent拟合好。

    当然,这得益于SVM的分类环境假设:一刀切二类分类。但是,更多情况下是K类分类。

    SVM确实有多类改进方法,比如基于投票机制的KNN-SVM,但是效果并不好。

    相比于传统神经网络天然的多分类输出层,比如Softmax,逊色不少。

    尤其在深度学习里,当样本和训练时间不作为第一要素、精度由深度网络保证,SVM的作用就弱化不少。

    比如用户物体识别的ImageNet网络里,共有1000类需要分类,训练多个SVM,来达到多分类的目的,效果不会很好。

    原因正如Bengio吐槽决策树那样,样本的输入空间被切分后,全样本之间的稀疏特征被分离,不利于整体的协同表达。

    当然这在浅层学习中,尤其是在数据挖掘这类的统计数据上,问题并不是很大。

    SVM的优势:

    SVM本质就是个MLP,但是这个MLP真的很赞,它智能地解决了MLP中隐层神经元个数问题。

    自MLP结构发明以来,隐层数、隐层神经元个数一直是最头疼的地方。

    比如,世界上的许多骗钱模拟大脑计划,大喊着要模拟人脑840亿个神经元,疯狂在隐层上做文章。纯粹在浪费时间。

    SVM直接出来打脸:隐层神经元无须很多,只要覆盖支持向量即可。

    其次,径向基函数的径向基中心选取,在二次规划的求解当中也被解决,而无须依靠训练。

    SVM的劣势:

    来自深度学习大师Yoshua Bengio的吐槽:

    学习好的表示(representations)是深度学习的核心目的,而非像 SVM 一样就是在特征的固定集合做一个线性预测。(吐槽 SVM 用 kernel 转移重点)。

    SVM终究是MLP,精确的MLP,但并不是拯救世界的神器,它只不过是浅层学习这个”数值游戏“的大赢家。

    尺有所短,寸有所长。对于SVM和传统神经网络,我们的态度应该是不吹不黑。知其然,知其所以然。

     

    ①最大化几何间隔。

    如果数据能一道线切开的话,我们希望离这道线最近的点的距离越长越好,否则容易分类错误,这就是SVM的核心。

    定义函数间隔di=ywxi+b),其中y=1 or -1,问题在于2*didi是等效的分隔平面,所以弃之。

    所以又定义几何距离D=d/||w||||w||=根号(w1^2+w2^2+...wn^2)

    目标函数max D,约束条件di=ywxi+b)>=d, 这里的d是取最小的函数间隔,保证离分割平面最近,且最大化距离。

    化简处理:令w’=w/d, b’=b/d, 这样约束条件等式相当于两边被除了d,变成di=yw’xi+b)>=1,

    D也相当于变成1/||w||,为了方便,以后w’全部用w代替。

    di=ywxi+b)>=1, 这是个有趣的式子,由于y=±1,单独取出(wxi+b),那么±1的就是传说中里分隔平面最近的点了,称之为支持向量。

     

    ②探究规划问题

    目标函数=max D=max 1/||w||,分式好讨厌,等效成min (数学原理不知道是啥==

    然后引入拉格朗日乘子α,把目标函数、约束条件捆在一起成一个式子。

    这样有新的目标函数:

    这个式子很容易被误解,它的max针对的是

    min针对的是,注意看min/max底部,不要很矛盾地理解成对整个式子。

    max部分是重点,引入变量α,成为拉格朗日乘子。max这部分的意义在于等效约束条件,

    由于ywxi+b)>=1,如果出现小于1的情况,那么减去的这部分就是一个负数,负负得正,会使前面的min黯然失色。至于大于1的情况,可以令α=0 踢掉它。

    最理想的情况下,=min 

    然而这个式子还是比较麻烦的,min规划是先决条件,然后还要考虑不定的乘子部分。

    利用拉格朗日对偶性大法,逆转下两个条件,变成对偶规划问题。,要满足KKT条件,就有d*<=p*

    KKT条件说白了就是0<α<C. (define C 100),0 or C称之为边界。

    这样就可以先定住maxmin,最后再求max了。

    ③大道至简

    上面这个目标函数的min(主元是w、b) 部分化简很神奇,先分别对主元w和b求偏导,令偏导数为0。

    第一个偏导结果巧妙地用alpha、y、x替代了恶心的w

    第二个偏导结果则是带来了一个新的约束条件,好处是化简目标函数时消掉了b,坏处是给计算alpha乘子带来麻烦。(由此SMO算法诞生了)

    将w的代替式拖到里,经过复杂的化简(详见July大神博客),b也被新的约束条件(为0)给消去了。

    最后=,很简单,很优美。

    加上max和两个约束条件之后

    max部分将由SMO算法完成,SMO每次抽取确定两个乘子,然后更新wb

    更新w

     ,(这是单乘子的更新方式,SMO中不能这么干)

    更新b

    这个b比较好玩,w确定之后,实际上分隔平面的方向被确定了。

    想想一次函数y=wx+b,如果b没确定,则这个一次函数就可能是两个边界间任意一条直线。

    那么b应该是什么值呢,应该取两条边界的中间那条比较好。(SMO中b也不能这么干)

    ④分类函数

    训练数据时,需要一个预测分类函数,以便算出误差E。(这个E在SMO中至关重要)

    分类函数自然就是带入式子y=wx+b,但是这可不是二元一次方程,w是多维的。

    在上面中我们有,那么分类函数就变成

    <xi,x>是内积。这里先不管这个内积怎么算。

    SVM的超级武器——核函数

    特征空间的映射的引入使得SVM秒掉了各种回归模型。它的原理就是将低维度数据,映射到高维度数据。

    比如原来数据是2维,如果是非线性数据,很难划分隔面。但是给它加到5维,就很容易一刀切出划分面了。

    巧妙的映射了维度,这对分类函数中的内积计算产生了影响,为了计算在高维空间的内积,出现了核函数。

    新的分类函数为,核函数取代了原本的内积计算。

    目前使用的核函数通常是 "高斯核“函数,又名径向基核函数。

    ⑥松弛变量的优化

    outlier(异常值)是SVM需要解决的一个问题。

    如果就存在一个异常值,离分隔平面超级超级超级近,设这么一个垃圾值为支持向量显然不是明智选择。(间隔越小,平面越难被划分,分类效果就越差)

    所以引入松弛变量,使得原目标函数变为

    切入约束条件后,拉格朗日函数变成:

    然后按照⑤中方法化简,多了一个新主元,并求导令倒数为0。

    你不必关系ri是什么,只要知道ri>=0即可,移个项,αi=C+ri。实际意义就是αi多了一个上界C。

    同⑤中那样化简拉格朗日函数,最后惊奇发现,松弛变量被消了,和⑤最后的式子一样。

    其实松弛变量引入的唯一变化,就是多了个KKT条件多了个上界C。(C的取值很难说,模板代码里取的是100)

    )新的KKT条件如下:

    α=0,表示非支持向量点。

    0<α<C,表示支持向量点。

    α=C,表示的是两条支持向量间的outlier(这些点通常被无视掉)

    ⑥SMO算法

    1998年4月,位于总部的微软研究院副主任John Platt发明了这个奇怪的SMO算法,从此SVM超神了。

    John Platt那篇论文省去了公式推导,所以大部分情况都是,直接给你个XX情况的公式是XX,以及给出的很残破的C伪代码

    对初学者很不友好,链接  ~Link~,想要完全解释清楚还是比较难的。

    感谢CSDN某网友提供的完整的SMO的C++代码,里面的变量名几乎与John Platt论文中的C伪代码一致。

    Part I  SMO的公式推导

    ①新的分类函数与新的目标函数

    SMO定义了新的分类函数u,其实也不算新,就是把b的符号变了。

    这个式子就是把y=wx-b,w替换了而已,上面已经出现了类似物了。

    也定义的新的目标函数

    减号两边换一下,从max变成了min,意义不明。

    ②两个乘子问题

    还记得这玩意不,偏导出来的坑爹约束条件。

    假设我们每次抽取一个乘子,设为α1,那么改了α1之后,能保证上面那个约束条件成立么?很可惜,不好办。

    所以John Platt提出了SMO(最小序列算法),他认为至少(最小)要同时改变两个乘子。

    分为主动乘子(α2)和被动乘子(α1)。每次主动算α2,然后根据约束条件,推出α1的值,这样α1光荣地做了嫁衣。

    一个有趣的想法,比如这次搞定了α2,到了下一次,上次的α2又变成了α1,被动算出来之后,会不会导致上次的α2过程白算了?然后恶性循环?

    其实想多了,实际测试,每次抽两个乘子,只会使结果变好,而不会卡死在循环里。

    于是算α2成为头等大事。

    先确定α2的范围,已有约束条件:0<=α1<=C, 0<=α2<=C (称为矩形条件)

    y1α1+y2α2=γ,γ=Σ(yi*αi),i=3,4....,m(那个头疼的约束条件,提取出两项)

    由于yi=±1, 这里分两种情况。(γ不知道正负)

    情况1: y1=y2

    y1α1+y2α2=γ  => α1+α2=±γ(两条直线)

    情况2:y1≠y2,即异号。

    y1α1+y2α2=γ  => α1-α2=±γ(两条直线)

    画个图

    这样当y1=y2,α2有下界L=max(0,γ-C),上界H=min(γ,C)

    y1≠y2,α2有下界L=max(0,-γ),上界H=min(C-γ,C)

    注意这里的α2称之为α2(new),原来的α2称为α2(old),同时下面所有打(*)号的变量都是old变量。

    α2的具体值怎么确定呢?有如下推导。

    首先,抽取两个乘子后,目标函数被剖分成这样

    就是把i、j∈[1,2]的部分提出来了,但是至今不理解vi部分是怎么出来的。

    对于条件,令两个式子各乘y1,有

    其中,化简目标函数:

    对α2求导并令导数为0:

    化简:

    化简:

    令ui-yi=Ei,继续化简,有:

    ,

    裁剪α2(new):

    算出α1(new)

    特别的,当η<0时,违反了Mercer条件,目标函数为可能∞,这时候α2不能使用上述方式计算,论文中这么写 

    看不懂,填坑,先按模板代码来。

    ③启发式抽取乘子。

    尽管α1是被动乘子,我们还是得先抽取α1,然后启发式抽取α2。

    启发式(经验式)主要是根据已有经验来安排三种抽取乘子的顺序。

    最坏情况其实就是O(m)扫一遍全部数据找到一个α2,当然启发式原则保证我们的RP不会那么差。

    一、非边界(0 or C)最大化|E1-E2|规则:选择绝对值最大的先计算。

    二、非边界随机规则:非边界alpha里随机选择α2

    三、含边界随机规则:实在没有办法了,算上边界再随机吧。

    ④收敛条件

     由于每次计算α2时,都是由导数决定的,也就是说,每次新的α2产生,都标志着目标函数的优化。

    一旦全部α2收敛(即全部乘子收敛,α1若不收敛,下次可能作为α2被调整),则标志着目标函数优化完毕。

    SMO主循环遍历全部数据点选择α1(mainProcess函数)

    次循环随机/最大化|E1-E2|规则选择起点开始遍历选择α2 (exampleExamine函数)

    相当于两层for循环验证全部乘子对,保证最后的目标函数是收敛的。

    ⑤双乘子下的几个参数求法

    一、α1

    α1先通过公式,即 a1 = alph1 - s * (a2 - alph2)

    若违反KKT条件,则设为边界,并按照约束条件对α2进行增/减。

    二、b

    John Platt不知道从哪搞来的公式。目前不知道推导过程。

    双乘子情况下求b,论文中这么解释:

    ①α1非边界:则b=

    ②α2非边界,则b=

    ③α1、α2均在边界,则取b1、b2均值

    ④α1、α2均非边界,则b1=b2,所以①、②任取一个就行了。

    三、w

    Part Ⅱ  SMO的C++代码研究 (坑ing)

    一、SMO主过程。

    void SMO::smo_main_process()
    {
        read_in_data();    //initialize
        if(!is_test_only)
        {
            alph.resize(end_support_i,0);
            b=0;
            error_cache.resize(N);
        }
        self_dot_product.resize(N);
        precomputed_self_dot_product();
        if (!is_test_only)
        {
            numChanged = 0;
            examineAll = 1;
            while (numChanged > 0 || examineAll)
            {
                numChanged = 0;
                if (examineAll)
                {
                    for (int k = 0; k < N; k++)
                        numChanged += examineExample (k);
                }
                else
                {
                    for (int k = 0; k < N; k++)
                        if (alph[k] != 0 && alph[k] != C)
                            numChanged += examineExample (k);
                }
                if (examineAll == 1)
                    examineAll = 0;
                else if (numChanged == 0)
                    examineAll = 1;
            }
        }
    }
    SMO主过程

    这个过程由两个Bool值控制,numChanged 、 examineAll.

    首先第一遍检查全部乘子,之后检查非边界乘子(边界乘子的值通常不会改变了),查不到了再重新检查全部,如果全部还查不到,则结束,计算完毕。

    检查并修改乘子是一个智能的过程,综合使用三种手段。

    int SMO::examineExample(int i1)
    {
        float y1, alph1, E1, r1;
        y1 = (float)target[i1];
        alph1 = alph[i1];
        if (alph1 > 0 && alph1 < C)
            E1 = error_cache[i1];
        else
            E1 = learned_func_nonlinear(i1) - y1;
    
        r1 = y1 * E1;
        if ((r1 < -tolerance && alph1 < C)||(r1 > tolerance && alph1 > 0))
        {
            /////////////使用三种方法选择第二个乘子
            //1:在non-bound乘子中寻找maximum fabs(E1-E2)的样本
            //2:如果上面没取得进展,那么从随机位置查找non-boundary 样本
            //3:如果上面也失败,则从随机位置查找整个样本,改为bound样本
            if (examineFirstChoice(i1,E1))  return 1;     //1
            if (examineNonBound(i1))   return 1;       //2
            if (examineBound(i1))  return 1;             //3
        }
    ///没有进展
        return 0;
    }
    利用三种手段修改两个乘子

    alpha1直接获取,由于每个乘子对应一条训练数据,所以该条数据误差E1=预测值-真实值,预测值就是把该条数据带入学习方程(分类函数里)去

    //径向基核函数
    float SMO::kernel_func(int i,int k)
    {
        float sum=dot_product_func(i,k);
        sum*=-2;
        sum+=self_dot_product[i]+self_dot_product[k];
        return exp(-sum/two_sigma_squared);
    }
    
    
    
    float SMO::learned_func_nonlinear(int k)
    {
        float sum=0;
        for(int i=0; i<end_support_i; i++)
        {
            if(alph[i]>0)
                sum+=alph[i]*target[i]*kernel_func(i,k);
        }
        sum-=b;
        return sum;
    }
    核函数与分类函数
  • 相关阅读:
    C# 5.0 in a Nutshell 读书笔记(一) 野峰
    CLR via C# 4th Edition 野峰
    杰佛里给 Windows Server 2019 Automation with PowerShell 一书所写序言 野峰
    .NET的跨平台调用一例(PreserveSig) 野峰
    Http请求响应
    为什么不要追随新的linux发行版
    make: *** [out/host/linuxx86/obj/STATIC_LIBRARIES/libgtest_host_intermediates/gtestall.o] Error change
    linux 下at&t语法的汇编之hello world!!
    android 编译错误001_cm9 for onda elite vi10
    把vim当作16进制编辑器使用、、
  • 原文地址:https://www.cnblogs.com/neopenx/p/4291420.html
Copyright © 2011-2022 走看看