作者:John Wittenauer
翻译:GreatX
源:Machine Learning Exercises In Python, Part 6
这篇文章是一系列 Andrew Ng 在 Coursera 上的机器学习课程的练习的一部分。这篇文章的原始代码,练习文本,数据文件可从这里获得。
Part 1 简单线性回归(Simple Linear Regression)
Part 2 多元线性回归(Multivariate Linear Regression)
Part 3 逻辑回归(Logistic Regression)
Part 4 多元逻辑回归(Multivariate Logistic Regression)
Part 5 神经网络(Neural Networks)
Part 6 支持向量机(Support Vector Machines)
Part 7 K-均值聚类与主成分分析(K-Means Clustering & PCA)
Part 8 异常检测与推荐(Anomaly Detection & Recommendation)
我们现在已经到了课程内容和本系列博客文章的最后阶段。本次练习我们将使用支持向量机(SVM)构建一个垃圾邮件分类器。首先我们会在一些简单的二维数据集上使用 SVM 以了解其是如何工作的。然后,我们将查看一组电子邮件数据,并使用 SVM 在已处理的电子邮件上构建分类器,以确定它们是否为垃圾邮件。 使用本课程的练习文本对我们理解是有帮助的(上传在此)。
在深入代码中之前,让我们快速回顾一下 SVM 是什么,以及 SVM 为什么值得学习。 一般来说,SVM 是一种监督学习(supervised learning )算法,其构建一种表示将训练数据中的样本如空间中的点一样映射,使得属于数据中的每个类的样本点被清晰的线划分。新的样本点被映射到同一空间,并基于它们落在线的哪一侧来预测其属于哪一类。 默认情况下,SVM 是一个二分类工具,但是有多种方法可以在多类的情形下使用它们。 SVM 也可处理非线性分类问题,使用所谓的 核技巧 在试图找到一个超平面前投影的数据到高维空间中。 SVM 是一种强大的算法并且经常在实际的机器学习应用中使用。
我们要做的第一件事是观察一个简单的二维数据集,并看看在数据集上为对于不同的 C 值(类似于线性/逻辑回归中的正则化项)线性 SVM 如何工作。让我们载入数据。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sb
from scipy.io import loadmat
%matplotlib inline
raw_data = loadmat('data/ex6data1.mat')
raw_data
{'X': array([[ 1.9643 , 4.5957 ], [ 2.2753 , 3.8589 ], [ 2.9781 , 4.5651 ], ..., [ 0.9044 , 3.0198 ], [ 0.76615 , 2.5899 ], [ 0.086405, 4.1045 ]]),
'__globals__': [],
'__header__': 'MATLAB 5.0 MAT-file, Platform: GLNXA64, Created on: Sun Nov 13 14:28:43 2011',
'__version__': '1.0',
'y': array([[1], [1], [1], ..., [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()
注意,虽然有一个离群正类样本点与其他点分离了。 类们仍然是线性可分但它是非常紧的拟合。 我们将训练一个线性支持向量机来学习类的边界。 在本练习中,我们不需要从头开始实现 SVM ,所以我将使用 scikit-learn 中内置的 SVM 。
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.98039215686274506
看起来,它错误地分类了离群点。来看一看使用更大的 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')
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')
差别是有点微妙,但观察边界附近点的颜色会发现。 在第一个图像中,边界附近的点是较深的红色或蓝色,这表明它们与超平面的距离较远。 而在第二图像中则不是这样,其中多个点几乎是白色的,指示它们直接邻近超平面。
现在我们将从线性 SVM 转移到能使用核(kernel)进行非线性分类的 SVM。 我们首先需要实现高斯核函数(gaussian kernel function)。虽然 scikit-learn 内置了高斯核,但为了理解更加透彻,我们将从头开始实现。
def gaussian_kernel(x1, x2, sigma):
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
该结果与练习的期望值相匹配。 接下来,我们将检验另一个数据集,这次是非线性决策边界。
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()
对于这个数据集,我们将使用内置的 RBF 核构建 SVM 分类器,并检查其对训练数据的准确性。 为了可视化决策边界,这次我们将基于实例具有负类标签的预测概率来阴影化点。 我们将从结果中看出,它使大多数点都正确的被分类。
svc = svm.SVC(C=100, gamma=10, probability=True)
svc.fit(data[['X1', 'X2']], data['y'])
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')
对于第三个数据集,我们给出训练集和验证集,并且任务是基于验证集的表现为 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}
for C in C_values:
for gamma in gamma_values:
svc = svm.SVC(C=C, gamma=gamma)
svc.fit(X, y)
score = svc.score(Xval, yval)
if score > best_score:
best_score = score
best_params['C'] = C
best_params['gamma'] = gamma
best_score, best_params
(0.96499999999999997, {'C': 0.3, 'gamma': 100})
现在我们将进行练习的最后一部分。 在这一部分,我们的目标是使用 SVM 构建垃圾邮件过滤器。 在练习文本中,有一个任务涉及一些文本预处理,以使我们的数据变成适合 SVM 处理的格式。 然而,该任务是相当微不足道的(将词语映射到为练习提供的字典中的 ID ),并且其余的预处理步骤(例如 HTML 移除,词干提取,规范化等)已经完成。 如其重现这些预处理步骤,不如直接跳过。我将从预处理的训练和测试数据集(包含垃圾邮件和非垃圾邮件电子邮件)转化为从词频向量(word occurance vector)上构建分类器。
spam_train = loadmat('data/spamTrain.mat')
spam_test = loadmat('data/spamTest.mat')
spam_train
{'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),
'__globals__': [],
'__header__': 'MATLAB 5.0 MAT-file, Platform: GLNXA64, Created on: Sun Nov 13 14:27:25 2011',
'__version__': '1.0',
'y': array([[1],
[1],
[0],
...,
[1],
[0],
[0]], dtype=uint8)}
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
((4000L, 1899L), (4000L,), (1000L, 1899L), (1000L,))
每个文档已经转换为具有 1899 个维度(对应于 vocabulary 中的 1899 个词)的向量。 值是二值的,指示文档中词的存在或不存在。 在这一点上,训练和评估就变成了拟合和测试分类器的问题。
svc = svm.SVC()
svc.fit(X, y)
print('Test accuracy = {0}%'.format(np.round(svc.score(Xtest, ytest) * 100, 2)))
Test accuracy = 95.3%
此结果是默认参数得出的。我们可以调整一些参数使它更高一点。但 95% 的精度仍然不错。
练习 6 结束! 在下一个练习中,我们将使用 K-Means 和 PCA 来进行聚类和图像压缩。