1. 程式人生 > >SVM支援向量機-《機器學習實戰》SMO演算法Python實現(5)

SVM支援向量機-《機器學習實戰》SMO演算法Python實現(5)

經過前幾篇文章的學習,SVM的優化目標,SMO演算法的基本實現步驟,模型對應引數的選擇,我們已經都有了一定的理解,結合《機器學習實戰》,動手實踐一個基本的SVM支援向量機,來完成一個簡單的二分類任務。

建立模型之前,首先看一下我們的資料,然後再用支援向量機實現分類:

                                                       

這裡只擷取部分,我們的資料是一個txt檔案,包含100個數據點,前兩行為x1,x2,負一行為標籤值,取值{-1,+1}.

讀取檔案資料

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#資料集,標籤值

LoadDataSet函式的作用是讀取資料檔案,return得到兩個列表,一個包含二維資料點,一個包含點對應的標籤值

隨機選擇j

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

selectJrand函式通過while函式判斷 j==i,從而達到隨機生成不等於 i 的 j ,因為這裡是簡化的SVM演算法,如果在複雜情況下,j的選擇會和這裡j所的選擇方法有所不同

調整alpha

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

clipAlpha函式根據(4)的alpha的界限,調整迭代得到的alpha

簡化版SMO演算法

import numpy as np

def smoSimple(dataMatIn,classLabels,C,toler,maxIter):
	#引數分別為資料集,類別標籤,常數c,容錯率和最大迴圈次數
	#外迴圈
	dataMatrix = np.mat(dataMatIn);labelMat = np.mat(classLabels).transpose() #資料與標籤矩陣轉化
	b = 0;m,n = np.shape(dataMatrix)#讀取資料維度
	alphas = np.mat(np.zeros((m,1)))#初始化alpha為0
	iter = 0#初始化迭代數
	while (iter < maxIter):
		#內迴圈
		alphaParisChanged = 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)):#判斷alpha是否滿足KKT條件
				j = selectJrand(i,m)#隨機選取j
				fXj = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b #計算預測類別
				Ej = fXj - float(labelMat[j])#計算誤差e
				alphaIold = alphas[i].copy()#為alphaIold和alphaJold分配新的記憶體
				alphaJold = alphas[j].copy()
				if (labelMat[i] != labelMat[j]):#alpha的上限與下限
					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
				#計算smo演算法中的η
				if eta > 0 : print("eta>=0");continue

				#對i進行修改,修改量與j相同,但方向相反
				alphas[j] -= labelMat[j]*(Ei-Ej)/eta
				#通過L H的值優化
				alphas[j] = clipAlpha(alphas[j],H,L)
                                #判斷alphaJ的優化程度,更新alphaI,並根據新的alpha得到新的b
				if (abs(alphas[j]-alphaJold) < 0.0001):
					print("j not moving enough");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
				alphaParisChanged += 1
				print("iter : %d i:%d,pairs changed %d" %(iter,i,alphaParisChanged))
		if (alphaParisChanged == 0):#判斷是否對alpha進行了更新
			iter += 1
		else:
			iter = 0
		print("iterration number :%d" % iter)
	return b,alphas#返回超平面需要的引數alpha,b

SmoSimple函式是smo演算法的簡單實現版本,與原始演算法思想基本一致,需要計算的是誤差 ei = f(xi)-yi,二次項η (eta),以及alpha的上界H下界L,需要判斷的是開頭的KKT條件是否滿足,判斷yi與yj的類別是否一致從而決定上下界的形式,判斷alpha的更新程度以及值的大小從而決定b,需要更新的是alpha的值,b的值,還有演算法迭代次數,最後返回我們超平面構建需要的引數alpha,b . Tips: KKT條件的判斷與西瓜書,《統計學習方法》有所不同,在(3)中解釋了程式碼中的KKT條件是如何判斷的.

繪製樣本資料點,超平面與支援向量

import matplotlib.pyplot as plt

def plot_point(dataMat,labelMat,alphas,b):

	for i in range(np.shape(dataMat)[0]):#繪製shape(dataMat)[0]個數據點
		plt.scatter(dataMat[i][0],dataMat[i][1])

	alphas, dataMat, labelMat = np.array(alphas), np.array(dataMat), np.array(labelMat)#轉為陣列求w
	sum = 0#初始化w
	for i in range(np.shape(dataMat)[0]):
		sum += alphas[i]*labelMat[i]*dataMat[i].T#通過求導後得到的w公式,求和得到w
	# print(sum)

	for i, alpha in enumerate(alphas):#根據kkt條件,選取alpha不為0的點作為支援向量
	    if abs(alpha) > 0:
	        x, y = dataMat[i]
	        plt.scatter(x, y, s=100, c = '', alpha=0.5, linewidth=1.5, edgecolor='red')

	x = np.arange(0,10,0.01)
	y = (sum[0]*x+b)/(-1*sum[1])#通過原始公式推匯出超平面

	plt.scatter(x,y,s=5,marker = 'h')
	
	plt.show()

plot_point函式是負責繪製超平面的,書上只給了示意圖,所以就只能自己動手豐衣足食了,首先繪製了所有資料點,由於資料點比較標準(無明顯越界),所以為了好看就沒有根據類別設定顏色,其次根據對偶問題得到w的計算公式

                                                                                

通過SmoSimple返回的alpha計算w,再加上b,就得到了最優分割超平面的公式,由於是二維資料點,所以稍加整理就可以得到超平面的二維表示式,然後我們設定適當的x範圍,繪製超平面就ok了;還有一點就是支援向量的標註,因為alpha≠0時,(wi.T).x+b=0,對應的點在分割邊界上,所以對應的點即為支援向量Support Vector,最後用c=' '空心標註出支援向量,就能得到書中的圖了.

主函式

if __name__ == '__main__':
	dataMat, labelMat = loadDataSet('testSet.txt')
	b,alphas = smoSimple(dataMat, labelMat, 0.6, 0.001, 40)
	plot_point(dataMat,labelMat,alphas,b)

可以自己加print語句,看一下w,b:

b =  [[-3.86378686]]
w =  [ 0.79293619 -0.23660327]
[Finished in 12.9s]


然後看一下得到的分隔超平面:

                          

總結:

第一次看書上的程式碼會有些懵,不過看一遍SMO演算法推導,程式碼部分就基本無壓力了,這裡運用的是簡化版演算法,用時較長,不過得到的結果比較滿意,之後還會用完整的SMO演算法和Sklearn實現SVM,執行速度會大大提升,同時結果也比較滿意。