1. 程式人生 > 其它 >支援向量機及Python程式碼實現

支援向量機及Python程式碼實現

做機器學習的一定對支援向量機(support vector machine-SVM)頗為熟悉,因為在深度學習出現之前,SVM一直霸佔著機器學習老大哥的位子。他的理論很優美,各種變種改進版本也很多,比如latent-SVM, structural-SVM等。這節先來看看SVM的理論吧,在(圖一)中A圖表示有兩類的資料集,圖B,C,D都提供了一個線性分類器來對資料進行分類?但是哪個效果好一些?

(圖一)

可能對這個資料集來說,三個的分類器都一樣足夠好了吧,但是其實不然,這個只是訓練集,現實測試的樣本分佈可能會比較散一些,各種可能都有,為了應對這種情況,我們要做的就是儘可能的使得線性分類器離兩個資料集都儘可能的遠,因為這樣就會減少現實測試樣本越過分類器的風險,提高檢測精度。這種使得資料集到分類器之間的間距(margin)最大化的思想就是支援向量機的核心思想,而離分類器距離最近的樣本成為支援向量。既然知道了我們的目標就是為了尋找最大邊距,怎麼尋找支援向量?如何實現?下面以(圖二)來說明如何完成這些工作。

(圖二)

假設(圖二)中的直線表示一個超面,為了方面觀看顯示成一維直線,特徵都是超面維度加一維度的,圖中也可以看出,特徵是二維,而分類器是一維的。如果特徵是三維的,分類器就是一個平面。假設超面的解析式為

,那麼點A到超面的距離為

,下面給出這個距離證明:

(圖三)

在(圖三)中,青色菱形表示超面,Xn為資料集中一點,W是超面權重,而且W是垂直於超面的。證明垂直很簡單,假設X’和X’’都是超面上的一點,

,因此W垂直於超面。知道了W垂直於超面,那麼Xn到超面的距離其實就是Xn和超面上任意一點x的連線在W上的投影,如(圖四)所示:

(圖四)

而(Xn-X)在W上的投影

可通過(公式一)來計算,另外(公式一)也一併完成距離計算:

(公式一)

注意最後使用了配項法並且用了超面解析式

才得出了距離計算。有了距離就可以來推導我們剛開始的想法:使得分類器距所有樣本距離最遠,即最大化邊距,但是最大化邊距的前提是我們要找到支援向量,也就是離分類器最近的樣本點,此時我們就要完成兩個優化任務,找到離分類器最近的點(支援向量),然後最大化邊距。如(公式二)所示:

(公式二)

大括號裡面表示找到距離分類超面最近的支援向量,大括號外面則是使得超面離支援向量的距離最遠,要優化這個函式相當困難,目前沒有太有效的優化方法。但是我們可以把問題轉換一下,如果我們把大括號裡面的優化問題固定住,然後來優化外面的就很容易了,可以用現在的優化方法來求解,因此我們做一個假設,假設大括號裡的分子

等於1,那麼我們只剩下優化W咯,整個優化公式就可以寫成(公式三)的形式:

(公式三)

這下就簡單了,有等式約束的優化,約束式子為

,這個約束等式背後還有個小竅門,假設我們把樣本Xn的標籤設為1或者-1,當Xn在超面上面(或者右邊)時,帶入超面解析式得到大於0的值,乘上標籤1仍然為本身,可以表示離超面的距離;當Xn在超面下面(或者左邊)時,帶入超面解析式得到小於0的值,乘上標籤-1也是正值,仍然可以表示距離,因此我們把通常兩類的標籤0和1轉換成-1和1就可以把標籤資訊完美的融進等式約束中,(公式三)最後一行也體現出來咯。下面繼續說優化 求解(公式四)的方法,在最優化中,通常我們需要求解的最優化問題有如下幾類:

(i)無約束優化問題,可以寫為:

min f(x);

(ii)有等式約束的優化問題,可以寫為:

min f(x),

s.t. h_i(x) = 0; i =1, ..., n

(iii)有不等式約束的優化問題,可以寫為:

min f(x),

s.t. g_i(x) <= 0; i =1, ..., n

h_j(x) = 0; j =1,..., m

對於第(i)類的優化問題,常常使用的方法就是Fermat定理,即使用求取f(x)的導數,然後令其為零,可以求得候選最優值,再在這些候選值中驗證;如果是凸函式,可以保證是最優解。

對於第(ii)類的優化問題,常常使用的方法就是拉格朗日乘子法(LagrangeMultiplier),即把等式約束h_i(x)用一個係數與f(x)寫為一個式子,稱為拉格朗日函式,而係數稱為拉格朗日乘子。通過拉格朗日函式對各個變數求導,令其為零,可以求得候選值集合,然後驗證求得最優值。

對於第(iii)類的優化問題,常常使用的方法就是KKT條件。同樣地,我們把所有的等式、不等式約束與f(x)寫為一個式子,也叫拉格朗日函式,係數也稱拉格朗日乘子,通過一些條件,可以求出最優值的必要條件,這個條件稱為KKT條件。

而(公式三)很明顯符合第二類優化方法,因此可以使用拉格朗日乘子法來對其求解,在求解之前,我們先對(公式四)做個簡單的變換。最大化||W||的導數可以最小化||W||或者W’W,如(公式四)所示:

(公式四)

套進拉格朗日乘子法公式得到如(公式五)所示的樣子:

(公式五)

在(公式五)中通過拉格朗日乘子法函式分別對W和b求導,為了得到極值點,令導數為0,得到

,然後把他們代入拉格朗日乘子法公式裡得到(公式六)的形式:

(公式六)

(公式六)後兩行是目前我們要求解的優化函式,現在只需要做個二次規劃即可求出alpha,二次規劃優化求解如(公式七)所示:

(公式七)

通過(公式七)求出alpha後,就可以用(公式六)中的第一行求出W。到此為止,SVM的公式推導基本完成了,可以看出數學理論很嚴密,很優美,儘管有些同行們認為看起枯燥,但是最好沉下心來從頭看完,也不難,難的是優化。二次規劃求解計算量很大,在實際應用中常用SMO(Sequential minimal optimization)演算法,SMO演算法打算放在下節結合程式碼來說。

上節基本完成了SVM的理論推倒,尋找最大化間隔的目標最終轉換成求解拉格朗日乘子變數alpha的求解問題,求出了alpha即可求解出SVM的權重W,有了權重也就有了最大間隔距離,但是其實上節我們有個假設:就是訓練集是線性可分的,這樣求出的alpha在[0,infinite]。但是如果資料不是線性可分的呢?此時我們就要允許部分的樣本可以越過分類器,這樣優化的目標函式就可以不變,只要引入鬆弛變數

即可,它表示錯分類樣本點的代價,分類正確時它等於0,當分類錯誤時

,其中Tn表示樣本的真實標籤-1或者1,回顧上節中,我們把支援向量到分類器的距離固定為1,因此兩類的支援向量間的距離肯定大於1的,當分類錯誤時

肯定也大於1,如(圖五)所示(這裡公式和圖示序號都接上一節)。

(圖五)

這樣有了錯分類的代價,我們把上節(公式四)的目標函式上新增上這一項錯分類代價,得到如(公式八)的形式:

(公式八)

重複上節的拉格朗日乘子法步驟,得到(公式九):

(公式九)

多了一個Un乘子,當然我們的工作就是繼續求解此目標函式,繼續重複上節的步驟,求導得到(公式十):

(公式十)

又因為alpha大於0,而且Un大於0,所以0<alpha<C,為了解釋的清晰一些,我們把(公式九)的KKT條件也發出來(上節中的第三類優化問題),注意Un是大於等於0:

推導到現在,優化函式的形式基本沒變,只是多了一項錯分類的價值,但是多了一個條件,0<alpha<C,C是一個常數,它的作用就是在允許有錯誤分類的情況下,控制最大化間距,它太大了會導致過擬合,太小了會導致欠擬合。接下來的步驟貌似大家都應該知道了,多了一個C常量的限制條件,然後繼續用SMO演算法優化求解二次規劃,但是我想繼續把核函式也一次說了,如果樣本線性不可分,引入核函式後,把樣本對映到高維空間就可以線性可分,如(圖六)所示的線性不可分的樣本:

(圖六)

在(圖六)中,現有的樣本是很明顯線性不可分,但是加入我們利用現有的樣本X之間作些不同的運算,如(圖六)右邊所示的樣子,而讓f作為新的樣本(或者說新的特徵)是不是更好些?現在把X已經投射到高維度上去了,但是f我們不知道,此時核函式就該上場了,以高斯核函式為例,在(圖七)中選幾個樣本點作為基準點,來利用核函式計算f,如(圖七)所示:

(圖七)

這樣就有了f,而核函式此時相當於對樣本的X和基準點一個度量,做權重衰減,形成依賴於x的新的特徵f,把f放在上面說的SVM中繼續求解alpha,然後得出權重就行了,原理很簡單吧,為了顯得有點學術味道,把核函式也做個樣子加入目標函式中去吧,如(公式十一)所示:

(公式十一)

其中K(Xn,Xm)是核函式,和上面目標函式比沒有多大的變化,用SMO優化求解就行了,程式碼如下:

[python] view plaincopy

  1. def smoPK(dataMatIn, classLabels, C, toler, maxIter): #full Platt SMO
  2. oS = optStruct(mat(dataMatIn),mat(classLabels).transpose(),C,toler)
  3. iter = 0
  4. entireSet = True; alphaPairsChanged = 0
  5. while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)):
  6. alphaPairsChanged = 0
  7. if entireSet: #go over all
  8. for i in range(oS.m):
  9. alphaPairsChanged += innerL(i,oS)
  10. print "fullSet, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged)
  11. iter += 1
  12. else:#go over non-bound (railed) alphas
  13. nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]
  14. for i in nonBoundIs:
  15. alphaPairsChanged += innerL(i,oS)
  16. print "non-bound, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged)
  17. iter += 1
  18. if entireSet: entireSet = False #toggle entire set loop
  19. elif (alphaPairsChanged == 0): entireSet = True
  20. print "iteration number: %d" % iter
  21. return oS.b,oS.alphas

下面演示一個小例子,手寫識別。

(1)收集資料:提供文字檔案

(2)準備資料:基於二值影象構造向量

(3)分析資料:對影象向量進行目測

(4)訓練演算法:採用兩種不同的核函式,並對徑向基函式採用不同的設定來執行SMO演算法。

(5)測試演算法:編寫一個函式來測試不同的核函式,並計算錯誤率

(6)使用演算法:一個影象識別的完整應用還需要一些影象處理的只是,此demo略。