1. 程式人生 > >Python機器學習筆記:SVM(4)——sklearn實現

Python機器學習筆記:SVM(4)——sklearn實現

  上一節我學習了SVM的推導過程,下面學習如何實現SVM,具體的參考連結都在第一篇文章中,SVM四篇筆記連結為:

Python機器學習筆記:SVM(1)——SVM概述

Python機器學習筆記:SVM(2)——SVM核函式

Python機器學習筆記:SVM(3)——證明SVM

Python機器學習筆記:SVM(4)——sklearn實現

  對SVM的概念理清楚後,下面我們對其使用sklearn進行實現。

1,Sklearn支援向量機庫概述

  我們知道SVM相對感知器而言,它可以解決線性不可分的問題,那麼它是如何解決的呢?其思想很簡單就是對原始資料的維度變換,一般是擴維變換,使得原樣本空間中的樣本點線性不可分,但是在變維之後的空間中樣本點是線性可分的,然後再變換後的高維空間中進行分類。

  上面將SVM再贅述了一下,下面學習sklearn中的SVM方法,sklearn中SVM的演算法庫分為兩類,一類是分類的演算法庫,主要包含LinearSVC,NuSVC和SVC三個類,另一類是迴歸演算法庫,包含SVR,NuSVR和LinearSVR三個類,相關模組都包裹在sklearn.svm模組中。

  對於SVC,NuSVC和LinearSVC 三個分類的庫,SVC和NuSVC差不多,區別僅僅在於對損失的度量方式不同,而LinearSVC從名字就可以看出,他是線性分類,也就是不支援各種低維到高維的核函式,僅僅支援線性核函式,對線性不可分的資料不能使用。

  同樣的對於SVR,NuSVR和LinearSVR 三個迴歸的類,SVR和NuSVR差不多,區別也僅僅在於對損失的度量方式不同。LinearSVR是線性迴歸,只能使用線性核函式。

  我們使用這些類的時候,如果有經驗知道資料是線性可以擬合的,那麼使用LinearSVC去分類或者LinearSVR去迴歸,他們不需要我們去慢慢的調參選擇各種核函式以及對應的引數,速度也快。如果我們對資料分佈沒有什麼經驗,一般使用SVC去分類或者SVR去迴歸,這就需要我們選擇核函式以及對核函式調參了。

2,回顧SVM分類演算法和迴歸演算法

  我們這裡仍然先對SVM演算法進行回顧,首先對於SVM分類演算法,其原始形式如下:

  其中 n 為樣本個數,我們的樣本為(x1,  y1),(x2,y2),....(xn,  yn),w,b是我們的分離超平面的 wT*xi + b = 0的係數,ξi 為第 i 個樣本的鬆弛係數,C 為懲罰係數,xi (也有時候寫為Φ(xi) 為低維到高維的對映函式)為樣本數。

  通過拉格朗日以及對偶化後的形式為:

  其中和原始形式不同的 α 為拉格朗日系數向量,<xi,  xj> 為我們要使用的核函式。

  對於SVM迴歸演算法,(我自己沒有總結,借用劉建平老師的部落格),其原始形式如下:

  其中 m 為樣本個數,我們的樣本為(x1, y1),(x2,  y2),....,(xm, ym),w,b是我們迴歸超平面 wT*xi + b = 0 的係數,ξv, ξ^ 為第 i 個樣本的鬆弛係數, C為懲罰係數,ε 為損失邊界,到超平面距離小於 ε 的訓練集的點沒有損失,Φ(xi) 為低維到高維的對映函式

  通過拉格朗日函式以及對偶後的形式為:

  其中和原始形式不同的 αv, α^ 為拉格朗日系數向量,K(xi, xj) 為我們要使用的核函式。

3,SVM核函式概述

  我在第二篇SVM中學習了核函式,有好幾種,最常用的就是線性核函式,多項式核函式,高斯核函式和Sigmoid核函式,在scikit-learn中,內建的核函式也剛好有這四種。

3.1,線性核函式(Linear Kernel)

  線性核函式表示式為:

  就是普通的內積,LinearSVC和LinearSVR只能使用它。

3.2,多項式核函式(Polynomial Kernel)

  多項式核函式是線性不可分SVM常用的核函式之一,表示式為:

  引數都需要自己調參定義,比較麻煩。

3.3,高斯核函式(Gaussian Kernel)

  高斯核函式,在SVM中也稱為 徑向基核函式(Radial Basisi Function,RBF),它是libsvm預設的核函式,當然也是sklearn預設的核函式,表示式為:

  其中 r 大於0,需要自己調參定義,不過一般情況,我們都使用高斯核函式。

3.4,Sigmoid核函式(Sigmoid Kernel)

  Sigmoid核函式也是線性不可分SVM常用的核函式之一,表示為:

  其中 beta, t 都需要自己調參定義。

  一般情況下,對於非線性資料使用預設的高斯核函式會有比較好的效果,如果你不是SVM調參高手的話,建議使用高斯核來做資料分析。

4,SVM分類演算法庫引數小結

  下面我們將具體介紹這三種分類方法都有那些引數值以及不同引數值的含義。

4.1, LinearSVC

  其函式原型如下:

class sklearn.svm.LinearSVC(self, penalty='l2', loss='squared_hinge', dual=True, tol=1e-4,
             C=1.0, multi_class='ovr', fit_intercept=True,
             intercept_scaling=1, class_weight=None, verbose=0,
             random_state=None, max_iter=1000)

  引數說明

  • penalty :正則化引數,L1 和L2兩種引數可選,僅LinearSVC有。預設是L2 正則化,如果我們需要產生稀疏的話,可以選擇L1正則化,這和線性迴歸裡面的Lasso迴歸類似
  • loss:損失函式,有“hinge” 和“squared_hinge” 兩種可選,前者又稱為L1損失,後者稱為L2損失,預設是“squared_hinge”,其中hinge是SVM的標準損失,squared_hinge是hinge的平方。
  • dual:是否轉化為對偶問題求解,預設是True。這是一個布林變數,控制是否使用對偶形式來優化演算法。
  • tol:殘差收斂條件,預設是0.0001,與LR中的一致。
  • C:懲罰係數,用來控制損失函式的懲罰係數,類似於LR中的正則化係數。預設為1,一般需要通過交叉驗證來選擇一個合適的C,一般來說,噪點比較多的時候,C需要小一些。
  • multi_class:負責多分類問題中分類策略制定,有‘ovr’和‘crammer_singer’ 兩種引數值可選,預設值是’ovr’,'ovr'的分類原則是將待分類中的某一類當作正類,其他全部歸為負類,通過這樣求取得到每個類別作為正類時的正確率,取正確率最高的那個類別為正類;‘crammer_singer’ 是直接針對目標函式設定多個引數值,最後進行優化,得到不同類別的引數值大小。
  •  fit_intercept:是否計算截距,與LR模型中的意思一致。
  • class_weight:與其他模型中引數含義一樣,也是用來處理不平衡樣本資料的,可以直接以字典的形式指定不同類別的權重,也可以使用balanced引數值。如果使用“balanced”,則演算法會自己計算權重,樣本量少的類別所對應的樣本權重會高,當然,如果你的樣本類別分佈沒有明顯的偏倚,則可以不管這個係數,選擇預設的None
  • verbose:是否冗餘,預設為False
  • random_state:隨機種子的大小
  • max_iter:最大迭代次數,預設為1000.

懲罰係數:

  錯誤項的懲罰係數。C越大,即對分錯樣本的懲罰程度越大,因此在訓練樣本中準確率越高,但是泛化能力降低,也就是對測試資料的分類準確率降低。相反,減少C的話,容許訓練樣本中有一些誤分類錯誤樣本,泛化能力強。對於訓練樣本帶有噪音的情況,一般採用後者,把訓練樣本集中錯誤分類的樣本作為噪音。

4.2,NuSVC

  其函式原型如下:

class sklearn.svm.NuSVC(self, nu=0.5, kernel='rbf', degree=3, gamma='auto_deprecated',
             coef0=0.0, shrinking=True, probability=False, tol=1e-3,
             cache_size=200, class_weight=None, verbose=False, max_iter=-1,
             decision_function_shape='ovr', random_state=None)

   引數說明:

  • nu:訓練誤差部分的上限和支援向量部分的下限,取值在(0,1)之間,預設是0.5,它和懲罰係數C類似,都可以控制懲罰的力度。
  • kernel:核函式,核函式是用來將非線性問題轉化為線性問題的一種方法,預設是“rbf”核函式

      常用的核函式有以下幾種:

  • degree:當核函式是多項式核函式(“poly”)的時候,用來控制函式的最高次數。(多項式核函式是將低維的輸入空間對映到高維的特徵空間),這個引數只對多項式核函式有用,是指多項式核函式的階數 n。如果給的核函式引數是其他核函式,則會自動忽略該引數。
  • gamma:核函式係數,預設是“auto”,即特徵維度的倒數。核函式係數,只對rbf  poly  sigmoid 有效。
  • coef0:核函式常數值( y = kx + b 的b值),只有“poly”和“sigmoid” 函式有,預設值是0.
  • max_iter:最大迭代次數,預設值是 -1 ,即沒有限制。
  • probability:是否使用概率估計,預設是False。
  • decision_function_shape:與“multi_class”引數含義類似,可以選擇“ovo” 或者“ovr”(0.18版本預設是“ovo”,0.19版本為“ovr”) OvR(one vs rest)的思想很簡單,無論你是多少元分類,我們都可以看做二元分類,具體的做法是,對於第K類的分類決策,我們把所有第K類的樣本作為正例,除第K類樣本以外的所有樣本作為負類,然後在上面做二元分類,得到第K類的分類模型。 OvO(one vs one)則是每次在所有的T類樣本里面選擇兩類樣本出來,不妨記為T1類和T2類,把所有的輸出為T1 和 T2的樣本放在一起,把T1作為正例,T2 作為負例,進行二元分類,得到模型引數,我們一共需要T(T-1)/2 次分類。從上面描述可以看出,OvR相對簡單,但是分類效果略差(這裡是指大多數樣本分佈情況,某些樣本分佈下OvR可能更好),而OvO分類相對精確,但是分類速度沒有OvR快,一般建議使用OvO以達到較好的分類效果
  • chache_size:緩衝大小,用來限制計算量大小,預設是200M,如果機器記憶體大,推薦使用500MB甚至1000MB

4.3,SVC

  其函式原型如下:

class sklearn.svm.SVC(self, C=1.0, kernel='rbf', degree=3, gamma='auto_deprecated',
             coef0=0.0, shrinking=True, probability=False,
             tol=1e-3, cache_size=200, class_weight=None,
             verbose=False, max_iter=-1, decision_function_shape='ovr',
             random_state=None)

  引數說明:

  • C:懲罰係數(前面有詳細學習)

  SVC和NuSVC方法基本一致,唯一區別就是損失函式的度量方式不同(NuSVC中的nu引數和SVC中的C引數)即SVC使用懲罰係數C來控制懲罰力度,而NuSVC使用nu來控制懲罰力度。

5,SVM迴歸演算法庫引數小結

  下面我們將具體介紹這三種分類方法都有那些引數值以及不同引數值的含義。

5.1, LinearSVR

  其函式原型如下:

class sklearn.svm.LinearSVR(self, epsilon=0.0, tol=1e-4, C=1.0,
             loss='epsilon_insensitive', fit_intercept=True,
             intercept_scaling=1., dual=True, verbose=0,
             random_state=None, max_iter=1000)

  引數說明

  • epsilon:距離誤差epsilon,即迴歸模型中的 epsilon,訓練集中的樣本需要滿足:

  • loss:損失函式,有“hinge” 和“squared_hinge” 兩種可選,前者又稱為L1損失,後者稱為L2損失,預設是“squared_hinge”,其中hinge是SVM的標準損失,squared_hinge是hinge的平方。
  • dual:是否轉化為對偶問題求解,預設是True。這是一個布林變數,控制是否使用對偶形式來優化演算法。
  • tol:殘差收斂條件,預設是0.0001,與LR中的一致。
  • C:懲罰係數,用來控制損失函式的懲罰係數,類似於LR中的正則化係數。預設為1,一般需要通過交叉驗證來選擇一個合適的C,一般來說,噪點比較多的時候,C需要小一些。
  •  fit_intercept:是否計算截距,與LR模型中的意思一致。
  • verbose:是否冗餘,預設為False
  • random_state:隨機種子的大小
  • max_iter:最大迭代次數,預設為1000.

5.2,NuSVR

  其函式原型如下:

class sklearn.svm.NuSVR(self, nu=0.5, C=1.0, kernel='rbf', degree=3,
             gamma='auto_deprecated', coef0=0.0, shrinking=True,
             tol=1e-3, cache_size=200, verbose=False, max_iter=-1)

   引數說明:

  • nu:訓練誤差部分的上限和支援向量部分的下限,取值在(0,1)之間,預設是0.5,它和懲罰係數C類似,都可以控制懲罰的力度。
  • kernel:核函式,核函式是用來將非線性問題轉化為線性問題的一種方法,預設是“rbf”核函式

      常用的核函式有以下幾種:

  • degree:當核函式是多項式核函式(“poly”)的時候,用來控制函式的最高次數。(多項式核函式是將低維的輸入空間對映到高維的特徵空間),這個引數只對多項式核函式有用,是指多項式核函式的階數 n。如果給的核函式引數是其他核函式,則會自動忽略該引數。
  • gamma:核函式係數,預設是“auto”,即特徵維度的倒數。核函式係數,只對rbf  poly  sigmoid 有效。
  • coef0:核函式常數值( y = kx + b 的b值),只有“poly”和“sigmoid” 函式有,預設值是0.
  • chache_size:緩衝大小,用來限制計算量大小,預設是200M,如果機器記憶體大,推薦使用500MB甚至1000MB

5.3,SVR

  其函式原型如下:

class sklearn.svm.SVC(self, kernel='rbf', degree=3, gamma='auto_deprecated',
             coef0=0.0, tol=1e-3, C=1.0, epsilon=0.1, shrinking=True,
             cache_size=200, verbose=False, max_iter=-1)

  引數說明:

  SVR和NuSVR方法基本一致,唯一區別就是損失函式的度量方式不同(NuSVR中的nu引數和SVR中的C引數)即SVR使用懲罰係數C來控制懲罰力度,而NuSVR使用nu來控制懲罰力度。

6,SVM的方法與物件

6.1 方法

  三種分類的方法基本一致,所以一起來說:

  • decision_function(x):獲取資料集X到分離超平面的距離
  • fit(x , y):在資料集(X,y)上使用SVM模型
  • get_params([deep]):獲取模型的引數
  • predict(X):預測數值型X的標籤
  • score(X,y):返回給定測試集合對應標籤的平均準確率

6.2  物件

  • support_:以陣列的形式返回支援向量的索引
  • support_vectors_:返回支援向量
  • n_support_:每個類別支援向量的個數
  • dual_coef:支援向量係數
  • coef_:每個特徵係數(重要性),只有核函式是LinearSVC的是可用,叫權重引數,即w
  • intercept_:截距值(常數值),稱為偏置引數,即b

  加粗的三個屬性是我們常用的,後面會舉例說明 support_vectors_。

 

7,SVM型別演算法的模型選擇

7.1 PPT總結

  這裡使用(http://staff.ustc.edu.cn/~ketang/PPT/PRLec5.pdf)的PPT進行整理。

7.2 SVM演算法庫其他調參要點

  下面再對其他調參要點做一個小結:

  • 1,一般推薦在做訓練之前對資料進行歸一化,當然測試集的資料也要做歸一化
  • 2,在特徵數非常多的情況下,或者樣本數遠小於特徵數的時候,使用線性核,效果就很好了,並且只需要選擇懲罰係數C即可
  • 3,在選擇核函式的時候,如果線性擬合效果不好,一般推薦使用預設的高斯核(rbf),這時候我們主要對懲罰係數C和核函式引數 gamma 進行調參,經過多輪的交叉驗證選擇合適的懲罰係數C和核函式引數gamma。
  • 4,理論上高斯核不會比線性核查,但是這個理論就建立在要花費更多的時間上調參上,所以實際上能用線性核解決的問題我們儘量使用線性核函式

   在SVM中,其中最重要的就是核函式的選取和引數選擇了,當然這個需要大量的經驗來支撐,這裡幾個例子只是自己網上找的SVM的小例子。

8,SVM調參例項1

  下面學習支援向量機的使用方法以及一些引數的調整,支援向量機的原理就是將低維不可分問題轉換為高維可分問題。這裡不再贅述。

8.1  線性可分支援向量機

  首先做一個簡單的線性可分的例子,這裡直接使用sklearn.datasets.make_blobs 生成資料。生成資料程式碼如下:

# 生成資料集
from sklearn.datasets.samples_generator import make_blobs
from matplotlib import pyplot as plt

# n_samples=50 表示取50個點,centers=2表示將資料分為兩類
X, y = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std=0.6)
# 畫圖形
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
plt.show()

   我們畫圖展示如下:

  我們嘗試繪製分離兩組資料的直線,從而建立分類模型,對於這裡所示的資料,這是我們可以手動完成的任務。但是立馬可以看出有很多分界線可以完美的區分兩個類。

   下面畫出決策邊界。

# 生成資料集
from sklearn.datasets.samples_generator import make_blobs
from matplotlib import pyplot as plt
import numpy as np

# n_samples=50 表示取50個點,centers=2表示將資料分為兩類
X, y = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std=0.6)

# 畫圖形
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
# 線性等分詳細
xfit = np.linspace(-1, 3.5)
plt.plot([0.6], [2.1], 'x', color='red', markeredgewidth=2, markersize=10)

for m, b in [(1, 0.65), (0.5, 1.6), (-0.2, 2.9)]:
    plt.plot(xfit, m * xfit + b, '-k')

plt.show()

   圖如下:

  (注意:這三條直線是我隨便畫的,其實你可以使用Logistic迴歸,線性迴歸等分類,畫出線,我這裡是為了方便)

   這裡是三條不同的分割直線,並且這些分割直線能夠完全區分這些樣例。但是根據支援向量機的思想,哪一條直線是最優的分割線呢?支援向量機並不是簡單的繪製一條直線,而是畫出邊距為一定寬度的直線,直到最近的點。

  下面我們對直線進行加粗,程式碼如下:

# 生成資料集
from sklearn.datasets.samples_generator import make_blobs
from matplotlib import pyplot as plt
import numpy as np

# n_samples=50 表示取50個點,centers=2表示將資料分為兩類
X, y = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std=0.6)

# 畫圖形
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
# 線性等分詳細
xfit = np.linspace(-1, 3.5)
plt.plot([0.6], [2.1], 'x', color='red', markeredgewidth=2, markersize=10)

for m, b, d in [(1, 0.65, 0.33), (0.5, 1.6, 0.55), (-0.2, 2.9, 0.2)]:
    yfit = m * xfit + b
    plt.plot(xfit, yfit, '-k')
    plt.fill_between(xfit, yfit - d, yfit + d, edgecolor='none',
                     color='#AAAAAA', alpha=0.4)  # alpha為透明度
plt.show()

   如圖所示:

   在支援向量機中,邊距最大化的直線是我們將選擇的最優模型。支援向量機是這種最大邊距估計器的一個例子。

  接下來,我們訓練一個基本的SVM,我們使用sklearn的支援向量機,對這些資料訓練SVM模型。目前我們將使用一個線性核並將C引數設定為一個預設的數值。如下:

from sklearn.svm import SVC  # Support Vector Classifier

model = SVC(kernel='linear') # 線性核函式
model.fit(X, y)

   我們順便看看SVC的所有引數情況:

SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
    decision_function_shape='ovr', degree=3, gamma='auto_deprecated',
    kernel='linear', max_iter=-1, probability=False, random_state=None,
    shrinking=True, tol=0.001, verbose=False)

   為了更好展現這裡發生的事情,下面我們建立一個輔助函式,為我們繪製SVM的決策邊界。

def plot_SVC_decision_function(model, ax=None, plot_support=True):
    '''Plot the decision function for a 2D SVC'''
    if ax is None:
        ax = plt.gca()  #get子圖
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()

    # create grid to evaluate model
    x = np.linspace(xlim[0], xlim[1], 30)
    y = np.linspace(ylim[0], ylim[1], 30)
    # 生成網格點和座標矩陣
    Y, X = np.meshgrid(y, x)
    # 堆疊陣列
    xy = np.vstack([X.ravel(), Y.ravel()]).T
    P = model.decision_function(xy).reshape(X.shape)

    # plot decision boundary and margins
    ax.contour(X, Y, P, colors='k', levels=[-1, 0, 1],
               alpha=0.5, linestyles=['--', '-', '--'])  # 生成等高線 --

    # plot support vectors
    if plot_support:
        ax.scatter(model.support_vectors_[:, 0],
                   model.support_vectors_[:, 1],
                   s=300, linewidth=1, facecolors='none')

    ax.set_xlim(xlim)
    ax.set_ylim(ylim)

   下面繪製決策邊界:

def train_SVM():
    # n_samples=50 表示取50個點,centers=2表示將資料分為兩類
    X, y = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std=0.6)

    # 線性核函式
    model = SVC(kernel='linear')
    model.fit(X, y)
    plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
    plot_SVC_decision_function(model)
    plt.show()
    return X, y

   結果如圖所示:

   這是最大化兩組點之間的間距的分界線,那中間這條線就是我們最終的決策邊界了。請注意:一些訓練點碰到了邊緣,如圖所示,在兩個邊界上包含兩個紅點和一個黃點,所以這三個點又稱為支援向量,是 alpha 值不為零的,這些點是這種擬合的關鍵要素,被稱為支援向量。在sklearn中,這些點儲存在分類器的 support_vectors_ 屬性中。

  我們通過下面程式碼可以得出支援向量的結果。

    print(model.support_vectors_)
    '''
    [[0.44359863 3.11530945]
     [2.33812285 3.43116792]
     [2.06156753 1.96918596]]
    '''

  在支援向量機只有位於支援向量上面的點才會對決策邊界有影響,也就是說不管有多少的點是非支援向量,那對最終的決策邊界都不會產生任何影響。我們可以看到這一點,例如,如果我們繪製該資料集的前 60個點和前120個點獲得的模型:

def plot_svm(N=10, ax=None):
    X, y = make_blobs(n_samples=200, centers=2, random_state=0, cluster_std=0.6)
    X, y = X[:N], y[:N]
    model = SVC(kernel='linear')
    model.fit(X, y)

    ax = ax or plt.gca()
    ax.scatter(X[:, 0], X[:, 1], c=y, cmap='autumn')
    ax.set_xlim(-1, 4)
    ax.set_ylim(-1, 6)
    plot_SVC_decision_function(model, ax)

if __name__ == '__main__':
    # train_SVM()
    fig, ax = plt.subplots(1, 2, figsize=(16, 6))
    fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1)
    for axi, N in zip(ax, [60, 120]):
        plot_svm(N, axi)
        axi.set_title('N = {0}'.format(N))

   結果如圖所示:

  上面就是我們繪製的該資料集前60個點和前120個點獲得的模型,可以發現無論使用60,還是使用120個數據點,決策邊界都沒有發生變換,所有隻要支援向量沒變,其他的資料怎麼加都無所謂。

   這個分類器成功的關鍵在於:為了擬合,只有支援向量的位置是最重要的;任何遠離邊距的點都不會影響擬合的結果,邊界之外的點無論有多少都不會對其造成影響,也就是說不管有多少點是非支援向量,對最終的決策邊界都不會產生任何影響。

8.2 線性不可分支援向量機

  下面引入核函式,來看看核函式的威力,首先我們匯入一個線性不可分的資料集。

def train_svm_plus():
    # 二維圓形資料 factor 內外圓比例(0, 1)
    X, y = make_circles(100, factor=0.1, noise=0.1)
    
    clf = SVC(kernel='linear')
    clf.fit(X, y)
    
    plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
    plot_SVC_decision_function(clf, plot_support=False)

   資料集如圖所示:

   很明顯,用線性分類器無論怎麼畫線也不能分好,那咋辦呢?下面試試高斯核變換吧。在進行核變換之前,先看看資料在高維空間下的對映:

def plot_3D(X, y, elev=30, azim=30):
    # 我們加入了新的維度 r
    r = np.exp(-(X ** 2).sum(1))
    ax = plt.subplot(projection='3d')
    ax.scatter3D(X[:, 0], X[:, 1], r, c=y, s=50, cmap='autumn')
    ax.view_init(elev=elev, azim=azim)
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')


if __name__ == '__main__':
    X, y = train_svm_plus()
    plot_3D(elev=30, azim=30, X=X, y=y)

   畫出三維圖形,如圖所示:

   見證核變換威力的時候到了,引入徑向基函式(也叫高斯核函式),進行核變換:

def train_svm_plus():
    # 二維圓形資料 factor 內外圓比例(0, 1)
    X, y = make_circles(100, factor=0.1, noise=0.1)
    # 加入徑向基函式
    clf = SVC(kernel='rbf')
    clf.fit(X, y)

    plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
    plot_SVC_decision_function(clf, plot_support=False)
    return X, y

   得到的SVM模型為:

SVC(C=1000000.0, cache_size=200, class_weight=None, coef0=0.0,
    decision_function_shape='ovr', degree=3, gamma='auto_deprecated',
    kernel='rbf', max_iter=-1, probability=False, random_state=None,
    shrinking=True, tol=0.001, verbose=False)

   再次進行分類任務,程式碼如下:

def train_svm_plus():
    # 二維圓形資料 factor 內外圓比例(0, 1)
    X, y = make_circles(100, factor=0.1, noise=0.1)
    # 加入徑向基函式
    clf = SVC(kernel='rbf')
    clf.fit(X, y)

    plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
    plot_SVC_decision_function(clf, plot_support=False)
    plt.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1],
                s=300, lw=1, facecolors='none')
    return X, y

   分類結果如圖所示:

   可以清楚的看到效果很好,我們將線性不可分的兩對資料分割開來。使用這種核支援向量機,我們學習一個合適的非線性決策邊界。這種核變換策略在機器學習中經常被使用。

8.3 線性近似可分支援向量機——軟間隔問題

  SVM模型有兩個非常重要的引數C與gamma,其中C是懲罰係數,即對誤差的寬容忍,C越高,說明越不能容忍出現誤差,容易過擬合。C越小,容易欠擬合。C過大或過小,泛化能力變差。

  gamma 是選擇 RBF 函式作為kernel後,該函式自帶的一個引數。隱含的決定了資料對映到新的特徵空間後的分佈,gamma越大,支援向量越小,gamma值越小,支援向量越多。

  下面我們分別調劑一下C和gamma來看一下對結果的影響。

  首先我們調節C,先做一個有噪音的資料分佈

# n_samples=50 表示取50個點,centers=2表示將資料分為兩類
X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=0.8)

plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')

   結果如圖所示:

   上面的分佈看起來要劃分似乎有點困難,所以我們可以進行軟體各調整看看。

# n_samples=50 表示取50個點,centers=2表示將資料分為兩類
X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=0.8)

fig, ax = plt.subplots(1, 2, figsize=(16, 6))
fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1)

for axi, C in zip(ax, [10.0, 0.1]):
    model = SVC(kernel='linear', C=C)
    model.fit(X, y)
    axi.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
    plot_SVC_decision_function(model, axi)
    axi.scatter(model.support_vectors_[:, 0],
                model.support_vectors_[:, 1],
                s=300, lw=1, facecolors='none')
    axi.set_title('C={0:.1f}'.format(C), size=14)

   結果如圖所示:

   可以看到左邊這幅圖C值比較大,要求比較嚴格,不能分錯東西,隔離帶中沒有進入任何一個點,但是隔離帶的距離比較小,泛化能力比較差。右邊這幅圖C值比較小,要求相對來說比較鬆一點,隔離帶較大,但是隔離帶中進入了很多的黃點和紅點。那麼C大一些好還是小一些好呢?這需要考慮實際問題,可以進行K折交叉驗證來得到最合適的C值。

   下面再看看另一個引數gamma值,這個引數值只是在高斯核函式裡面才有,這個引數控制著模型的複雜程度,這個值越大,模型越複雜,值越小,模型就越精簡。

  程式碼如下:

# n_samples=50 表示取50個點,centers=2表示將資料分為兩類
X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=0.8)

fig, ax = plt.subplots(1, 3, figsize=(16, 6))
fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1)

for axi, gamma in zip(ax, [10.0, 1.0, 0.1]):
    model = SVC(kernel='rbf', gamma=gamma)
    model.fit(X, y)
    axi.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
    plot_SVC_decision_function(model, axi)
    axi.scatter(model.support_vectors_[:, 0],
                model.support_vectors_[:, 1],
                s=300, lw=1, facecolors='none')
    axi.set_title('gamma={0:.1f}'.format(gamma), size=14)

   結果如下:

   可以看出,當這個引數較大時,可以看出模型分類效果很好,但是泛化能力不太好。當這個引數較小時,可以看出模型裡面有些分類是錯誤的,但是這個泛化能力更好,一般也應有的更多。

  通過這個簡單的例子,我們對支援向量機在SVM中的基本使用,以及軟間隔引數的調整,還有核函式變換和gamma值等一些引數的比較。

  完整程式碼請參考我的GitHub(地址:https://github.com/LeBron-Jian/MachineLearningNote)。

9,SVM調參例項2

  下面我們用一個例項學習SVM RBF分類調參(此例子是劉建平老師的部落格內容,連結在文後)。

  首先,我們生成一些隨機資料,為了讓資料難一點,我們加入了一些噪音,程式碼如下:

# _*_coding:utf-8_*_
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_moons, make_circles
from sklearn.preprocessing import StandardScaler
from matplotlib.colors import ListedColormap

X, y = make_circles(noise=0.2, factor=0.5, random_state=1)
# 對資料進行標準化
X = StandardScaler().fit_transform(X)

# 下面看看資料長什麼樣子
cm = plt.cm.RdBu
cm_birght = ListedColormap(['#FF0000', '#0000FF'])
ax = plt.subplot()

ax.set_title('Input data')
# plot the training points
ax.scatter(X[:, 0], X[:, 1], c=y, cmap=cm_birght)
ax.set_xticks([])
ax.set_yticks([])
plt.tight_layout()
plt.show()

  上面程式碼對資料做了標準化,注意做標準化和不做標準化的差異(不一定所有的資料標準化後的效果更好,但是絕大多數確實更好)。比如下圖:

  我們看,當不做資料標準化,我們x1的取值範圍由0~90不等,當做了資料標準化之後,其取值範圍就在-2~2之間了。說明標準化的作用還是很明顯的,不多贅述,下面繼續。

   生成的資料如下(可能下一次執行,就變了哈):

   知道資料長什麼樣了,下面我們要對這個資料集進行SVM RBF分類了,分類時我們採用了網格搜尋,在C=(0.1, 1, 10)和 gamma=(1, 0.1, 0.01)形成的9種情況中選擇最好的超引數,我們用了4折交叉驗證。這裡只是一個例子,實際運用中,可能需要更多的引數組合來進行調參。

  程式碼及其結果如下:

# 網格搜尋尋找最佳引數
grid = GridSearchCV(SVC(), param_grid={'C': [0.1, 1, 10], 'gamma': [1, 0.1, 0.01]}, cv=4)
grid.fit(X, y)
print("The best parameters are %s with a score of %0.2f"
      % (grid.best_params_, grid.best_score_))
# The best parameters are {'C': 10, 'gamma': 0.1} with a score of 0.91

   就是說,我們通過網格搜尋,在我們給定的9組超引數組合中,C=10, gamma=0.1 分數最高,這就是我們最終的引數候選。

  下面我們看看SVM分類後的視覺化,這裡我們把上面九種組合各個訓練後,通過對網格里的點預測來標色,觀察分類的效果圖,程式碼如下:

# SVM 分類後進行視覺化
x_min, x_max = X[:, 0].min(), X[:, 0].max() + 1
y_min, y_max = X[:, 1].min(), X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
                     np.arange(y_min, y_max, 0.02))

for i, C in enumerate((0.1, 1, 10)):
    for j, gamma in enumerate((1, 0.1, 0.01)):
        # plt.subplot()
        clf = SVC(C=C, gamma=gamma)
        clf.fit(X, y)
        Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])

        # put the result into a color plot
        Z = Z.reshape(xx.shape)
        plt.contourf(xx, yy, Z, cmap=plt.cm.coolwarm)

        # Plot also the training points
        plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.coolwarm)

        plt.xlim(xx.min(), xx.max())
        plt.ylim(yy.min(), yy.max())
        plt.xticks(())
        plt.yticks(())
        plt.xlabel(" gamma=" + str(gamma) + " C=" + str(C))
        plt.show()

   結果如下:

   從我測試的結果來看,劉老師的程式碼還是有一點點問題,顯示不出九個,所以這裡我打算重新學習一個例子。

   完整程式碼請參考我的GitHub(地址:https://github.com/LeBron-Jian/MachineLearningNote)。

10,SVM調參例項3(非線性支援向量機)

  非線性的話,我們一方面可以利用核函式構造出非線性,一方面我們可以自己構造非線性。下面首先學習自己構造非線性。

10.1 自己構造非線性資料

  我們構造非線性資料的程式碼如下:

# _*_coding:utf-8_*_
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_moons, make_circles
from sklearn.preprocessing import StandardScaler

X1D = np.linspace(-4, 4, 9).reshape(-1, 1)
# np.c_是按行連線兩個矩陣,就是把兩矩陣左右相加,要求行數相等。
X2D = np.c_[X1D, X1D ** 2]
y = np.array([0, 0, 1, 1, 1, 1, 1, 0, 0])

plt.figure(figsize=(11, 4))

plt.subplot(121)
plt.grid(True, which='both')
plt.axhline(y=0, color='k')
plt.plot(X1D[:, 0][y == 0], np.zeros(4), 'bs')
plt.plot(X1D[:, 0][y == 1], np.zeros(5), 'g*')
plt.gca().get_yaxis().set_ticks([])
plt.xlabel(r'$x_1$', fontsize=20)
plt.axis([-4.5, 4.5, -0.2, 0.2])

plt.subplot(122)
plt.grid(True, which='both')
plt.axhline(y=0, color='k')
plt.axvline(x=0, color='k')
plt.plot(X2D[:, 0][y == 0], X2D[:, 1][y == 0], 'bs')
plt.plot(X2D[:, 0][y == 1], X2D[:, 1][y == 1], 'g*')
plt.xlabel(r'$x_1$', fontsize=20)
plt.ylabel(r'$x_2$', fontsize=20, rotation=0)
plt.gca().get_yaxis().set_ticks([0, 4, 8, 12, 16])
plt.plot([-4.5, 4.5], [6.5, 6.5], 'r--', linewidth=3)
plt.axis([-4.5, 4.5, -1, 17])

plt.subplots_adjust(right=1)
plt.show()

   圖如下:

  從這個圖可以看到,我們利用對資料的變換,可以對資料的維度增加起來,變成非線性。

   假設我們不使用核函式的思想,先對資料做變換,看能不能達到一個比較好的結果,首先我們做一個測試的資料,程式碼如下:

# _*_coding:utf-8_*_
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons

X, y = make_moons(n_samples=100, noise=0.15, random_state=42)


def plot_dataset(X, y, axes):
    plt.plot(X[:, 0][y == 0], X[:, 1][y == 0], 'bs')
    plt.plot(X[:, 0][y == 1], X[:, 1][y == 1], 'g*')
    plt.axis(axes)
    plt.grid(True, which='both')
    plt.xlabel(r'$x_1$', fontsize=20)
    plt.ylabel(r'$x_2$', fontsize=20, rotation=0)

plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.show()

   生成的圖如下:

   下面程式碼將兩類資料分出來了:

Polynomial_svm_clf = Pipeline((('poly_features', PolynomialFeatures(degree=3)),
                               ('scaler', StandardScaler()),
                               ('svm_clf', LinearSVC(C=10))
                               ))
Polynomial_svm_clf.fit(X, y)

def plot_predictions(clf, axes):
    x0s = np.linspace(axes[0], axes[1], 100)
    x1s = np.linspace(axes[2], axes[3], 100)
    x0, x1 = np.meshgrid(x0s, x1s)
    X = np.c_[x0.ravel(), x1.ravel()]
    y_pred = clf.predict(X).reshape(x0.shape)
    # 下面填充一個等高線, alpha表示透明度
    plt.contourf(x0, x1, y_pred, cmap=plt.cm.brg, alpha=0.2)

plot_predictions(Polynomial_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.show()

   結果如下:

   從結果來看,我們使用線性支援向量機將兩類資料區分開是沒有問題的。而最重要的是我們如何使用核函式呢?下面繼續學習

10.2 如何對非線性資料進行核函式的變換

   我們首先看svm的官方文件:

   核函式預設是 rbf,也就是徑向基核函式。下面分別演示核函式。

  我們依舊拿上面的資料,首先取核函式為 多項式核 看看效果(這裡對比的是多項式核的degree,也就是多項式核的維度):

# _*_coding:utf-8_*_
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVC, SVC

X, y = make_moons(n_samples=100, noise=0.15, random_state=42)

def plot_dataset(X, y, axes):
    plt.plot(X[:, 0][y == 0], X[:, 1][y == 0], 'bs')
    plt.plot(X[:, 0][y == 1], X[:, 1][y == 1], 'g*')
    plt.axis(axes)
    plt.grid(True, which='both')
    plt.xlabel(r'$x_1$', fontsize=20)
    plt.ylabel(r'$x_2$', fontsize=20, rotation=0)

# 展示影象
def plot_predictions(clf, axes):
    x0s = np.linspace(axes[0], axes[1], 100)
    x1s = np.linspace(axes[2], axes[3], 100)
    x0, x1 = np.meshgrid(x0s, x1s)
    X = np.c_[x0.ravel(), x1.ravel()]
    y_pred = clf.predict(X).reshape(x0.shape)
    # 下面填充一個等高線, alpha表示透明度
    plt.contourf(x0, x1, y_pred, cmap=plt.cm.brg, alpha=0.2)


Poly_kernel_svm_clf = Pipeline((('scaler', StandardScaler()),
                                ('svm_clf', SVC(kernel='poly', degree=3, coef0=1, C=5))
                                ))
Poly_kernel_svm_clf.fit(X, y)
# 下面做一個對比試驗,看看degree的值的變換
Poly_kernel_svm_clf_plus = Pipeline((('scaler', StandardScaler()),
                                     ('svm_clf', SVC(kernel='poly', degree=10, coef0=1, C=5))
                                     ))
Poly_kernel_svm_clf_plus.fit(X, y)

plt.subplot(121)
plot_predictions(Poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.title(r'$d=3, r=1, C=5$', fontsize=18)

plt.subplot(122)
plot_predictions(Poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.title(r'$d=10, r=100, C=5$', fontsize=18)
plt.show()

   結果如下:

   我們是把資料對映到高維空間,然後再拿回來看效果,實際上並沒有去高維空間做運算。。這就是我們想要展示的多項式核函式,下面學習高斯核函式。

  高斯核函式:利用相似度來變換特徵

  我們選擇一份一維資料,並在 x1=-2,  x1=1 處為其新增兩個高斯函式,接下來讓我門將相似度函式定義為 gamma=0.3 的徑向基核函式(RBF):

  例如: x1 = -1:它位於距第一個地標距離為1的地方,距離第二個地標距離為2。因此其新特徵為 x2 = exp(-0.3*1^2)=0.74 ,並且  x3 = exp(-0.3 * 2^2)=0.3。

  圖如下:

   這裡說一下,就是假設 X2和 X3為兩個高斯函式,我們看 x這個點距離兩個地標的距離。離高斯分佈的中心越近,就越發生什麼。。經過計算出來距離兩個地標的距離,我們就可以依此類推,來計算所有一維座標相對應的二維座標。(二維座標就是距離兩個高斯函式的距離)。

  我們這裡用相似度特徵來替換原本的特徵。

  下面我們做一個實驗,我們只看 gamma的變換,高斯函式的開口變化:

X1D = np.linspace(-4, 4, 9).reshape(-1, 1)
X2D = np.c_[X1D, X1D ** 2]

X, y = make_moons(n_samples=100, noise=0.15, random_state=42)


def gaussian_rbf(x, landmark, gamma):
    return np.exp(-gamma * np.linalg.norm(x - landmark, axis=1) ** 2)


gamma = 0.3

# 下面進行訓練,得到一個支援向量機的模型(這裡我們沒有訓練,直接畫出來了)
# 因為測試的資料是我們自己寫的,為了方便,我們自己畫出來,當然你也可以自己做
xls = np.linspace(-4.5, 4.5, 200).reshape(-1, 1)
x2s = gaussian_rbf(xls, -2, gamma)
x3s = gaussian_rbf(xls, 1, gamma)

XK = np.c_[gaussian_rbf(X1D, -2, gamma), gaussian_rbf(X2D, 1, gamma)]
yk = np.array([0, 0, 1, 1, 1, 1, 1, 0, 0])

plt.figure(figsize=(11, 4))

# plt.subplot(121)
plt.grid(True, which='both')
plt.axhline(y=0, color='k')
plt.scatter(x=[-2, 1], y=[0, 0], s=150, alpha=0.5, c='red')
plt.plot(X1D[:, 0][yk == 0], np.zeros(4), 'bs')
plt.plot(X1D[:, 0][yk == 1], np.zeros(5), 'g*')
plt.plot(xls, x2s, 'g--')
plt.plot(xls, x3s, 'b:')
plt.gca().get_yaxis().set_ticks([0, 0.25, 0.5, 0.75, 1])
plt.xlabel(r'$x_1$', fontsize=20)
plt.ylabel(r'Similarity', fontsize=14)

plt.annotate(r'$\mathbf{x}$',
             xy=(X1D[3, 0], 0),
             xytest=(-0.5, 0.20),
             ha='center',
             arrowprops=dict(facecolor='black', shrink=0.1),
             fontsize=18,
             )
plt.text(-2, 0.9, "$x_2$", ha='center', fontsize=20)
plt.text(1, 0.9, "$x_3$", ha='center', fontsize=20)
plt.axis([-4.5, 4.5, -0.1, 1.1])

   結果如下(下面我們分別除錯gamma):

   理論情況下,我們會得到怎麼維特徵呢?可以對每一個例項(樣本資料點)建立一個地標,此時會將mn 的訓練集轉換成 mm 的訓練集(m表示樣本個數,n表示特徵維度個數)。

  SVM中利用核函式的計算技巧,大大降低了計算複雜度:

  • 增加gamma 使高斯曲線變窄,因此每個例項的影響範圍都較小,決策邊界最終變得不規則,在個別例項周圍擺動
  • 減少gamma 使高斯曲線變寬,因此例項具有更大的影響範圍,並且決策邊界更加平滑

   下面做一個對比試驗(gamma值(0.1  0.5), C值(0.001, 1000)):

rbf_kernel_svm_clf = Pipeline((('scaler', StandardScaler()),
                               ('svm_clf', SVC(kernel='rbf', gamma=5, C=0.001))
                               ))

gamma1, gamma2 = 0.1, 5
C1, C2 = 0.001, 1000
hyperparams = (gamma1, C1), (gamma1, C2), (gamma2, C1), (gamma2, C2)

svm_clfs = []
for gamma, C in hyperparams:
    rbf_kernel_svm_clf.fit(X, y)
    svm_clfs.append(rbf_kernel_svm_clf)

plt.figure(figsize=(11, 7))

for i, svm_clfs in enumerate(svm_clfs):
    plt.subplot(221 + i)
    plot_predictions(svm_clfs, [-1.5, 2.5, -1, 1.5])
    plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
    gamma, C = hyperparams[i]
    plt.title(r'$\gamma={}, C={}$'.format(gamma, C), fontsize=16)
plt.show()

   結果如下:

   我們看第一幅圖,邊界比較平穩,沒有過擬合的風險,我們看當 gamma比較大的時候,過擬合的風險卻比較大了。所以說最終我們看的還是高斯函式的開口大還是小,大一點,也就是gamma小,過擬合風險大,反之同理。

  完整程式碼請參考我的GitHub(地址:https://github.com/LeBron-Jian/MachineLearningNote)。

完整程式碼及其資料,請移步小編的GitHub

  傳送門:請點選我

  如果點選有誤:https://github.com/LeBron-Jian/MachineLearningNote

 

 

參考文獻:https://blog.csdn.net/BIT_666/article/details/79979580

https://www.cnblogs.com/tonglin0325/p/6107114.html

https://cloud.tencent.com/developer/article/1146077

https://www.cnblogs.com/xiaoyh/p/11604168.html

https://www.cnblogs.com/pinard/p/6126077.html

https://www.cnblogs.com/pinard/p/6117515.