第6章支持向量機

支持向量(support vector)就是離分隔超平面最近的點

SVM特點:幾乎所有分類問題都可以解決,SVM本身是一個二類分類器,對多類問題應用SVM需要對代碼做一些修改

序列最小優化算法(Sequential Minimal Optimization,SMO)算法:

支持向量機的學習問題可以形式化爲求解凸二次規劃問題。

SMO算法將原來的大問題不斷分解爲子問題並對子問題求解,進而達到求解原問題的目的。

整個SMO算法包含兩個部分:求解兩個變量二次規劃的解析方法和選擇變量的啓發方法。

先看代碼:(簡要的SMO實現代碼)

# encoding: utf-8
import random
import numpy as np
from  numpy import *
#6-1 SMO算法中的輔助函數
def loadDataSet(fileName):#讀取txt中數據
    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

def selectJrand(i,m):#i是第一個alpha的下標,m是所有alpha的數目
    j = i
    while (j == i ):
        j = int(random.uniform(0,m))
    return j

def clipAlpha(aj,H,L):#調整大於H或者小於L的alpha值
    if aj > H:
        aj = H
    if L > aj:
        aj = L
    return aj

#6-2簡化版SMO算法
def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
    #輸入的五個參數分別爲數據集、類別標籤、常數C、容錯率和退出前最大的循環次數
    dataMatrix = mat(dataMatIn);labelMat = mat(classLabels).transpose()
    b = 0  #是一個參數
    m,n = shape(dataMatrix)
    alphas = mat(zeros((m,1)))
    iter = 0
    while (iter < maxIter):
        alphaPairsChanged = 0 #這個參數用於記錄alphas參數是否改變,改變的話即爲1
        for i in range(m):
            fXi = float(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)#j是隨機取得的index,是我們取得一個隨機樣本
                fXj = float(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是alpha[j]的最優修改量
                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]
                alphas[j] -= labelMat[j]*(Ei - Ej)/eta
                #通過門限函數來選取範圍
                alphas[j] = clipAlpha(alphas[j],H,L)
                #檢查alphas[j]是否有輕微的改變
                if (abs(alphas[j] - alphaJold) < 0.00001):
                    print('j not moving enough');continue
                #alphas[i]進行和alphas[j]同樣的改變,但兩者改變的方向相反,若一個增加,另一個就減小
                alphas[i] += labelMat[j] * labelMat[i] * (alphaJold -alphas[j])
                #爲兩個alphas位置常數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]):#在0到C的範圍內
                    b = b1
                elif (0 < alphas[j]) and (C > alphas[j]):#同樣alphas[j]也這樣判斷
                    b = b2
                else:b = (b1 + b2)/2.0
                #如果到這都沒有跳出這個循環,說明兩個alphas已經修改過了
                alphaPairsChanged += 1
                print('iter: %d i:%d, pairs changed %d' %(iter,i,alphaPairsChanged))
        #檢查alpha是否做了更新,如果有更新則將iter歸0
        if (alphaPairsChanged == 0):iter += 1
        else: iter = 0
        print('iteration number: %d'% iter)
    return b,alphas

一、兩個變量二次規劃的求解方法

我們首先選取一個alpha值 i,並計算它的預測值和誤差。

g(x) = \sum_{i=1}^{N}{\alpha _i}{y_i}K({x_i},x)+b

g(x)就是我們的預測值,但不是實際的預測值,這裏面肯定有誤差

誤差就是Ei = g(xi) - yi

在簡化版裏我們隨機選擇另一個alpha值 j,選之前判斷一下第一個alpha值能否需要繼續優化,不需要就不用往下走了。

同樣的方法,我們再計算j對應的預測值和誤差。

然後就是比較這兩個alpha的預測值即y1和y2,確定最大範圍H和最小範圍L:

1.當y1 = y2時,L = max(0,{\alpha _{2}}^{old}-{\alpha _{1}}^{old})      

H = min(C,C+{\alpha _{2}}^{old}-{\alpha _{1}}^{old})

2.當y1 != y2時,L = max(0,{\alpha _{2}}^{old}+{\alpha _{1}}^{old}-C)

H = min(C,{\alpha _{2}}^{old}+{\alpha _{1}}^{old})

算出最優化分量η(eta),\eta = K_{11}+K_{22}-2K_{12}=\left \| \varphi (x_{1})-\varphi (x_{2}) \right \|^2

\varphi (x) 是輸入空間到特徵空間的映射。

未經剪輯時的{\alpha _{2}}^{new} ={\alpha _{2}}^{old} +\frac{y_{2}(E_{1}-E_{2})}{\eta }

剪輯後在L、H和 {\alpha _{2}}^{new}中選取。{\alpha _1}}^{new}也同樣要改變,數值上和alpha2一致,但方向相反。

{\alpha _{1}}^{new}={\alpha _{1}}^{old}+y_{1}+y_{2}({\alpha _{2}}^{old}-{\alpha _{2}}^{new})

同時更新b1和b2的值:

b_{1}^{new}=-E_{1}-y_{1}K_{11}(\alpha _{1}^{new}-\alpha _{1}^{old})-y_{2}K_{21}(\alpha _{2}^{new}-\alpha _{2}^{old})+b^{old}

b_{2}^{new}=-E_{2}-y_{1}K_{12}(\alpha _{1}^{new}-\alpha _{1}^{old})-y_{2}K_{22}(\alpha _{2}^{new}-\alpha _{2}^{old})+b^{old}

如果{\alpha _{1}}^{new}{\alpha _{2}}^{new}同時滿足0<{\alpha _{i}}^{new}<C那麼b_{1}^{new}=b_{2}^{new},否則 b = (b1 + b2)/2.0(中點)

二、變量的選擇方法

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):#計算E值並返回(誤差)
    #fXk = float(multiply(oS.alphas,oS.labelMat).T*(oS.X*oS.X[k,:].T)) + oS.b
    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):#用於選擇第二個alpha或者說內循環的alpha值,選擇其中使得改變最大的那個值
    #第2個變量的選擇標準是:希望能使alpha2有足夠大的變化
    maxK = -1;maxDeltaE = 0; Ej = 0
    oS.eCache[i] = [1,Ei] #根據Ei更新誤差緩存
    #nonzero()返回一個array,但這裏[0],使得返回的是非0元素的行號
    validEcacheList = nonzero(oS.eCache[:,0].A)[0]#儲存着非0元素索引
    if (len(validEcacheList)) > 1:
        for k in validEcacheList:
            #通過for循環遍歷選擇其中使得改變最大的那個值
            if k == i:continue #如果座標與第一個alpha索引一樣,退出
            Ek = calcEk(oS,k) #計算出這個alpha值對應的誤差
            deltaE = abs(Ei - Ek) #兩個誤差的差值
            if (deltaE > maxDeltaE):#判斷語句作用就是尋找最大差值對應的alpha索引
                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]

#6-4完整Platt 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)#選擇第二個alpha,但不是隨機選取了
        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是alpha[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
        eta = 2.0 * oS.K[i,j] - oS.K[i,i] - oS.K[j,j]
        if eta >= 0:print('eta >= 0');return 0
        # 計算出一個新的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 not moving enough');return 0
        # alphas[i]進行和alphas[j]同樣的改變,但兩者改變的方向相反,若一個增加,另一個就減小
        oS.alphas[i] += oS.labelMat[j]*oS.labelMat[i]*(alphaJold - oS.alphas[j])
        updateEk(oS,i)
        # 爲兩個alphas位置常數b
        #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
        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.X[i, :] * oS.X[j, :].T - oS.labelMat[j] * \
             #(oS.alphas[j] - alphaJold) * oS.X[j, :] * oS.X[j, :].T
        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]) 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

#6-5完整版Platt SMO的外循環代碼
def smop(dataMatIn, classLabels, C, toler, maxIter, kTup=('lin',0)):
    #輸入的五個參數分別爲數據集、類別標籤、常數C、容錯率和退出前最大的循環次數
    #構建一個數據結構來容納所有數據
    oS = optStruct(mat(dataMatIn),mat(classLabels).transpose(),C,toler,kTup)
    iter = 0 #進行各項的初始化
    entireSet = True; alphaPairsChanged = 0
    #循環推出條件:1.迭代次數大於最大迭代次數 2.遍歷整個集合都未對任意alpha進行修改
    while (iter < maxIter) and ((alphaPairsChanged > 0)or (entireSet)):
        alphaPairsChanged = 0
        if entireSet:
            for i in range (oS.m):#遍歷alpha
                alphaPairsChanged += innerL(i,oS)
                #調用innerL()來選擇第二個alpha,並在可能時對其進行優化處理。如果alpha改變返回1,沒有改變,返回0
                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

1.第1個變量的選擇:SMO稱選擇第1個變量的過程爲外層循環。外層循環在訓練樣本中選取違反KKT條件最嚴重的樣本點

2.第2個變量的選擇:SMO稱選擇第1個變量的過程爲內層循環。第2個變量選擇的標準是希望是alpha2有足夠大的變化。

簡化版:採取的僅僅是隨機選取alpha2。而完整版裏採用的是selectJ()函數來選取。

三、核函數來解決非線性分類

#6-6核轉換函數
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)

#6-8利用核函數進行分類的徑向基測試函數
def testRbf(k1=0.31):#該參數就是核函數中的σ的大小
    dataArr,labelArr = loadDataSet('testSetRBF.txt')#讀取數據集
    b,alphas = smop(dataArr,labelArr,200,0.0001,10000,('rbf',k1))#使用SMO算法,其中核函數類型爲rbf
    datMat = mat(dataArr); labelMat = mat(labelArr).transpose()
    svInd = nonzero(alphas.A > 0)[0]#找出非0的alpha值的索引
    sVs = datMat[svInd] #找到支持向量
    labelSV = labelMat[svInd] #alpha的類別標籤值
    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',k1))
        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,labelArr = loadDataSet('testSetRBF2.txt')
    errorCount = 0
    datMat = mat(dataArr);labelMat = mat(labelArr).transpose()
    m,n = shape(datMat)
    for i in range(m):
        kernelEval = kernelTrans(sVs,datMat[i,:],('rbf',k1))
        predict = kernelEval.T * multiply(labelSV,alphas[svInd]) + b
        if sign(predict)!=sign(labelArr[i]):
            errorCount += 1
    print('the test error rate is :%f'%(float(errorCount)/m))

四、手寫識別系統SVM來分類

#6-9基於SVM的手寫數字識別
#爲了使用前面兩個分類器,因爲前面的例子都是隻有一行的,我們必須將圖像格式化處理爲一個向量。
# 我們將把一個32*32的二進制圖像矩陣轉換爲1*1024的向量,這樣前兩節使用的分類器就可以處理數字圖像信息了。
def img2vector(filename):
    returnVect = zeros((1,1024))
    fr = open(filename)
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0,32*i+j] = int(lineStr[j])
    return returnVect

def loadImages(dirName):
    from os import listdir
    hwLabels = []
    trainingFileList = listdir(dirName)
    #os.listdir() 方法用於返回指定的文件夾包含的文件或文件夾的名字的列表。
    # 這個列表以字母順序。 它不包括 '.' 和'..' 即使它在文件夾中。
    m = len(trainingFileList)
    trainingMat = zeros((m,1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        if classNumStr == 9:
            hwLabels.append(-1)
        else:
            hwLabels.append(1)
        trainingMat[i,:] = img2vector('digits/trainingDigits/%s' % fileNameStr)
    return trainingMat,hwLabels

def testDigits(KTup = ('rbf',10)):
    dataArr,labelArr = loadImages('digits/trainingDigits')#讀取訓練集數據
    b,alphas = smop(dataArr,labelArr,200,0.0001,10000,KTup)
    datMat = mat(dataArr);labelMat = mat(labelArr).transpose()
    svInd = nonzero(alphas.A > 0)[0]
    sVs = datMat[svInd]  # 找到支持向量
    labelSV = labelMat[svInd]  # alpha的類別標籤值
    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,:], KTup)
        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, labelArr = loadImages('digits/testDigits')#讀取測試集
    errorCount = 0
    datMat = mat(dataArr)
    labelMat = mat(labelArr).transpose()
    m, n = shape(datMat)
    for i in range(m):
        kernelEval = kernelTrans(sVs, datMat[i, :], KTup)
        predict = kernelEval.T * multiply(labelSV, alphas[svInd]) + b
        if sign(predict) != sign(labelArr[i]):
            errorCount += 1
    print "the test error rate is: %f" % (float(errorCount) / m)

開始測試:

1.(rbf,100)時:

2.(rbf,10)時:

3.('rbf',0.1)時:

四、使用sklearn封裝的庫中的SVM

參考文章:https://blog.csdn.net/yuanlulu/article/details/81011849

https://www.cnblogs.com/pinard/p/6117515.html

def img2vector(filename):
    returnVect = zeros((1,1024))
    fr = open(filename)
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0,32*i+j] = int(lineStr[j])
    return returnVect

def loadImages(dirName):
    from os import listdir
    hwLabels = []
    trainingFileList = listdir('digits/%s' %(dirName))
    #os.listdir() 方法用於返回指定的文件夾包含的文件或文件夾的名字的列表。
    # 這個列表以字母順序。 它不包括 '.' 和'..' 即使它在文件夾中。
    m = len(trainingFileList)
    trainingMat = zeros((m,1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        if classNumStr == 9:#類別標籤只有-1和1,當等於9時,爲-1,,否則則爲1。
            hwLabels.append(-1)
        else:
            hwLabels.append(1)
        trainingMat[i,:] = img2vector('digits/%s/%s' % (dirName,fileNameStr))
    return trainingMat,hwLabels
TraindataArr,TrainlabelArr = loadImages('trainingDigits')#讀取訓練集數據
dataArr, labelArr = loadImages('testDigits')  # 讀取測試集
#print (TrainlabelArr)
#clf = svm.SVC(C=1.0, kernel='rbf', degree=3, gamma='auto', coef0=0.0, shrinking=True, probability=False, tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape='ovr', random_state=None)
thresholds = np.linspace(0,0.001,100)
param_grid = {'gamma': thresholds}
clf = GridSearchCV(svm.SVC(kernel='rbf'), param_grid, cv=5)
clf.fit(TraindataArr,TrainlabelArr)
print("best param: {0}\nbest score: {1}".format(clf.best_params_,clf.best_score_))
y_pred = clf.predict(dataArr)
print("y_pred:{}".format(y_pred))
print("y_test:{}".format(labelArr))

print("查準率:",metrics.precision_score(y_pred, labelArr))
print("召回率:",metrics.recall_score(y_pred, labelArr))
print("F1:",metrics.f1_score(y_pred, labelArr))

通過GridSearchCV函數(sklearn裏面的)來尋找rbf模型中的參數\sigma最合適的情況。運行結果如下:

以及召回率、準確率等等評價參數:https://blog.csdn.net/quiet_girl/article/details/70830796

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章