【機器學】核支持向量機SVM

線性支持向量機用於分類任務,而核支持向量機(SVM)是可以推廣到更復雜模型的,但無法被輸入空間的超平面定義。

雖然支持向量機可以同時用於分類和加歸,一般SVC用於分類,SVR用於迴歸。

# 在學習之前,先導入這些常用的模塊
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import mglearn

一、線性模型與非線性特徵

線性模型在低維空間中可能非常受限,因爲線和平面的靈性性有限。

有一種方法可以讓線性模型更加靈活,就是添加更多的特徵——舉個例子,添加輸入特徵的交互項或多項式。

先看下面的例子,這是一幅二分類數據庫集,其類別並不是線性可分的

from sklearn.datasets import make_blobs
X, y = make_blobs(centers=4, random_state=8)
y = y % 2
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
plt.xlabel('Feature 0')
plt.ylabel('Feature 1')
plt.show()

如果要對上例的數據強行進行線性劃分的話,對這個數據集將無法給出較好的結果,如下圖:

from sklearn.svm import LinearSVC
linear_svm = LinearSVC().fit(X, y)

mglearn.plots.plot_2d_separator(linear_svm, X)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
plt.xlabel('Feature 0')
plt.ylabel('Feature 1')
plt.show()

可以看出,SVM無法對該數據集進行線性的分割,它感到很爲難。

現在對輸入特徵進行擴展,說添加第二個特徵的平方(feature1 ** 2)作爲一個新特徵。現在我們將每個數據點表示爲三維點(feature0, feature1, feature1**2),而不是二維的點,如下圖:

# 添加第二個特徵的平方,作爲一個新特徵
X_new = np.hstack([X, X[:, 1:] ** 2])

# 如果你的anaconda上沒有mpl_toolkits,可以通過 conda install mpl_toolkits進行安裝
# 導入玩具數據的3D圖展示模塊
from mpl_toolkits.mplot3d import Axes3D, axes3d
# 創建一個幕布
figure = plt.figure()
# 創建3D可視化的繪圖區
ax = Axes3D(figure, elev=-152, azim=-26)
# 首先畫出所有 y == 0 的點,然後畫出所有 y == 1 的點
mask = y == 0
ax.scatter(X_new[mask, 0], X_new[mask, 1], X_new[mask, 2], c='b', cmap=mglearn.cm2, s=60)
ax.scatter(X_new[~mask, 0], X_new[~mask, 1], X_new[~mask, 2], c='r', marker='^', cmap=mglearn.cm2, s=60)
ax.set_xlabel('feature0')
ax.set_ylabel('feature1')
ax.set_zlabel('feature1**2')
plt.show()

數據的三個特徵在三維的空間裏被展示出來,現在我們可以用線性模型擬合擴展的數據了:

# 使用LinearSVC訓練數據
linear_svm_3d = LinearSVC().fit(X_new, y)
# 獲得模型的斜率集合與截距集合
coef, intercept = linear_svm_3d.coef_.ravel(), linear_svm_3d.intercept_

# 顯示線性的決策邊界
figure = plt.figure()
ax = Axes3D(figure, elev=-152, azim=-26)
xx = np.linspace(X_new[:, 0].min() - 2, X_new[:, 0].max() + 2, 50)
yy = np.linspace(X_new[:, 1].min() - 2, X_new[:, 1].max() + 2, 50)

XX, YY = np.meshgrid(xx, yy)
ZZ = (coef[0] * XX + coef[1] * YY + intercept) / -coef[2]
ax.plot_surface(XX, YY, ZZ, rstride=8, cstride=8, alpha=0.3)
ax.scatter(X_new[mask, 0], X_new[mask, 1], X_new[mask, 2], c='b', cmap=mglearn.cm2, s=60)
ax.scatter(X_new[~mask, 0], X_new[~mask, 1], X_new[~mask, 2], c='r', marker='^', cmap=mglearn.cm2, s=60)
ax.set_xlabel('feature0')
ax.set_ylabel('feature1')
ax.set_zlabel('feature1**2')
plt.show()

SVM對三維的數據近平面對等劃劃分,這是由線到面的一種改進。

如果將該3D圖壓縮成一張二維的圖,將得出一個非線性的,而是橢圓的分界區,如下展示:

ZZ = YY ** 2
dec = linear_svm_3d.decision_function(np.c_[XX.ravel(), YY.ravel(), ZZ.ravel()])
plt.contourf(XX, YY, dec.reshape(XX.shape), levels=[dec.min(), 0, dec.max()], cmap=mglearn.cm2, alpha=.5)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
plt.xlabel('Feature 0')
plt.ylabel('Feature 1')
plt.show()

通過將數據立體化,再進行分類,由於多出了一個維度,將能更好的找到各數據之間的界線!

二、核技巧

這裏需要記住的是,向數據表示中添加非線性的特徵,可以讓線性模型變得更強大。但是通常來說我們並不知道要添加哪些特徵,而且添加許多特徵(比如100維特徵空間所有可能的交互項)的計算開銷可能會很大。幸運的是,有一種巧妙的數學技巧,讓我們可以在更空間中學習分類器,而不是用實際計算可能非常大的新的數據表示。這種技巧叫作核技巧,它的原理是直接計算擴展特徵表示中數據點之間的距離(更準確地說是內積),而不是實際對擴展進行計算。

對於支持向量機,將數據映射到更高維空間中有兩種常用的方法:一種是多項式核,在一定除數內計算原始特徵所有可能的多項式(比如 feature1 ** 2 * feature2 ** 5);另一種是徑向基函數核(RBF核),也叫高斯核。高斯核有點難以解釋,因爲它對應無限維的特徵空間。一種對高斯核的解釋是它考慮所有階數的所有可能的多項式,但階數越高,特徵的重要性越小。

不過在實踐中,核SVM背後的數學細節並不重要,可以簡單地總結出使用高斯核SVM進行預測的方法——將在以後進行介紹。

三、理解

在訓練過程中,SVM學習每個訓練數據點對於表示兩個類別之間的決策邊界的重要性。通常只有一部分訓練數據點對於定義決策邊界來說很重要:位於類別之間邊界上的那些點。這些點叫做支持向量,支持向量機正是由此得名。

想要對新樣本點進行預測,需要測量它與每個支持向理之間的距離。分類決策是基於它與支持向量之間的距離以及在訓練過程中學到的支持向量重要性(保存在 SVC 的 dua_coef_屬性中)來做出的。

而數據點之間的距離由離斯核給出:

k_{rbs}(x_1, x_2) = exp(-\gamma || x_1 - x_2 || ^2)

這裏 x1 和 x2 是數據點,||x1 - x2||表示歐氏距離, \gamma 是控制高斯核寬度的參數。

下圖是支持向量機對一個二維二分類數據集的訓練結果。決策邊界用黑色表示,支持向量是較大的點。下列代碼將在 forge 數據集上訓練 SVM 並創建此圖:

from sklearn.svm import SVC
X, y = mglearn.tools.make_handcrafted_dataset()
svm = SVC(kernel='rbf', C=10, gamma=0.1).fit(X, y)
mglearn.plots.plot_2d_separator(svm, X, eps=.5)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
# 畫出支持向理
sv = svm.support_vectors_
# 支持向量的類別標籤由dual_cofe_的正負號給出
sv_labels = svm.dual_coef_.ravel() > 0
mglearn.discrete_scatter(sv[:, 0], sv[:, 1], sv_labels, s=15, markeredgewidth=3)
plt.xlabel('Feature 0')
plt.ylabel('Feature 1')
plt.show()

SVM給出了非常平滑且非常線性(不是直線)的邊界。這時在我們調節了兩個參數:C參數和gamma參數,下面我們將詳細討論。

四、SVM調參

  • gamma 參數用於控制高斯核的寬度。它決定了點與點之間“靠近”是指多大距離。
  • C 參數是正則化參數,與線性模型中用到的類似。這限制每個點的重要性(或者更確切的說,第個點的 dual_cofe_)。

接下來,通過圖例來展示改變這兩個參數時模型的表現:

# 創建三個幕布,三個繪圖區
fig, axes = plt.subplots(3, 3, figsize=(15, 10))

# 指定三個C值
for ax, C in zip(axes, [-1, 0, 3]):
    # 指定三個gmma值
    for a, gamma in zip (ax, range(-1, 2)):
        # 繪圖
        mglearn.plots.plot_svm(log_C=C, log_gamma=gamma, ax=a)

# 添加圖例
axes[0, 0].legend(['class 0', 'class 1', 'sv class 0', 'sv class 1'], ncol=4, loc=(.9, 1.2))
plt.show()

黑色的線條是決策邊界,大的數據點是支持向量。

觀察上圖得:

  • 從左到右,gamma的值由0.1增加到10,越來越大。而決策邊界與支持向量的距離從左到右越來越小,左側的決策邊界更平滑,右側的決策邊界更關注單個數據點,更復雜。結論是,gamma值越大,生成的模型越複雜。
  • 從上到下,C 值從0.1增加到1000,上面的決策線比下面的更加平整,說明上面的模型受到的約束更大,而下面的決策線會更多的關注到單個的數據點,更自由一點,說明下面的模型受到的約束更小。結論是:C值越大,模型受到的約束越小。

再來看看將SVM運用到腺癌數據集上的效果。默認情況下,C=1, gamma=1/n_features:

from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()

X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=0)

svc = SVC()
svc.fit(X_train, y_train)

print('訓練集評估結果:', svc.score(X_train, y_train))
print('測試集評估結果:', svc.score(X_test, y_test))
訓練集評估結果: 1.0
測試集評估結果: 0.6293706293706294

這個結果表明,模型在訓練集上存在過擬合的情況。

雖然SVM的表現通常都很好,但它對參數的設定和數據的縮放非常敏感。特別地,它要求所有特徵有相似的變化範圍。

我們來看一下每個特徵的最小值和最大傳下,它們繪製在對數座標上:

plt.plot(X_train.min(axis=0), 'o', label='min')
plt.plot(X_train.max(axis=0), '^', label='max')
plt.legend(loc=4)
plt.xlabel('Feature index')
plt.ylabel('Feature magnitude')
plt.yscale('log')
plt.show()

可以看出,乳腺癌數據集的特徵具有完全不同的數量級。這對其他模型來說(比如線性模型)可能是小問題,但對核SVM卻有極大影響。我們來研究蝁這個問題的幾種方法:

五、爲SVM預處理數據

解決方法之一是:對數據進行縮放,使其儘量在一個範圍(0~1)之間。

代碼如下:

# 計算訓練集中每個特徵的最小值
min_on_training = X_train.min(axis=0)
# 計算訓練集中每個特徵的範圍(最大值-最小值)
range_on_training = (X_train - min_on_training).max(axis=0)

# 減去最小值,然後除以範圍
# 這樣每個特徵都是min=0和max=1
X_train_scaled = (X_train - min_on_training) / range_on_training
print('最小值:', X_train_scaled.min(axis=0))
print('最大值:', X_train_scaled.max(axis=0))
最小值: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0.]
最大值: [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
 1. 1. 1. 1. 1. 1.]
# 利用訓練集的最小值和範圍對測試集做相同的變換
X_test_scaled = (X_test - min_on_training) / range_on_training
svc = SVC()
svc.fit(X_train_scaled, y_train)

print('訓練集評估結果:', svc.score(X_train_scaled, y_train))
print('測試集評估結果:', svc.score(X_test_scaled, y_test))
訓練集評估結果: 0.9483568075117371
測試集評估結果: 0.951048951048951

數據縮放的作用如此之大!但因爲訓練集與測試集的評估結果十分接近,說明模型還是存在欠擬合的情況。

現在嘗試增大C或gamma來擬合更爲複雜的模型:

svc = SVC(C=1000)
svc.fit(X_train_scaled, y_train)

print('訓練集評估結果:', svc.score(X_train_scaled, y_train))
print('測試集評估結果:', svc.score(X_test_scaled, y_test))
訓練集評估結果: 0.9882629107981221
測試集評估結果: 0.972027972027972

這裏只增大的了C,模型的精度提高了!

總結:

  • SVM很強大,即使只有幾個特徵,它也允許決策邊界很複雜。
  • 對樣本的縮放不是很好,因爲如果數量特別大的時候,其運行與表現將受到挑戰。
  • SVM參數的調整,對於我們來說也是個挑戰,有時候很難對SVM做出合理的檢查。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章