机器学习实战笔记:支持向量机

    SVM的一般流程:

  1. 收集数据;
  2. 准备数据:数值型
  3. 分析数据:有助于可视化分隔超平面
  4. 训练算法;
  5. 测试算法;
  6. 使用算法;

    简化的SMO算法:

    SMO算法中的外循环确定要优化的最佳alpha对,简化版跳过这一步骤,首先在数据集上遍历每一个alpha,然后在剩下的alpha集合中随机选择另一个alpha,从而构建alpha对。为此,下述代码构建一个辅助函数,用于在某个区间范围内随机选择一个整数。另一个辅助函数,用于在数值太大时对其进行调整。

    伪代码如下:

创建alpha向量并将其初始化为0向量
while 迭代次数<最大迭代次数:(外循环)
    对数据集中每个数据向量(内循环):
        if 该数据向量可以被优化:
            随机选择另外一个数据向量
            同时优化这两个向量
            if 这两个向量都不能被优化:
                退出内循环
if 所有向量都没被优化:
    增加迭代数目,继续下一次循环
def somSimple(dataMatIn,classLabels,C,toler,maxIter):   #5个输入参数:数据集,类别标签,常数C,容错率和最大循环次数
    dataMatrix=mat(dataMatIn)
    labelMat=mat(classLabels).transpose()
    b=0
    m,n=shape(dataMatrix)  #m是样本个数,n是特征数
    alphas=mat(zeros((m,1)))  #alpha矩阵,alpha个数等于样本个数
    iter=0  #用于存储在没有任何alpha改变的情况下遍历数据集的次数
    while(iter<maxIter):
        alphaPairsChanged=0   #记录alpha是否已经进行了优化
        for i in range(m):
            fXi=float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T))+b  #multiply是对应元素相乘,*就是矩阵乘法 fXi是预测的f(x),是预测类别
            Ei=fXi-float(labelMat[i])  #预测类别和真实标签的差值即为误差
            if((labelMat[i]*Ei<-toler)and(alphas[i]<C))or((labelMat[i]*Ei>toler)and(alphas[i]>0)): #如果i样本的预测误差很大且αi不等于0或C就可以进行优化
                j=selectJrand(i,m)  #随机选择一个αj
                fXj=float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T))+b  #计算αj的预测值
                Ej=fXj-float(labelMat[j])  #计算αj的误差
                alphaIold=alphas[i].copy()  #将现在的alpha[i]和alpha[j]相应的保存
                alphaJold=alphas[j].copy()
                #L和H用于将alpha[j]调整到0和C之间
                if(labelMat[i]!=labelMat[j]):
                    L=max(0,alphas[j]-alphas[i])
                    H=min(C,C+alphas[j]-alphas[i])
                else:
                    L=max(0,alphas[j]+alphas[i]-C)
                    H=min(C,alphas[j]+alphas[i])
                if L==H:  #如果L==H就不做任何调整,直接做下一次的for循环
                    print("L==H")
                    continue
                #计算δ,δ是αj的最优修改量,如果δ>=0就要跳出for循环的当前迭代
                eta=2.0*dataMatrix[i,:]*dataMatrix[j,:].T-dataMatrix[i,:]*dataMatrix[i,:].T-dataMatrix[j,:]*dataMatrix[j,:].T
                if eta>=0:
                    print("eta>=0")
                    continue
                #用求出的L和H对αj进行调整
                alphas[j]-=labelMat[j]*(Ei-Ej)/eta
                alphas[j]=clipAlpha(alphas[j],H,L)
                if(abs(alphas[j]-alphaJold)<0.00001):  #如果αj调整过于轻微,则跳出当前的循环
                    print("j not moving enough")
                    continue
                alphas[i]+=labelMat[j]*labelMat[i]*(alphaJold-alphas[j])  #αi也做调整,大小同αj方向相反
                #为αi和αj设置一个常数项b
                #由f(x)=∑(m,i=1)αi*yi*xi.T*x+b,可以求得常数项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
                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  #表示该alpha对进行了优化
                print("iter:{}".format(iter),"i:{}".format(i),",pairs changed{}".format(alphaPairsChanged))
        if(alphaPairsChanged==0):   #遍历数据集结束后,没有一对alpha进行了优化,则要将遍历次数加一
            iter+=1
        else:
            iter=0
        print("iteration number:{}".format(iter))   #否则遍历结束
    return b,alphas  #返回SMO求得的b和α值

    通过上述代码,就可以获得支持向量机模型的参数α和b,就可以构造出支持向量机。

    然而简化的SMO在数据处理的速度上性能极差,因此下面实现完整版的SMO算法。完整的SMO和简化版SMO在实现alpha的更改和代数运算环节一模一样,在优化过程中,唯一不同的就是选择alpha的方式上。完整的SMO算法通过一个外循环选择第一个alpha值,并且选择过程会在两种方式之间进行交替:

  1. 在所有数据集上进行单遍扫描
  2. 在非边界(不等于边界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))  #用来存储误差E,eCache的第一列给出eCache是否有效地标志位,第二列给出的实际的E值

"""辅助函数用来计算误差E"""
def calcEk(oS,k):
    fXk=float(multiply(oS.alphas,oS.labelMat).T*(oS.X*os.X[k,:].T))+oS.b
    Ek=fXk-float(oS.labelMat[k])
    return Ek

"""用于选择第二个alpha(内循环的alpha)"""
"""目标是选择合适的第二个alpha保证在每次优化中采用最大步长"""
def selectJ(i,oS,Ei):
    maxK=-1
    maxDeltaE=0
    Ej=0
    oS.eCache[i]=[1,Ei]   #设置Ei有效(1)
    validEcacheList=nonzero(oS.eCache[:,0].A)[0]   #构造一个非零列表".A"将矩阵转化为列表,非零E值对应的alpha值
    if(len(validEcacheList))>1:
        for k in validEcacheList:   #validEcacheList的所有值上循环找到使得改变最大的那个值
            if k==i:
                continue
            Ek=calcEk(oS,k)
            deltaE=abs(Ei-Ek)
            if(deltaE>maxDeltaE):
                maxK=k
                maxDeltaE=deltaE
                Ej=Ek
        return maxK,Ej
    else:  #如果是第一次循环就随机选择一个alpha
        j=selectJ(i,oS.m)
        Ej=calcEk(oS,j)
    return j,Ej

"""误差值并存入缓存,在对alpha优化时会用到"""
def updateEk(oS,k):
    Ek=calcEk(oS,k)
    oS.eCache[k]=[1,Ek]

"""调整大于H或小于L的alpha值"""
def clipAlpha(aj,H,L):
    if aj>H:
        aj=H
    if L>aj:
        aj=L
    return aj

"""完整SMO中的优化例程"""
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)):
        j,Ej=selectJ(i,oS,Ei)
        alphaIold=oS.alphas[i].copy()
        alphaJold=oS.alphas[j].copy()
        if(oS.labelMat[i]!=oS.alphas[i]):
            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.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 not mving enough")
            return 0
        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]-alphaJold)*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]-alphaJold)*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
    else:
        return 0

"""完整的SMO外循环代码"""
def smoP(dataMatIn,classLabels,C,toler,maxIter,kTup=('lin',0)):
    oS=optStruct(mat(dataMatIn),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+=innerL(i,oS)
                print("fullSet,iter:{}".format(iter),"i:{}".format(i),"pairs changed:{}".format(alphaPairsChanged))
                iter+=1
        else:
            nonBoundIs=nonzero((oS.alphas.A>0)*(oS.alphas.A<C))[0]
            for i in nonBoundIs:
                alphaPairsChanged+=innerL(i,oS)
                print("non-bound,iter:{}".format(iter),"i:{}".format(i),"pairs changed:{}".format(alphaPairsChanged))
                iter+=1
        if entireSet:
            entireSet=False
        elif(alphaPairsChanged==0):
            entireSet=True
        print("iteration number:{}".format(iter))
    return oS.b,oS.alphas

以上我们就得到了SVM中的α,通过如下的函数,就可以求得w,并基于alpha获得分隔超平面。

"""求w"""
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
对于测试样本如果y=wx+b的结果大于0则属于正类,如果小于0则属于负类。

猜你喜欢

转载自blog.csdn.net/qq_29599907/article/details/80554266
今日推荐