機器學習與支持向量機

第一層、瞭解SVM

1.0、什麼是支持向量機SVM

    要明白什麼是SVM,便得從分類說起。

    分類作爲數據挖掘領域中一項非常重要的任務,它的目的是學會一個分類函數或分類模型(或者叫做分類器),而支持向量機本身便是一種監督式學習的方法(至於具體什麼是監督學習與非監督學習,請參見此係列Machine L&Data Mining第一篇),它廣泛的應用於統計分類以及迴歸分析中。

    支持向量機(SVM)是90年代中期發展起來的基於統計學習理論的一種機器學習方法,通過尋求結構化風險最小來提高學習機泛化能力,實現經驗風險和置信範圍的最小化,從而達到在統計樣本量較少的情況下,亦能獲得良好統計規律的目的。

    通俗來講,它是一種二類分類模型,其基本模型定義爲特徵空間上的間隔最大的線性分類器,即支持向量機的學習策略便是間隔最大化,最終可轉化爲一個凸二次規劃問題的求解。

1.1、線性分類

    OK,在講SVM之前,咱們必須先弄清楚一個概念:線性分類器(也可以叫做感知機,這裏的機表示的是一種算法,本文第三部分、證明SVM中會詳細闡述)。

1.1.1、分類標準

    這裏我們考慮的是一個兩類的分類問題,數據點用 x 來表示,這是一個 n 維向量,w^T中的T代表轉置,而類別用 y 來表示,可以取 1 或者 -1 ,分別代表兩個不同的類。一個線性分類器的學習目標就是要在 n 維的數據空間中找到一個分類超平面,其方程可以表示爲:

                                                           

    上面給出了線性分類的定義描述,但或許讀者沒有想過:爲何用y取1 或者 -1來表示兩個不同的類別呢?其實,這個1或-1的分類標準起源於logistic迴歸,爲了完整和過渡的自然性,咱們就再來看看這個logistic迴歸。

1.1.2、1或-1分類標準的起源:logistic迴歸

    Logistic迴歸目的是從特徵學習出一個0/1分類模型,而這個模型是將特性的線性組合作爲自變量,由於自變量的取值範圍是負無窮到正無窮。因此,使用logistic函數(或稱作sigmoid函數)將自變量映射到(0,1)上,映射後的值被認爲是屬於y=1的概率。     形式化表示就是     假設函數

    其中x是n維特徵向量,函數g就是logistic函數。     的圖像是

    可以看到,將無窮映射到了(0,1)。     而假設函數就是特徵屬於y=1的概率。

     當我們要判別一個新來的特徵屬於哪個類時,只需求,若大於0.5就是y=1的類,反之屬於y=0類。

1.4、最大間隔分類器Maximum Margin Classifier的定義

    於此,我們已經很明顯的看出,函數間隔functional margin 和 幾何間隔geometrical margin 相差一個的縮放因子。按照我們前面的分析,對一個數據點進行分類,當它的 margin 越大的時候,分類的 confidence 越大。對於一個包含 n 個點的數據集,我們可以很自然地定義它的 margin 爲所有這 n 個點的 margin 值中最小的那個。於是,爲了使得分類的 confidence 高,我們希望所選擇的超平面hyper plane 能夠最大化這個 margin 值。

    通過上節,我們已經知道:

1、functional margin 明顯是不太適合用來最大化的一個量,因爲在 hyper plane 固定以後,我們可以等比例地縮放 w 的長度和 b 的值,這樣可以使得的值任意大,亦即 functional margin可以在 hyper plane 保持不變的情況下被取得任意大,

2、而 geometrical margin 則沒有這個問題,因爲除上了這個分母,所以縮放 w 和 b 的時候的值是不會改變的,它只隨着 hyper plane 的變動而變動,因此,這是更加合適的一個 margin 。

    通過最大化 margin ,我們使得該分類器對數據進行分類時具有了最大的 confidence,從而設計決策最優分類超平面。

1.5、到底什麼是Support Vector

    上節,我們介紹了Maximum Margin Classifier,但並沒有具體闡述到底什麼是Support Vector,本節,咱們來重點闡述這個概念。咱們不妨先來回憶一下上節1.4節最後一張圖:

    可以看到兩個支撐着中間的 gap 的超平面,它們到中間的純紅線separating hyper plane 的距離相等,即我們所能得到的最大的 geometrical margin,而「支撐」這兩個超平面的必定會有一些點,而這些「支撐」的點便叫做支持向量Support Vector。

   或亦可看下來自PPT中的一張圖,Support Vector便是那藍色虛線和粉紅色虛線上的點:

    很顯然,由於這些 supporting vector 剛好在邊界上,所以它們滿足還記得我們把 functional margin 定爲 1 了嗎?上節中:「處於方便推導和優化的目的,我們可以令=1」),而對於所有不是支持向量的點,也就是在「陣地後方」的點,則顯然有。當然,除了從幾何直觀上之外,支持向量的概念也可以從下文優化過程的推導中得到。

    OK,到此爲止,算是瞭解到了SVM的第一層,對於那些只關心怎麼用SVM的朋友便已足夠,不必再更進一層深究其更深的原理。

2.2、核函數Kernel

2.2.1、特徵空間的隱式映射:核函數

    咱們首先給出核函數的來頭:

  • 在上文中,我們已經瞭解到了SVM處理線性可分的情況,而對於非線性的情況,SVM 的處理方法是選擇一個核函數 κ(,) ,通過將數據映射到高維空間,來解決在原始空間中線性不可分的問題。由於核函數的優良品質,這樣的非線性擴展在計算量上並沒有比原來複雜多少,這一點是非常難得的。當然,這要歸功於核方法——除了 SVM 之外,任何將計算表示爲數據點的內積的方法,都可以使用核方法進行非線性擴展。

    也就是說,Minsky和Papert早就在20世紀60年代就已經明確指出線性學習器計算能力有限。爲什麼呢?因爲總體上來講,現實世界複雜的應用需要有比線性函數更富有表達能力的假設空間,也就是說,目標概念通常不能由給定屬性的簡單線性函數組合產生,而是應該一般地尋找待研究數據的更爲一般化的抽象特徵。

    而下文我們將具體介紹的核函數則提供了此種問題的解決途徑,從下文你將看到,核函數通過把數據映射到高維空間來增加第一節所述的線性學習器的能力,使得線性學習器對偶空間的表達方式讓分類操作更具靈活性和可操作性。因爲訓練樣例一般是不會獨立出現的,它們總是以成對樣例的內積形式出現,而用對偶形式表示學習器的優勢在爲在該表示中可調參數的個數不依賴輸入屬性的個數,通過使用恰當的核函數來替代內積,可以隱式得將非線性的訓練數據映射到高維空間,而不增加可調參數的個數(當然,前提是核函數能夠計算對應着兩個輸入特徵向量的內積)。

    1、簡而言之:在線性不可分的情況下,支持向量機通過某種事先選擇的非線性映射(核函數)將輸入變量映射到一個高維特徵空間,在這個空間中構造最優分類超平面。我們使用SVM進行數據集分類工作的過程首先是同預先選定的一些非線性映射將輸入空間映射到高維特徵空間(下圖很清晰的表達了通過映射到高維特徵空間,而把平面上本身不好分的非線性數據分了開來):
    使得在高維屬性空間中有可能最訓練數據實現超平面的分割,避免了在原輸入空間中進行非線性曲面分割計算。SVM數據集形成的分類函數具有這樣的性質:它是一組以支持向量爲參數的非線性函數的線性組合,因此分類函數的表達式僅和支持向量的數量有關,而獨立於空間的維度,在處理高維輸入空間的分類時,這種方法尤其有效,其工作原理如下圖所示:
   

2.2.3、幾個核函數

    通常人們會從一些常用的核函數中選擇(根據問題和數據的不同,選擇不同的參數,實際上就是得到了不同的核函數),例如:

  • 多項式核,顯然剛纔我們舉的例子是這裏多項式核的一個特例(R = 1,d = 2。雖然比較麻煩,而且沒有必要,不過這個核所對應的映射實際上是可以寫出來的,該空間的維度是,其中  是原始空間的維度。
  • 高斯核,這個核就是最開始提到過的會將原始空間映射爲無窮維空間的那個傢伙。不過,如果選得很大的話,高次特徵上的權重實際上衰減得非常快,所以實際上(數值上近似一下)相當於一個低維的子空間;反過來,如果選得很小,則可以將任意的數據映射爲線性可分——當然,這並不一定是好事,因爲隨之而來的可能是非常嚴重的過擬合問題。不過,總的來說,通過調控參數,高斯覈實際上具有相當高的靈活性,也是使用最廣泛的核函數之一。下圖所示的例子便是把低維線性不可分的數據通過高斯核函數映射到了高維空間:
  • 線性核,這實際上就是原始空間中的內積。這個核存在的主要目的是使得「映射後空間中的問題」和「映射前空間中的問題」兩者在形式上統一起來了(意思是說,咱們有的時候,寫代碼,或寫公式的時候,只要寫個模板或通用表達式,然後再代入不同的核,便可以了,於此,便在形式上統一了起來,不用再分別寫一個線性的,和一個非線性的)

2.2.4、核函數的本質

         上面說了這麼一大堆,讀者可能還是沒明白核函數到底是個什麼東西?我再簡要概括下,即以下三點:
  1. 實際中,我們會經常遇到線性不可分的樣例,此時,我們的常用做法是把樣例特徵映射到高維空間中去(如上文2.2節最開始的那幅圖所示,映射到高維空間後,相關特徵便被分開了,也就達到了分類的目的);
  2. 但進一步,如果凡是遇到線性不可分的樣例,一律映射到高維空間,那麼這個維度大小是會高到可怕的(如上文中19維乃至無窮維的例子)。那咋辦呢?
  3. 此時,核函數就隆重登場了,核函數的價值在於它雖然也是講特徵進行從低維到高維的轉換,但核函數絕就絕在它事先在低維上進行計算,而將實質上的分類效果表現在了高維上,也就如上文所說的避免了直接在高維空間中的複雜計算。

.3、使用鬆弛變量處理 outliers 方法

    在本文第一節最開始討論支持向量機的時候,我們就假定,數據是線性可分的,亦即我們可以找到一個可行的超平面將數據完全分開。後來爲了處理非線性數據,在上文2.2節使用 Kernel 方法對原來的線性 SVM 進行了推廣,使得非線性的的情況也能處理。雖然通過映射  將原始數據映射到高維空間之後,能夠線性分隔的概率大大增加,但是對於某些情況還是很難處理。

    例如可能並不是因爲數據本身是非線性結構的,而只是因爲數據有噪音。對於這種偏離正常位置很遠的數據點,我們稱之爲 outlier ,在我們原來的 SVM 模型裏,outlier 的存在有可能造成很大的影響,因爲超平面本身就是隻有少數幾個 support vector 組成的,如果這些 support vector 裏又存在 outlier 的話,其影響就很大了。例如下圖:

    用黑圈圈起來的那個藍點是一個 outlier ,它偏離了自己原本所應該在的那個半空間,如果直接忽略掉它的話,原來的分隔超平面還是挺好的,但是由於這個 outlier 的出現,導致分隔超平面不得不被擠歪了,變成途中黑色虛線所示(這只是一個示意圖,並沒有嚴格計算精確座標),同時 margin 也相應變小了。當然,更嚴重的情況是,如果這個 outlier 再往右上移動一些距離的話,我們將無法構造出能將數據分開的超平面來。

    爲了處理這種情況,SVM 允許數據點在一定程度上偏離一下超平面。例如上圖中,黑色實線所對應的距離,就是該 outlier 偏離的距離,如果把它移動回來,就剛好落在原來的超平面上,而不會使得超平面發生變形了

一、導入sklearn算法包

  Scikit-Learn庫已經實現了所有基本機器學習的算法,具體使用詳見官方文檔說明:http://scikit-learn.org/stable/auto_examples/index.html#support-vector-machines

  skleran中集成了許多算法,其導入包的方式如下所示,

  邏輯迴歸:from sklearn.linear_model import LogisticRegression

      樸素貝葉斯:from sklearn.naive_bayes import GaussianNB

   K-近鄰:from sklearn.neighbors import KNeighborsClassifier

   決策樹:from sklearn.tree import DecisionTreeClassifier

   支持向量機:from sklearn import svm

 二、sklearn中svc的使用

(1)使用numpy中的loadtxt讀入數據文件

  loadtxt()的使用方法:

  

  fname:文件路徑。eg:C:/Dataset/iris.txt。

  dtype:數據類型。eg:float、str等。

  delimiter:分隔符。eg:‘,’。

  converters:將數據列與轉換函數進行映射的字典。eg:{1:fun},含義是將第2列對應轉換函數進行轉換。

  usecols:選取數據的列。

  Iris蘭花數據集爲例子:

  由於從UCI數據庫中下載的Iris原始數據集的樣子是這樣的,前四列爲特徵列,第五列爲類別列,分別有三種類別Iris-setosa, Iris-versicolor, Iris-virginica。   

  

  當使用numpy中的loadtxt函數導入該數據集時,假設數據類型dtype爲浮點型,但是很明顯第五列的數據類型並不是浮點型。

  因此我們要額外做一個工作,即通過loadtxt()函數中的converters參數將第五列通過轉換函數映射成浮點類型的數據。

  首先,我們要寫出一個轉換函數:

1
2
3
def  iris_type(s):
     it  =  { 'Iris-setosa' 0 'Iris-versicolor' 1 'Iris-virginica' 2 }
     return  it[s]

  接下來讀入數據,converters={4: iris_type}中「4」指的是第5列:

1
2
path  =  u 'D:/f盤/python/學習/iris.data'   # 數據文件路徑
data  =  np.loadtxt(path, dtype = float , delimiter = ',' , converters = { 4 : iris_type})

  讀入結果:

  

(2)將Iris分爲訓練集與測試集

1
2
3
x, y  =  np.split(data, ( 4 ,), axis = 1 )
=  x[:, : 2 ]
x_train, x_test, y_train, y_test  =  train_test_split(x, y, random_state = 1 , train_size = 0.6 )

  1. split(數據,分割位置,軸=1(水平分割) or 0(垂直分割))。

  2. x = x[:, :2]是爲方便後期畫圖更直觀,故只取了前兩列特徵值向量訓練。

  3. sklearn.model_selection.train_test_split隨機劃分訓練集與測試集。train_test_split(train_data,train_target,test_size=數字, random_state=0)

  參數解釋:

  train_data:所要劃分的樣本特徵集

  train_target:所要劃分的樣本結果

  test_size:樣本佔比,如果是整數的話就是樣本的數量

  random_state:是隨機數的種子。

  隨機數種子:其實就是該組隨機數的編號,在需要重複試驗的時候,保證得到一組一樣的隨機數。比如你每次都填1其他參數一樣的情況下你得到的隨機數組是一樣的。但填0或不填,每次都會不一樣。隨機數的產生取決於種子,隨機數和種子之間的關係遵從以下兩個規則:種子不同,產生不同的隨機數;種子相同,即使實例不同也產生相同的隨機數。

(3)訓練svm分類器

1
2
3
# clf = svm.SVC(C=0.1, kernel='linear', decision_function_shape='ovr')
     clf  =  svm.SVC(C = 0.8 , kernel = 'rbf' , gamma = 20 , decision_function_shape = 'ovr' )
     clf.fit(x_train, y_train.ravel())

   kernel='linear'時,爲線性核,C越大分類效果越好,但有可能會過擬合(defaul C=1)。

   kernel='rbf'時(default),爲高斯核,gamma值越小,分類界面越連續;gamma值越大,分類界面越「散」,分類效果越好,但有可能會過擬合。

  decision_function_shape='ovr'時,爲one v rest,即一個類別與其他類別進行劃分,

  decision_function_shape='ovo'時,爲one v one,即將類別兩兩之間進行劃分,用二分類的方法模擬多分類的結果。

(4)計算svc分類器的準確率

1
2
3
4
5
6
print  clf.score(x_train, y_train)   # 精度
y_hat  =  clf.predict(x_train)
show_accuracy(y_hat, y_train,  '訓練集' )
print  clf.score(x_test, y_test)
y_hat  =  clf.predict(x_test)
show_accuracy(y_hat, y_test,  '測試集' )

 結果爲:

  如果想查看決策函數,可以通過decision_function()實現

1
2
print  'decision_function:\n' , clf.decision_function(x_train)
print  '\npredict:\n' , clf.predict(x_train)

 結果爲:

  decision_function中每一列的值代表距離各類別的距離。

(5)繪製圖像

  1.確定座標軸範圍,x,y軸分別表示兩個特徵

1
2
3
4
5
x1_min, x1_max  =  x[:,  0 ]. min (), x[:,  0 ]. max ()   # 第0列的範圍
x2_min, x2_max  =  x[:,  1 ]. min (), x[:,  1 ]. max ()   # 第1列的範圍
x1, x2  =  np.mgrid[x1_min:x1_max: 200j , x2_min:x2_max: 200j ]   # 生成網格採樣點
grid_test  =  np.stack((x1.flat, x2.flat), axis = 1 )   # 測試點
# print 'grid_test = \n', grid_testgrid_hat = clf.predict(grid_test)       # 預測分類值grid_hat = grid_hat.reshape(x1.shape)  # 使之與輸入的形狀相同

   這裏用到了mgrid()函數,該函數的作用這裏簡單介紹一下:

   假設假設目標函數F(x,y)=x+y。x軸範圍1~3,y軸範圍4~6,當繪製圖像時主要分四步進行:

  【step1:x擴展】(朝右擴展):

       [1 1 1]

   [2 2 2]

   [3 3 3]

  【step2:y擴展】(朝下擴展):

   [4 5 6]

   [4 5 6]

   [4 5 6]

  【step3:定位(xi,yi)】:

   [(1,4) (1,5) (1,6)]

   [(2,4) (2,5) (2,6)]

   [(3,4) (3,5) (3,6)]

  【step4:將(xi,yi)代入F(x,y)=x+y】

  因此這裏x1, x2 = np.mgrid[x1_min:x1_max:200j, x2_min:x2_max:200j]後的結果爲:

  

  再通過stack()函數,axis=1,生成測試點

  

  2.指定默認字體

1
2
mpl.rcParams[ 'font.sans-serif' =  [u 'SimHei' ]
mpl.rcParams[ 'axes.unicode_minus' =  False

  3.繪製

1
2
3
4
5
6
7
8
9
10
11
12
cm_light  =  mpl.colors.ListedColormap([ '#A0FFA0' '#FFA0A0' '#A0A0FF' ])
cm_dark  =  mpl.colors.ListedColormap([ 'g' 'r' 'b' ])
plt.pcolormesh(x1, x2, grid_hat, cmap = cm_light)
plt.scatter(x[:,  0 ], x[:,  1 ], c = y, edgecolors = 'k' , s = 50 , cmap = cm_dark)   # 樣本
plt.scatter(x_test[:,  0 ], x_test[:,  1 ], s = 120 , facecolors = 'none' , zorder = 10 )   # 圈中測試集樣本
plt.xlabel(u '花萼長度' , fontsize = 13 )
plt.ylabel(u '花萼寬度' , fontsize = 13 )
plt.xlim(x1_min, x1_max)
plt.ylim(x2_min, x2_max)
plt.title(u '鳶尾花SVM二特徵分類' , fontsize = 15 )
# plt.grid()
plt.show()

   pcolormesh(x,y,z,cmap)這裏參數代入x1,x2,grid_hat,cmap=cm_light繪製的是背景。

   scatter中edgecolors是指描繪點的邊緣色彩,s指描繪點的大小,cmap指點的顏色。

   xlim指圖的邊界。

最終結果爲:

 

完整代碼

# -*- coding:utf-8 -*-
# -*- coding:utf-8 -*-
from  sklearn  import  svm
import  numpy as np
import  matplotlib.pyplot as plt
import  matplotlib as mpl
from  matplotlib  import  colors
from  sklearn.model_selection  import  train_test_split
def  iris_type(s):
     it  =  { 'Iris-setosa' 0 'Iris-versicolor' 1 'Iris-virginica' 2 }
     return  it[s]
def  show_accuracy(y_hat, y_test, param):
     pass
 
path  =  'F:\\Test\\iris.data'  # 數據文件路徑
data  =  np.loadtxt(path, dtype = float , delimiter = ',' , converters = { 4 : iris_type})
 
x, y  =  np.split(data, ( 4 ,), axis = 1 )
=  x[:, : 2 ]
x_train, x_test, y_train, y_test  =  train_test_split(x, y, random_state = 1 , train_size = 0.6 )
 
# clf = svm.SVC(C=0.1, kernel='linear', decision_function_shape='ovr')
clf  =  svm.SVC(C = 0.8 , kernel = 'rbf' , gamma = 20 , decision_function_shape = 'ovr' )
clf.fit(x_train, y_train.ravel())
 
print  clf.score(x_train, y_train)   # 精度
y_hat  =  clf.predict(x_train)
show_accuracy(y_hat, y_train,  '訓練集' )
print  clf.score(x_test, y_test)
y_hat  =  clf.predict(x_test)
show_accuracy(y_hat, y_test,  '測試集' )
 
print  'decision_function:\n' , clf.decision_function(x_train)
print  '\npredict:\n' , clf.predict(x_train)
 
x1_min, x1_max  =  x[:,  0 ]. min (), x[:,  0 ]. max ()   # 第0列的範圍
x2_min, x2_max  =  x[:,  1 ]. min (), x[:,  1 ]. max ()   # 第1列的範圍
x1, x2  =  np.mgrid[x1_min:x1_max: 200j , x2_min:x2_max: 200j ]   # 生成網格採樣點
grid_test  =  np.stack((x1.flat, x2.flat), axis = 1 )   # 測試點
 
 
mpl.rcParams[ 'font.sans-serif' =  [u 'SimHei' ]
mpl.rcParams[ 'axes.unicode_minus' =  False
 
cm_light  =  mpl.colors.ListedColormap([ '#A0FFA0' '#FFA0A0' '#A0A0FF' ])
cm_dark  =  mpl.colors.ListedColormap([ 'g' 'r' 'b' ])
 
# print 'grid_test = \n', grid_test
grid_hat  =  clf.predict(grid_test)        # 預測分類值
grid_hat  =  grid_hat.reshape(x1.shape)   # 使之與輸入的形狀相同
 
alpha  =  0.5
plt.pcolormesh(x1, x2, grid_hat, cmap = cm_light)      # 預測值的顯示
# plt.scatter(x[:, 0], x[:, 1], c=y, edgecolors='k', s=50, cmap=cm_dark)  # 樣本
plt.plot(x[:,  0 ], x[:,  1 ],  'o' , alpha = alpha, color = 'blue' , markeredgecolor = 'k' )
plt.scatter(x_test[:,  0 ], x_test[:,  1 ], s = 120 , facecolors = 'none' , zorder = 10 )   # 圈中測試集樣本
plt.xlabel(u '花萼長度' , fontsize = 13 )
plt.ylabel(u '花萼寬度' , fontsize = 13 )
plt.xlim(x1_min, x1_max)
plt.ylim(x2_min, x2_max)
plt.title(u '鳶尾花SVM二特徵分類' , fontsize = 15 )
# plt.grid()
plt.show()