第6章 支持向量機
如果公式不現實,請使用MarkDown的編輯器,加載 js 就可以看見了
<script type="text/javascript" src="
http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=default"></script>
支持向量機 概述
支持向量機(Support Vector Machines, SVM):是一種機器學習算法。
- 支持向量(Support Vector)就是離分隔超平面最近的那些點。
- 機(Machine)就是表示一種算法,而不是表示機器。
支持向量機 場景
- 要給左右兩邊的點進行分類
- 明顯發現:選擇D會比B、C分隔的效果要好很多。
支持向量機 原理
SVM 工作原理
對於上述的蘋果和香蕉,我們想象為2種水果類型的炸彈。(保證距離最近的炸彈,距離它們最遠)
- 尋找最大分類間距
- 轉而通過拉格朗日函數求優化的問題
- 數據可以通過畫一條直線就可以將它們完全分開,這組數據叫
線性可分(linearly separable)數據,而這條分隔直線稱為分隔超平面(separating hyperplane)。 - 如果數據集上升到1024維呢?那么需要1023維來分隔數據集,也就說需要N-1維的對象來分隔,這個對象叫做
超平面(hyperlane),也就是分類的決策邊界。
尋找最大間隔
為什么尋找最大間隔
摘錄地址:http://slideplayer.com/slide/8610144 (第12條信息)
Support Vector Machines: Slide 12 Copyright © 2001, 2003, Andrew W. Moore Why Maximum Margin?
denotes +1 denotes -1 f(x,w,b) = sign(w. x - b) The maximum margin linear classifier is the linear classifier with the, um, maximum margin.
This is the simplest kind of SVM (Called an LSVM) Support Vectors are those datapoints that the margin pushes up against
1.Intuitively this feels safest.
2.If we’ve made a small error in the location of the boundary (it’s been jolted in its perpendicular direction) this gives us least chance of causing a misclassification.
3.CV is easy since the model is immune to removal of any non-support-vector datapoints.
4.There’s some theory that this is a good thing.
5.Empirically it works very very well.
* * *
1. 直覺上是安全的
2. 如果我們在邊界的位置發生了一個小錯誤(它在垂直方向上被顛倒),這給我們最小的錯誤分類機會。
3. CV(Computer Vision 計算機視覺 - 這縮寫看着可怕�)很容易,因為該模型對任何非支持向量數據點的去除是免疫的。
4. 有一些理論,這是一件好事。
5. 通常它的工作非常好。
怎么尋找最大間隔
點到超平面的距離
- 分隔超平面
函數間距: \(y(x)=w^Tx+b\) - 分類的結果: \(f(x)=sign(w^Tx+b)\) (sign表示>0為1,<0為-1,=0為0)
- 點到超平面的
幾何間距: \(d(x)=(w^Tx+b)/||w||\) (||w||表示w矩陣的二范式=> \(\sqrt{w*w^T}\), 點到超平面的距離也是類似的)
拉格朗日乘子法
- 類別標簽用-1、1,是為了后期方便 \(lable*(w^Tx+b)\) 的標識和距離計算;如果 \(lable*(w^Tx+b)>0\) 表示預測正確,否則預測錯誤。
- 現在目標很明確,就是要找到
w和b,因此我們必須要找到最小間隔的數據點,也就是前面所說的支持向量。- 也就說,讓最小的距離取最大.(最小的距離:就是最小間隔的數據點;最大:就是最大間距,為了找出最優超平面--最終就是支持向量)
- 目標函數:\(arg: max_{關於w, b} \left( min[lable*(w^Tx+b)]*\frac{1}{||w||} \right) \)
- 如果 \(lable*(w^Tx+b)>0\) 表示預測正確,也稱
函數間隔,\(||w||\) 可以理解為歸一化,也稱幾何間隔。 - 令 \(lable*(w^Tx+b)>=1\), 因為0~1之間,得到的點是存在誤判的可能性,所以要保障 \(min[lable*(w^Tx+b)]=1\),才能更好降低噪音數據影響。
- 所以本質上是求 \(arg: max_{關於w, b} \frac{1}{||w||} \);也就說,我們約束(前提)條件是: \(lable*(w^Tx+b)=1\)
- 如果 \(lable*(w^Tx+b)>0\) 表示預測正確,也稱
- 新的目標函數求解: \(arg: max_{關於w, b} \frac{1}{||w||} \)
- => 就是求: \(arg: min_{關於w, b} ||w|| \) (求矩陣會比較麻煩,如果x只是 \(\frac{1}{2}*x^2\) 的偏導數,那么。。同樣是求最小值)
- => 就是求: \(arg: min_{關於w, b} (\frac{1}{2}*||w||^2)\) (二次函數求導,求極值,平方也方便計算)
- 本質上就是求線性不等式的二次優化問題(求分隔超平面,等價於求解相應的凸二次規划問題)
- 通過拉格朗日乘子法,求二次優化問題
- 假設需要求極值的目標函數 (objective function) 為 f(x,y),限制條件為 φ(x,y)=M # M=1
- 設g(x,y)=M-φ(x,y) # 臨時φ(x,y)表示下文中 \(label*(w^Tx+b)\)
- 定義一個新函數: F(x,y,λ)=f(x,y)+λg(x,y)
- a為λ(a>=0),代表要引入的拉格朗日乘子(Lagrange multiplier)
- 那么: \(L(w,b,\alpha)=\frac{1}{2} * ||w||^2 + \sum_{i=1}^{n} \alpha_i * [1 - label * (w^Tx+b)]\)
- 因為:\(label*(w^Tx+b)>=1, \alpha>=0\) , 所以 \(\alpha*[1-label*(w^Tx+b)]<=0\) , \(\sum_{i=1}^{n} \alpha_i * [1-label*(w^Tx+b)]<=0\)
- 相當於求解: \(max_{關於\alpha} L(w,b,\alpha) = \frac{1}{2} *||w||^2\)
- 如果求: \(min_{關於w, b} \frac{1}{2} *||w||^2\) , 也就是要求: \(min_{關於w, b} \left( max_{關於\alpha} L(w,b,\alpha)\right)\)
- 現在轉化到對偶問題的求解
- \(min_{關於w, b} \left(max_{關於\alpha} L(w,b,\alpha) \right) \) >= \(max_{關於\alpha} \left(min_{關於w, b}\ L(w,b,\alpha) \right) \)
- 現在分2步
- 先求: \(min_{關於w, b} L(w,b,\alpha)=\frac{1}{2} * ||w||^2 + \sum_{i=1}^{n} \alpha_i * [1 - label * (w^Tx+b)]\)
- 就是求
L(w,b,a)關於[w, b]的偏導數, 得到w和b的值,並化簡為:L和a的方程。 - 參考: 如果公式推導還是不懂,也可以參考《統計學習方法》李航-P103<學習的對偶算法>

- 終於得到課本上的公式: \(max_{關於\alpha} \left( \sum_{i=1}^{m} \alpha_i - \frac{1}{2} \sum_{i, j=1}^{m} label_i·label_j·\alpha_i·\alpha_j·<x_i, x_j> \right) \)
- 約束條件: \(a>=0\) 並且 \(\sum_{i=1}^{m} a_i·label_i=0\)
松弛變量(slack variable)
- 我們知道幾乎所有的數據都不那么干凈, 通過引入松弛變量來
允許數據點可以處於分隔面錯誤的一側。 - 約束條件: \(C>=a>=0\) 並且 \(\sum_{i=1}^{m} a_i·label_i=0\)
- 這里常量C用於控制“最大化間隔”和“保證大部分點的函數間隔小於1.0” 這兩個目標的權重。
- 常量C是一個常數,我們通過調節該參數得到不同的結果。一旦求出了所有的alpha,那么分隔超平面就可以通過這些alpha來表示。
- 這一結論十分直接,SVM中的主要工作就是要求解 alpha.
SMO 高效優化算法
- SVM有很多種實現,最流行的一種實現是:
序列最小優化(Sequential Minimal Optimization, SMO)算法。 - 下面還會介紹一種稱為
核函數(kernel)的方式將SVM擴展到更多數據集上。 - 注意:
SVM幾何含義比較直觀,但其算法實現較復雜,牽扯大量數學公式的推導。
序列最小優化(Sequential Minimal Optimization, SMO)
- 創建作者:John Platt
- 創建時間:1996年
- SMO用途:用於訓練 SVM
- SMO目標:求出一系列 alpha 和 b,一旦求出 alpha,就很容易計算出權重向量 w 並得到分隔超平面。
- SMO思想:是將大優化問題分解為多個小優化問題來求解的。
- SMO原理:每次循環選擇兩個 alpha 進行優化處理,一旦找出一對合適的 alpha,那么就增大一個同時減少一個。
- 這里指的合適必須要符合一定的條件
- 這兩個 alpha 必須要在間隔邊界之外
- 這兩個 alpha 還沒有進行過區間化處理或者不在邊界上。
- 之所以要同時改變2個 alpha;原因是我們有一個約束條件: \(\sum_{i=1}^{m} a_i·label_i=0\);如果只是修改一個 alpha,很可能導致約束條件失效。
- 這里指的合適必須要符合一定的條件
SMO 偽代碼大致如下:
創建一個 alpha 向量並將其初始化為0向量
當迭代次數小於最大迭代次數時(外循環)
對數據集中的每個數據向量(內循環):
如果該數據向量可以被優化
隨機選擇另外一個數據向量
同時優化這兩個向量
如果兩個向量都不能被優化,退出內循環
如果所有向量都沒被優化,增加迭代數目,繼續下一次循環
SVM 開發流程
收集數據:可以使用任意方法。
准備數據:需要數值型數據。
分析數據:有助於可視化分隔超平面。
訓練算法:SVM的大部分時間都源自訓練,該過程主要實現兩個參數的調優。
測試算法:十分簡單的計算過程就可以實現。
使用算法:幾乎所有分類問題都可以使用SVM,值得一提的是,SVM本身是一個二類分類器,對多類問題應用SVM需要對代碼做一些修改。
SVM 算法特點
優點:泛化(由具體的、個別的擴大為一般的,就是說:模型訓練完后的新樣本)錯誤率低,計算開銷不大,結果易理解。
缺點:對參數調節和核函數的選擇敏感,原始分類器不加修改僅適合於處理二分類問題。
使用數據類型:數值型和標稱型數據。
課本案例(無核函數)
項目概述
對小規模數據點進行分類
開發流程
收集數據
文本文件格式:
3.542485 1.977398 -1 3.018896 2.556416 -1 7.551510 -1.580030 1 2.114999 -0.004466 -1 8.127113 1.274372 1
准備數據
def loadDataSet(fileName): """ 對文件進行逐行解析,從而得到第行的類標簽和整個特征矩陣 Args: fileName 文件名 Returns: dataMat 特征矩陣 labelMat 類標簽 """ 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 smoSimple(dataMatIn, classLabels, C, toler, maxIter): """smoSimple Args: dataMatIn 特征集合 classLabels 類別標簽 C 松弛變量(常量值),允許有些數據點可以處於分隔面的錯誤一側。 控制最大化間隔和保證大部分的函數間隔小於1.0這兩個目標的權重。 可以通過調節該參數達到不同的結果。 toler 容錯率(是指在某個體系中能減小一些因素或選擇對某個系統產生不穩定的概率。) maxIter 退出前最大的循環次數 Returns: b 模型的常量值 alphas 拉格朗日乘子 """ dataMatrix = mat(dataMatIn) # 矩陣轉置 和 .T 一樣的功能 labelMat = mat(classLabels).transpose() m, n = shape(dataMatrix) # 初始化 b和alphas(alpha有點類似權重值。) b = 0 alphas = mat(zeros((m, 1))) # 沒有任何alpha改變的情況下遍歷數據的次數 iter = 0 while (iter < maxIter): # w = calcWs(alphas, dataMatIn, classLabels) # print("w:", w) # 記錄alpha是否已經進行優化,每次循環時設為0,然后再對整個集合順序遍歷 alphaPairsChanged = 0 for i in range(m): # print 'alphas=', alphas # print 'labelMat=', labelMat # print 'multiply(alphas, labelMat)=', multiply(alphas, labelMat) # 我們預測的類別 y[i] = w^Tx[i]+b; 其中因為 w = Σ(1~n) a[n]*lable[n]*x[n] fXi = float(multiply(alphas, labelMat).T*(dataMatrix*dataMatrix[i, :].T)) + b # 預測結果與真實結果比對,計算誤差Ei Ei = fXi - float(labelMat[i]) # 約束條件 (KKT條件是解決最優化問題的時用到的一種方法。我們這里提到的最優化問題通常是指對於給定的某一函數,求其在指定作用域上的全局最小值) # 0<=alphas[i]<=C,但由於0和C是邊界值,我們無法進行優化,因為需要增加一個alphas和降低一個alphas。 # 表示發生錯誤的概率:labelMat[i]*Ei 如果超出了 toler, 才需要優化。至於正負號,我們考慮絕對值就對了。 ''' # 檢驗訓練樣本(xi, yi)是否滿足KKT條件 yi*f(i) >= 1 and alpha = 0 (outside the boundary) yi*f(i) == 1 and 0<alpha< C (on the boundary) yi*f(i) <= 1 and alpha = C (between the boundary) ''' if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)): # 如果滿足優化的條件,我們就隨機選取非i的一個點,進行優化比較 j = selectJrand(i, m) # 預測j的結果 fXj = float(multiply(alphas, labelMat).T*(dataMatrix*dataMatrix[j, :].T)) + b Ej = fXj - float(labelMat[j]) alphaIold = alphas[i].copy() alphaJold = alphas[j].copy() # L和H用於將alphas[j]調整到0-C之間。如果L==H,就不做任何改變,直接執行continue語句 # labelMat[i] != labelMat[j] 表示異側,就相減,否則是同側,就相加。 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是alphas[j]的最優修改量,如果eta==0,需要退出for循環的當前迭代過程 # 參考《統計學習方法》李航-P125~P128<序列最小最優化算法> 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 # 並使用輔助函數,以及L和H對其進行調整 alphas[j] = clipAlpha(alphas[j], H, L) # 檢查alpha[j]是否只是輕微的改變,如果是的話,就退出for循環。 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]) # 在對alpha[i], alpha[j] 進行優化之后,給這兩個alpha值設置一個常數b。 # w= Σ[1~n] ai*yi*xi => b = yj- Σ[1~n] ai*yi(xi*xj) # 所以: b1 - b = (y1-y) - Σ[1~n] yi*(a1-a)*(xi*x1) # 為什么減2遍? 因為是 減去Σ[1~n],正好2個變量i和j,所以減2遍 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 alphaPairsChanged += 1 




