對SVM與核函式的理解及sklearn引數詳解
支援向量機是在深度學習流行開來之前,效能表現最好的一種機器學習方法。在看這篇blog之前,預設讀者已經有了對支援向量機的基本概念的認識。
一、支援向量機的進一步理解
支援向量機的優化目標在邏輯迴歸優化目標基礎上進一步產生的。具體優化目標不說了,參看各種svm的書籍和部落格。
1、提升線性迴歸值的劃分要求
具體來講,邏輯迴歸的優化目標是使用sigmoid函式將線性迴歸 weight * x + b 進行非線性對映,然後得到大於0屬於正類,小於0屬於反類。然而這樣的分類造成了一個問題,比如在測試一個樣本時,我們得到了一個線性迴歸的值0.1,按照sigmoid函式進行對映,0.1對應的函式值是略微大於0.5,該樣本應當屬於正類。
但是這時有個問題,把判定樣本是正類還是反類當做一個隨機變數,觀測到的值是0.1。我們只是根據當前觀測到的值進行判定,得到了正類的結果,事實上是正還是負依然不確定,這屬於概率學中的假設檢驗內容,對於0.1這個樣本的線性迴歸值,我們有很大的置信度它屬於反類,因為0.1這個值太小了,太靠近0。即在靠近0附近的線性迴歸取值造成了一種問題,越靠近0,邏輯迴歸的判定越不準確。
因此,SVM提出了必須使線性迴歸值大於1時才判定為正,小於-1時判定為反類,我們把判定的閾值線劃在了1和-1,此時的判定當然要比在0處更可信,因為此時根據sigmoid函式判定錯誤的置信度會小很多。
進一步,我們知道兩個向量的內積的演算法以及向量夾角與內積值的關係。若要使兩個向量的內積值最大,其夾角為0最好。weight * x就是這樣,也就是說,權重和樣本的屬性向量的夾角為0時,即方向一致時,取值最大,這樣,線性迴歸的取值就能夠很方便地大於1或者小於-1。而根據空間幾何知識,超平面 weight * x + b = 0 的法線方向就是 weight 的方向
2、最大間隔
在優化目標的條件確定之後,最大間隔就是要使 weight最大化,這樣找到的超平面的法線方向可以使margin最大。使用歐氏距離的平方,這樣可以保證凸函式,且求導簡便。
3、核函式
核函式的作用很簡單,就是強硬將不可分的資料提升一個維度,讓其可分。最常用的就是高斯核,核函式難以理解在於它沒有提供一個直觀的理解。因為不論是不是核函式,但凡是函式,都是將資料從 x 維對映到了 y 維。我們可以假想一個核函式,正類的樣本經過它,取值都是1,反類經過核函式,核函式的取值都是0
核函式作為一個函式,它的定義域就是樣本的屬性空間,值域就是一個實數數軸。可以理解所有核函式都是在定義一種距離,定義屬性空間中,兩個樣本之間的距離,大於某個距離判為正,小於某個距離判為反類。核函式的作用就是強行拉出距離來。
核函式就是將線性資料非線性化,讓資料在高維可分。這種方法叫做核方法。可以推廣至其它機器學習演算法。但是,似乎在其它方法中的應用都沒有在SVM上好。
二、程式碼
1、 使用sklearn中的svm庫
Svm庫中包含了如下幾個svm類,svc,svr,以SVC為例來具體解釋一下所有的引數的含義,以及方法和屬性。
SVC引數解釋
(1)C: 目標函式的懲罰係數C,用來平衡分類間隔margin和錯分樣本的,default C = 1.0;
(2)kernel:引數選擇有RBF, Linear, Poly, Sigmoid,precomputed或者自定義一個核函式, 預設的是"RBF",即徑向基核,也就是高斯核函式;而Linear指的是線性核函式,Poly指的是多項式核,Sigmoid指的是雙曲正切函式tanh核。
在這裡需要具體講一下各種核函式怎麼使用:
RBF核:高斯核函式就是在屬性空間中找到一些點,這些點可以是也可以不是樣本點,把這些點當做base,以這些base為圓心向外擴充套件,擴充套件半徑即為頻寬,即可劃分資料。換句話說,在屬性空間中找到一些超圓,用這些超圓來判定正反類。
線性核和多項式核:這兩種核的作用也是首先在屬性空間中找到一些點,把這些點當做base,核函式的作用就是找與該點距離和角度滿足某種關係的樣本點。當樣本點與該點的夾角近乎垂直時,兩個樣本的歐式長度必須非常長才能保證滿足線性核函式大於0;而當樣本點與base點的方向相同時,長度就不必很長;而當方向相反時,核函式值就是負的,被判為反類。即,它在空間上劃分出一個梭形,按照梭形來進行正反類劃分。
Sigmoid核:同樣地是定義一些base,核函式就是將線性核函式經過一個tanh函式進行處理,把值域限制在了-1到1上。
總之,都是在定義距離,大於該距離,判為正,小於該距離,判為負。至於選擇哪一種核函式,要根據具體的樣本分佈情況來確定。
(3)degree:degree決定了多項式的最高次冪;
(4)gamma:核函式的係數('Poly', 'RBF' and 'Sigmoid'), 預設是gamma = 1 / n_features,即核函式的頻寬,超圓的半徑;
(5)coef0:核函式中的獨立項,'RBF' and 'Poly'有效,即加在這兩種核函式後面的bias值,次冪為0的項,預設是0.0,即認為多項式的核的常數項為0;
(6)probablity: 布林取值,預設是false,指的是預測判定的時候是否採用概率估計;
(7)shrinking:是否進行啟發式;
(8)tol(default = 1e - 3): svm結束標準的精度,即容忍1000分類裡出現一個錯誤;
(9)cache_size: 制定訓練所需要的記憶體(以MB為單位);
(10)class_weight: 正類和反類的樣本數量是不一樣的,這裡就會出現類別不平衡問題,該引數就是指每個類所佔據的權重,預設為1,即預設正類樣本數量和反類一樣多,也可以用一個字典dict指定每個類的權值,或者選擇預設的引數balanced,指按照每個類中樣本數量的比例自動分配權值。
(11)verbose: 在訓練資料完成之後,會把訓練的詳細資訊全部輸出打印出來,可以看到訓練了多少步,訓練的目標值是多少;但是在多執行緒環境下,由於多個執行緒會導致執行緒變數通訊有困難,因此verbose選項的值就是出錯,所以多執行緒下不要使用該引數。
(12)max_iter: 最大迭代次數,這個是硬限制,它的優先順序要高於tol引數,不論訓練的標準和精度達到要求沒有,都要停止訓練。預設值是-1,指沒有最大次數限制;
(13)decision_function_shape : 原始的SVM只適用於二分類問題,如果要將其擴充套件到多類分類,就要採取一定的融合策略,這裡提供了三種選擇。‘ovo’ 一對一,決策所使用的返回的是(樣本數,類別數*(類別數-1)/2), ‘ovr’ 一對多,返回的是(樣本數,類別數),或者None,就是不採用任何融合策略, 預設是ovr,因為此種效果要比oro略好一點。
(14)random_state :在使用SVM訓練資料時,要先將訓練資料打亂順序,用來提高分類精度,這裡就用到了偽隨機序列。如果該引數給定的是一個整數,則該整數就是偽隨機序列的種子值;如果給定的就是一個隨機例項,則採用給定的隨機例項來進行打亂處理;如果啥都沒給,則採用預設的 np.random例項來處理。
提供的屬性:
(1)support_ : 是一個array型別,它指的是訓練出的分類模型的支援向量的索引,即在所有的訓練樣本中,哪些樣本成為了支援向量。
(2)support_vectors_: 支援向量的集合,即彙總了當前模型的所有的支援向量。
(3)n_support_ : 比如SVC將資料集分成了4類,該屬性表示了每一類的支援向量的個數。
(4)dual_coef_ :array, shape = [n_class-1, n_SV]對偶係數
支援向量在決策函式中的係數,在多分類問題中,這個會有所不同。
(5)coef_ : array,shape = [n_class-1, n_features]
該引數僅線上性核時才有效,指的是每一個屬性被分配的權值。
(6)intercept_ :array, shape = [n_class * (n_class-1) / 2]決策函式中的常數項bias。和coef_共同構成決策函式的引數值。
from sklearn import svm import numpy as np import pylab as pl # we create 40 separable points np.random.seed(2) X = np.r_[np.random.randn(20, 2) - [2, 2], np.random.randn(20, 2) + [2, 2]] # 一個是隨機數加上均值為2,方差為2的正太分佈, Y = [0] * 20 + [1] * 20 print "++++++++++++++++++++++++++++++++++++++" clf = svm.SVC(kernel='linear', verbose=True) clf.fit(X, Y) print "對訓練資料的準確性統計: ", clf.score(X, Y) # 獲取分割超平面 w = clf.coef_[0] # 分割超平面的引數權值,由於屬性只有兩維,所以 weight 也只有 2 維 a = -w[0] / w[1] # 超平面直線的斜率 xx = np.linspace(-5, 5) # 將 -5 到 5 上的數均分 yy = a * xx - (clf.intercept_[0]) / w[1] # 畫出經過支援向量的直線 b = clf.support_vectors_[0] # 第一個支援向量,一定屬於正類 yy_down = a * xx + (b[1] - a * b[0]) # 經過支援向量的點的直線 b = clf.support_vectors_[-1] # 最後一個支援向量,一定屬於反類 yy_up = a * xx + (b[1] - a * b[0]) print "w: ", w print "a: ", a print "支援向量: ", clf.support_vectors_ print "正類和負類的支援向量的索引: ", clf.support_ print "每個類的支援向量的個數: ", clf.n_support_ print "超平面的係數,僅在核函式為 RBF 和 Poly 時有效: ", clf.coef_ pl.plot(xx, yy, 'k-') pl.plot(xx, yy_down, 'k--') # 分界線 pl.plot(xx, yy_up, 'k--') pl.scatter(clf.support_vectors_[:, 0], # 正類的支援向量 clf.support_vectors_[:, 1], # 反類的支援向量 s=80, # 點的半徑大小 facecolors='red') # face colors,支援向量的樣本點的顏色 pl.scatter(X[:, 0], # 樣本的 x 軸資料 X[:, 1], # 樣本集的 y 軸資料 c=Y, # 分類結果集 cmap=pl.cm.Paired) # cmap 確定顏色 pl.axis('tight') pl.show()
2、支援向量機在何時失效?
即在高維上仍然不可分,導致分類器失效。我們可以假設,如果核函式極為複雜,能夠擬合任何函式,則支援向量機的精確度會是100%,然而正如下面sklearn包中提供的核函式,都是一些初等函式,根據高等代數泰勒公式,多項式函式隨著次數上升,可以以任意精度逼近任何函式,那麼在程式碼中規定最高次數為10,則精度上升,當然這個計算代價是非常大的。
比如上面的例子,將程式碼中的變數X改成
X = np.r_[np.random.randn(20, 2), np.random.randn(20, 2) + [2, 2]]此時,X樣本資料在屬性空間無法做到線性可分,需要使用核函式對映到高維。這裡,我們將SVC的引數改為多項式核函式,degree表示多項式核函式的次冪,我們可以多次調解degree的值,從2到10,觀察預測資料的準確率。
clf = svm.SVC(kernel='poly', degree=5, verbose=True)
得到的預測準確率就無法達到100%了,取值為0.925,隨著degree的升高,準確率能夠逐漸升高,此時,迭代次數也越來越多,使用verbose引數可以看到,迭代次數從7次即可收斂得到超平面,直到300次才能收斂,這就是多項式函式在擬合複雜曲面時候的劣勢。
然而,繼續升高多項式核函式的次冪,準確率的提升微乎其微,也就是說,SVM演算法達到了自己的效能頂峰,即資料在屬性空間上出現了雜糅,細緻的核函式可以描述,但是代價巨大。