第八篇:支援向量機 (SVM)分類器原理分析與基本應用

穆晨發表於2017-01-19

前言

       支援向量機,也即SVM,號稱分類演算法,甚至機器學習界老大哥。其理論優美,發展相對完善,是非常受到推崇的演算法。

       本文將講解的SVM基於一種最流行的實現 - 序列最小優化,也即SMO。

       另外還將講解將SVM擴充套件到非線性可分的資料集上的大致方法。

預備術語

       1. 分割超平面:就是決策邊界

       2. 間隔:樣本點到分割超平面的距離

       3. 支援向量:離分割超平面距離最近的樣本點

演算法原理

       在前一篇文章 - 邏輯迴歸中,講到了通過擬合直線來進行分類。

       而擬合的中心思路是求錯誤估計函式取得最小值,得到的擬合直線是到各樣本點距離和最小的那條直線。

       然而,這樣的做法很多時候未必是最合適的。

       請看下圖:

      

       一般來說,邏輯迴歸得到的直線線段會是B或者C這樣的形式。而很顯然,從分類演算法的健壯性來說,D才是最佳的擬合線段。

       SVM分類演算法就是基於此思想:找到具有最小間隔的樣本點,然後擬合出一個到這些樣本點距離和最大的線段/平面

如何計算最優超平面

       1. 首先根據演算法思想 - "找到具有最小間隔的樣本點,然後擬合出一個到這些樣本點距離和最大的線段/平面。" 寫出目標函式:

      

       該式子的解就是待求的迴歸係數。

       然而,這是一個巢狀優化問題,非常難進行直接優化求解。為了解這個式子,還需要以下步驟。

       2. 不去計算內層的min優化,而是將距離值界定到一個範圍 - 大於1,即最近的樣本點,也即支援向量到超平面的距離為1。下圖可以清楚表示這個意思:

      

       去掉min操作,代之以界定:label * (wTx + b) >= 1。

       3. 這樣得到的式子就是一個帶不等式的優化問題,可以採用拉格朗日乘子法(KKT條件)去求解。

       具體步驟推論本文不給出。推導結果為:

      

       另外,可加入鬆弛係數 C,用於控制 "最大化間隔" 和"保證大部分點的函式間隔小於1.0" 這兩個目標的權重。

       將 α >= 0 條件改為 C >= α >= 0 即可。

       α 是用於求解過程中的一個向量,它和要求的結果迴歸係數是一一對應的關係。

       將其中的 α 解出後,便可依據如下兩式子(均為推導過程中出現的式子)進行轉換得到迴歸係數:

      

      

       說明: 要透徹理解完整的數學推導過程需要一些時間,可參考某位大牛的文章http://blog.csdn.net/v_july_v/article/details/7624837。

使用SMO - 高效優化演算法求解 α 值

       演算法思想:

              每次迴圈中選擇兩個 α 進行優化處理。一旦找到一對合適的 α,那麼就增大其中一個減小另外一個。

              所謂合適,是指必須符合兩個條件:1. 兩個 α 值必須要在 α 分隔邊界之外 2. 這兩個α 還沒有進行過區間化處理或者不在邊界上。

       使用SMO求解 α 虛擬碼:

1 建立一個 alpha 向量並將其初始化為全0
2 當迭代次數小於最大迭代次數(外迴圈):
3     對資料集中的每個向量(內迴圈):
4         如果該資料向量可以被優化
5         隨機選擇另外一個資料向量
6         同時優化這兩個向量
7         如果都不能被優化,推出內迴圈。
8     如果所有向量都沒有被優化,則增加迭代數目,繼續下一次的迴圈。

       實現及測試程式碼:

  1 #!/usr/bin/env python
  2 # -*- coding:UTF-8 -*-
  3 
  4 '''
  5 Created on 20**-**-**
  6 
  7 @author: fangmeng
  8 '''
  9 
 10 from numpy import *
 11 from time import sleep
 12 
 13 #=====================================
 14 # 輸入:
 15 #        fileName: 資料檔案
 16 # 輸出:
 17 #        dataMat: 測試資料集
 18 #        labelMat: 測試分類標籤集
 19 #=====================================
 20 def loadDataSet(fileName):
 21     '載入資料'
 22     
 23     dataMat = []; labelMat = []
 24     fr = open(fileName)
 25     for line in fr.readlines():
 26         lineArr = line.strip().split('\t')
 27         dataMat.append([float(lineArr[0]), float(lineArr[1])])
 28         labelMat.append(float(lineArr[2]))
 29     return dataMat,labelMat
 30 
 31 #=====================================
 32 # 輸入:
 33 #        i: 返回結果不等於該引數
 34 #        m: 指定隨機範圍的引數
 35 # 輸出:
 36 #        j: 0-m內不等於i的一個隨機數
 37 #=====================================
 38 def selectJrand(i,m):
 39     '隨機取數'
 40     
 41     j=i
 42     while (j==i):
 43         j = int(random.uniform(0,m))
 44     return j
 45 
 46 #=====================================
 47 # 輸入:
 48 #        aj: 資料物件
 49 #        H: 資料物件最大值
 50 #        L: 資料物件最小值
 51 # 輸出:
 52 #        aj: 定界後的資料物件。最大H 最小L
 53 #=====================================
 54 def clipAlpha(aj,H,L):
 55     '為aj定界'
 56     
 57     if aj > H: 
 58         aj = H
 59     if L > aj:
 60         aj = L
 61     return aj
 62 
 63 #=====================================
 64 # 輸入:
 65 #        dataMatIn: 資料集
 66 #        classLabels: 分類標籤集
 67 #        C: 鬆弛引數
 68 #        toler: 榮錯率
 69 #        maxIter: 最大迴圈次數
 70 # 輸出:
 71 #        b: 偏移
 72 #        alphas: 拉格朗日對偶因子
 73 #=====================================
 74 def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
 75     'SMO演算法求解alpha'
 76     
 77     # 資料格式轉化
 78     dataMatrix = mat(dataMatIn); 
 79     labelMat = mat(classLabels).transpose()
 80     m,n = shape(dataMatrix)
 81     alphas = mat(zeros((m,1)))
 82     
 83     
 84     iter = 0 
 85     b = 0
 86     while (iter < maxIter):
 87         # alpha 改變標記
 88         alphaPairsChanged = 0
 89         
 90         # 對所有資料集
 91         for i in range(m):
 92             # 預測結果
 93             fXi = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b
 94             # 預測結果與實際的差值
 95             Ei = fXi - float(labelMat[i])
 96             # 如果差值太大則進行優化
 97             if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
 98                 # 隨機選擇另外一個樣本
 99                 j = selectJrand(i,m)
100                 # 計算另外一個樣本的預測結果以及差值
101                 fXj = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b
102                 Ej = fXj - float(labelMat[j])
103                 # 暫存當前alpha值對
104                 alphaIold = alphas[i].copy(); 
105                 alphaJold = alphas[j].copy();
106                 # 確定alpha的最大最小值
107                 if (labelMat[i] != labelMat[j]):
108                     L = max(0, alphas[j] - alphas[i])
109                     H = min(C, C + alphas[j] - alphas[i])
110                 else:
111                     L = max(0, alphas[j] + alphas[i] - C)
112                     H = min(C, alphas[j] + alphas[i])
113                 if L==H: 
114                     pass
115                 # eta為alphas[j]的最優修改量
116                 eta = 2.0 * dataMatrix[i,:]*dataMatrix[j,:].T - dataMatrix[i,:]*dataMatrix[i,:].T - dataMatrix[j,:]*dataMatrix[j,:].T
117                 if eta >= 0:
118                     print "eta>=0"; continue
119                 # 訂正alphas[j]
120                 alphas[j] -= labelMat[j]*(Ei - Ej)/eta
121                 alphas[j] = clipAlpha(alphas[j],H,L)
122                 # 如果alphas[j]發生了輕微變化
123                 if (abs(alphas[j] - alphaJold) < 0.00001): 
124                     continue
125                 # 訂正alphas[i]
126                 alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j])
127                 
128                 # 訂正b
129                 b1 = b - Ei- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[i,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[i,:]*dataMatrix[j,:].T
130                 b2 = b - Ej- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[j,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[j,:]*dataMatrix[j,:].T
131                 if (0 < alphas[i]) and (C > alphas[i]): b = b1
132                 elif (0 < alphas[j]) and (C > alphas[j]): b = b2
133                 else: b = (b1 + b2)/2.0
134                 
135                 # 更新修改標記引數
136                 alphaPairsChanged += 1
137                 
138         if (alphaPairsChanged == 0): iter += 1
139         else: iter = 0
140         
141     return b,alphas
142     
143 def test():
144     '測試'
145     
146     dataArr, labelArr = loadDataSet('/home/fangmeng/testSet.txt')
147     b, alphas = smoSimple(dataArr, labelArr, 0.6, 0.001, 40)
148     print b
149     print alphas[alphas>0]
150     
151 
152 if __name__ == '__main__':
153     test()

       其中,testSet.txt資料檔案格式為三列,前兩列特徵,最後一列分類結果。

       測試結果:

      

       結果具有隨機性,多次執行的結果不一定一致。

       得到 alphas 陣列和 b 向量就能直接算到迴歸係數了,參考上述程式碼 93 行,稍作變換即可。

非線性可分情況的大致解決思路

       當資料分析圖類似如下的情況:

      

       則顯然無法擬合出一條直線來。碰到這種情況的解決辦法是使用核函式 - 將在低維處理非線性問題轉換為在高維處理線性問題。

       也就是說,將在SMO中所有出現了向量內積的地方都替換成核函式處理。

       具體的用法,程式碼本文不做講解。

小結

       支援向量機是分類演算法中目前用的最多的,也是最為完善的。

       關於支援向量機的討論遠遠不會止於此,本文初衷僅僅是對這個演算法有一定的瞭解,認識

       若是在以後的工作中需要用到這方面的知識,還需要全面深入的學習,研究。

相關文章