机器学习练习(六)—— 支持向量机

作者:John Wittenauer

翻译:GreatX

源:Machine Learning Exercises In Python, Part 6

这篇文章是一系列 Andrew NgCoursera 上的机器学习课程的练习的一部分。这篇文章的原始代码,练习文本,数据文件可从这里获得。

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 来进行聚类和图像压缩。

你可能感兴趣的:(翻译文档,机器学习)