支持向量机
在本节课中,我们介绍一种非常流行的模型——支持向量机。
什么是支持向量机?
支持向量机(SVM)广泛用于分类任务(SVM 也支持回归任务)。一般来说,SVM 会寻找一个超平面,以最大的间隔将数据点分隔开。
SVM 的核心思想是找到一个最大间隔的超平面来划分数据集。对于一个包含两个类别的数据集,如果它们是线性可分的,那么可以找到无数个能够分离它们的超平面。而 SVM 只会选择其中唯一的一个——即具有最大间隔的超平面。
如上图所示,黑白圆圈可以通过多条直线分开。H2 和 H3 都能分离该数据集,但离这两条线最近的点不同,这意味着它们的“间隔”(margin)不同。根据 SVM 的定义,我们需要的是具有最大间隔的那条线,也就是 H3。
什么是“支持向量”?它们是距离超平面最近的数据点。更直观地说,这些点决定了边界的范围,也就是上图中的虚线部分。红色的线是我们所说的“超平面”,它完美地分开了两类数据点。“间隔”是指在两类最近数据点之间形成的间隙。
SVM 是一种具有深厚理论基础、数学定义和推导都非常优美的方法。但本课程中不会深入探讨其理论细节;如果你对此感兴趣,可以参考维基百科的相关内容。
SVM 与逻辑回归的区别
你可能已经学过“逻辑回归”(logistic regression)。SVM 和逻辑回归都是分类器,它们之间有什么区别呢?
- SVM 是一种几何方法,逻辑回归是一种统计方法。
- SVM 更不容易出现过拟合。
- SVM 在非结构化和半结构化数据上表现良好。
- 逻辑回归在大规模数据集上效果较好。
- SVM 的整体性能通常很出色。
- 理论上,SVM 只需记住少数几个关键点(支持向量),因此内存消耗非常小。
线性 SVM
在第一个示例中,我们将通过线性超平面展示一个简单的应用。SVM 有一个重要特性:核函数(kernel function),用于处理非线性可分的数据。核函数将在第二个示例中介绍。
按照惯例,我们跳过数据加载与划分的部分,直接进入模型构建。完整代码稍后给出。如下代码所示,我们从 linear_model 模块创建了一个 LinearSVC。LinearSVC 表示线性支持向量分类。你也可以使用 SVM 模块中的 SVC 并设置参数 kernel=linear,两者等价。
1from sklearn.svm import LinearSVC 2 3svm = LinearSVC(dual=False) 4# train_x 和 train_y 分别为训练样本和标签 5svm.fit(train_x, train_y)
以下是完整的代码,你可以尝试运行:
1import sklearn.datasets as datasets 2from sklearn.model_selection import train_test_split 3from sklearn.svm import LinearSVC 4import sklearn.metrics as metrics 5import matplotlib.pyplot as plt 6from sklearn.svm import SVC 7import numpy as np 8 9cancer = datasets.load_breast_cancer() 10print("乳腺癌数据的形状为 {}".format(cancer.data.shape)) 11print("该数据集中包含 {} 个类别".format(cancer.target_names.size)) 12 13train_x, test_x, train_y, test_y = train_test_split(cancer.data, 14 cancer.target, 15 test_size=0.2, 16 random_state=42) 17 18print("前五个样本 {}".format(train_x[:5])) 19print("前五个标签 {}".format(train_y[:5])) 20 21svm = LinearSVC(dual=False) 22svm.fit(train_x, train_y) 23 24pred_y = svm.predict(test_x) 25print("前五个预测结果 {}".format(pred_y[:5])) 26print("真实的前五个标签 {}".format(test_y[:5])) 27 28cm = metrics.confusion_matrix(test_y, pred_y) 29print(cm)
- 第 9 到 13 行:使用
load_breast_cancer加载乳腺癌数据集,并将其划分为训练集和测试集。 - 第 21 行创建了一个
SVM对象,并设置dual=False。当样本数量大于特征数量时,推荐使用dual=False。随后在第 22 行进行模型训练。 - 本例中,在第 28 行使用混淆矩阵(confusion_matrix)评估模型性能。
核函数 SVM(Kernel SVM)
当数据不是线性可分时,SVM 应该如何处理?这里我们要介绍 SVM 中一个非常强大的功能:核函数(kernel function)。简单来说,核函数可以将低维空间中无法线性分离的数据映射到高维空间,使得在高维空间中数据变得可分。
关于核函数本身是一个非常大的话题,此处不做过多展开。常见的核函数包括以下几种:
- 多项式核(Polynomial kernel)
- 高斯核(Gaussian kernel)
这是一种通用型核函数,适用于对数据分布没有先验知识的情况。
- 径向基核函数(Gaussian radial basis)
首先,我们创建一个非线性可分的数据集。这里使用 make_circles 函数。从下图可以看出,黄色点位于中心区域,紫色点围绕在外围,这样的数据无法用一条直线分开。
1import sklearn.metrics as metrics 2import matplotlib.pyplot as plt 3 4X, y = datasets.make_circles(200, noise=0.2, factor=0.1, random_state=12)
使用核函数创建 SVM 非常简单,只需在创建 SVC 对象时指定核函数参数即可。本示例中我们选择 rbf 作为核函数。
1from sklearn.svm import SVC 2 3svc = SVC(kernel='rbf') 4svc.fit(X, y)
训练完成后,我们希望评估模型的表现。由于所使用的数据集是非线性可分的,那么使用核函数后决策边界是什么样的呢?
如下图所示,数据被一个红色的圆形边界分开,这就是 SVC 的决策边界。这正是核函数的强大之处。
以下是完整代码,供你尝试:
1import sklearn.datasets as datasets 2from sklearn.model_selection import train_test_split 3from sklearn.svm import LinearSVC 4import sklearn.metrics as metrics 5import matplotlib.pyplot as plt 6from sklearn.svm import SVC 7import numpy as np 8 9X, y = datasets.make_circles(200, noise=0.2, factor=0.1, random_state=12) 10 11fig, axe = plt.subplots() 12axe.scatter(X[:, 0], X[:, 1], c=y) 13 14svc = SVC(kernel='rbf') 15svc.fit(X, y) 16 17xlim = axe.get_xlim() 18ylim = axe.get_ylim() 19 20xlin = np.linspace(xlim[0], xlim[1], 30) 21ylin = np.linspace(ylim[0], ylim[1], 30) 22Yc, Xc = np.meshgrid(ylin, xlin) 23xy = np.vstack([Xc.ravel(), Yc.ravel()]).T 24P = svc.decision_function(xy).reshape(Xc.shape) 25axe.contour(Xc, Yc, P, levels=[0], colors='r') 26 27fig.savefig("output/img.png", dpi = 300) 28plt.close(fig)
- 第 9 行使用
make_circles创建了一个非线性可分的数据集。 - 第 11 到 12 行绘制了该数据集,可对照上方标注为“非线性数据集”的图像查看。
- 第 14 行创建了一个 SVM 对象,并设置参数
kernel="rbf",表示使用径向基函数(radial basis function)作为核函数。 - 第 17 到 25 行绘制了决策边界(即红色圆圈)。首先在第 22 行基于数据集的 X/Y 范围创建了一个网格(mesh grid)。
- 接着在第 24 行,SVM 模型将网格中的每个点作为输入进行预测,得到输出值
P,并将其重塑为与网格相同形状。最后在第 25 行利用P绘制等高线(contour),形成决策边界。有关contour的更多细节,可查阅 Matplotlib 文档。
前文提到,核函数的作用是将数据从低维空间映射到更高维空间。下面我们尝试将上一节使用的二维数据投影到三维空间中。如下图所示,在增加一个维度后,数据可以用一个平面轻松分开。