支持向量機
在本練習中,我們將使用支持向量機(SVM)來構建垃圾郵件分類器。 我們將從一些簡單的2D數據集開始使用SVM來查看它們的工作原理。 然後,我們將對一組原始電子郵件進行一些預處理工作,並使用SVM在處理的電子郵件上構建分類器,以確定它們是否爲垃圾郵件。
我們要做的第一件事是看一個簡單的二維數據集,看看線性SVM如何對數據集進行不同的C值(類似於線性/邏輯迴歸中的正則化項)。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sb
from scipy.io import loadmat
raw_data = loadmat('data/ex6data1.mat')
raw_data
{'__header__': b'MATLAB 5.0 MAT-file, Platform: GLNXA64, Created on: Sun Nov 13 14:28:43 2011',
'__version__': '1.0',
'__globals__': [],
'X': array([[1.9643 , 4.5957 ],
[2.2753 , 3.8589 ],
[2.9781 , 4.5651 ],
[2.932 , 3.5519 ],
[3.5772 , 2.856 ],
[4.015 , 3.1937 ],
[3.3814 , 3.4291 ],
[3.9113 , 4.1761 ],
[2.7822 , 4.0431 ],
[2.5518 , 4.6162 ],
[3.3698 , 3.9101 ],
[3.1048 , 3.0709 ],
[1.9182 , 4.0534 ],
[2.2638 , 4.3706 ],
[2.6555 , 3.5008 ],
[3.1855 , 4.2888 ],
[3.6579 , 3.8692 ],
[3.9113 , 3.4291 ],
[3.6002 , 3.1221 ],
[3.0357 , 3.3165 ],
[1.5841 , 3.3575 ],
[2.0103 , 3.2039 ],
[1.9527 , 2.7843 ],
[2.2753 , 2.7127 ],
[2.3099 , 2.9584 ],
[2.8283 , 2.6309 ],
[3.0473 , 2.2931 ],
[2.4827 , 2.0373 ],
[2.5057 , 2.3853 ],
[1.8721 , 2.0577 ],
[2.0103 , 2.3546 ],
[1.2269 , 2.3239 ],
[1.8951 , 2.9174 ],
[1.561 , 3.0709 ],
[1.5495 , 2.6923 ],
[1.6878 , 2.4057 ],
[1.4919 , 2.0271 ],
[0.962 , 2.682 ],
[1.1693 , 2.9276 ],
[0.8122 , 2.9992 ],
[0.9735 , 3.3881 ],
[1.25 , 3.1937 ],
[1.3191 , 3.5109 ],
[2.2292 , 2.201 ],
[2.4482 , 2.6411 ],
[2.7938 , 1.9656 ],
[2.091 , 1.6177 ],
[2.5403 , 2.8867 ],
[0.9044 , 3.0198 ],
[0.76615 , 2.5899 ],
[0.086405, 4.1045 ]]),
'y': array([[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[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]], dtype=uint8)}
我們將其用散點圖表示,其中類標籤由符號表示(+表示正類,o表示負類)。
data = pd.DataFrame(raw_data['X'], columns=['X1', 'X2'])
data['y'] = raw_data['y']
positive = data[data['y'].isin([1])]
negative = data[data['y'].isin([0])]
fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(positive['X1'], positive['X2'], s=50, marker='x', label='Positive')
ax.scatter(negative['X1'], negative['X2'], s=50, marker='o', label='Negative')
ax.legend()
plt.show()
![在這裏插入圖片描述
請注意,還有一個異常的正例在其他樣本之外。
這些類仍然是線性分離的,但它非常緊湊。 我們要訓練線性支持向量機來學習類邊界。 在這個練習中,我們沒有從頭開始執行SVM的任務,所以我要用scikit-learn。
from sklearn import svm
svc = svm.LinearSVC(C=1, loss='hinge', max_iter=1000)
svc
LinearSVC(C=1, class_weight=None, dual=True, fit_intercept=True,
intercept_scaling=1, loss='hinge', max_iter=1000, multi_class='ovr',
penalty='l2', random_state=None, tol=0.0001, verbose=0)
首先,我們使用 C=1 看下結果如何。
svc.fit(data[['X1', 'X2']], data['y'])
svc.score(data[['X1', 'X2']], data['y'])
0.9803921568627451
其次,讓我們看看如果C的值越大,會發生什麼
svc2 = svm.LinearSVC(C=100, loss='hinge', max_iter=1000)
svc2.fit(data[['X1', 'X2']], data['y'])
svc2.score(data[['X1', 'X2']], data['y'])
1.0
這次我們得到了訓練數據的完美分類,但是通過增加C的值,我們創建了一個不再適合數據的決策邊界。 我們可以通過查看每個類別預測的置信水平來看出這一點,這是該點與超平面距離的函數。
data['SVM 1 Confidence'] = svc.decision_function(data[['X1', 'X2']])
fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(data['X1'], data['X2'], s=50, c=data['SVM 1 Confidence'], cmap='seismic')
ax.set_title('SVM (C=1) Decision Confidence')
plt.show()
data['SVM 2 Confidence'] = svc2.decision_function(data[['X1', 'X2']])
fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(data['X1'], data['X2'], s=50, c=data['SVM 2 Confidence'], cmap='seismic')
ax.set_title('SVM (C=100) Decision Confidence')
plt.show()
可以看看靠近邊界的點的顏色,區別是有點微妙。 如果您在練習文本中,則會出現繪圖,其中決策邊界在圖上顯示爲一條線,有助於使差異更清晰。
現在我們將從線性SVM轉移到能夠使用內核進行非線性分類的SVM。 我們首先負責實現一個高斯核函數。 雖然scikit-learn具有內置的高斯內核,但爲了實現更清楚,我們將從頭開始實現。
def gaussian_kernel(x1, x2, sigma):
# INPUT:兩個維度的值x1,x2,高斯核參數sigma
# OUTPUT:高斯核函數計算後的值
# TODO:根據參數和輸入的數據計算高斯核函數
# STEP:計算高斯核函數,這裏的輸入時向量化的
# your code here (appro ~ 1 lines)
return np.exp(-(np.sum((x1 - x2) ** 2) / (2 * (sigma ** 2))))
x1 = np.array([1.0, 2.0, 1.0])
x2 = np.array([0.0, 4.0, -1.0])
sigma = 2
gaussian_kernel(x1, x2, sigma)
0.32465246735834974
如果你的程序正確,這裏的輸出應該是0.32465246735834974
raw_data = loadmat('data/ex6data2.mat')
data = pd.DataFrame(raw_data['X'], columns=['X1', 'X2'])
data['y'] = raw_data['y']
positive = data[data['y'].isin([1])]
negative = data[data['y'].isin([0])]
fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(positive['X1'], positive['X2'], s=30, marker='x', label='Positive')
ax.scatter(negative['X1'], negative['X2'], s=30, marker='o', label='Negative')
ax.legend()
plt.show()
對於該數據集,我們將使用內置的RBF內核構建支持向量機分類器,並檢查其對訓練數據的準確性。 爲了可視化決策邊界,這一次我們將根據實例具有負類標籤的預測概率來對點做陰影。 從結果可以看出,它們大部分是正確的。
svc = svm.SVC(C=100, gamma=10, probability=True)
svc
SVC(C=100, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma=10, kernel='rbf',
max_iter=-1, probability=True, random_state=None, shrinking=True,
tol=0.001, verbose=False)
svc.fit(data[['X1', 'X2']], data['y'])
svc.score(data[['X1', 'X2']], data['y'])
0.9698725376593279
data['Probability'] = svc.predict_proba(data[['X1', 'X2']])[:,0]
fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(data['X1'], data['X2'], s=30, c=data['Probability'], cmap='Reds')
plt.show()
對於第三個數據集,我們給出了訓練和驗證集,並且基於驗證集性能爲SVM模型找到最優超參數。 雖然我們可以使用scikit-learn的內置網格搜索來做到這一點,但是本着遵循練習的目的,我們將從頭開始實現一個簡單的網格搜索。
raw_data = loadmat('data/ex6data3.mat')
X = raw_data['X']
Xval = raw_data['Xval']
y = raw_data['y'].ravel()
yval = raw_data['yval'].ravel()
#設置可選的超參數
C_values = [0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30, 100]
gamma_values = [0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30, 100]
#初始化變量
best_score = 0
best_params = {'C': None, 'gamma': None}
# TODO:尋找SVM模型最優的超參數
# STEP1:遍歷每一個超參數
for C in C_values:
for gamma in gamma_values:
# STEP2:調用SVM包,計算當前參數下的得分
# your code here (appro ~ 2 lines)
svc = svm.SVC(C=C, gamma=gamma)
svc.fit(X, y)
score = svc.score(Xval, yval)
# STEP3:替換得分最高的超參數組合
# your code here (appro ~ 3 lines)
if score > best_score:
best_score = score
best_params['C'] = C
best_params['gamma'] = gamma
best_score, best_params
(0.965, {'C': 0.3, 'gamma': 100})
如果代碼正確,這裏的輸出應該是:(0.96499999999999997, {‘C’: 0.3, ‘gamma’: 100})
現在,我們將進行第二部分的練習。 在這一部分中,我們的目標是使用SVM來構建垃圾郵件過濾器。 在練習文本中,有一個任務涉及一些文本預處理,以獲得適合SVM處理的格式的數據。 然而,這個任務很簡單(將字詞映射到爲練習提供的字典中的ID),而其餘的預處理步驟(如HTML刪除,詞幹,標準化等)已經完成。 我將跳過機器學習任務,而不是重現這些預處理步驟,其中包括從預處理過的訓練集構建分類器,以及將垃圾郵件和非垃圾郵件轉換爲單詞出現次數的向量的測試數據集。
spam_train = loadmat('data/spamTrain.mat')
spam_test = loadmat('data/spamTest.mat')
spam_train
{'__header__': b'MATLAB 5.0 MAT-file, Platform: GLNXA64, Created on: Sun Nov 13 14:27:25 2011',
'__version__': '1.0',
'__globals__': [],
'X': array([[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, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]], dtype=uint8),
'y': array([[1],
[1],
[0],
...,
[1],
[0],
[0]], dtype=uint8)}
# TODO: 獲取訓練和測試數據,這裏應注意把標籤y矩陣拉直
X = spam_train['X']
Xtest = spam_test['Xtest']
y = spam_train['y'].ravel()
ytest = spam_test['ytest'].ravel()
X.shape, y.shape, Xtest.shape, ytest.shape
((4000, 1899), (4000,), (1000, 1899), (1000,))
如果你的代碼正確,這裏輸出的應該是((4000, 1899), (4000,), (1000, 1899), (1000,))
每個文檔已經轉換爲一個向量,其中1,899個維對應於詞彙表中的1,899個單詞。 它們的值爲二進制,表示文檔中是否存在單詞。 在這一點上,訓練評估是用一個分類器擬合測試數據的問題。
svc = svm.SVC()
svc.fit(X, y)
print('Training accuracy = {0}%'.format(np.round(svc.score(X, y) * 100, 2)))
Training accuracy = 94.4%
print('Test accuracy = {0}%'.format(np.round(svc.score(Xtest, ytest) * 100, 2)))
Test accuracy = 95.3%
這個結果是使用默認參數的。 我們可能會使用一些參數調整來獲得更高的精度,儘管95%的精度相當不錯。
在下一個作業中,我們將使用K-Means和主成分分析進行聚類和圖像壓縮。