《机器学习实战:基于Scikit-LearnKeras和TensorFlow第2版》-学习笔记:支持向量机
Posted 新四石路打卤面
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《机器学习实战:基于Scikit-LearnKeras和TensorFlow第2版》-学习笔记:支持向量机相关的知识,希望对你有一定的参考价值。
第五章 支持向量机
· Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow, 2nd Edition, by Aurélien Géron (O’Reilly). Copyright 2019 Aurélien Géron, 978-1-492-03264-9.
· 环境:Anaconda(Python 3.8) + Pycharm
· 学习时间:2022.05.04~2022.05.04
支持向量机(Support Vector Machine,SVM)是一个功能强大并且全面的机器学习模型,它能够执行线性或非线性分类、回归,甚至是异常值检测任务。它是机器学习领域最受欢迎的模型之一,任何对机器学习感兴趣的人都应该在工具箱中配备一个。SVM特别适用于中小型复杂数据集的分类。
本章将会介绍不同SVM的核心概念、如何使用它们以及它们的工作原理。
文章目录
5.1 线性SVM分类
5.1.1 基本思想
SVM的基本思想可以用一些图来说明。
两个类可以轻松地被一条直线(它们是线性可分离的)分开。左图显示了三种可能的线性分类器的决策边界。其中虚线所代表的模型表现非常糟糕,甚至都无法正确实现分类。其余两个模型在这个训练集上表现堪称完美,但是它们的决策边界与实例过于接近,导致在面对新实例时,表现可能不会太好。相比之下,右图中的实线代表SVM分类器的决策边界,这条线不仅分离了两个类,并且尽可能远离了最近的训练实例。你可以将SVM分类器视为在类之间拟合可能的最宽的街道(平行的虚线所示)。因此这也叫作大间隔分类。
请注意,在“街道以外”的地方增加更多训练实例不会对决策边界产生影响,也就是说它完全由位于街道边缘的实例所决定(或者称之为“支持”)。这些实例被称为支持向量(在图中已圈出)。
SVM对特征的缩放非常敏感。
如图所示,在左图中,垂直刻度比水平刻度大得多,因此可能的最宽的街道接近于水平。在特征缩放(例如使用Scikit-Learn的StandardScaler)后,决策边界看起来好了很多(见右图)。
5.1.2 软间隔分类
如果我们严格地让所有实例都不在街道上,并且位于正确的一边,这就是硬间隔分类。硬间隔分类有两个主要问题。首先,它只在数据是线性可分离的时候才有效;其次,它对异常值非常敏感。
下图显示了有一个额外异常值的鸢尾花数据:左图的数据根本找不出硬间隔,而右图最终显示的决策边界与我们在上图中所看到的无异常值时的决策边界也大不相同,可能无法很好地泛化。
要避免这些问题,最好使用更灵活的模型。目标是尽可能在保持街道宽阔和限制间隔违例(即位于街道之上,甚至在错误的一边的实例)之间找到良好的平衡,这就是软间隔分类。
使用Scikit-Learn创建SVM模型时,我们可以指定许多超参数。C是这些超参数之一。
如果将其设置为较低的值,则最终得到下图左侧的模型。如果设置为较高的值,我们得到右边的模型。间隔冲突很糟糕,通常最好要少一些。但是,在这种情况下,左侧的模型存在很多间隔违例的情况,但泛化效果可能会更好
如果你的SVM模型过拟合,可以尝试通过降低C来对其进行正则化。
以下Scikit-Learn代码可加载鸢尾花数据集,缩放特征,然后训练线性SVM模型(使用C=1的LinearSVC类和稍后描述的hinge损失函数)来检测维吉尼亚鸢尾花:
import numpy as np
from sklearn import datasets
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVC
iris = datasets.load_iris()
X = iris["data"][:, (2, 3)] # petal length, petal width
y = (iris["target"] == 2).astype(np.float64) # Iris virginica
svm_clf = Pipeline([
("scaler", StandardScaler()),
("linear_svc", LinearSVC(C=1, loss="hinge"))
])
svm_clf.fit(X, y)
# 使用模型进行预测:
svm_clf.predict([[5.5, 1.7]]) # 结果输出为array([1.])
与Logistic回归分类器不同,SVM分类器不会输出每个类的概率。
我们可以将SVC类与线性内核一起使用,而不使用LinearSVC类。创建SVC模型时,我们可以编写SVC(kernel=“linear”,C=1)。或者我们可以将SGDClassifier类与SGDClassifier(loss=“hinge”,alpha=1/(m*C))一起使用。这将使用常规的随机梯度下降(见第4章)来训练线性SVM分类器。它的收敛速度不如LinearSVC类,但是对处理在线分类任务或不适合内存的庞大数据集(核外训练)很有用。
LinearSVC类会对偏置项进行正则化,所以你需要先减去平均值,使训练集居中。如果使用StandardScaler会自动进行这一步。此外,请确保超参数loss设置为"hinge",因为它不是默认值。最后,为了获得更好的性能,还应该将超参数dual设置为False,除非特征数量比训练实例还多(本章后文将会讨论)。
5.2 非线性SVM分类
虽然在许多情况下,线性SVM分类器是有效的,并且通常出人意料的好,但是,有很多数据集远不是线性可分离的。处理非线性数据集的方法之一是添加更多特征,比如多项式特征(如第4章所述)。某些情况下,这可能导致数据集变得线性可分离。
为了使用Scikit-Learn来实现这个想法,创建一个包含PolynomialFeatures转换器(见4.3节)的Pipeline,然后是StandardScaler和LinearSVC。让我们在卫星数据集上进行测试:这是一个用于二元分类的小数据集,其中数据点的形状为两个交织的半圆(见下图)。你可以使用make_moons()
函数生成此数据集:
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVC
from sklearn.datasets import make_moons
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
X, y = make_moons(n_samples=100, noise=0.15)
polynomial_svm_clf = Pipeline([
("poly_features", PolynomialFeatures(degree=3)),
("scaler", StandardScaler()),
("svm_clf", LinearSVC(C=10, loss="hinge"))
])
polynomial_svm_clf.fit(X, y)
使用多项式特征的线性SVM分类器图如下:
5.2.1 多项式内核
添加多项式特征实现起来非常简单,并且对所有的机器学习算法(不只是SVM)都非常有效。但问题是,如果多项式太低阶,则处理不了非常复杂的数据集。而高阶则会创造出大量的特征,导致模型变得太慢。
幸运的是,使用SVM时,有一个魔术般的数学技巧可以应用,这就是核技巧(稍后解释)。它产生的结果就跟添加了许多多项式特征(甚至是非常高阶的多项式特征)一样,但实际上并不需要真的添加。因为实际没有添加任何特征,所以也就不存在数量爆炸的组合特征了。这个技巧由SVC类来实现,我们看看在卫星数据集上的测试:
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_moons
from sklearn.pipeline import Pipeline
from sklearn.svm import SVC
X, y = make_moons(n_samples=100, noise=0.15)
poly_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="poly", degree=3, coef0=1, C=5))
])
poly_kernel_svm_clf.fit(X, y)
这段代码使用了一个3阶多项式内核训练SVM分类器(kernel="poly", degree=3
)。如下图的左图所示。而右图是另一个使用了10阶多项式核的SVM分类器。显然,如果模型过拟合,你应该降低多项式阶数;反过来,如果欠拟合,则可以尝试使之提升。超参数coef0
控制的是模型受高阶多项式还是低阶多项式影响的程度。
寻找正确的超参数值的常用方法是网格搜索(见第2章)。先进行一次粗略的网格搜索,然后在最好的值附近展开一轮更精细的网格搜索,这样通常会快一些。多了解每个超参数实际上是用来做什么的,有助于你在超参数空间层正确搜索。
5.2.2 相似特征
解决非线性问题的另一种技术是添加相似特征,这些特征经过相似函数计算得出,相似函数可以测量每个实例与一个特定地标之间的相似度。
个人理解就是将数据进行转换,地标就是转换后的数据。
你可能想知道怎么选择地标。最简单的方法是在数据集里每一个实例的位置上创建一个地标。这会创造出许多维度,因而也增加了转换后的训练集线性可分离的机会。缺点是一个有m个实例n个特征的训练集会被转换成一个m个实例m个特征的训练集(假设抛弃了原始特征)。如果训练集非常大,那就会得到同样大数量的特征。
5.2.3 高斯RBF内核
与多项式特征方法一样,相似特征法也可以用任意机器学习算法,但是要计算出所有附加特征,其计算代价可能非常昂贵,尤其是对大型训练集来说。然而,核技巧再一次施展了它的SVM魔术:它能够产生的结果就跟添加了许多相似特征一样(但实际上也并不需要添加)。我们来使用SVC类试试高斯RBF核:
rbf_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="rbf", gamma=5, C=0.001))
])
rbf_kernel_svm_clf.fit(X, y)
下图的左下方显示了这个模型。其他图显示了超参数gamma(γ)和C使用不同值时的模型。增加gamma值会使钟形曲线变得更窄,因此每个实例的影响范围随之变小:决策边界变得更不规则,开始围着单个实例绕弯。反过来,减小gamma值使钟形曲线变得更宽,因而每个实例的影响范围增大,决策边界变得更平坦。所以γ就像是一个正则化的超参数:模型过拟合,就降低它的值,如果欠拟合则提升它的值(类似超参数C)。
还有一些其他较少用到的核函数,例如专门针对特定数据结构的核函数。字符串核常用于文本文档或是DNA序列(如使用字符串子序列核或是基于莱文斯坦距离的核函数)的分类。
有这么多的核函数,该如何决定使用哪一个呢?有一个经验法则是,永远先从线性核函数开始尝试(要记住,LinearSVC比SVC(kernel=“linear”)快得多),特别是训练集非常大或特征非常多的时候。如果训练集不太大,你可以试试高斯RBF核,大多数情况下它都非常好用。如果你还有多余的时间和计算能力,可以使用交叉验证和网格搜索来尝试一些其他的核函数,特别是那些专门针对你的数据集数据结构的核函数。
5.2.4 计算复杂度
liblinear库为线性SVM实现了一个优化算法,LinearSVC正是基于该库的。该算法不支持核技巧,不过它与训练实例的数量和特征数量几乎呈线性相关:其训练时间复杂度大致为 O ( m × n ) O(m×n) O(m×n)。
如果你想要非常高的精度,算法需要的时间更长。它由容差超参数ε(在Scikit-Learn中为tol)来控制。大多数分类任务中,默认的容差就够了。
SVC则是基于libsvm库的,这个库的算法支持核技巧。训练时间复杂度通常在 O ( m 2 × n ) O(m^2×n) O(m2×n)和 O ( m 3 × n ) O(m^3×n) O(m3×n)之间。很不幸,这意味着如果训练实例的数量变大(例如成千上万的实例),它将会慢得可怕,所以这个算法完美适用于复杂但是中小型的训练集。但是,它还是可以良好地适应特征数量的增加,特别是应对稀疏特征(即每个实例仅有少量的非零特征)。在这种情况下,算法复杂度大致与实例的平均非零特征数成比例。
下表比较了Scikit-Learn的SVM分类器类:
5.3 SVM回归
正如前面提到的,SVM算法非常全面:它不仅支持线性和非线性分类,而且还支持线性和非线性回归。诀窍在于将目标反转一下:不再尝试拟合两个类之间可能的最宽街道的同时限制间隔违例,SVM回归要做的是让尽可能多的实例位于街道上,同时限制间隔违例(也就是不在街道上的实例)。街道的宽度由超参数ε控制。
下图显示了用随机线性数据训练的两个线性SVM回归模型,一个间隔较大(ε=1.5),另一个间隔较小(ε=0.5)。
在间隔内添加更多的实例不会影响模型的预测,所以这个模型被称为ε不敏感。
你可以使用Scikit-Learn的LinearSVR类来执行线性SVM回归。以下代码生成如图5-10左图所示的模型(训练数据需要先缩放并居中):
from sklearn.svm import LinearSVR
svm_reg = LinearSVR(epsilon=1.5)
svm_reg.fit(X, y)
要解决非线性回归任务,可以使用核化的SVM模型。例如,下图显示了在一个随机二次训练集上使用二阶多项式核的SVM回归。左图几乎没有正则化(C值很大),右图则过度正则化(C值很小)。
以下代码使用Scikit-Learn的SVR类(支持核技巧)生成如上图左图所示的模型:
from sklearn.svm import SVR
svm_poly_reg = SVR(kernel="poly", degree=2, C=100, epsilon=0.1)
svm_poly_reg.fit(X, y)
SVR类是SVC类的回归等价物,LinearSVR类也是LinearSVC类的回归等价物。LinearSVR与训练集的大小线性相关(与LinearSVC一样),而SVR则在训练集变大时,变得很慢(SVC也一样)。
SVM也可用于异常值检测,详细信息请参考Scikit-Learn文档。
5.4 工作原理
本节将会介绍SVM如何进行预测,以及它们的训练算法是如何工作的,从线性SVM分类器开始。如果你刚刚开始接触机器学习,可以安全地跳过本节,直接进入本章末尾的练习,等到想要更深入地了解SVM时再回来也不迟。
首先,说明一下符号。在第4章里,我们使用过一个约定,将所有模型参数放在一个向量 θ θ θ中,包括偏置项 θ 0 θ_0 θ0,以及输入特征的权重 θ 1 θ_1 θ1到 θ n θ_n θn,同时在所有实例中添加偏置项 x 0 = 1 x_0=1 x0=1。在本章中,我们将会使用另一个约定,在处理SVM时它更为方便(也更常见):偏置项表示为 b b b,特征权重向量表示为 w w w,同时输入特征向量中不添加偏置特征。
5.4.1 决策函数和预测
线性SVM分类器通过简单地计算决策函数 w T x + b = w 1 x 1 + … + w n x n + b w^Tx + b = w_1x_1 + …+ w_nx_n + b wTx+b=w1x1+…+wnxn+b来预测新实例x的分类。如果结果为正,则预测类别是正类(1),否则预测其为负类(0),即 y ′ = 0 , w T x + b < 0 1 , w T x + b ≥ 0 y' = \\begincases0,\\ &w^Tx + b<0\\\\1,\\ &w^Tx + b≥0\\endcases y′=0, 1, wTx+b<0wTx+b≥0。
5.4.2 训练目标
思考一下决策函数的斜率:它等于权重向量的范数,即 ∣ ∣ w ∣ ∣ ||w|| ∣∣w∣∣。如果我们将斜率除以2,那么决策函数等于±1的点也将变得离决策函数两倍远。也就是说,将斜率除以2,将会使间隔乘以2。也许2D图更容易将其可视化,见下图。权重向量w越小,间隔越大。
我们要最小化
∣
∣
w
∣
∣
||w||
∣∣w∣∣来得到尽可能大的间隔。但是,如果我们想避免任何间隔违例(硬间隔),那么就要使所有正类训练集的决策函数大于1,负类训练集的决策函数小于-1。如果我们定义实例为负类(如果y(i)=0)时,t(i)=-1;实例为正类(如果y(i)=1)时,t(i)=1。那么就可以将这个约束条件表示为:对所有实例来说,
t
(
i
)
(
w
T
x
(
i
)
+
b
)
≥
1
t^(i)(w^Tx^(i) + b) ≥1
t(i)(wTx(i)+b)≥1。因此,我们可以将硬间隔线性SVM分类器的目标看作一个约束优化问题(优化
1
2
w
T
w
\\frac12w^Tw
21wTw而不是
∣
∣
w
∣
∣
||w||
∣∣w∣∣,这样可以使优化算法在可微函数上的工作效果更好): 以上是关于《机器学习实战:基于Scikit-LearnKeras和TensorFlow第2版》-学习笔记:支持向量机的主要内容,如果未能解决你的问题,请参考以下文章 ☀️机器学习实战☀️基于 YOLO网络 的人脸识别 |(文末送机器学习书籍~) 机器学习实战3:基于朴素贝叶斯实现单词拼写修正器(附Python代码)
m
i
n
i
m
i
z
e
1
2
w
T
w
,
使
得
t
(
i
)
(
w
T
x
(
i
)
+
b
)
≥
1
,
i
=
1
,
2
,
3
…
minimize \\frac12w^Tw, 使得t^(i)(w^Tx^(i) + b) ≥ 1, i=1,2,3…
minimize21wTw,使得t(i)(wTx(i)+b)≥1,i=1,2,3…
要达到软间隔的目标,我们需要为每个实例引入一个松弛变量(衡量的是第i个实例多大程度上允许间隔违例):
m
i
n
i
m
i
z
e
1
2
w
T
w
+
C
∑
i