支持向量(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)就是我們的預測值,但不是實際的預測值,這裏面肯定有誤差
誤差就是Ei = g(xi) - yi
在簡化版裏我們隨機選擇另一個alpha值 j,選之前判斷一下第一個alpha值能否需要繼續優化,不需要就不用往下走了。
同樣的方法,我們再計算j對應的預測值和誤差。
然後就是比較這兩個alpha的預測值即y1和y2,確定最大範圍H和最小範圍L:
1.當y1 = y2時,
2.當y1 != y2時,
算出最優化分量η(eta),
是輸入空間到特徵空間的映射。
未經剪輯時的
剪輯後在L、H和 中選取。也同樣要改變,數值上和alpha2一致,但方向相反。
同時更新b1和b2的值:
如果和同時滿足那麼,否則 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模型中的參數最合適的情況。運行結果如下:
以及召回率、準確率等等評價參數:https://blog.csdn.net/quiet_girl/article/details/70830796