【Python机器学习】支持向量机——利用完整platt SMO算法加速优化

在几百个数据点组成的小规模数据集上,简化版SMO算法的运行是没有什么问题,但是在更大的数据集上的运行速度就会变慢。完整版的platt SMO算法应用了一些能够提速的启动方法。

platt SMO算法时通过一个外循环来选择第一个alpha值的,并且其选择过程会在两种方式之间交替进行:一种方式是在所有数据集上进行单遍扫描,另一种方式则是在非边界alpha中实现单遍扫描。而所谓非边界alpha指的就是那些不等于边界0或C的alpha值。对整个数据集的扫描相当容易,而实现非边界alpha值的扫描时,首先需要建立这些alpha值的列表,然后再对这个表进行遍历。同时,该步骤会跳过那些已知的不会改变的alpha值。

在选择第一个alpha值后,算法会通过一个内循环来选择第二个alpha值。在优化过程中,会通过最大化步长的方式来获取第二个alpha值。我们会建立一个全局的缓存用来保存误差值,并从中选择使得步长或者说Ei-Ej最大的alpha值。

具体实现:

class optStruct:
    def __init__(self,dataMatIn,classLabels,C,toler):
        self.X=dataMatIn
        self.labelMat=classLabels
        self.C=C
        self.tol=toler
        self.m=shape(dataMatIn)[0]
        self.alphas=mat(zeros((self.m,1)))
        self.b=0
        #误差缓存
        self.eCache=mat(zeros((self.m,2)))

def calcEk(oS,k):
    #对于给定的alpha值,计算E值并返回
    fXk=float(multiply(oS.alphas,oS.labelMat).T*(oS.X*oS.X[k,:].T))+oS.b
    Ek=fXk-float(oS.labelMat[k])
    return Ek
#内循环中的启动式方法
def selectJ(i,oS,Ei):
    #用于选择第二个alpha(内循环的alpha值),函数的误差值与第一个alpha值Ei和下标i有关
    maxK=-1
    maxDeltaE=0
    Ej=0
    oS.eCache[i]=[1,Ei]
    validEcacheList=nonzero(oS.eCache[:,0].A)[0]
    #构建一个非零表,包含以输入列表为目录的列表值,nonzero()语句返回的是非零E值所对应的alpha值而不是E值本身
    if (len(validEcacheList))>1:
        for k in validEcacheList:
            if k==i:
                continue
            Ek=calcEk(oS,k)
            deltaE=abs(Ei-Ek)
            if (deltaE>maxDeltaE):
                #选择具有最大步长的j
                maxK=k
                maxDeltaE=deltaE
                Ej=Ek
        return maxK,Ej
    else:
        j=selectJrand(i,oS.m)
        Ej=calcEk(oS,j)
    return j,Ej
def updateEk(oS,k):
    #计算误差值并存入缓存中。
    Ek=calcEk(oS,k)
    oS.eCache[k]=[1,Ek]

用于寻找决策边界的优化例程:

def innerL(i,oS):
    Ei=calcEk(oS,i)
    if ((oS.labelMat[i]*Ei<-oS.tol) and (oS.alphas[i]<oS.C)) or ((oS.labelMat[i]*Ei>oS.tol) and (oS.alphas[i]>0)):
        # 如果alpha可以更改,进入优化过程
        j,Ej=selectJ(i,oS,Ei)
        #随机选择第二个alpha
        alphaIold = oS.alphas[i].copy()
        alphaJold = oS.alphas[j].copy()
        # 保证alpha在0与C之间
        if (oS.labelMat[i]!=oS.labelMat[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为最优修改量,如果eta=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.alphas[j]-oS.labelMat[j]*(Ei-Ej)/eta
        oS.alphas[j]=clipAlpha(oS.alphas[j],H,L)
        updateEk(oS,j)
        if (abs(oS.alphas[j]-alphaJold)<0.00001):
            print('j mot moving enough')
            return 0
        oS.alphas[i]=oS.alphas[i]+oS.labelMat[j]*oS.labelMat[i]*(alphaJold-oS.alphas[j])
        updateEk(oS,i)
        # 设置常数项
        b1 = oS.b - Ei - oS.labelMat[i] * (oS.alphas[i] - alphaIold) * oS.X[i, :] * oS.X[i, :].T - oS.labelMat[j] * (
                    oS.alphas[j] - alphaJold) * oS.X[i, :] * oS.X[j, :].T
        b2 = oS.b - Ej - oS.labelMat[i] * (oS.alphas[i] - alphaIold) * oS.X[i, :] * oS.X[j, :].T - oS.labelMat[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.0
    else:
        return 0

这里函数使用了自己的数据结构。该结构在oS中传递。

并且,在alpha值改变时更新Ecache。

完整的platt SMO的外循环代码:

def smoP(dataMaxIn,classLabels,C,toler,maxIter,kTup=('lin',0)):
    #输入参数分别为:数据集、类别标签、常数C、容错率、退出前最大的循环次数
    #构建一个数据结构来容纳所有的数据
    oS=optStruct(mat(dataMaxIn),mat(classLabels).transpose(),C,toler)
    iter=0
    entireSet=True
    alphaPairsChanged=0
    while(iter<maxIter) and ((alphaPairsChanged>0) or (entireSet)):
        alphaPairsChanged=0
        if entireSet:
            #遍历所有的值
            for i in range(oS.m):
                alphaPairsChanged=alphaPairsChanged+innerL(i,oS)
                print(iter,i,alphaPairsChanged)
            iter=iter+1
        else:
            #遍历非边界值
            nonBoundIs=nonzero((oS.alphas.A>0)*(oS.alphas.A<C))[0]
            for i in nonBoundIs:
                alphaPairsChanged=alphaPairsChanged+innerL(i,oS)
                print(iter,i,alphaPairsChanged)
            iter = iter + 1
        if entireSet:
            entireSet=False
        elif (alphaPairsChanged==0):
            entireSet=True
        print(iter)
    return oS.b,oS.alphas

这里函数的主题是while循环,这里的退出条件比较多:当迭代次数超过指定的最大值,或者遍历这个集合都未对任意alpha对进行修改时,就退出循环。此外,如果在优化过程中存在波动就会停止。

while循环中,一开始的for循环在数据集上遍历任意可能的alpha,我们可以通过调用innerL()来选择第二个alpha,并在可能时对其进行优化处理。如果有任意一对alpha值发生改变,那么就会返回1,第二个for循环遍历所有的飞边界alpha值,也就是不在边界0或C的值。

执行代码:


dataArr,labelArr=loadDataSet('testSet.txt')
# print(labelArr)
b,alphas=smoP(dataArr,labelArr,0.6,0.001,40)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值