SVM SMO算法代码详细剖析

前言

0aaa36de157986b4c5547f49bcaaf6d1.gif

一:本文要结合SVM理论部分来看即笔者另一篇:

SVM原理从头到尾详细推导

二:有了理论部分下面就是直接代码啦,本文用四部分进行介绍:最简版的SMO,改进版platt SMO,核函数,sklearn库的SVM,采取的顺序是先给代码及结果,然后分析。

三:这里代码大部分来自于Peter Harrington编写的Machine Learning in Action

其网络资源:https://www.manning.com/books/machine-learning-in-action

四:代码中需要注意的一点就是采用启发式来寻找需要优化的  

简版SMO算法

14e0a0b76b2212ffe9aa56e753233a90.gif

这里有两个py文件,一个是用来构造SVM的,一个是用来测试的:

MySVM:

# -*- coding: utf-8 -*-
import random
import  numpy  as np
import matplotlib.pyplot as plt
#辅助函数一
def selectJrand(i, m):
    j = i  
    while (j == i):
        j = int(random.uniform(0, m))
    return j
#辅助函函数二
def clipAlpha(aj,H,L):
    if aj > H:
        aj = H
    if L > aj:
        aj = L
    return aj


#最简版本SMO算法
def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
    dataMatrix = np.mat(dataMatIn); 
    labelMat = np.mat(classLabels).transpose()
    b = 0; 
    m,n = np.shape(dataMatrix)
    alphas = np.mat(np.zeros((m,1)))
    iter_num = 0
    while (iter_num < maxIter):
        alphaPairsChanged = 0
        for i in range(m):
            #注意一
            fXi = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b
            Ei = fXi - float(labelMat[i])
            if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
                j = selectJrand(i,m)
                fXj = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b
                Ej = fXj - float(labelMat[j])
                alphaIold = alphas[i].copy();
                alphaJold = alphas[j].copy();
                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: 
                    print("L==H"); 
                    continue
                #注意二
                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
                
                alphas[j] -= labelMat[j]*(Ei - Ej)/eta
              
                alphas[j] = clipAlpha(alphas[j],H,L)
               
                if (abs(alphas[j] - alphaJold) < 0.00001): print("alpha_j变化小,不需要更新"); continue
                #注意三
                alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j])


                #注意四
                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
              
                print("第%d次迭代 样本:%d, alpha优化次数:%d" % (iter_num,i,alphaPairsChanged))
       
        if (alphaPairsChanged == 0): 
            iter_num += 1
        else: iter_num = 0
        print("迭代次数: %d" % iter_num)
    #注意五
    return b,alphas
def calcWs(dataMat, labelMat, alphas):
    alphas, dataMat, labelMat = np.array(alphas), np.array(dataMat), np.array(labelMat)
    w = np.dot((np.tile(labelMat.reshape(1, -1).T, (1, 2)) * dataMat).T, alphas)
    return w.tolist()


def showClassifer(dataMat,labelMat,alphas, w, b):
    data_plus = []                                  
    data_minus = []
    #注意六
    for i in range(len(dataMat)):
        if labelMat[i] > 0:
            data_plus.append(dataMat[i])
        else:
            data_minus.append(dataMat[i])
    data_plus_np = np.array(data_plus)              
    data_minus_np = np.array(data_minus)            
    plt.scatter(np.transpose(data_plus_np)[0], np.transpose(data_plus_np)[1], s=30, alpha=0.7)   
    plt.scatter(np.transpose(data_minus_np)[0], np.transpose(data_minus_np)[1], s=30, alpha=0.7) 
    #注意七
    x1 = max(dataMat)[0]
    x2 = min(dataMat)[0]
    a1, a2 = w
    b = float(b)
    a1 = float(a1[0])
    a2 = float(a2[0])
    y1, y2 = (-b- a1*x1)/a2, (-b - a1*x2)/a2
    plt.plot([x1, x2], [y1, y2])
    #注意八
    for i, alpha in enumerate(alphas):
        if 0.6>abs(alpha) > 0:
            x, y = dataMat[i]
            plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='red')
        if 0.6==abs(alpha) :
            x, y = dataMat[i]
            plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='yellow')
    plt.show()

接着是测试函数(MyTest):

# -*- coding: utf-8 -*-#
import MySVM as svm
x=[[1,8],[3,20],[1,15],[3,35],[5,35],[4,40],[7,80],[6,49],[1.5,25],[3.5,45],[4.5,50],[6.5,15],[5.5,20],[5.8,74],[2.5,5]]
y=[1,1,-1,-1,1,-1,-1,1,-1,-1,-1,1,1,-1,1]
b,alphas = svm.smoSimple(x,y,0.6,0.001,40)
w = svm.calcWs(x,y,alphas)
svm.showClassifer(x,y,alphas, w, b)

运行结果:

 aab5a7f453647a749f9cb7b526d3d282.png

,,,,,,,,,,,,,

 787194e1720d9a8587e31d6752bd552a.png

 c470990ba5ad0e0e2b541b3364784db9.png

‍接下来一步步分析MySVM中的代码

首次看两个简单的辅助函数,第一个函数的作用就是用来选择  对的(即寻找i,j这一对)

第二个函数就是为了将  规划到[0,C]范围内,对应到理论推导部分的:

接下来就是smo算法的最简版本:

注意一下面的fXi对应推导公式的即w的更新:

可能这里和上篇最后给出w的更新形式上看上去有点不对应,其实是一样的,推导部分即最后一张图是:

而这里的其实在程序就是对应的就是如下三步:

fXi = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b


 alphas[j] -= labelMat[j]*(Ei - Ej)/eta
 alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j])

紧接着的if这里就是启发式选择,即寻找那些误差过大(正间隔和否间隔)且在(0,C)范围内的  进行优化,选择误差大的进行优化我们很容易理解,那为什么要选择(0,C)范围内,而不选择边界值呢(值等于0或C),那是因为它们已经在边界啦,因此不再能够减少或者增大具体细节请看推导部分,该部分包括L和H为什么要这样赋值,以及为什么L==H的时候要返回都有讲到。

注意二的部分就是类似我们在推导部分的  更新,只不过这里有一步如果变化太小我们就不更新了,直接跳过,只不过原公式中的  ,所以推导中原本  是加上后面的,而这里是减即:本质是一样的啦,当然  更新方向要相反,所以代码中对应的是+

alphas[j] -= labelMat[j]*(Ei - Ej)/eta

至于为什么eta >= 0为什么要跳过该次循环,请看推导部分,只不过因为  所以原来是过滤掉<=0,这里是>=0

注意三部分就是类似  的更新即大小和  相同,方向相反

注意四的部分应该很直观啦,看我们推导的b的更新结论一目了然

注意五返回的b是一个实数,alphas是一个[m,1]矩阵

最后说一下smo函数的alphaPairsChanged和iter_num以及maxIter的参数意义,maxIter是最外部的大循环,是人为设定的最大循环次数,循环为最大次数后就强行结束返回,在每一个大循环下都有一个for循环,用以遍历一遍所有的  ,遍历完这一遍所有的  后,alphaPairsChange用以记录看有多少对被优化啦,如果alphaPairsChange不为0,即这一遍走下来后,我们进行了优化,也就代表目前  还不够好,所以我们将iter_num设为0,继续优化,当alphaPairsChange为0时,说明我们这一遍走下来,说明  都很好啦,没有优化的必要啦,我们将iter_num加一,接着下一遍再去整体看看  ,如果还是alphaPairsChange为0,恩恩,不错,不错,将iter_num再加一,如果iter_num到了maxIter即连续进行了maxIter遍整体(for)观察都没发现需要优化的  

,说明足够好了,返回吧!!!!!!一旦中间出现意外,发现有需要优化的,就至少说明有不完善的地方,那么我们立马让iter_num为0,即一定要达到连续遍历maxIter次都没发现不足,我们才放心,才返回,发现瑕疵立马iter_num=0从头开始,怎么样?就是这么严格,这也是上面运行结果开始的时候为什么迭代次数一直都是0,后面趋于收敛,迭代次数连续增加,直到maxIter结束返回

正是因为如此,可以想象得的到带来的结果就是 时间复杂度太高,所以有了后来改进版本的Platt SMO,后面介绍

接下来的calcWs函数作用是:根据训练出来的  生成w:

对应的公式就是:

目前我们已经训练除了SVM模型,即得到了我们想要的w和b,对应的步骤就在上面所说的黄色部分

接下来可视化看一下结果showClassifer:

注意六的部分就是我们把原始点画出来,不同的颜色代表不同的分离(橙色的点对应的标签是-1,蓝色的点对应的标签是1)

注意七的部分就是画出训练出来的超平面

y1, y2 = (-b- a1*x1)/a2, (-b - a1*x2)/a2

这个很好理解啦,超平面是:

所以:

程序中为了让超平面尽可能的横穿整个数据点,所以选取了所有点中x坐标最大和最小的点即x1和x2:

然后利用上面的公式,计算出了对应的纵坐标

注意八的部分就是画出向量机点,和那些我们“忽略”的点,依据是推导的:

即在点在两条间隔线外则,对应前面的系数  为0,在两条间隔线里面的对应的系数为C,在两条间隔线上的点对应的系数在0和C之间。至于为什么请看上篇的推导细节

带有红色圆圈的是支持向量机点即间隔线上的点,带有黄色的点是间隔线内的点

Platt SMO

680dd8afff4ca23621f8d6b258aa826e.gif

‍其是SMO算法的一个改进版,速度更快。

其主要变化的地方有两个

一:在使用启发式方法选择了一个  

后,我们会去选择另外一个与之对应是吧,即‍

‍‍

但是改进的的SMO算法中,这里也使用启发式来选择,即选择与Ei误差最大的Ej即选择最大步长,简单来说就是找最需要优化的j,而不是像最简版本那样,毫无目的的随机去选择,所以对应到推导公式里面就是  和  都采用启发式来寻找

二:改进后的算法是采用在“非边界值”和“边界值”范围内交替遍历优化的

下面来看一下具体代码:

smoP:

# -*- coding: utf-8 -*-
from numpy import *
import matplotlib.pyplot as plt
import random
def loadDataSet(filename): 
    dataMat=[]
    labelMat=[]
    fr=open(filename)
    for line in fr.readlines():
        lineArr=line.strip().split('\t')
        dataMat.append([float(lineArr[0]),float(lineArr[1])])
        labelMat.append(float(lineArr[2]))
    return dataMat,labelMat 
class optStruct:
    def __init__(self,dataMatIn, classLabels, C, toler, kTup):  
        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 selectJrand(i,m): 
    j=i
    while (j==i):
        j=int(random.uniform(0,m))
    return j


def clipAlpha(aj,H,L):  
    if aj>H:
        aj=H
    if L>aj:
        aj=L
    return aj
        
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


def selectJ(i, oS, Ei):
    maxK = -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)
            deltaE = abs(Ei - Ek)
            if (deltaE > maxDeltaE): 
                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)): 
        j,Ej = selectJ(i, oS, Ei)
        alphaIold = oS.alphas[i].copy()
        alphaJold = oS.alphas[j].copy()
        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 = 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) < oS.tol): 
            print("j not moving 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]-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]<oS.C):
            oS.b = b1
        elif (0 < oS.alphas[j]<oS.C):
            oS.b = b2
        else:
            oS.b = (b1 + b2)/2.0
        return 1
    else:
        return 0


def calcWs(dataMat, labelMat, alphas):
    alphas, dataMat, labelMat = array(alphas), array(dataMat), array(labelMat)
    w = dot((tile(labelMat.reshape(1, -1).T, (1, 2)) * dataMat).T, alphas)
    return w.tolist()


def smoP(dataMatIn, classLabels, C, toler, maxIter,kTup=('lin', 0)): 
    oS = optStruct(mat(dataMatIn),mat(classLabels).transpose(),C,toler, kTup)
    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: %d i:%d, pairs changed %d" % (iter,i,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: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
            iter += 1
        if entireSet:
            entireSet = False
        elif (alphaPairsChanged == 0):
            entireSet = True
        print("iteration number: %d" % iter)
    return oS.b,oS.alphas


def showClassifer(dataMat,labelMat,alphas, w, b):
    data_plus = []                                  
    data_minus = []
    for i in range(len(dataMat)):
        if labelMat[i] > 0:
            data_plus.append(dataMat[i])
        else:
            data_minus.append(dataMat[i])
    data_plus_np = array(data_plus)              
    data_minus_np = array(data_minus)            
    plt.scatter(transpose(data_plus_np)[0], transpose(data_plus_np)[1], s=30, alpha=0.7)   
    plt.scatter(transpose(data_minus_np)[0], transpose(data_minus_np)[1], s=30, alpha=0.7) 
    x1 = max(dataMat)[0]
    x2 = min(dataMat)[0]
    a1, a2 = w
    b = float(b)
    a1 = float(a1[0])
    a2 = float(a2[0])
    y1, y2 = (-b- a1*x1)/a2, (-b - a1*x2)/a2
    plt.plot([x1, x2], [y1, y2])
    for i, alpha in enumerate(alphas):
        if 0.6>abs(alpha) > 0:
            x, y = dataMat[i]
            plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='red')
        if 50==abs(alpha) :
            x, y = dataMat[i]
            plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='yellow')
    plt.show()

接着还是测试函数(MyTest):

# -*- coding: utf-8 -*-#
import smoP as svm
x=[[1,8],[3,20],[1,15],[3,35],[5,35],[4,40],[7,80],[6,49],[1.5,25],[3.5,45],[4.5,50],[6.5,15],[5.5,20],[5.8,74],[2.5,5]]
y=[1,1,-1,-1,1,-1,-1,1,-1,-1,-1,1,1,-1,1]
b,alphas = svm.smoP(x,y,50,0.001,40)
w = svm.calcWs(x,y,alphas)
svm.showClassifer(x,y,alphas, w, b)

‍‍

 30a2fc074b0060d4d5f3aae772dc7b1a.png

,,,,,,,,,,,

 6858340692ae5477dcc66e95d6e00d75.png

aae15e903ddd8d84d5260d1e19d7e1f6.png

这里首先optStruct函数定义了一个类作为数据结构来存储一些信息,这里面的alphas就是我们的  ,eCache第一列就是一个是否有效的标志位,第二列存储着误差值E,总之这个结构体的定义就是为了作为一个整体,方便调用,管理。

calcEk和最简版本没什么差别,只不过我们已经定义了结构体,所以直接可以调用结构体便可得到一些信息,所以下面所有代码都是这样,比如C我们可以直接用oS.C等等

selectJ和最简版本不一样啦,这里也就是我们说的用启发式来寻找j,这里的:

if (len(validEcacheList)) > 1:

主要是防止第一次循环的时候,如果是第一次那么就随机选择,之后都使用启发式来选择

 updateEk就是用来在计算完i和j的Ei和Ej后更新数据结构中的的eCache

innerL和最简版本的smoSimple内循环(就是for循环下面的代码:用来优化  和b的核心代码)一模一样,只不过这里要把一些东西,改为数据结构中定义的,而且这里的selectJ已经采用了启发式寻找

接下来就是我们的smoP,也是Platt SMO利用主循环封装整个算法的过程,其和最简版本一样,也是两个循环:

外训练也是使用了一个maxIter,同时使用了iter来记录遍历次数(对应最简版本的iter_num),但是两者含义却不一样,这里的iter就是单纯的代表一次循环,而不管循环内部具体做了什么,它没有被清0这个过程,随着程序运行一直是个累加的过程,上面运行结果也可以看到iter是一直递增的,这也是Platt SMO之所以能够加快算法的一个重要原因,而最简版本的iter_num要肩负着连续这一条件,同时这里的外循环相对于最简版本的的外循环多了一个退出条件即:遍历整个集合都没发现需要改变的  (说明都优化好啦,退出吧)

再来看一下内循环,这里对应着两种情况,一种是在全集上面遍历([0,C]),另一种是非边界上面((0,C)),通过

if entireSet:
            entireSet = False
        elif (alphaPairsChanged == 0):
            entireSet = True

使两种情况交替遍历

其他部分包括W的获得,可视化什么的就和最简版本一样啦,不再重复介绍啦

核函数

59cb4fe5384c18e9d26f9838e19d3f20.gif

核函数的作用细节请看推导部分,核函数种类很多,这里看一下最常用的径向基高斯(RBF)核函数 

下面来简单说一下部分代码(这里只说不同的地方,相同的地方不再重述)

# -*- coding: utf-8 -*-
from numpy import *
import matplotlib.pyplot as plt
def loadDataSet(filename): 
    dataMat=[]
    labelMat=[]
    fr=open(filename)
    for line in fr.readlines():
        lineArr=line.strip().split(',')
        dataMat.append([float(lineArr[0]),float(lineArr[1])])
        labelMat.append(float(lineArr[2]))
    return dataMat,labelMat 


def selectJrand(i,m): 
    j=i
    while (j==i):
        j=int(random.uniform(0,m))
    return j


def clipAlpha(aj,H,L):  
    if aj>H:
        aj=H
    if L>aj:
        aj=L
    return aj


def kernelTrans(X, A, kTup): 
    m,n = shape(X)
    K = mat(zeros((m,1)))
    if kTup[0]=='lin': 
        K = X * A.T
    elif kTup[0]=='rbf': 
        for j in range(m):
            deltaRow = X[j,:] - A
            K[j] = deltaRow*deltaRow.T
        K = exp(K/(-1*kTup[1]**2)) 
    else:
        raise NameError('Houston We Have a Problem -- That Kernel is not recognized')
    return K




class optStruct:
    def __init__(self,dataMatIn, classLabels, C, toler, kTup):  
        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))) 
        self.K = mat(zeros((self.m,self.m))) 
        for i in range(self.m):
            self.K[:,i] = kernelTrans(self.X, self.X[i,:], kTup)




def calcEk(oS, k):
    fXk = float(multiply(oS.alphas,oS.labelMat).T*oS.K[:,k] + oS.b)
    Ek = fXk - float(oS.labelMat[k])
    return Ek


def selectJ(i, oS, Ei):
    maxK = -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)
            deltaE = abs(Ei - Ek)
            if (deltaE > maxDeltaE): 
                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)): 
        j,Ej = selectJ(i, oS, Ei) 
        alphaIold = oS.alphas[i].copy()
        alphaJold = oS.alphas[j].copy()
        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 = 2.0 * oS.K[i,j] - oS.K[i,i] - oS.K[j,j] 
        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) < oS.tol): 
            print("j not moving 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]-alphaIold)*oS.K[i,i] - oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.K[i,j]
        b2 = oS.b - Ej- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.K[i,j]- oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.K[j,j]
        if (0 < oS.alphas[i]<oS.C):
            oS.b = b1
        elif (0 < oS.alphas[j]<oS.C):
            oS.b = b2
        else:
            oS.b = (b1 + b2)/2.0
        return 1
    else:
        return 0




def smoP(dataMatIn, classLabels, C, toler, maxIter,kTup=('lin', 0)):
    oS = optStruct(mat(dataMatIn),mat(classLabels).transpose(),C,toler, kTup)
    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: %d i:%d, pairs changed %d" % (iter,i,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: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
            iter += 1
        if entireSet:
            entireSet = False
        elif (alphaPairsChanged == 0):
            entireSet = True
        print("iteration number: %d" % iter)
    return oS.b,oS.alphas




def testRbf(data_train,data_test):
    dataArr,labelArr = loadDataSet(data_train) 
    b,alphas = smoP(dataArr, labelArr, 200, 0.0001, 10000, ('rbf', 0.2)) 
    datMat=mat(dataArr)
    labelMat = mat(labelArr).transpose()
    svInd=nonzero(alphas)[0]  
    sVs=datMat[svInd] 
    labelSV = labelMat[svInd] 
    print("there are %d Support Vectors" % shape(sVs)[0]) 
    m,n = shape(datMat)
    errorCount = 0
    for i in range(m):
        kernelEval = kernelTrans(sVs,datMat[i,:],('rbf', 1.3)) 
        predict=kernelEval.T * multiply(labelSV,alphas[svInd]) + b  
        if sign(predict)!=sign(labelArr[i]): 
            errorCount += 1
    print("the training error rate is: %f" % (float(errorCount)/m)) 
    dataArr_test,labelArr_test = loadDataSet(data_test) 
    errorCount_test = 0
    datMat_test=mat(dataArr_test)
    labelMat = mat(labelArr_test).transpose()
    m,n = shape(datMat_test)
    for i in range(m): 
        kernelEval = kernelTrans(sVs,datMat_test[i,:],('rbf', 0.1))
        predict=kernelEval.T * multiply(labelSV,alphas[svInd]) + b
        if sign(predict)!=sign(labelArr_test[i]):
            errorCount_test += 1
    print("the test error rate is: %f" % (float(errorCount_test)/m))
    
    return dataArr,labelArr,alphas


def showClassifer(dataMat,labelMat,alphas):
    data_plus = []                                  
    data_minus = []
    for i in range(len(dataMat)):
        if labelMat[i] > 0:
            data_plus.append(dataMat[i])
        else:
            data_minus.append(dataMat[i])
    data_plus_np = array(data_plus)              
    data_minus_np = array(data_minus)            
    plt.scatter(transpose(data_plus_np)[0], transpose(data_plus_np)[1], s=30, alpha=0.7)   
    plt.scatter(transpose(data_minus_np)[0], transpose(data_minus_np)[1], s=30, alpha=0.7) 
    for i, alpha in enumerate(alphas):
        if abs(alpha) > 0:
            x, y = dataMat[i]
            plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='red')
    plt.show()

MyTest:

# -*- coding: utf-8 -*-
import smoPrbf as svm
traindata='C:\\Users\\asus-\\Desktop\\train_data.csv'
testdata='C:\\Users\\asus-\\Desktop\\test_data.csv'
TraindataArr,TrainlabelArr,alphas = svm.testRbf(traindata,testdata)
svm.showClassifer(TraindataArr,TrainlabelArr,alphas)

当  时:

 025cad6b6a02eb83be9cef6bc18ed661.png

c7e352d14066ae808b86067599247025.png

 当  时:

 36a4ac37d4114ef8e2b92dbef3c6257b.png

c7569246049fd4d09cc6a221231ad979.png

kernelTrans函数的作用就是核函数的计算部分,对应到推导公式是:

这里的kTup就是指定使用什么核函数,kTup[0]参数是核函数类型,kTup[1]是核函数需要的超参数,注意这里只支持线性和径向基高斯(RBF)核函数 两种.

optStruct函数增加了一个字段即K,其是一个m*m的矩阵。注意它的含义:

我们的核函数是  即拿来一个点x,要和所有样本  做运算,这里的行代表的意义就是所有样本,列代表的是x所以这里是:

elf.K[:,i] = kernelTrans(self.X, self.X[i,:], kTup)

innerL变化的部分是:

eta = 2.0 * oS.K[i,j] - oS.K[i,i] - oS.K[j,j]

对比之前的:

eta = 2.0 * oS.X[i,:]*oS.X[j,:].T-oS.X[i,:]*oS.X[i,:].T-oS.X[j,:]*oS.X[j,:].T

就可以更好的理解为什么在optStruct结构体中K字段要这么设计

同理变化的地方还有:

b1 = oS.b - Ei- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.K[i,i] - oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.K[i,j]
 b2 = oS.b - Ej- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.K[i,j]- oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.K[j,j]

calcEk变化的地方:

fXk = float(multiply(oS.alphas,oS.labelMat).T*oS.K[:,k] + oS.b)

简单来说就是原先有  的地方都要换成核函数的内积形式即  

testRbf这里主要作用就是使用了训练集去训练SVM模型,然后分别统计该模型在训练集上和测试集上的错误率。

注意这里在通过  构建权重w时是只用到是支持向量机那些点即  那些点,其实SVM的原理不就是使用这些向量机来构建的模型的嘛,那些远离间隔线的点我们是用不到的,对我们没什么作用。所以先筛选出哪些点是向量机:

svInd=nonzero(alphas)[0]

程序也会将向量机的个数打印出来。

最后讨论一下rbf的超参数意义即

值对应在在代码的:

b,alphas = smoP(dataArr, labelArr, 200, 0.0001, 10000, ('rbf', 0.2))

b,alphas = smoP(dataArr, labelArr, 200, 0.0001, 10000, ('rbf', 1.2))

通过上面的实验我们可以大体看出随着  的增加,支持向量机的个数在减少,由原来的43个减少到25(红色圆圈的点就是支持向量机),  的取值存在一个最优解,当  太大,支持向量机太少,也就是说我们利用了很少的点去决策,显然结果不好,正如上面体现的那样,测试集的错误在上升,当  太小,支持向量机太多,也就是我们基本利用了所有样本点,其实这个时候已经退化到类似KNN啦,因为KNN就是利用了到所有样本点的距离来决策的,可能会有这样的疑问?不对呀?使用KNN时不是会指定利用多少个点吗?不是利用所有点呀?哈哈哈,仔细想想,它的过程是先计算到全部样本的距离,然后再从中选择K个最近距离的点来进行比较的,所以它每次要用到的是全部样本点,而SVM是一旦训练出  后,在之后的决策中就只使用  的样本,即使用部分点,这回明白了吧,再者SVM本质也是和KNN一样使用距离来决策的,所以才说当支持向量机太多的时候,我们不就是使用全部样本点通过计算距离来决策的嘛,这和KNN特别相似,当然啦,说了半天这也不是什么重要的事情,只是为了增加SVM

的理解,最重要的还是要通过调试找到RBF最佳的超参数值。

总结

a613b6f672712885cf4bd4464aa30a4c.gif

关于更多sklearn的SVM更多调用请看笔者之前写的一篇博客:

python_sklearn机器学习算法系列之SVM支持向量机算法_爱吃火锅的博客-CSDN博客

当然还有其他机器学习的调用

# -*- coding: utf-8 -*-
from numpy import *
import matplotlib.pyplot as plt
from sklearn import metrics
from sklearn import svm


def loadDataSet(filename): 
    dataMat=[]
    labelMat=[]
    fr=open(filename)
    for line in fr.readlines():
        lineArr=line.strip().split(',')
        dataMat.append([float(lineArr[0]),float(lineArr[1])])
        labelMat.append(float(lineArr[2]))
    return dataMat,labelMat




traindata='C:\\Users\\asus-\\Desktop\\train_data.csv'
testdata='C:\\Users\\asus-\\Desktop\\test_data.csv'




x_train,y_train = loadDataSet(traindata)
x_test,y_test = loadDataSet(testdata)




clf1 = svm.SVC(C=0.8, kernel='rbf', gamma=10, decision_function_shape='ovr')
clf2 = svm.SVC(C=0.8, kernel='rbf', gamma=20, decision_function_shape='ovr')


clf1.fit(x_train, y_train)
clf2.fit(x_train, y_train)






y_predict1=clf1.predict(x_test)
y_predict2=clf2.predict(x_test)
print('--------------------- gamma=10--------------------------------')
print(metrics.classification_report(y_test,y_predict1))
print('--------------------- gamma=20--------------------------------')
print(metrics.classification_report(y_test,y_predict2))

 1be9ad2b7bd091e5ad25b0fbd8b014f7.png

结尾给一下我们用的数据集,方便大家实验:

Train_data:

-0.2148240.662756-1
-0.061569-0.0918751
0.4069330.648055-1
0.223650.1301421
0.2313170.766906-1
-0.7488-0.531637-1
-0.5577890.375797-1
0.207123-0.0194631
0.2864620.71947-1
0.1953-0.1790391
-0.152696-0.153031
0.3844710.653336-1
-0.11728-0.1532171
-0.2380760.0005831
-0.4135760.1456811
0.490767-0.680029-1
0.199894-0.1993811
-0.3560480.53796-1
-0.392868-0.1252611
0.353588-0.0706171
0.0209840.92572-1
-0.475167-0.346247-1
0.0749520.0427831
0.394164-0.0582171
0.6634180.436525-1
0.4021580.577744-1
-0.449349-0.0380741
0.61908-0.088188-1
0.268066-0.0716211
-0.0151650.3593261
0.539368-0.374972-1
-0.3191530.629673-1
0.6944240.64118-1
0.0795220.1931981
0.253289-0.2858611
-0.035558-0.0100861
-0.4034830.474466-1
-0.0343120.995685-1
-0.5906570.438051-1
-0.098871-0.0239531
-0.2500010.1416211
-0.0129980.525985-1
0.1537380.491531-1
0.388215-0.656567-1
0.0490080.0134991
0.0682860.3927411
0.7478-0.06663-1
0.004621-0.0429321
-0.70160.190983-1
0.055413-0.024381
0.035398-0.3336821
0.2117950.0246891
-0.0456770.1729071
0.5952220.20957-1
0.2294650.2504091
-0.0892930.0681981
0.3843-0.176571
0.834912-0.110321-1
-0.3077680.503038-1
-0.777063-0.348066-1
0.017390.1524411
-0.293382-0.1397781
-0.2032720.2868551
0.957812-0.152444-1
0.004609-0.0706171
-0.7554310.096711-1
-0.5264870.547282-1
-0.2468730.833713-1
0.185639-0.0661621
0.8519340.456603-1
-0.8279120.117122-1
0.233512-0.1062741
0.583671-0.709033-1
-0.4870230.62514-1
-0.4489390.1767251
0.155907-0.1663711
0.3342040.381237-1
0.081536-0.1062121
0.2272220.527437-1
0.759290.33072-1
0.204177-0.0235161
0.5779390.403784-1
-0.5685340.442948-1
-0.011520.0211651
0.875720.422476-1
0.297885-0.632874-1
-0.0158210.0312261
0.541359-0.205969-1
-0.689946-0.508674-1
-0.3430490.841653-1
0.523902-0.436156-1
0.249281-0.71184-1
0.1934490.574598-1
-0.257542-0.753885-1
-0.0216050.158081
0.601559-0.727041-1
-0.7916030.095651-1
-0.908298-0.053376-1
0.122020.850966-1
-0.725568-0.292022-1

Test_data:

0.676771-0.486687-1
0.0084730.186071
-0.7277890.594062-1
0.1123670.2878521
0.383633-0.0380681
-0.927138-0.032633-1
-0.842803-0.423115-1
-0.003677-0.3673381
0.443211-0.698469-1
-0.4738350.0052331
0.6167410.590841-1
0.557463-0.373461-1
-0.498535-0.223231-1
-0.2467440.2764131
-0.76198-0.244188-1
0.641594-0.479861-1
-0.659140.52983-1
-0.054873-0.23891
-0.089644-0.2446831
-0.431576-0.481538-1
-0.0995350.728679-1
-0.1884280.1564431
0.2670510.3181011
0.222114-0.528887-1
0.0303690.1133171
0.3923210.0260891
0.298871-0.915427-1
-0.034581-0.1338871
0.4059560.206981
0.144902-0.605762-1
0.274362-0.4013381
0.397998-0.780144-1
0.0378630.1551371
-0.010363-0.004171
0.5065190.486619-1
0.000082-0.0206251
0.057761-0.155141
0.027748-0.553763-1
-0.413363-0.74683-1
0.0815-0.0142641
0.047137-0.4912711
-0.2674590.024771
-0.148288-0.532471-1
-0.225559-0.2016221
0.77236-0.518986-1
-0.440670.688739-1
0.329064-0.0953491
0.97017-0.010671-1
-0.689447-0.318722-1
-0.465493-0.227468-1
-0.049370.4057111
-0.1661170.2748071
0.0544830.0126431
0.0213890.0761251
-0.104404-0.914042-1
0.2944870.440886-1
0.107915-0.493703-1
0.0763110.438861
0.370593-0.728737-1
0.409890.306851-1
0.2854450.474399-1
-0.870134-0.161685-1
-0.654144-0.675129-1
0.285278-0.76731-1
0.049548-0.0009071
0.030014-0.0932651
-0.1288590.2788651
0.3074630.0856671
0.023440.2986381
0.053920.2353441
0.0596750.533339-1
0.8171250.016536-1
-0.1087710.4772541
-0.1181060.0172841
0.2883390.1954571
0.567309-0.200203-1
-0.2024460.4093871
-0.330769-0.2407971
-0.4223770.480683-1
-0.2952690.3260171
0.2611320.0464781
-0.492244-0.319998-1
-0.3844190.099171
0.101882-0.781145-1
0.234592-0.3834461
-0.020478-0.901833-1
0.3284490.1866331
-0.150059-0.4091581
-0.155876-0.843413-1
-0.098134-0.1367861
0.110575-0.1972051
0.2190210.0543471
0.0301520.2516821
0.033447-0.1228241
-0.686225-0.020779-1
-0.911211-0.262011-1
0.5725570.377526-1
-0.073647-0.519163-1
-0.28183-0.797236-1
-0.5552630.126232-1
推荐阅读:

我的2022届互联网校招分享

我的2021总结

浅谈算法岗和开发岗的区别

互联网校招研发薪资汇总
2022届互联网求职现状,金9银10快变成铜9铁10!!

公众号:AI蜗牛车

保持谦逊、保持自律、保持进步

发送【蜗牛】获取一份《手把手AI项目》(AI蜗牛车著)
发送【1222】获取一份不错的leetcode刷题笔记

发送【AI四大名著】获取四本经典AI电子书
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值