<基础原理进阶>机器学习算法python实现【5】--文本分析之支持向量机SVM(下)

本文是SVM下篇,主要内容包括:软间隔SVM的介绍,完整版SMO算法的原理与实现,以及SVM整体思路的回顾。软间隔SVM通过引入惩罚参数C,允许一定的误分类,扩大了SVM的适用范围。SMO算法的优化考虑了加速效果,确保支持向量的更新。最后,回顾了从问题提出到解决的SVM核心思想。
摘要由CSDN通过智能技术生成

################################################################################################

作者也是一边学习一边领悟一边更新blog,所以有混乱和错误的地方请多多包涵,都会在随后的blog进行改善的~

################################################################################################

SVM下篇,我们主要进行以下几点:


i) 软间隔SVM的说明


ii) 完整版SMO算法的原理与算法代码


iii) 回顾SVM的整体思路(重要!


核方法不日单独专题说明~


一.软间隔SVM

数据可按以下标准划分为三种:线性可分,近似线性可分和非线性可分。对于完全线性可分的数据,原始优化目标即为:


这个我们在上篇已经讲过,约束条件是限制函数间隔>=1。这样经过Lagrange对偶+SMO算法出来的可叫做硬间隔向量机,因为它非常“是非分明”:支持向量完全在约束边界上,不允许一点“出错”。可对于近似线性可分的数据,存在一些“特异点”并不一定能满足约束条件,那么我们可以加上一个松弛变量L,使原始的函数间隔+L >= 1。这样原始优化目标变成:


C可以看成和SRM的正则因子一样的东西,叫做惩罚参数,是用来平衡间隔与误分类点的:因为做最小化时要使它们同时减小,而它们又是彼此矛盾的变量。

有了原始问题赶快用Lagrange橙子法,记得它有两个约束项故有两个参数alpha, beta:



别忘了对偶时的KKT条件,常规思路先求偏导得到一些关系式(求min),再代入原式求max。注意根据第一次求min时的结果得到两个参数的关系式并代入beta的约束,得到完整的alpha约束(额符号打错):


所以这里就可以看成,上篇里我们写的实际上是软间隔向量机,只不过昨天我看的那本书里没写,今天看李航老师的书才发现(气死我了!)

所以整理完毕之后我们的问题就很明朗,最终学习的算法(优化目标)如下【注意本来是求max但把目标函数符号正负调换所以变成了求min】:


看起来和我们之前的硬间隔向量机没啥区别,只不过改变了alpha的取值范围。根据KKT条件:



可知所有alpha>0对应的点都是支持向量,但是有别于硬间隔向量机的是这里的支持向量既可位于边界上,也可在边界与超平面间,超平面上,甚至误分类那侧,根据以下判据决定:

alpha<C,显然Li = 0,在边界上(g(w)=1)

alpha=C, 0<Li<1,在正确分类这边的边界和超平面之间(0<g(w)<1)

alpha=C, Li=1, 在超平面上(g(w)=0)

alpha=C, Li>1, 在误分类那侧(g(w)<0)

至此我们大概说明了什么是软间隔向量机,它会有更加的容错效果。


二.完整版SMO算法

简单的SMO算法里,我们对alpha的选择是随意的,而为了获取加速效果我们需要改进一下:

class optStruct:
    def __init__(self, dataMatrixIn, classMatrixIn, C, toler):
        self.X = dataMatrixIn
        self.label = classMatrixIn
        self. C = C
        self.toler = toler
        self.m = shape(dataMatrixIn)[0]
        self.b = 0
        self.alphas = mat(zeros((self.m, 1)))
        self.eCache = mat(zeros((self.m, 2)))
定义一个数据结构,用来做参数传递的,基本内容和简化SMO一样,不同的是加入了缓存Ei的矩阵,它的元素是(i,Ei)i是表示Ei是否为0。因为我们在改变alpha值时就会改变Ei值,而Ei是我们用来衡量步长加速算法的,所以必须要全局缓存。

def calcEk(os, k):
    res = float(multiply(os.alphas, os.label).T * (os.X*os.X[k, :].T))+os.b
    Ek = res - os.label[k]
    return Ek
calcEk函数:用来计算Ek的,根据如下公式:



配套更新函数,每次改变alpha值就要更新一次:

def updateEk(os, k):
    Ek = calcEk(os, k)
    os.eCache[k] = [1, Ek]

接着是选择第二个alpha的函数,即“内循环”,遵循以下规则:选择能使“步长”:|Ei-Ej|最大化的那个alphaJ。如果一开始Ej是空的,就随机选择一个。注意nonzero()函数属于numpy,它返回大于0的Ei的脚标的列表。

def selectJ(i, os, Ei):
    maxJ = -1;maxDeltaE = 0;Ej = 0
    os.eCache[i] = [1, Ei]
    validEcacheList = nonzero(os.eCache[:, 0].A)[0]
    if len(validEcacheList)>1:
        for k in validEcacheList:
            if k == i: continue
            Ek = calcEk(os, k)
            if(abs(Ek - Ei)>maxDeltaE):
                maxDeltaE = abs(Ek - Ei); maxJ = k; Ej = Ek
        return maxJ, Ej
    else:
        maxJ = selectJrand(i, os.m)
        Ej   = calcEk(os, maxJ)
    return maxJ, Ej
接下来是由简化版SMO改进来的优化与参数更新过程,数学推导如下:



def innerProcess(i, os):
    Ei = calcEk(os, i)
    if ((os.label[i]*Ei < -os.toler) and (os.alphas[i] < os.C)) or ((os.label[i]*Ei > os.toler) and (os.alphas[i] > 0)):
        j, Ej = selectJ(i,os, Ei)
        alphaIold = os.alphas[i].copy(); alphaJold = os.alphas[j].copy();
        if (os.label[i] != os.label[j]):
            L = max(0, os.alphas[j] - os.alphas[i])
            H = min(os.C, os.C + os.alphas[j] - os.alphas[i])
        else:
            L = max(0, os.alphas[j] + os.alphas[i] - os.C)
            H = min(os.C, os.alphas[j] + os.alphas[i])
        if L==H: print "L==H"; return 0
        eta = 2.0 * os.X[i,:]*os.X[j,:].T - os.X[i,:]*os.X[i,:].T - os.X[j,:]*os.X[j,:].T
        if eta >= 0: print "eta>=0"; return 0
        os.alphas[j] -= os.label[j]*(Ei - Ej)/eta
        os.alphas[j] = adjustAlpha(os.alphas[j],H,L)
        updateEk(os, j)
        if (abs(os.alphas[j] - alphaJold) < 0.00001): print "j not moving enough"; return 0
        os.alphas[i] += os.label[j]*os.label[i]*(alphaJold - os.alphas[j])#update i by the same amount as j
                                                                #the update is in the oppostie direction
        updateEk(os, i)
        b1 = os.b - Ei- os.label[i]*(os.alphas[i]-alphaIold)*os.X[i,:]*os.X[i,:].T - os.label[j]*(os.alphas[j]-alphaJold)*os.X[i,:]*os.X[j,:].T
        b2 = os.b - Ej- os.label[i]*(os.alphas[i]-alphaIold)*os.X[i,:]*os.X[j,:].T - os.label[j]*(os.alphas[j]-alphaJold)*os.X[j,:]*os.X[j,:].T
        if (0 < os.alphas[i]) and (os.C > os.alphas[i]):    os.b = b1
        elif (0 < os.alphas[j]) and (os.C > os.alphas[j]):  os.b = b2
        else: os.b = (b1 + b2)/2.0
        return 1
    else:return 0
改进的地方:

1.全部使用os作为统一的数据传递结构

2.每次更新alphaI, alphaJ时更新eCache

3.之前中途continue的语句改成return 0,即没有做任何修改

接下来是外循环,即svm一开始执行的部分,它主要目的是选择第一个参数alpha,同时调用外循环即函数innerProcess进行一对儿alpha,b的优化与参数更新。

外循环的思路是“交替选择”:先线性扫描整个样本集,如果没有做任何改变则转入扫描所有在边界上(0<apha_i<C)对应的样本i,这里没有任何改变的话再回退到整个样本集的扫描......如此交替直到收敛。整个交替过程用一个while()循环来控制,退出条件是达到最大迭代次数(这里定义为一次循环就是一次迭代)并且扫描边界时没有任何alpha值改变:

def SMO(dataMatrixIn, classMatrixIn, C, toler, maxIter):
    os = optStruct(mat(dataMatrixIn), mat(classMatrixIn).transpose(), C, toler)
    iter = 0; alphaPairsChanged = 0; entireSet = 1
    while (iter<maxIter) and ((alphaPairsChanged>0)or(entireSet)):
        alphaPairsChanged = 0
        if entireSet:
            for i in range(os.m):
                alphaPairsChanged += innerProcess(i, os)
                print 'fullSet, iter %d, i %d, pairs changed %d' % (iter, i, alphaPairsChanged)
            iter += 1
        else:#go over non-bound (railed) alphas
            nonBoundIs = nonzero((os.alphas.A > 0) * (os.alphas.A < C))[0]
            for i in nonBoundIs:
                alphaPairsChanged += innerProcess(i,os)
                print "non-bound, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged)
            iter += 1
        if entireSet: entireSet = False #toggle entire set loop
        elif (alphaPairsChanged == 0): entireSet = True
        print "iteration number: %d" % iter
    return os.b,os.alphas
entireSet变量是控制交替切换的开关。
之后我们写一个用学习到的参数计算W的函数:

def calcW(alphas, dataMatrixIn, classMatrixIn):
    X = mat(dataMatrixIn)
    labelMat = mat(classMatrixIn).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

最后就是执行函数了,这里用学习到的W,b对样本进行测试,如果>0说明是+1类,反之-1类,并与原类比较(应该全输出Right才对,毕竟是从样本学习的)

def execu_2(filepath):
    dataArr, labelArr = loadData(filepath)
    b, alphas = SMO(dataArr, labelArr, 0.6, 0.001, 40)
    W = calcW(alphas, dataArr, labelArr)
    dataMat = mat(dataArr)
    m = shape(dataMat)[0]
    ResList = []
    for i in range(m):
        Res = dataMat[i]*mat(W) + b
        if Res[0] > 0 : ResList.append(1)
        else          : ResList.append(-1)
        if ResList[i] == labelArr[i]:
            print 'Right!'
        else:
            print 'Wrong!'
测试结果也说明了这点~


三.SVM的整体回顾

这里要梳理一遍我们整个提出问题-分析问题-解决问题的思路,请允许我用手绘图而不是latex表示QAQ.....




那么SVM的本篇先到这里,有时间再来写一下它的“外传”:核方法,是用来处理数据非线性可分时的一种方法,适用性要比SVM广。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值