跟我一起學(xué)scikit-learn19:支持向量機(jī)算法

支持向量機(jī)(SVM,Support Vector Machine)算法是一種常見(jiàn)的分類(lèi)算法,在工業(yè)界和學(xué)術(shù)界都有廣泛的應(yīng)用。特別是針對(duì)數(shù)據(jù)集較小的情況下,往往其分類(lèi)效果比神經(jīng)網(wǎng)絡(luò)好。

1.SVM算法原理

SVM的原理就是使用分隔超平面來(lái)劃分?jǐn)?shù)據(jù)集,并使得支持向量(數(shù)據(jù)集中離分隔超平面最近的點(diǎn))到該分隔超平面的距離最大。其最大特點(diǎn)是能構(gòu)造出最大間距的決策邊界,從而提高分類(lèi)算法的魯棒性。

1.大間距分類(lèi)算法

假設(shè)要對(duì)一個(gè)數(shù)據(jù)集進(jìn)行分類(lèi),如下圖所示,可以構(gòu)造一個(gè)分隔線把圓形的點(diǎn)和方形的點(diǎn)分開(kāi)。這個(gè)分隔線稱(chēng)為分隔超平面(Separating Hyperplane)。

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
class1 = np.array([[1, 1], [1, 3], [2, 1], [1, 2], [2, 2]])
class2 = np.array([[4, 4], [5, 5], [5, 4], [5, 3], [4, 5], [6, 4]])
plt.figure(figsize=(8, 6), dpi=144)

plt.title('Decision Boundary')

plt.xlim(0, 8)
plt.ylim(0, 6)
ax = plt.gca()                                  # gca 代表當(dāng)前坐標(biāo)軸,即 'get current axis'
ax.spines['right'].set_color('none')            # 隱藏坐標(biāo)軸
ax.spines['top'].set_color('none')

plt.scatter(class1[:, 0], class1[:, 1], marker='o')
plt.scatter(class2[:, 0], class2[:, 1], marker='s')
plt.plot([1, 5], [5, 1], '-r')
plt.arrow(4, 4, -1, -1, shape='full', color='r')
plt.plot([3, 3], [0.5, 6], '--b')
plt.arrow(4, 4, -1, 0, shape='full', color='b', linestyle='--')
plt.annotate(r'margin 1',
             xy=(3.5, 4), xycoords='data',
             xytext=(3.1, 4.5), fontsize=10,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.annotate(r'margin 2',
             xy=(3.5, 3.5), xycoords='data',
             xytext=(4, 3.5), fontsize=10,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.annotate(r'support vector',
             xy=(4, 4), xycoords='data',
             xytext=(5, 4.5), fontsize=10,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.annotate(r'support vector',
             xy=(2, 2), xycoords='data',
             xytext=(0.5, 1.5), fontsize=10,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
figure8_1.png

從上圖可以明顯地看出,實(shí)線的分隔線比虛線的分隔線更好,因?yàn)槭褂脤?shí)線的分隔線進(jìn)行分類(lèi)時(shí),離分隔線最近的點(diǎn)到分隔線的距離最大,即margin2>margin1。這段距離的2倍稱(chēng)為間距(margin)。那些離分隔超平面最近的點(diǎn),稱(chēng)為支持向量(Support Vector)。為了達(dá)到最好的分類(lèi)效果,SVM算法的原理就是要找到一個(gè)分隔超平面,它能把數(shù)據(jù)集正確地分類(lèi),并且間距最大。

首先,我們來(lái)看怎么計(jì)算間距。在二維空間里,可以使用方程w_1x_1+w_2x_2+b=0來(lái)表示分隔超平面。針對(duì)高維度空間,可以寫(xiě)成一般的向量化形式,即w^Tx+b=0。我們畫(huà)出與分隔超平面平行的兩條直線,分別穿過(guò)兩個(gè)類(lèi)別的支持向量(離分隔超平面距離最近的點(diǎn))。這兩條直線的方程分別為w^Tx+b=-1w^Tx+b=1。如下圖所示。

plt.figure(figsize=(8, 6), dpi=144)

plt.title('Support Vector Machine')

plt.xlim(0, 8)
plt.ylim(0, 6)
ax = plt.gca()                                  # gca 代表當(dāng)前坐標(biāo)軸,即 'get current axis'
ax.spines['right'].set_color('none')            # 隱藏坐標(biāo)軸
ax.spines['top'].set_color('none')

plt.scatter(class1[:, 0], class1[:, 1], marker='o')
plt.scatter(class2[:, 0], class2[:, 1], marker='s')
plt.plot([1, 5], [5, 1], '-r')
plt.plot([0, 4], [4, 0], '--b', [2, 6], [6, 2], '--b')
plt.arrow(4, 4, -1, -1, shape='full', color='b')
plt.annotate(r'$w^T x + b = 0$',
             xy=(5, 1), xycoords='data',
             xytext=(6, 1), fontsize=10,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.annotate(r'$w^T x + b = 1$',
             xy=(6, 2), xycoords='data',
             xytext=(7, 2), fontsize=10,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.annotate(r'$w^T x + b = -1$',
             xy=(3.5, 0.5), xycoords='data',
             xytext=(4.5, 0.2), fontsize=10,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.annotate(r'd',
             xy=(3.5, 3.5), xycoords='data',
             xytext=(2, 4.5), fontsize=10,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.annotate(r'A',
             xy=(4, 4), xycoords='data',
             xytext=(5, 4.5), fontsize=10,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
figure8_2.png

根據(jù)點(diǎn)到直線的距離公式,可以容易地算出支持向量A到分隔超平面的距離為:
d=\frac{|w^TA+b|}{||w||}

由于點(diǎn)A在直線w^Tx+b=1上,因此w^TA+b=1,代入即可算出支持向量A到分隔超平面的距離為d=\frac{1}{||w||}。為了使得距離最大,我們只需要找到合適的參數(shù)w和b,使得\frac{1}{||w||}最大即可。||w||是向量w的L2范數(shù),其計(jì)算公式為:
||w||=\sqrt{\sum_{i=1}^{n}w_i^2}
由此可得,求\frac{1}{||w||}的最大值,等價(jià)于求||w||^2的最小值:
||w||^2=\sum_{i=1}^{n}w_i^2

其中n為向量w的維度。除了間距最大外,我們選出來(lái)的分隔超平面還要能正確地把數(shù)據(jù)集分類(lèi)。問(wèn)題來(lái)了,怎樣在數(shù)學(xué)上表達(dá)出“正確地把數(shù)據(jù)集分類(lèi)”這個(gè)描述呢?

根據(jù)上圖可以看出,針對(duì)方形的點(diǎn),必定滿(mǎn)足w^Tx+b\geq1的約束條件。類(lèi)別是離散的值,我們使用-1來(lái)表示圓形的類(lèi)別,用1來(lái)表示方形的類(lèi)別,即y \in \left\{-1,1\right\}。針對(duì)數(shù)據(jù)集中的所有樣本x^{(i)}y^{(i)},只要它們都滿(mǎn)足以下的約束條件,則由w和b定義的分隔超平面即可正確地把數(shù)據(jù)集分類(lèi):
y^{(i)}(w^Tx^{(i)}+b)\geq1

等等,怎么得出這個(gè)數(shù)學(xué)表達(dá)式的呢?
其技巧在于使用1和-1來(lái)定義類(lèi)別標(biāo)簽。針對(duì)y^{(i)}=1的情況,由于其滿(mǎn)足w^Tx^{(i)}+b\geq1的約束,兩邊都乘以y^{(i)}后,大于等于號(hào)保持不變。針對(duì)y^{(i)}=-1的情況,由于其滿(mǎn)足w^Tx^{(i)}+b\leq-1的約束,兩邊都乘以y^{(i)}后,負(fù)負(fù)得正,小于等于號(hào)變成了大于等于號(hào)。這樣,我們就可以使用一個(gè)公式來(lái)表達(dá)針對(duì)兩個(gè)不同類(lèi)別的約束函數(shù)了。

在邏輯回歸算法里,使用0和1作為類(lèi)別標(biāo)簽,而這里我們使用-1和1作為類(lèi)別標(biāo)簽。其目的都是為了讓數(shù)學(xué)表達(dá)盡量簡(jiǎn)潔。

總結(jié):求解SVM算法,就是在滿(mǎn)足約束條件y^{(i)}(w^Tx^{(i)}+b)\geq1的前提下,求解||w||^2的最小值。

2.松弛系數(shù)

針對(duì)線性不可分的數(shù)據(jù)集,上面介紹的方法就失靈了。因?yàn)闊o(wú)法找到最大間距的分隔超平面,如下圖左所示。

from sklearn.datasets import make_blobs

plt.figure(figsize=(13, 6), dpi=144)

# sub plot 1
plt.subplot(1, 2, 1)

X, y = make_blobs(n_samples=100, 
                  n_features=2, 
                  centers=[(1, 1), (2, 2)], 
                  random_state=4, 
                  shuffle=False,
                  cluster_std=0.4)

plt.title('Non-linear Separatable')

plt.xlim(0, 3)
plt.ylim(0, 3)
ax = plt.gca()                                  # gca 代表當(dāng)前坐標(biāo)軸,即 'get current axis'
ax.spines['right'].set_color('none')            # 隱藏坐標(biāo)軸
ax.spines['top'].set_color('none')

plt.scatter(X[y==0][:, 0], X[y==0][:, 1], marker='o')
plt.scatter(X[y==1][:, 0], X[y==1][:, 1], marker='s')
plt.plot([0.5, 2.5], [2.5, 0.5], '-r')

# sub plot 2
plt.subplot(1, 2, 2)

class1 = np.array([[1, 1], [1, 3], [2, 1], [1, 2], [2, 2], [1.5, 1.5], [1.2, 1.7]])
class2 = np.array([[4, 4], [5, 5], [5, 4], [5, 3], [4, 5], [6, 4], [5.5, 3.5], [4.5, 4.5], [2, 1.5]])

plt.title('Slack Variable')

plt.xlim(0, 7)
plt.ylim(0, 7)
ax = plt.gca()                                  # gca 代表當(dāng)前坐標(biāo)軸,即 'get current axis'
ax.spines['right'].set_color('none')            # 隱藏坐標(biāo)軸
ax.spines['top'].set_color('none')

plt.scatter(class1[:, 0], class1[:, 1], marker='o')
plt.scatter(class2[:, 0], class2[:, 1], marker='s')
plt.plot([1, 5], [5, 1], '-r')
plt.plot([0, 4], [4, 0], '--b', [2, 6], [6, 2], '--b')
plt.arrow(2, 1.5, 2.25, 2.25, shape='full', color='b')
plt.annotate(r'violate margin rule.',
             xy=(2, 1.5), xycoords='data',
             xytext=(0.2, 0.5), fontsize=10,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.annotate(r'normal sample. $\epsilon = 0$',
             xy=(4, 5), xycoords='data',
             xytext=(4.5, 5.5), fontsize=10,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.annotate(r'$\epsilon > 0$',
             xy=(3, 2.5), xycoords='data',
             xytext=(3, 1.5), fontsize=10,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
figure8_3.png

解決這個(gè)問(wèn)題的辦法是引入一個(gè)參數(shù)\varepsilon,稱(chēng)為松弛系數(shù)。然后把優(yōu)化的目標(biāo)函數(shù)變?yōu)椋?br> argmin||w||^2+R\sum_{i=1}^{m}\varepsilon_i

其中,m為數(shù)據(jù)集的個(gè)數(shù),R為算法參數(shù)。其約束條件相應(yīng)地變?yōu)椋?br> y^{(i)}(w^Tx^{(i)}+b)\geq1-\varepsilon_i

怎么理解松弛系數(shù)呢?我們可以把\varepsilon_i理解為數(shù)據(jù)樣本x^{(i)}違反最大間距規(guī)則的程度,如上圖右所示。針對(duì)大部分正常的樣本,即滿(mǎn)足約束條件的樣本\varepsilon_i=0。而對(duì)部分違反最大間距規(guī)則的樣本\varepsilon_i>0。而參數(shù)R則表示對(duì)違反最大間距規(guī)則的樣本的“懲罰”力度。當(dāng)R選擇一個(gè)很大的值時(shí),我們的目標(biāo)函數(shù)對(duì)違反最大間距規(guī)則的點(diǎn)的懲罰力度將變得很大。當(dāng)R選擇一個(gè)較小的值時(shí),針對(duì)那些違反最大間距規(guī)則的樣本的懲罰力度較小,我們的模型就會(huì)傾向于允許部分點(diǎn)違反最大間距規(guī)則。我們可以把y^{(i)}(w^Tx^{(i)}+b)作為橫坐標(biāo),把樣本由于違反約束條件所付出的代價(jià)J_i作為縱坐標(biāo),畫(huà)出二者的關(guān)系如下圖所示。

plt.figure(figsize=(8, 4), dpi=144)

plt.title('Cost')

plt.xlim(0, 4)
plt.ylim(0, 2)
plt.xlabel('$y^{(i)} (w^T x^{(i)} + b)$')
plt.ylabel('Cost')
ax = plt.gca()                                  # gca 代表當(dāng)前坐標(biāo)軸,即 'get current axis'
ax.spines['right'].set_color('none')            # 隱藏坐標(biāo)軸
ax.spines['top'].set_color('none')

plt.plot([0, 1], [1.5, 0], '-r')
plt.plot([1, 3], [0.015, 0.015], '-r')
plt.annotate(r'$J_i = R \epsilon_i$ for $y^{(i)} (w^T x^{(i)} + b) \geq 1 - \epsilon_i$',
             xy=(0.7, 0.5), xycoords='data',
             xytext=(1, 1), fontsize=10,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.annotate(r'$J_i = 0$ for $y^{(i)} (w^T x^{(i)} + b) \geq 1$',
             xy=(1.5, 0), xycoords='data',
             xytext=(1.8, 0.2), fontsize=10,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
figure8_4.png

從圖中可以清楚地看出,針對(duì)那些沒(méi)有違反約束條件y^{(i)}(w^Tx^{(i)}+b)\geq1的樣本,其成本為0。而針對(duì)那些違反了約束條件y^{(i)}(w^Tx^{(i)}+b)\geq1-\varepsilon_i的樣本,其成本與\varepsilon_i成正比。

從這里的描述可知,引入松弛系數(shù)類(lèi)似于邏輯回歸算法里的成本函數(shù)引入正則項(xiàng),目的都是為了糾正過(guò)擬合問(wèn)題,讓支持向量機(jī)對(duì)噪聲數(shù)據(jù)有更強(qiáng)的適應(yīng)性。當(dāng)出現(xiàn)一些違反最大間距規(guī)則的噪聲樣本時(shí),仍然希望我們的分隔超平面是原來(lái)的樣子,這就是松弛系數(shù)的作用。

2.核函數(shù)

什么是核函數(shù)?核函數(shù)是特征轉(zhuǎn)換函數(shù)。

1.最簡(jiǎn)單的核函數(shù)

根據(jù)上面的介紹,我們的任務(wù)是找出合適的參數(shù)w和b,使得由它們決定的分隔超平面間距最大,且能正確地對(duì)數(shù)據(jù)集進(jìn)行分類(lèi)。間距最大是我們的優(yōu)化目標(biāo),正確地對(duì)數(shù)據(jù)集進(jìn)行分類(lèi)是約束條件。用數(shù)學(xué)來(lái)表達(dá),在滿(mǎn)足約束條件y^{(i)}(w^Tx^{(i)}+b)\geq1,即y^{(i)}(w^Tx^{(i)}+b)-1\geq0的前提下,求\frac{1}{2}||w||^2的最小值。

拉格朗日乘子法是解決約束條件下,求函數(shù)極值的理想方法。其方法是引入非負(fù)系數(shù)\alpha作為約束條件的權(quán)重:
L=\frac{1}{2}||w||^2-\sum_{i=1}^{m}\alpha_i(y^{(i)}(w^Tx^{(i)}+b)-1)

公式中,針對(duì)數(shù)據(jù)集中的每個(gè)樣本x^{(i)}y^{(i)},都有一個(gè)系數(shù)\alpha_i與之對(duì)應(yīng)。根據(jù)微積分的知識(shí),極值處的偏導(dǎo)數(shù)為0。我們先求L對(duì)w的偏導(dǎo)數(shù),并令其偏導(dǎo)數(shù)為0:
\frac{\partial L}{\partial w}=w-\sum_{i=1}^{m}\alpha_iy^{(i)}x^{(i)}=0

從而可以得到w和\alpha的關(guān)系:
w=\sum_{i=1}^{m}\alpha_iy^{(i)}x^{(i)}

至此,我們就知道了,為什么要把求\frac{2}{||w||}的最大值轉(zhuǎn)換為求\frac{1}{2}||w||^2的最小值,其目的就是為了使得w的數(shù)學(xué)表達(dá)盡量簡(jiǎn)潔優(yōu)美。接著我們繼續(xù)求L對(duì)b的偏導(dǎo)數(shù),并令其偏導(dǎo)數(shù)為0:
\frac{\partial L}{\partial b}=\sum_{i=1}^{m}y^{(i)}\alpha_i=0

w=\sum_{i=1}^{m}\alpha_iy^{(i)}x^{(i)}\sum_{i=1}^{m}y^{(i)}\alpha_i=0代入L,通過(guò)代數(shù)運(yùn)算可得:
L=\sum_{i=1}^{m}\alpha_i-\frac{1}{2}\sum_{i=1}^{m}\sum_{j=1}^{m}\alpha_i\alpha_jy^{(i)}y^{(j)}x^{(i)T}x^{(j)}

這個(gè)公式看起來(lái)很復(fù)雜。我們解釋一下公式里各個(gè)變量的含義。其中,m是數(shù)據(jù)集的個(gè)數(shù),\alpha是拉格朗日乘子法引入的一個(gè)系數(shù),針對(duì)數(shù)據(jù)集中的每個(gè)樣本x^{(i)},都有對(duì)應(yīng)的\alpha_i。x^{(i)}是數(shù)據(jù)集中第i個(gè)樣本的輸入,它是一個(gè)向量,y^{(i)}是數(shù)據(jù)集第i個(gè)樣本的輸出標(biāo)簽,其值為y^{(i)} \in \left \{-1,1 \right \}。

怎么求這個(gè)公式的最小值,是數(shù)學(xué)分析要解決的問(wèn)題。這是一個(gè)典型的二次規(guī)劃問(wèn)題。目前廣泛應(yīng)用的是一個(gè)稱(chēng)為SMO(序列最小優(yōu)化)的算法。這里不再展開(kāi)。

最后求解出來(lái)的\alpha有個(gè)明顯的特點(diǎn),即大部分\alpha_i=0,這個(gè)結(jié)論背后的原因很直觀,因?yàn)橹挥心切┲С窒蛄克鶎?duì)應(yīng)的樣本,直接決定了間隙的大小,其他離分隔超平面太遠(yuǎn)的樣本,對(duì)間隙大小根本沒(méi)有影響。

實(shí)際上,推導(dǎo)這個(gè)公式的目的就是為了引入支持向量機(jī)的另外一個(gè)核心概念:核函數(shù)。

我們注意到L里的x^{(i)T}x^{(j)}部分,其中x^{(i)}是一個(gè)特征向量,所以x^{(i)T}x^{(j)}是一個(gè)數(shù)值,它是兩個(gè)輸入特征向量的內(nèi)積。另外,我們的預(yù)測(cè)函數(shù)為:
\hat y = w^Tx+b=\sum_{i=1}^{m}\alpha_iy^{(i)}x^{(i)T}x+b

當(dāng)\hat y > 0時(shí)預(yù)測(cè)類(lèi)別為1,當(dāng)\hat y < 0時(shí)預(yù)測(cè)類(lèi)別為-1。注意到預(yù)測(cè)函數(shù)里也包含了式子x^{(i)T}x。我們把K(x^{(i)},x^{(j)})=x^{(i)T}x^{(j)}稱(chēng)為核函數(shù)。x^{(i)T}x^{(j)}是兩個(gè)向量的內(nèi)積,它的物理含義是衡量?jī)蓚€(gè)向量的相似性。典型的,當(dāng)這兩個(gè)向量相互垂直時(shí),即完全線性無(wú)關(guān),此時(shí)x^{(i)T}x^{(j)}=0。引入核函數(shù)之后,預(yù)測(cè)函數(shù)就變成:
\hat y=\sum_{i=1}^{m}\alpha_iy^{(i)}K(x^{(i)},x)+b

思考:在前面我們把方形類(lèi)別的約束定義為w^Tx+b\geq1,把圓形類(lèi)別的約束定義為w^Tx+b\leq-1。而這里的預(yù)測(cè)函數(shù),又以0為分界點(diǎn),即針對(duì)輸入特征向量x,當(dāng)w^Tx+b>0時(shí),預(yù)測(cè)為方形類(lèi)別,這是為什么呢?

2.相似性函數(shù)

為什么我們要引入核函數(shù)?假設(shè)我們有一個(gè)數(shù)據(jù)集,只有一個(gè)輸入特征,要對(duì)這個(gè)數(shù)據(jù)集進(jìn)行分類(lèi)。由于只有一個(gè)輸入特征,這些訓(xùn)練樣本分布在一條直線上,此時(shí)我們很難找出一個(gè)分隔超平面來(lái)分隔這個(gè)數(shù)據(jù)集。如左圖所示。

plt.figure(figsize=(13, 6), dpi=144)

class1 = np.array([[1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [3, 2], [4, 1], [5, 1]])
class2 = np.array([[2.2, 4], [1.5, 5], [1.8, 4.6], [2.4, 5], [3.2, 5], [3.7, 4], [4.5, 4.5], [5.4, 3]])

# sub plot 1
plt.subplot(1, 2, 1)

plt.title('Non-linear Separatable in Low Dimension')

plt.xlim(0, 6)
plt.ylim(0, 6)
plt.yticks(())
plt.xlabel('X1')
ax = plt.gca()                                  # gca 代表當(dāng)前坐標(biāo)軸,即 'get current axis'
ax.spines['right'].set_color('none')            # 隱藏坐標(biāo)軸
ax.spines['top'].set_color('none')
ax.spines['left'].set_color('none')

plt.scatter(class1[:, 0], np.zeros(class1[:, 0].shape[0]) + 0.05, marker='o')
plt.scatter(class2[:, 0], np.zeros(class2[:, 0].shape[0]) + 0.05, marker='s')

# sub plot 2
plt.subplot(1, 2, 2)

plt.title('Linear Separatable in High Dimension')

plt.xlim(0, 6)
plt.ylim(0, 6)
plt.xlabel('X1')
plt.ylabel('X2')
ax = plt.gca()                                  # gca 代表當(dāng)前坐標(biāo)軸,即 'get current axis'
ax.spines['right'].set_color('none')            # 隱藏坐標(biāo)軸
ax.spines['top'].set_color('none')

plt.scatter(class1[:, 0], class1[:, 1], marker='o')
plt.scatter(class2[:, 0], class2[:, 1], marker='s')
plt.plot([1, 5], [3.8, 2], '-r')
figure8_5.png

為了解決這個(gè)問(wèn)題,我們可以想個(gè)辦法,用一定的規(guī)則把這些無(wú)法進(jìn)行線性分隔的樣本,映射到更高維度的空間里,然后在高維度空間里找出分隔超平面。針對(duì)這個(gè)例子,把一維空間里的樣本映射到二維空間,這樣很容易就能找出一個(gè)分隔超平面把這些樣本分離開(kāi)。如上面右圖所示。

SVM的核函數(shù)就是為了實(shí)現(xiàn)這種相似性映射。從前面的介紹我們知道,最簡(jiǎn)單的核函數(shù)是K(x^{(i)},x^{(j)})=x^{(i)T}x^{(j)},它衡量的是兩個(gè)輸入特征向量的相似性??梢酝ㄟ^(guò)定義核函數(shù)K(x^{(i)},x^{(j)})來(lái)重新定義相似性,從而得到想要的映射。例如,在基因測(cè)試領(lǐng)域,我們需要根據(jù)DNA分子的特征來(lái)定義相似性函數(shù),即核函數(shù)。在文本處理領(lǐng)域,也可以自己定義核函數(shù)來(lái)衡量?jī)蓚€(gè)詞之間的相似性。

怎樣把低維度的空間映射到高維度的空間呢?我們是否還記得之前介紹過(guò)的一個(gè)解決欠擬合的方法,就是使用多項(xiàng)式來(lái)增加特征數(shù),這個(gè)本質(zhì)上就是從低維度到高維度。針對(duì)上圖左中的例子,我們的輸入特征是一維的,即只有[x_1]變量,如果我們要變成二維的,一個(gè)方法是把輸入特征變?yōu)?img class="math-inline" src="https://math.jianshu.com/math?formula=%5Bx_1%2Cx_1%5E2%5D" alt="[x_1,x_1^2]" mathimg="1">,此時(shí)的輸入特征就變成了一個(gè)二維向量。定義這種特征映射的函數(shù)為\Phi(x),稱(chēng)之為相似性函數(shù)。針對(duì)一個(gè)輸入特征向量x,經(jīng)過(guò)\Phi(x)作用之后,會(huì)變成一個(gè)新的、更高維度的輸入特征向量。這樣在原來(lái)低維度計(jì)算相似性的運(yùn)算x^{(i)T}x^{(j)},就可以轉(zhuǎn)換為高維度空間里進(jìn)行相似性運(yùn)算\Phi(x^{(i)})^T\Phi(x^{(j)})

思考:核函數(shù)K(x^{(i)},x^{(j)})和相似性函數(shù)\Phi(x)有什么關(guān)系?
相似性函數(shù)是特征映射函數(shù),比如針對(duì)二維的特征向量[x_1,x_2],我們可以定義相似性函數(shù)為\Phi(x)=[x_1,x_2,x_1x_2,x_1^2,x_2^2],經(jīng)過(guò)相似性函數(shù)轉(zhuǎn)換后,二維的特征向量就變成了五維的特征向量。而核函數(shù)定義為特征向量的內(nèi)積,經(jīng)過(guò)相似性函數(shù)\Phi(x)轉(zhuǎn)換后,核函數(shù)即變?yōu)閮蓚€(gè)五維特征向量的內(nèi)積,即K(x^{(i)},x^{(j)})=\Phi(x^{(i)})^T\Phi(x^{(j)})。

這里我們介紹相似性函數(shù)\Phi(x)的目的,是為了幫助理解核函數(shù)的生成過(guò)程有其背后的思想。在實(shí)際計(jì)算過(guò)程中,我們不會(huì)計(jì)算相似性函數(shù)及其映射值,因?yàn)檫@樣做的計(jì)算效率很低。例如,我們把二維空間映射到n維空間,如果n非常大,要在n維空間里計(jì)算兩個(gè)向量的內(nèi)積,需要n*n次運(yùn)算才可以完成,這個(gè)計(jì)算成本是非常高的。

3.常用的核函數(shù)

核函數(shù)一般和應(yīng)用場(chǎng)景相關(guān),例如在基因測(cè)試領(lǐng)域和文本處理領(lǐng)域,它們的核函數(shù)可能是不一樣的,有專(zhuān)門(mén)針對(duì)特定應(yīng)用領(lǐng)域進(jìn)行核函數(shù)開(kāi)發(fā)和建模的科研人員在從事這方面的研究。雖然核函數(shù)和應(yīng)用場(chǎng)景相關(guān),但實(shí)際上還是有一些通用的、“萬(wàn)金油”式的核函數(shù)。常用的核函數(shù)有兩種,一種是多項(xiàng)式核函數(shù),顧名思義,是對(duì)輸入特征向量增加多項(xiàng)式的一種相似性映射函數(shù),其數(shù)學(xué)表達(dá)為:
K(x^{(i)},x^{(j)})=(\gamma x^{(i)T}x^{(j)}+c)^n

其中\gamma為正數(shù),c為非負(fù)數(shù)。我們介紹過(guò)的線性核函數(shù)K(x^{(i)},x^{(j)})=x^{(i)T}x^{(j)}是多項(xiàng)式核函數(shù)在n=1, \gamma=1, c=0處的一種特例。在二維空間里,K(x^{(i)},x^{(j)})=x^{(i)T}x^{(j)}只能表達(dá)直線的分隔超平面,而多項(xiàng)式核函數(shù)K(x^{(i)},x^{(j)})=(\gamma x^{(i)T}x^{(j)}+c)^nn>1時(shí),可以表達(dá)更復(fù)雜的、非直線的分隔超平面。

另一個(gè)常用的核函數(shù)是高斯核函數(shù),其數(shù)學(xué)表達(dá)式為:
K(x^{(i)},x^{(j)})=exp\big (-\frac{(x^{(i)}-x^{(j)})^2}{2\sigma^2}\big)

如果我們的輸入特征是一維的標(biāo)量,那么高斯核函數(shù)對(duì)應(yīng)的形狀就是一個(gè)反鐘形的曲線,其參數(shù)\sigma控制反鐘形的寬度,如下所示。

def gaussian_kernel(x, mean, sigma):
    return np.exp(- (x - mean)**2 / (2 * sigma**2))

x = np.linspace(0, 6, 500)
mean = 1
sigma1 = 0.1
sigma2 = 0.3

plt.figure(figsize=(10, 3), dpi=144)

# sub plot 1
plt.subplot(1, 2, 1)
plt.title('Gaussian for $\sigma={0}$'.format(sigma1))

plt.xlim(0, 2)
plt.ylim(0, 1.1)
ax = plt.gca()                                  # gca 代表當(dāng)前坐標(biāo)軸,即 'get current axis'
ax.spines['right'].set_color('none')            # 隱藏坐標(biāo)軸
ax.spines['top'].set_color('none')

plt.plot(x, gaussian_kernel(x, mean, sigma1), 'r-')

# sub plot 2
plt.subplot(1, 2, 2)
plt.title('Gaussian for $\sigma={0}$'.format(sigma2))

plt.xlim(0, 2)
plt.ylim(0, 1.1)
ax = plt.gca()                                  # gca 代表當(dāng)前坐標(biāo)軸,即 'get current axis'
ax.spines['right'].set_color('none')            # 隱藏坐標(biāo)軸
ax.spines['top'].set_color('none')

plt.plot(x, gaussian_kernel(x, mean, sigma2), 'r-')
figure8_6.png

由于K(x^{(i)},x^{(j)})=\Phi(x^{(i)})^T\Phi(x^{(j)}),經(jīng)過(guò)合適的數(shù)學(xué)變換,可得高斯核函數(shù)對(duì)應(yīng)的特征轉(zhuǎn)換函數(shù)為:
\Phi(x)=\sum_{i=0}^{\infty}exp(-x^2)\sqrt{\frac{2^i}{i!}}x^i

注意前面無(wú)限多項(xiàng)的累加器\sum_{i=0}^{\infty},其物理意義就是把特征向量轉(zhuǎn)換到無(wú)限多維向量空間里,即高斯核函數(shù)可以把輸入特征向量擴(kuò)展到無(wú)限維空間里。

接下來(lái)看一下高斯核函數(shù)對(duì)應(yīng)的預(yù)測(cè)函數(shù):
\hat y=\sum_{i=1}^{m}\alpha_iy^{(i)}K(x^{(i)},x)+b

其中,K(x^{(i)},x)是高斯核函數(shù),而\alpha_i只在支持向量對(duì)應(yīng)的樣本處不為0,其他的樣本為0。由此得知,預(yù)測(cè)函數(shù)是中心點(diǎn)在支持向量處的高斯函數(shù)的線性組合,其線性組合的系數(shù)為\alpha_iy^{(i)}。因此,高斯核函數(shù)也稱(chēng)為徑向基核函數(shù)(RBF,Radial Basic Function),即反鐘形函數(shù)的線性組合。

4.核函數(shù)的對(duì)比

我們對(duì)幾個(gè)常用的核函數(shù)進(jìn)行對(duì)比,看看它們各有哪些優(yōu)缺點(diǎn)。

1.線性核函數(shù)
K(x^{(i)},x^{(j)})=x^{(i)T}x^{(j)}

這是最簡(jiǎn)單的核函數(shù),它直接計(jì)算兩個(gè)輸入特征向量的內(nèi)積。它的優(yōu)點(diǎn)是簡(jiǎn)單,運(yùn)算效率高,因?yàn)椴簧婕皬?fù)雜的變換;結(jié)果容易解釋?zhuān)驗(yàn)榭偸悄苌梢粋€(gè)最簡(jiǎn)潔的線性分隔超平面。它的缺點(diǎn)也很明顯,即對(duì)線性不可分的數(shù)據(jù)集沒(méi)有很好的辦法。

2.多項(xiàng)式核函數(shù)
K(x^{(i)},x^{(j)})=(\gamma x^{(i)T}x^{(j)}+c)^n

多項(xiàng)式核函數(shù)通過(guò)多項(xiàng)式來(lái)作為特征映射函數(shù),它的優(yōu)點(diǎn)是可以擬合出復(fù)雜的分隔超平面。它的缺點(diǎn)是可選的參數(shù)太多,有\gamma,c,n這三個(gè)參數(shù)要選擇,在實(shí)踐過(guò)程中,選擇一組合適的參數(shù)會(huì)變得比較困難;另外一個(gè)缺點(diǎn)是,多項(xiàng)式的階數(shù)n不宜太高,否則會(huì)給模型的求解帶來(lái)一些困難。典型地,當(dāng)x^{(i)T}x^{(j)}<1時(shí),經(jīng)過(guò)n次方運(yùn)算后會(huì)接近于0,而當(dāng)x^{(i)T}x^{(j)}>1時(shí),經(jīng)過(guò)n次方運(yùn)算后,又會(huì)變得非常大,這樣核函數(shù)就會(huì)變得不夠穩(wěn)定。

3.高斯核函數(shù)
K(x^{(i)},x^{(j)})=exp\big (-\frac{(x^{(i)}-x^{(j)})^2}{2\sigma^2}\big)
高斯核函數(shù)可以把輸入特征映射到無(wú)限多維,所以它會(huì)比線性核函數(shù)功能上要強(qiáng)大很多,并且沒(méi)有多項(xiàng)式核函數(shù)的數(shù)值計(jì)算那么困難,因?yàn)樗暮撕瘮?shù)計(jì)算出來(lái)的值永遠(yuǎn)都在[0,1]之間。高斯核函數(shù)還有一個(gè)優(yōu)點(diǎn)是參數(shù)容易選擇,因?yàn)樗挥幸粋€(gè)參數(shù)\sigma。它的缺點(diǎn)是不容易解釋?zhuān)驗(yàn)橛成涞綗o(wú)限多維向量空間這個(gè)事情顯得太不直觀;計(jì)算速度比較慢;容易造成過(guò)擬合,原因是映射到無(wú)限維向量空間,這是個(gè)非常復(fù)雜的模型,它會(huì)試圖去擬合所有的樣本,從而造成過(guò)擬合。

在實(shí)踐中怎么選擇核函數(shù)呢?更進(jìn)一步,邏輯回歸算法也可以用來(lái)解決分類(lèi)問(wèn)題,到底是用邏輯回歸算法還是用SVM算法呢?假設(shè)n是特征個(gè)數(shù),m是訓(xùn)練數(shù)據(jù)集的樣本個(gè)數(shù),一般情況下可以按照下面的規(guī)則來(lái)選擇算法。

如果n相對(duì)m來(lái)說(shuō)比較大,例如n=10000,m=10 ~ 1000,如文本處理問(wèn)題,這個(gè)時(shí)候使用邏輯回歸算法或者線性核函數(shù)的SVM算法都可以;如果n比較小,m中等大小,例如n=1 ~ 1000,m=10 ~ 10000,那么可以使用高斯核函數(shù)的SVM算法;如果n比較小,m比較大,例如n=1 ~ 1000,m=50000+,那么一般需要增加特征,此時(shí)需要使用多項(xiàng)式核函數(shù)或者高斯核函數(shù)的SVM算法。

更一般的算法選擇原則是,針對(duì)數(shù)據(jù)量很大的問(wèn)題,我們可以選擇復(fù)雜一點(diǎn)的模型。雖然復(fù)雜模型容易造成過(guò)擬合,但由于數(shù)據(jù)量很大,可以有效地彌補(bǔ)過(guò)擬合問(wèn)題。如果數(shù)據(jù)量比較小,一般需要選擇簡(jiǎn)單一點(diǎn)的模型,否則很容易造成過(guò)擬合,此時(shí)要特別注意模型是否欠擬合,如果出現(xiàn)了欠擬合,可以使用增加多項(xiàng)式特征的方法糾正欠擬合問(wèn)題。

3.scikit-learn里的SVM

在scikit-learn里對(duì)SVM的算法實(shí)現(xiàn)都在包sklearn.svm下面,其中SVC類(lèi)是用來(lái)進(jìn)行分類(lèi)任務(wù)的,SVR類(lèi)是用來(lái)進(jìn)行數(shù)值回歸任務(wù)的。我們可能會(huì)有疑問(wèn),SVM不是用來(lái)進(jìn)行分類(lèi)的算法嗎,為什么可以用來(lái)進(jìn)行數(shù)值回歸?實(shí)際上這只是數(shù)學(xué)上的一些擴(kuò)展而已,在計(jì)算機(jī)里,可以用離散的數(shù)值計(jì)算來(lái)代替連續(xù)的數(shù)值回歸。我們?cè)贙-近鄰算法中已經(jīng)看到過(guò)這種擴(kuò)展實(shí)現(xiàn)。

我們以SVC為例。首先需要選擇SVM的核函數(shù),由參數(shù)kernel來(lái)指定,其中值linear表示線性核函數(shù),它只能產(chǎn)生直線形狀的分隔超平面;值poly表示多項(xiàng)式核函數(shù),用它可以構(gòu)建出復(fù)雜形狀的分隔超平面;值rbf表示徑向基核函數(shù),即高斯核函數(shù)。

不同的核函數(shù)需要指定不同的參數(shù)。針對(duì)線性核函數(shù),只需要指定參數(shù)C,它表示對(duì)不符合最大間距規(guī)則的樣本的懲罰力度,即前面介紹的系數(shù)R。針對(duì)多項(xiàng)式核函數(shù),除了參數(shù)C之外,還需要指定degree,它表示多項(xiàng)式的階數(shù)。針對(duì)高斯核函數(shù),除了參數(shù)C之外,還需要指定gamma值,這個(gè)值對(duì)應(yīng)的是高斯核函數(shù)公式中的\frac{1}{2 \sigma^2}的值。

下面先來(lái)看一個(gè)最簡(jiǎn)單的例子。我們生成一個(gè)有兩個(gè)特征、包含兩種類(lèi)別的數(shù)據(jù),然后用線性核函數(shù)的SVM算法進(jìn)行分類(lèi):

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
def plot_hyperplane(clf, X, y, 
                    h=0.02, 
                    draw_sv=True, 
                    title='hyperplan'):
    # create a mesh to plot in
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))

    plt.title(title)
    plt.xlim(xx.min(), xx.max())
    plt.ylim(yy.min(), yy.max())
    plt.xticks(())
    plt.yticks(())
    
    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='hot', alpha=0.5)

    markers = ['o', 's', '^']
    colors = ['b', 'r', 'c']
    labels = np.unique(y)
    for label in labels:
        plt.scatter(X[y==label][:, 0], 
                    X[y==label][:, 1], 
                    c=colors[label], 
                    marker=markers[label])
    if draw_sv:
        sv = clf.support_vectors_
        plt.scatter(sv[:, 0], sv[:, 1], c='y', marker='x')
from sklearn import svm
from sklearn.datasets import make_blobs

X, y = make_blobs(n_samples=100, centers=2, 
                  random_state=0, cluster_std=0.3)
clf = svm.SVC(C=1.0, kernel='linear')
clf.fit(X, y)

plt.figure(figsize=(12, 4), dpi=144)
plot_hyperplane(clf, X, y, h=0.01, 
                title='Maximum Margin Hyperplan')

輸出的圖形如下所示,其中帶有x標(biāo)記的點(diǎn)即為支持向量,它保存在模型的support_vectors里。


figure8_7.png

此處需要注意的是plot_hyperplane()函數(shù),其主要功能是畫(huà)出樣本點(diǎn),同時(shí)畫(huà)出分類(lèi)區(qū)間。它的主要原理是使用numpy.meshgrid()生成一個(gè)坐標(biāo)矩陣,最后用contourf()函數(shù)為坐標(biāo)矩陣中不同類(lèi)別的點(diǎn)填充不同的顏色。其中,contourf()函數(shù)是畫(huà)等高線并填充顏色的函數(shù)。

接著來(lái)看另外一個(gè)例子。我們生成一個(gè)有兩個(gè)特征、包含三種類(lèi)別的數(shù)據(jù)集,然后分別構(gòu)造出4個(gè)SVM算法來(lái)擬合數(shù)據(jù)集,分別是線性核函數(shù)、三階多項(xiàng)式核函數(shù)、gamma=0.5的高斯核函數(shù),以及gamma=0.1的高斯核函數(shù)。最后把這4個(gè)SVM算法擬合出來(lái)的分隔超平面畫(huà)出來(lái)。

from sklearn import svm
from sklearn.datasets import make_blobs

X, y = make_blobs(n_samples=100, centers=3, 
                  random_state=0, cluster_std=0.8)
clf_linear = svm.SVC(C=1.0, kernel='linear')
clf_poly = svm.SVC(C=1.0, kernel='poly', degree=3)
clf_rbf = svm.SVC(C=1.0, kernel='rbf', gamma=0.5)
clf_rbf2 = svm.SVC(C=1.0, kernel='rbf', gamma=0.1)

plt.figure(figsize=(10, 10), dpi=144)

clfs = [clf_linear, clf_poly, clf_rbf, clf_rbf2]
titles = ['Linear Kernel', 
          'Polynomial Kernel with Degree=3', 
          'Gaussian Kernel with $\gamma=0.5$', 
          'Gaussian Kernel with $\gamma=0.1$']
for clf, i in zip(clfs, range(len(clfs))):
    clf.fit(X, y)
    plt.subplot(2, 2, i+1)
    plot_hyperplane(clf, X, y, title=titles[i])

輸出的圖形如下所示,其中帶有x標(biāo)記的點(diǎn)即為支持向量。


figure8_8.png

左上角是線性核函數(shù),它只能擬合出直線分隔超平面。右上角是三階多項(xiàng)式核函數(shù),它能擬合出復(fù)雜曲線分隔超平面。左下角是gamma=0.5的高斯核函數(shù),右下角是gamma=0.1的高斯核函數(shù),通過(guò)調(diào)整參數(shù)gamma的值,可以調(diào)整分隔超平面的形狀。典型地,gamma值太大,越容易造成過(guò)擬合,gamma值太小,高斯核函數(shù)會(huì)退化成線性核函數(shù)。我們把代碼中的gamma值改為100和0.01后看一下輸出圖形是什么樣的。

思考:左下角gamma=0.5的高斯核函數(shù)的圖片,帶有x標(biāo)記的點(diǎn)是支持向量。我們之前介紹過(guò),離分隔超平面最近的點(diǎn)是支持向量,為什么很多離分隔超平面很遠(yuǎn)的點(diǎn),也是支持向量呢?

原因是高斯核函數(shù)把輸入特征向量映射到了無(wú)限維的向量空間里,在映射后的高維向量空間里,這些點(diǎn)其實(shí)是離分隔超平面最近的點(diǎn)。當(dāng)回到二維向量空間中時(shí),這些點(diǎn)“看起來(lái)”就不像是距離分隔超平面最近的點(diǎn)了,但實(shí)際上它們就是支持向量。

4.示例:乳腺癌檢測(cè)

之前我們使用邏輯回歸算法進(jìn)行過(guò)乳腺癌檢測(cè)模型的學(xué)習(xí)和訓(xùn)練。這里我們?cè)偈褂弥С窒蛄繖C(jī)算法來(lái)解決這個(gè)問(wèn)題。首先我們載入數(shù)據(jù):

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import train_test_split
# 載入數(shù)據(jù)
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
X = cancer.data
y = cancer.target
print('data shape: {0}; no. positive: {1}; no. negative: {2}'.format(
    X.shape, y[y==1].shape[0], y[y==0].shape[0]))
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

輸出如下:

data shape: (569, 30); no. positive: 357; no. negative: 212

可以看出,我們的數(shù)據(jù)集很小。高斯核函數(shù)太復(fù)雜,容易造成過(guò)擬合,模型效果應(yīng)該不會(huì)太好。我們先用高斯核函數(shù)來(lái)試一下,看與我們的猜測(cè)是否一致。

from sklearn.svm import SVC
clf = SVC(C=1.0, kernel='rbf', gamma=0.1)
clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train)
test_score = clf.score(X_test, y_test)
print('train score: {0}; test score: {1}'.format(train_score, test_score))

輸出如下:

train score: 1.0; test score: 0.5877192982456141

訓(xùn)練數(shù)據(jù)集分?jǐn)?shù)為1.0,交叉驗(yàn)證數(shù)據(jù)集分?jǐn)?shù)只有0.52,這是典型的過(guò)擬合現(xiàn)象。這里gamma=0.1,這個(gè)值相對(duì)已經(jīng)比較小了。我們可以把gamma改的更小如0.0001看看什么結(jié)果。

當(dāng)然,我們完全可以使用前面介紹過(guò)的GridSearchCV來(lái)自動(dòng)選擇最優(yōu)參數(shù)。我們看看如果使用高斯模型,最優(yōu)的gamma參數(shù)值是多少,其對(duì)應(yīng)的模型交叉驗(yàn)證評(píng)分是多少。

from sklearn.model_selection import learning_curve
import numpy as np
def plot_learning_curve(plt, estimator, title, X, y, ylim=None, cv=None, n_jobs=1, train_sizes=np.linspace(.1, 1.0, 5)):
    plt.title(title)
    if ylim is not None:
        plt.ylim(*ylim)
    plt.xlabel("Training examples")
    plt.ylabel("Score")
    train_sizes, train_scores, test_scores = learning_curve(estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)
    train_scores_mean = np.mean(train_scores, axis=1)
    train_scores_std = np.std(train_scores, axis=1)
    test_scores_mean = np.mean(test_scores, axis=1)
    test_scores_std = np.std(test_scores, axis=1)
    plt.grid()
    plt.fill_between(train_sizes, train_scores_mean - train_scores_std, train_scores_mean + train_scores_std, alpha=0.1, color="r")
    plt.fill_between(train_sizes, test_scores_mean - test_scores_std, test_scores_mean + test_scores_std, alpha=0.1, color="g")
    plt.plot(train_sizes, train_scores_mean, 'o--', color="r", label="Training score")
    plt.plot(train_sizes, test_scores_mean, 'o-', color="g", label="Cross-validation score")
    plt.legend(loc="best")
    return plt

def plot_param_curve(plt, train_sizes, cv_results, xlabel):
    train_scores_mean = cv_results['mean_train_score']
    train_scores_std = cv_results['std_train_score']
    test_scores_mean = cv_results['mean_test_score']
    test_scores_std = cv_results['std_test_score']
    plt.title('parameters turning')
    plt.grid()
    plt.xlabel(xlabel)
    plt.ylabel('score')
    plt.fill_between(train_sizes, train_scores_mean - train_scores_std, train_scores_mean + train_scores_std,alpha=0.1, color="r")
    plt.fill_between(train_sizes, test_scores_mean - test_scores_std,test_scores_mean + test_scores_std, alpha=0.1, color="g")
    plt.plot(train_sizes, train_scores_mean, '.--', color="r", label="Training score")
    plt.plot(train_sizes, test_scores_mean, '.-', color="g", label="Cross-validation score")
    plt.legend(loc="best")
    return plt
from sklearn.model_selection import GridSearchCV
gammas = np.linspace(0, 0.0003, 30)
param_grid = {'gamma': gammas}
clf = GridSearchCV(SVC(), param_grid, cv=5)
clf.fit(X, y)
print("best param: {0}\nbest score: {1}".format(clf.best_params_,clf.best_score_))
plt.figure(figsize=(10, 4), dpi=144)
plot_param_curve(plt, gammas, clf.cv_results_, xlabel='gamma');

輸出如下:

best param: {'gamma': 0.00011379310344827585}
best score: 0.9367311072056239
figure8_8_2.png

由此可見(jiàn),即使在最好的gamma參數(shù)下,其平均最優(yōu)得分也只是0.9367311072056239。我們選擇在gamma為0.01時(shí),畫(huà)出學(xué)習(xí)曲線,更直觀地觀察模型擬合情況。

import time
from sklearn.model_selection import ShuffleSplit
cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
title = 'Learning Curves for Gaussian Kernel'
start = time.clock()
plt.figure(figsize=(10, 4), dpi=144)
plot_learning_curve(plt, SVC(C=1.0, kernel='rbf', gamma=0.01), title, X, y, ylim=(0.5, 1.01), cv=cv)
print('elaspe: {0:.6f}'.format(time.clock()-start))

輸出如下:

elaspe: 0.680074

畫(huà)出來(lái)的圖形如下所示:


figure8_9.png

這是明顯的過(guò)擬合現(xiàn)象,交叉驗(yàn)證數(shù)據(jù)集的評(píng)分非常低,且離訓(xùn)練數(shù)據(jù)集評(píng)分非常遠(yuǎn)。

接下來(lái)?yè)Q一個(gè)模型,使用二階多項(xiàng)式核函數(shù)的SVM來(lái)擬合模型,看看結(jié)果如何。

from sklearn.svm import SVC
clf = SVC(C=1.0, kernel='poly', degree=2)
clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train)
test_score = clf.score(X_test, y_test)
print('train score: {0}; test score: {1}'.format(train_score, test_score))

輸出如下:

train score: 0.9758241758241758; test score: 0.9736842105263158

看起來(lái)結(jié)果好多了。作為對(duì)比,我們畫(huà)出一階多項(xiàng)式核函數(shù)的SVM和二階多項(xiàng)式核函數(shù)的SVM的學(xué)習(xí)曲線,觀察模型的擬合情況。

import time
from sklearn.model_selection import ShuffleSplit
cv = ShuffleSplit(n_splits=5, test_size=0.2, random_state=0)
title = 'Learning Curves with degree={0}'
degrees = [1, 2]
start = time.clock()
plt.figure(figsize=(12, 4), dpi=144)
for i in range(len(degrees)):
    plt.subplot(1, len(degrees), i + 1)
    plot_learning_curve(plt, SVC(C=1.0, kernel='poly', degree=degrees[i]),
                        title.format(degrees[i]), X, y, ylim=(0.8, 1.01), cv=cv, n_jobs=4)
print('elaspe: {0:.6f}'.format(time.clock()-start))

輸出如下:

elaspe: 90.780260

輸出的圖形如下所示:


figure8_10.png

從圖中可以看出,二階多項(xiàng)式核函數(shù)SVM的擬合效果更好。平均交叉驗(yàn)證數(shù)據(jù)集評(píng)分可以達(dá)到0.950,最高時(shí)達(dá)到0.975(綠色實(shí)線為平均值,綠色陰影上邊界為最大值,下邊界為最小值)。從輸出的運(yùn)行時(shí)間可以看出,二階多項(xiàng)式核函數(shù)計(jì)算代價(jià)很高。

前面我們使用邏輯回歸算法來(lái)處理乳腺癌檢測(cè)問(wèn)題時(shí),使用二階多項(xiàng)式增加特征,同時(shí)使用L1范數(shù)作為正則項(xiàng),其擬合效果比這里的支持向量機(jī)效果好。更重要的是,邏輯回歸算法的運(yùn)算效率遠(yuǎn)遠(yuǎn)高于二階多項(xiàng)式核函數(shù)的支持向量機(jī)算法。當(dāng)然,這里的支持向量機(jī)算法的效果還是比使用L2范數(shù)作為正則項(xiàng)的邏輯回歸算法好的。由此可見(jiàn),模型選擇和模型參數(shù)調(diào)優(yōu),在工程實(shí)踐中有著非常重要的作用的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容