一、支援向量機(SVM: support vector machine)
support vector machine (SVM): a support vector machine is supervised machine learning model that uses classification algorithms for two-group classification problems。 After giving an SVM model sets of labeled training data for each category, they’re able to categorize new text。
支援向量機是一種使用分類演算法解決二分類問題的監督學習模型。
上圖中不同顏色的點表示不同的型別(class),找到一個平面使得兩類support vector支援向量之間的距離(margin)最大。之間的距離透過計算後得
,分類問題就變成了一個求解
最大值的過程,即求解
的最小值的過程。具體理論計算網上找找會有很多寫得很全面的文章。理論我就不多贅述啦。下面還是直接來程式碼比較實際一點。希望看完對大家有些幫助。
上面圖片中用直線就可以將這些點劃分兩個區域了,但在實際很多情況下,只是 線性劃分並不能將這兩個不同顏色的類別劃分開來。如下右圖,這種無法用線性劃分的,我們應該可以用一條封閉的曲線將兩個類別劃分開來。接下來本文會用程式碼針對線性和非線性的情況分別進行劃分。
二、支援向量機處理線性關係問題:
%matplotlib inline
import numpy as np
import matplotlib。pyplot as plt
from scipy import stats
# use Seaborn plotting defaults
import seaborn as sns; sns。set()
from sklearn。datasets。samples_generator import make_blobs
X, y = make_blobs(n_samples=[10,20,30], centers=[[0,0],[1,1],[2,2]], random_state=0, cluster_std=0。30)
plt。scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=‘autumn’)
X, y = make_blobs(n_samples=50, centers=2,random_state=0, cluster_std=0。60)
plt。scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=‘autumn’)
sklearn.datasets.samples_generator.Make_blobs
(n_sample=100,n_features=2,*,centers=None,cluster_std=1。0,random_state=None,renturn_centers=False,。。。)
Make_blobs:
generate isotropic Gaussian blobs for clustering。生成各向同性的高斯斑點用來進行聚類。
N_samples
: int,optional(default=2)
N_samples為陣列時,則序列中的每個元素表示每個簇(cluster)的樣本數。此時對應的center裡面也需要寫出每個簇(cluster)中心點的座標。比如在N_sample=[10,20,30]的時候,表示有三個簇(cluster),他們每個簇點的個數分別為10個,20個和30個,此時在對應的centers引數中就要設定這三個簇(cluster)的中心座標,這裡設定為centers=[[0,0],[1,1],[2,2]],那麼三個中心點(x,y)對應的座標分別為(0,0),(1,1),(2,2),在上面的圖中分別用紅,橙,黃顏色的點來表示。
n_samples為整數時,表示一共有多少個散點,那麼這些點在所有的叢集之間平均分佈,n_sample=50,而centers=2,那麼這50個點在兩個中心之間平均分佈,每個中心所在的cluster的點的個數為50/2=25。
Centers
: int or array of shape[n_center, n_features],optional
產生的中心點的數量,或者固定的中心點的位置座標。
如果N_samples是整數但是又沒有定義centers的個數,那麼自定義為3個centers。
如果n_samples是矩陣的形式,那麼centers必須為設定為空值(None)或者和n_sample長度一樣的矩陣。
random_state
: int, randomstate instance, default=None
這個引數在之前文章裡面講過的,要讓你的隨機的結果復現,就需要設定一個這個的引數。
Return_centers
:bool,optional(default=False)
該引數為True時,make_blobs的返回值中會返回每個叢集的中心點。
Cluster_std
: floot or sequence of floats, optional(default=1。0)
The standard deviation of the clusters。 叢集的標準差,標準差越小,資料越集中於自己的中心點。
Make_blobs 返回的值:
X
: array of shape [n_samples, n_features]
The generated samples。產生的點的x,y座標。
Y
: array of shape[n_samples]
The integer labels for cluster membership of each sample。產生的每個點的標籤,紅顏色和黃顏色各一個標籤,對應的值非0即1。如果有多個標籤,假設有n個,那麼這個y值的取值為[0,1。。。n-1]。
Centers
: array , shape[n_centers,n_feature]
The centers of each cluster。 Only returned if return_center=True。每個簇(cluster)中心點(centers)的座標,只有make_blobs裡面的return_centers引數設定為True時才會返回Centers的座標。Random_state取不同值時,中心點的座標也會隨著不同,所以有時候為了讓結果復現,也就是產生的中心點的座標與上一次使用make_blobs function產生的一樣,這裡最好設定一下random_state。
from sklearn。datasets。samples_generator import make_blobs
X, y,C = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std = 0。30, return_centers =True)
plt。scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=‘autumn’)
在這個例子中要將這兩個cluster分開來,可以直接do by hand,
xfit = np。linspace(-1, 3。5)
plt。scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=‘autumn’)
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。xlim(-1, 3。5)
Numpy.linspace
(start,stop,num=50,endpoint=True,retstep=False,dtype=None,axis=0)
numpy.linspace()
返回區間在[start,stop],間隔均勻的num個數據點,
Start
:array_like, the starting value of the sequence。區間的左端點。
Stop
: array_like the end value of the sequence。區間的右端點。
Num
: 在[start,stop]區間內產生的點(sample)的個數,自定義為50個。
Matplotlib。pyplot。scatter 畫散點。
Matplotlib.pyplot.plot
(scalex=True,scaley=Ture,**kwargs)
Fmt
=’[marker][line][color]’
可以看到要將兩組資料用直線分開可以有很多不同的直線,上圖中畫了三條直線,均可以將這兩個(cluster)完全分開,這三條直線的(斜率,截距)分別為(1,0。65),(0。5,1。6)和(-0。2,2。9)這些直線的寬度都是零,我們可以給這些直線畫一個給定寬度的margin,使灰色區域的邊界正好接觸到最近的cluster中的點。
xfit = np。linspace(-1, 3。5)
plt。scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=‘autumn’)
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)
plt。xlim(-1, 3。5);
那麼這三條直線中,哪條直線是最優的解呢?我們希望對於新加入的點,仍能能有較好的劃分能力,在三條直線中,斜率為1或-0。2的兩條直線容易把新加入的點劃分到另一方區域內。
比如對於圖中紅色的x,斜率為-0。2的那條直線容易把它劃分到黃顏色點的簇(cluster)中,而對於其他新加入的點,如果新加入的點的橫座標(x)超過三條直線交叉點的橫座標,此時很容易被斜率為1的直線將屬於紅色的簇(cluster)的點劃分到黃色的cluster中,由此看來,只有中間那條線具有很好的容錯能力,與它平行的兩條灰色區域邊界所圍成的區域比其他兩條直線擁有更大的margin。
在支援向量機(support vector machines)中,這條能夠使灰色區域擁有最大margin的直線(上圖可以看到最中間這條直線的灰色部分寬度最寬)就會被選擇成為我們的最優模型。由此,支援向量機(support vector machines)可以看作是這種最大化margin時的estimator(中文叫運算元)的一個例子。
下面我們使用scikit-learn的support vector classifier(SVC) 在上面那些資料上來訓練一個SVM 模型。為了避免過於擬合或擬合不夠充分,會在求解
最小值的過程種引入懲罰引數C。
C>0稱為懲罰引數,C值越大,容許的誤差越小,C值過大時,容易出現過擬合,過小則出現欠擬合。在這個例子中,對於兩個簇(cluster),可以明顯區分邊界時,我們要求這個邊界非常地有原則,涇渭分明,也就是邊界very hard,此時可以把C設定為一個非常大的值。
from sklearn。svm import SVC # “Support vector classifier”
model = SVC(kernel=‘linear’, C=1E10)
model。fit(X, y)
sklearn.svm.SVC
(*, C=1。0, kernel=‘rbf’, degree=3, gamma=‘scale’, coef0=0。0, shrinking=True, probability=False, tol=0。001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape=‘ovr’, break_ties=False, random_state=None)
SVC:
support Vector Classification。
C
:float, default=1。0 C為剛剛提及的懲罰引數,這裡為滿足劃分地邊界非常清晰,所以給C取值為C=1E10。
Kernel
{‘linear’,‘poly’,‘rbf’,‘sigmoid’,‘precomputed’},default=‘rbf’。
Specifies the kernel type to be used in the algorithm。
對於輸入空間中的非線性分類問題,可以透過非線性變換將它轉化為某個維特徵空間中的線性分類問題,在高維特徵空間中學習線性支援向量機。 由於線上性支援向量機學習的對偶問題裡,目標函式核分類決策函式都只涉及例項核實例之間的內積,所以不需要顯式地指定非線性變換,而是用
核函式
替換當中的內積。目前這裡還是線性劃分,所以kernel=’linear’,關於非線性的,下面待會兒會講到。
Kernel函式的選擇可以參考下面這個連線:
http://crsouza。com/2010/03/17/kernel-functions-for-machine-learning-applications/
Degree
:int,default=3
這個引數僅適用於polynomial kernel多項式的維度,一次函式,二次函式,三次函式等,對於其他kernel,這個引數不適用。
Coef0
:float, default=0。0
kernel function 中的獨立項,僅對 kernel = ‘poly’或 ‘sigmoid’時才有用。
gamma{‘scale’, ‘auto’} or float, default=’scale’
Kernel coefficient for ‘rbf’, ‘poly’ and ‘sigmoid’。
gamma這個引數是否具體存在要看你使用的是什麼樣的核函式(kernel),在kernel引數的介紹中那個連結裡面可以發現有些kernel裡面是含有gamma這個引數的。
以Gaussian Kernel為例子做個簡短的說明:
圖中
值越大,高斯分佈越扁平,對於所有的X更為雨露均霑,
(gamma)值越小,那麼這個模型它就不能提取到資料裡面更為複雜的部分,也是更具有特徵性的那些部分,因為向量機的影響範圍包含了整個訓練集。與此同時,對於單一的training example,它的影響範圍越廣,它的訓練和預測的速度也會慢下來。
如果gamma值非常大,支援向量的影響範圍的半徑(radius of the area of the influence)將會只包括支援向量本身,這時防止過擬合而設立的C值起到的作用就會變得很小。
Gamma 和C的取值也需要自己trade-off 一下,下圖為在不同的gamma和C取值下,模型對兩個cluster的分類情況。
sklearn。svm。
SVC
(gamma=)裡面,如果gamma=‘scale’(default),那麼在計算時,gamma的值為1/(n_features*X。var())。如果 gamma=‘auto’,計算時gamma的值為1/n_features。
為了更好地視覺化,下面定義一種快速的function用來畫SVM的決策邊界(decision boundaries)。
def plot_svc_decision_function(model, ax=None, plot_support=True):
‘’‘Plot the decision function for a two-dimensional SVC’‘’
if ax is None:
ax = plt。gca()
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)
Matplotlib。pyplot。gca: get the current Axes instance on the current figure matching the given keyword arguments, or create one。
plt。scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=‘autumn’)
plot_svc_decision_function(model)
畫出如下圖:
上圖可以看出得到的分割線使得這兩群點中間的margin最大。有些點正好在直線上面。在Scikit-Learn中,那些正好在邊界的點組成了我們的support vector,並且儲存在support_vectors_attribute中:
model。support_vectors_
對於非線性關係,可以透過定義多項式和高斯基本函式,將資料投影到高維度空間,由此可以使用線性classifier擬合非線性關係。
三、支援向量機處理非線性關係問題
from sklearn。datasets。samples_generator import make_circles
X, y = make_circles(100, factor=。1, noise=。1)
clf = SVC(kernel=‘linear’)。fit(X, y)
plt。scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=‘autumn’)
plot_svc_decision_function(clf, plot_support=False)
Sklearn.datasets.make_circles
(n_sample=100,*,shuffle=True,noise=None,random_state=None,factor=0。8)
Make_circles
用來在二維平面上產生一個大的circle,該circle裡面包含一個小一點的circle。
N_samples
: 如果為偶數,外圈和內圈點的數量一樣多;如果為奇數,內圈比外圈多一個點。
Factor
:0 Scale factor between inner and outer circle。 在其他情況不變時,Factor 設為不同值的圖如下: Noise : double or None (default=None) Standard deviation of Gaussian noise added to the data。 在其他引數不變的情況下,noise設為不同值時的影象如下: 由上面一系列圖片可以看到,使用線性(直線)去劃分非線性(中間一個圈,外面一個圈)的資料得到的結果並不理想,在二維平面上使用線性的可能永遠無法將這兩類點分開,那我們如何將這些點投影到更高維度才足以使用線性分開呢。舉個簡單的投影例子,我們可以使用計算一個集中在中間的 radial basis function。 radial basis function: a radial basis function(RBF) is a real-valued function whose value depends only on the distance between the input and some fixed point, either the origin。 ——wiki 對於在有限維度向量空間中線性不可分的樣本,我們將其對映到更高維度的向量空間裡,再透過間隔最大化的方式,學習得到支援向量機,就是非線性 SVM。 我們用 x 表示原來的樣本點,用 表示 x 對映到特徵新的特徵空間後到新向量。那麼分割超平面可以表示為: 。 對於非線性 SVM 的對偶問題就變成了,在三維空間,僅使用一個平面就能將其很好地分類: r = np。exp(-(X ** 2)。sum(1)) 然後你可以使用下面的程式碼檢視: from mpl_toolkits import mplot3d def plot_3D(elev=30, azim=30, X=X, y=y): 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(‘r’) interact(plot_3D, elev=[-90,30, 90], azip=(-180, 180), X=fixed(X), y=fixed(y)); 也可以自己計算一個z座標,然後畫一個三維的,比如剛剛講的把上面的二維散點投影到三維,我們可以給一個z軸,使這些散點的z軸的值與二維影象上的散點到原座標(0,0)的距離呈正關係,這裡我們定義: X為上面那些散點的x,y座標,下面程式碼使用上式生成z座標。 import pandas as pd df=pd。DataFrame(data=X,columns={‘x’,‘y’}) df[‘z’]=(df[‘x’]**2+df[‘y’]**2)**0。5 然後使用plotly畫三維影象(記得先在anaconda prompt裡面安裝:pip install plotly): import plotly import plotly。graph_objs as go plotly。offline。init_notebook_mode() trace=[go。Scatter3d(x=df[‘x’]。tolist(),y=df[‘y’]。tolist(),z=df[‘z’]。tolist(), mode=‘markers’,marker=dict(size=6,color=y,colorscale=‘Viridis’))] fig=go。Figure(data=trace) fig。show() color=y中y就是之前畫的100個二維散點的時候給他們的分類標籤,0或1值,這裡對不同的類用不同的顏色區別。那麼這個從x,y得到z的過程就是一個核函式的功能。 錄了一個小小的video,小夥伴們感受一下將二維投影到三維後的效果: 上圖可以看到投影之後我們很好地將這兩類不同顏色地點分開來,只需要在中間插入一個平面(比如說z=0。5)就可以很easy地將兩類分開。 如果我們這個投影project的basic function沒有選擇好,那麼這個分離的效果可能就不那麼明顯了。 比如下面這種情況就需要用polynomial kernel來mapping(對映) 插入一個video: 每對點的前後轉換過程叫做核心轉換。 這種策略存在潛在的問題,投影N個點到N維,會隨著N的增大使得運算量相當龐大,有一種過程可以替代掉這種一對一的對映,把它叫做kernel trick, 比如上面講的 公式,SVM裡面內建了這樣的kernel trick, 這也是SVM強有力的原因之一。 下面看看如何使用SVM的內建kernel trick,其中kernel=‘rbf’。 clf = SVC(kernel=‘rbf’, C=1E6) clf。fit(X, y) plt。scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=‘autumn’) plot_svc_decision_function(clf) plt。scatter(clf。support_vectors_[:, 0], clf。support_vectors_[:, 1], s=300, lw=1, facecolors=‘none’) Kernel=‘rbf’中的rbf:radial basic function,使用方式和其他的model一樣,大致形式都是model。fit(X,y)。 SVC。fit(self,X,y[,sample_weight]): fit the SVM model according to the given training data。 透過使用這種kernelized 的SVM模型,學習到了非線性決策邊界,得到的下圖很完美地區分了這兩組非線性關係的點。 四、關於軟邊界 左圖為硬邊界,右圖為軟邊界,軟就是模糊,不清楚的意思。硬邊界有明顯的界限,而軟邊界裡面,就會有部分互相參雜,那麼互相參雜多少,這個容錯的範圍多大,也是由引數來確定的。 剛剛提及的都是邊界很清晰的,可以將兩類劃分,有時候,這兩種不同顏色的散點的邊界並沒有那麼清晰,允許一部分點互相摻雜。比如當你碰到下面的這種情況時: X, y = make_blobs(n_samples=100, centers=2,random_state=0, cluster_std=1。2) plt。scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=‘autumn’) 透過mak_blobs人為產生了如上圖的資料,下面用SVC線性劃分這兩個類別,因為邊界不是很清晰,所以這裡控制懲罰程度的C的值,不會像最初那樣設定得很大,而這個容錯的範圍也由C的值確定,下面是SVM裡面C分別為10和0。1的結果的影象: 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)。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=0。1有部分點進入了兩個cluster的support vector之間,而當C=10的時候沒有。