Python 数据科学手册 5.7 支持向量机

2017-07-03 10:58:11来源:http://git.oschina.net/wizardforcel/py-ds-hb/blob/master/5.7作者:极客头条人点击

5.7 支持向量机

原文: In-Depth: Support Vector Machines


译者: 飞龙


协议: CC BY-NC-SA 4.0


译文没有得到原作者授权,不保证与原文的意思严格一致。


支持向量机(SVM)是一种特别强大且灵活的监督算法,用于分类和回归。 在本节中,我们将探索支持向量机背后的直觉,及其在分类问题中的应用。


我们以标准导入开始:


%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=50, centers=2,
random_state=0, cluster_std=0.60)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn');


线性判别分类器将尝试绘制分离两组数据的直线,从而创建分类模型。 对于这里所示的二维数据,这是我们可以手动完成的任务。 但是立刻我们看到一个问题:有两个以上的可能的分界线可以完美地区分两个类!


我们可以画出如下:


线性判别分类器尝试绘制分离两组数据的直线,从而创建分类模型。 对于这里所示的二维数据,这是我们可以手动完成的任务。 但是我们立刻看到一个问题:有两个以上的可能的分界线,可以完美地区分两个类!


我们可以这样绘制:


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);


这些是三个非常不同的分隔直线,然而,这些分隔直线能够完全区分这些样例。 根据你的选择,为新数据点(例如,该图中由“X”标记的数据点)分配不同的标签! 显然,我们简单的直觉,“在分类之间划线”是不够的,我们需要进一步思考。


支持向量机:间距最大化

支持向量机提供了一种改进方法。 直觉是这样的:我们并非在分类之间,简单绘制一个零宽度的直线,而是画出边距为一定宽度的直线,直到最近的点。 这是一个例子:


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);


在支持向量机中,边距最大化的直线是我们将选择的最优模型。 支持向量机是这种最大边距估计器的一个例子。


拟合支持向量机

我们来看看这个数据的实际结果:我们将使用 Scikit-Learn 的支持向量分类器,对这些数据训练 SVM 模型。 目前,我们将使用一个线性核并将 C 参数设置为一个非常大的数值(我们之后深入讨论这些参数的含义)。


from sklearn.svm import SVC # "Support vector classifier"
model = SVC(kernel='linear', C=1E10)
model.fit(X, y)
SVC(C=10000000000.0, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape=None, degree=3, gamma='auto', kernel='linear',
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False)

为了更好展现这里发生的事情,让我们创建一个辅助函数,为我们绘制 SVM 的决策边界。


def plot_svc_decision_function(model, ax=None, plot_support=True):
"""Plot the decision function for a 2D 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)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
plot_svc_decision_function(model);


这是最大化两组点之间的间距的分界线。 请注意,一些训练点只是碰到了边缘:它们由该图中的黑色圆圈表示。 这些点是这种拟合的关键要素,被称为支持向量,并提供了算法的名称。 在 Scikit-Learn 中,这些点存储在分类器的 support_vectors_ 属性中:


model.support_vectors_
array([[ 0.44359863,3.11530945],
[ 2.33812285,3.43116792],
[ 2.06156753,1.96918596]])

这个分类器的成功的关键在于,为了拟合,只有支持向量的位置是重要的;任何远离边距的点,都不会影响拟合。 从技术上讲,这是因为这些要点不用于拟合模型的损失函数,所以只要不超过边距,它们的位置和数值就不重要了。


我们可以看到这一点,例如,如果我们绘制该数据集的前 60 个点和前120个点获得的模型:


def plot_svm(N=10, ax=None):
X, y = make_blobs(n_samples=200, centers=2,
random_state=0, cluster_std=0.60)
X = X[:N]
y = y[:N]
model = SVC(kernel='linear', C=1E10)
model.fit(X, y)
ax = ax or plt.gca()
ax.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
ax.set_xlim(-1, 4)
ax.set_ylim(-1, 6)
plot_svc_decision_function(model, ax)
fig, ax = plt.subplots(1, 2, figsize=(16, 6))
fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1)
for axi, N in zip(ax, [60, 120]):
plot_svm(N, axi)
axi.set_title('N = {0}'.format(N))


在左图中,我们看到了 60 个训练点的模型和支持向量。 在右图中,我们将训练点数量翻了一番,但是模型没有改变:左图的三个支持向量仍然是右图的支持向量。 远程点的确切行为的这种不敏感性,是 SVM 模型的优点之一。


如果你正在运行这个笔记,可以使用 IPython 的交互式小部件,以交互方式查看 SVM 模型的此功能:


from ipywidgets import interact, fixed
interact(plot_svm, N=[10, 200], ax=fixed(None));


超越支持向量机:核

SVM 与核结合在一起,就会变得非常强大。 之前,我们已经看到了一个核的版本,就是“ 线性回归 ”中的基函数。 在那里,我们将数据投影到更高维空间中,由多项式和高斯基函数定义,从而能够将线性分类器用于非线性关系。


在 SVM 模型中,我们可以使用相同想法的一个版本。 为了阐述核的动机,我们来看一些不是线性分离的数据:


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);


很明显,这些数据不可能线性分隔。 但是,我们可以从 线性回归 中的基函数回归中吸取经验,并考虑如何将数据投影到更高的维度,使得线性分隔就足够了。 例如,我们可以使用的一个简单的投影是径向基函数,中心是中间那一堆点:


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, 90], azip=(-180, 180),
X=fixed(X), y=fixed(y));


我们可以看到,使用这个附加维度,通过在 r = 0.7 处绘制分离平面,数据可以线性分离。


在这里,我们必须选择并仔细调整我们的预测:如果我们没有将径向基函数置于正确的位置,我们就不会看到这样清晰的线性可分离结果。一般来说,做出这样的选择的需求是一个问题:我们想以某种方式自动找到最佳的基函数来使用。


为此,一个策略是计算以数据集中每个点为中心的基函数,并使 SVM 算法筛选出结果。这种类型的基函数变换被称为核变换,因为它基于每对点之间的相似关系(或核)。


这种策略的潜在问题 - 将 N 个点投影到 N 个维度 - 就是随着 N 增长,它的计算开销可能会变得非常大。然而,由于一个被称为 核技巧 的简洁的小过程,内核转换数据上的拟合可以隐式完成,也就是说,不需要为核投影构建完全的 N 维数据表示!这个核技巧内置在 SVM 中,也是该方法如此强大的原因之一。


在 Scikit-Learn 中,我们可以通过使用 kernel 模型超参数,将线性核更改为 RBF(径向基函数)核来应用核化 SVM:


clf = SVC(kernel='rbf', C=1E6)
clf.fit(X, y)
SVC(C=1000000.0, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape=None, degree=3, gamma='auto', kernel='rbf',
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False)
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');


使用这个核化的 SVM,我们得到了合适的非线性决策边界。这个核的转换策略,通常用在机器学习中,将线性方法快速调整为非线性方法,尤其是可以使用核技巧的模型。


调整 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');


为了处理这种情况,SVM 实现了软化因子,即“软化”边距:也就是说,如果允许更好的匹配,它允许某些点进入边距。 边缘的硬度由调整参数控制,通常称为 C 。 对于非常大的 C ,边距是硬的,点不能进入。 对于较小的 C ,边缘较软,可以扩展并包含一些点。


下图显示了参数的变化 C ,如何影响最终拟合,通过软化边缘:


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 的最佳值将取决于你的数据集,并应使用交叉验证或类似的过程进行调整(请参阅 超参数和模型验证 )。


示例:人脸识别

作为支持向量机的一个例子,我们来看看人脸识别问题。 我们将使用 Wild 数据集中的标记人脸,其中包含数千张各种公众人物的整理照片。 数据集的获取器内置于 Scikit-Learn中:


from sklearn.datasets import fetch_lfw_people
faces = fetch_lfw_people(min_faces_per_person=60)
print(faces.target_names)
print(faces.images.shape)
['Ariel Sharon' 'Colin Powell' 'Donald Rumsfeld' 'George W Bush'
'Gerhard Schroeder' 'Hugo Chavez' 'Junichiro Koizumi' 'Tony Blair']
(1348, 62, 47)

让我们绘制这些人脸来看看我们要处理什么:


fig, ax = plt.subplots(3, 5)
for i, axi in enumerate(ax.flat):
axi.imshow(faces.images[i], cmap='bone')
axi.set(xticks=[], yticks=[],
xlabel=faces.target_names[faces.target[i]])


每个图像包含 [62×47] 或近 3,000 个像素。 我们可以简单地使用每个像素值作为特征,但是通常使用某种预处理器,来提取更有意义的特征更有效;在这里,我们将使用主成分分析(参见 主成分分析 )来提取150个基本成分,并扔给我们的支持向量机分类器。 我们可以将预处理器和分类器打包成单个管道,来最直接地执行此操作:


from sklearn.svm import SVC
from sklearn.decomposition import RandomizedPCA
from sklearn.pipeline import make_pipeline
pca = RandomizedPCA(n_components=150, whiten=True, random_state=42)
svc = SVC(kernel='rbf', class_weight='balanced')
model = make_pipeline(pca, svc)

出于测试我们的分类器输出的目的,我们将数据分割成训练集和测试集。


from sklearn.cross_validation import train_test_split
Xtrain, Xtest, ytrain, ytest = train_test_split(faces.data, faces.target,
random_state=42)

最后,我们可以使用网格搜索的交叉验证来探索参数的组合。 这里我们将调整 C (控制边缘硬度)和 gamma (其控制径向基函数核的大小),并确定最佳模型:


from sklearn.grid_search import GridSearchCV
param_grid = {'svc__C': [1, 5, 10, 50],
'svc__gamma': [0.0001, 0.0005, 0.001, 0.005]}
grid = GridSearchCV(model, param_grid)
%time grid.fit(Xtrain, ytrain)
print(grid.best_params_)
CPU times: user 47.8 s, sys: 4.08 s, total: 51.8 s
Wall time: 26 s
{'svc__gamma': 0.001, 'svc__C': 10}

最优值落在我们网格中间;如果他们落在边缘,我们需要扩大网格,来确保我们找到了真正的最优值。


现在有了这种交叉验证的模型,我们可以预测测试数据的标签,该模型还没有看到:


model = grid.best_estimator_
yfit = model.predict(Xtest)

让我们看一看一些测试图像,以及它们的预测值。


fig, ax = plt.subplots(4, 6)
for i, axi in enumerate(ax.flat):
axi.imshow(Xtest[i].reshape(62, 47), cmap='bone')
axi.set(xticks=[], yticks=[])
axi.set_ylabel(faces.target_names[yfit[i]].split()[-1],
color='black' if yfit[i] == ytest[i] else 'red')
fig.suptitle('Predicted Names; Incorrect Labels in Red', size=14);


在这个小样本中,我们的最佳估计器只错误标记一个人脸(底部的行中,布什的脸错误标记为布莱尔)。 我们可以使用分类报告更好了解我们的估计器的表现,该分类报告按标签列出了恢复统计量:


from sklearn.metrics import classification_report
print(classification_report(ytest, yfit,
target_names=faces.target_names))
precisionrecallf1-score support
Ariel Sharon 0.650.730.6915
Colin Powell 0.810.870.8468
Donald Rumsfeld 0.750.870.8131
George W Bush 0.930.830.88 126
Gerhard Schroeder 0.860.780.8223
Hugo Chavez 0.930.700.8020
Junichiro Koizumi 0.801.000.8912
Tony Blair 0.830.930.8842
avg / total 0.850.850.85 337

我们也可以展示这些分类之间的混淆矩阵:


from sklearn.metrics import confusion_matrix
mat = confusion_matrix(ytest, yfit)
sns.heatmap(mat.T, square=True, annot=True, fmt='d', cbar=False,
xticklabels=faces.target_names,
yticklabels=faces.target_names)
plt.xlabel('true label')
plt.ylabel('predicted label');


这有助于我们了解哪些标签可能被估算器混淆。


对于真实的人脸识别任务,其中照片不会被预先裁剪成漂亮的网格,人脸分类方案的唯一区别是特征选择:您需要使用更复杂的算法来查找人脸, 并提取独立于像素的特征。 对于这种应用,一个很好的选择是使用 OpenCV,除了别的以外,它包括用于一般图像的,以及专用于人脸的现代化特征提取工具。


支持向量机总结

我们在这里看到了支持向量机背后的原则的简单直观的介绍。这些方法是强大的分类方法,原因有很多:


他们依赖相对较少的支持向量,意味着它们是非常紧凑的模型,并且占用很少的内存。
一旦训练了模型,预测阶段非常快。
因为它们仅受边缘附近的点的影响,它们适用于高维数据,甚至维度大于样本的数据,这对于其他算法来说是一个挑战。
内核方法的集成使得它们非常通用,能够适应许多类型的数据。

然而,SVM也有几个缺点:


在最差的情况下,样本数 N 的复杂度为 O(N^3) ,对于高效的实现,是 O(N^2) 。对于大量的训练样本,这种计算成本可能令人望而却步。
结果强烈依赖于软化参数 C 的合适选择。这必须通过交叉验证仔细选择,随着数据集增大,开销也增大。
结果没有直接的概率解释。这可以通过内部交叉验证来估计(参见SVC的概率参数),但这种额外的估计是昂贵的。

考虑到这些特性,一般来说,只要其他更简单,更快,并且不需要调优的方法不足以满足我的需求,我一般只会考虑 SVM。然而,如果你投入了足够的 CPU 周期,使用 SVM 训练和验证你的数据,这个方法有很好的效果。


微信扫一扫

第七城市微信公众平台