目录
6 实战(使用支持向量机(SVM)进行线性和非线性分类任务):
1 总述:
我们先简单了解一下SVM的关键概念及优缺点,并在下文深入开展探讨。
支持向量机(Support Vector Machine,SVM)是一种常用的监督学习算法,用于二分类和多分类问题。SVM的目标是找到一个最优的超平面,将不同类别的样本分开,并且使得分类边界具有最大的间隔。
SVM的主要思想是将数据映射到高维特征空间,并在该空间中寻找一个最优的超平面来进行分类。它基于以下关键概念:
支持向量:在训练过程中,SVM选择一部分样本作为支持向量,这些支持向量是离分类边界最近的样本点,它们决定了最终的分类结果。
超平面:在二维空间中,超平面就是一条直线,而在高维空间中,超平面是一个具有 N-1 维的线性子空间,其中 N 是特征的维数。SVM试图找到一个最优的超平面,使得两个不同类别的样本在该超平面上的投影尽可能分开。
间隔:SVM通过最大化分类边界两侧样本点到超平面的间隔来确定最优超平面。这个间隔被称为几何间隔,是指分类边界两侧最近的样本点到超平面的距离。SVM的目标是找到具有最大几何间隔的超平面。
核函数:对于非线性可分的问题,SVM使用核函数将输入数据映射到高维特征空间,使其线性可分。常用的核函数有线性核、多项式核、高斯核等。
SVM的训练过程可以简化为一个凸优化问题,通过求解约束最优化问题来确定最优超平面和支持向量。一旦训练完成,SVM可以用于预测新样本的类别。
SVM在实际应用中具有以下优点:
- 在处理高维数据和特征空间较小的情况下表现良好。
- 可以通过选择不同的核函数适应不同的数据分布和非线性问题。
- 通过最大化间隔,SVM对噪声数据具有较强的鲁棒性。
- 支持向量的数量相对较少,占用内存较少。
然而,SVM也有一些注意事项:
- 对于大规模数据集,训练时间可能较长。
- 需要选择合适的核函数和调节相关参数,否则可能导致性能下降。
- SVM本身是二分类算法,对于多分类问题需要进行扩展,如使用一对多或一对一策略。
总的来说,SVM是一种强大的分类算法,广泛应用于模式识别、图像分类、文本分类、生物信息学等领域。它在处理小样本、高维数据和非线性问题时表现出色,并且具有较好的泛化能力。
2 引入原因:
在简单了解了SVM是什么之后,我们先思考,我们为什么需要SVM?简单的逻辑回归、线性模型它不香吗?是的,它不香。为什么?
从解决问题的角度讲,有什么问题是SVM能轻松解决但是这哥俩死活解决不了的?
2.1 用于解决线性不可分问题(如:异或问题)
下图这类异或问题,线性模型就解决不了,即找不到任何一条直线能分割两类样本。我们一般有两种思路解决这个问题。一种是用感知机组成的有向图构造函数逼近器,思路是多画几条线以区分样本点,对应多层感知机;另一种就是使用核函数将这些点映射到高维空间,再用超平面分割,对应支持向量机。
从更好的解决问题的角度讲,SVM具有啥优势?
2.2 更好的泛化能力(强的鲁棒性)
我们可以看到,SVM找到的超平面拥有强大的泛化能力,对于可能出现的新的接近划分平面的样本点(蓝色标注点)的分类能力强于另外两种方法,因为线性模型很难保证每次都取到距离所有点都很远的划分线。
2.3 处理小样本数
当数据量较小时,逻辑回归和线性模型可能容易过拟合。而SVM通过间隔最大化的策略,可以更好地处理小样本数据,避免过拟合问题。
2.4 多类别分类
逻辑回归和线性模型通常是二分类器,对于多类别分类问题需要采用其他策略,如一对多或一对一。而SVM本身是二分类器,通过一对多的方式可以直接进行多类别分类。
2.5 依赖支持向量(减小存储和计算的开销)
SVM在训练过程中选择支持向量作为决策边界的依据,这些支持向量是离分类边界最近的样本点。通过依赖支持向量,SVM可以忽略大部分非支持向量样本,从而降低了对整个训练集的依赖,减少了存储和计算的开销。
3 前置知识:
在了解了SVM的引入原因之后,我们需要先了解一些关于SVM的前置知识。
3.1 线性可分:
在二维空间上,两类点被一条直线完全分开叫做线性可分。
3.2 最大间隔超平面:
3.3 支持向量:
样本中距离超平面最近的一些点,这些点叫做支持向量。
3.4 SVM最优化问题:
SVM想要的就是找到各类样本点到超平面的距离最远,也就是找到最大间隔超平面。任意超平面可以用下面这个线性方程来描述:
一些补充:
正如聪明的读者所料,添加的系数二分之一和平方,皆是为求导数所需;
最小间隔为啥要设置为1呢?这是因为如果我们设置为0等,相当于没有限制条件,若取0的时候,两个直线之间的距离会达到无穷大,此时所有样本点都将落在这两个平行直线内,这不符合我们预期的效果。
4 如何解决这个最优化问题呢?
- 在标准的线性可分支持向量机(SVM)中,优化问题是一个凸二次规划问题。其目标是最小化一个带有约束的凸二次目标函数,其中约束包括数据点的正确分类和间隔的限制。通过拉格朗日乘子法和KKT条件,可以将这个问题转化为对拉格朗日乘子进行求解的凸二次规划问题。
- 然而,在实际应用中,当数据线性不可分或需要使用非线性决策边界时,SVM可以通过引入核函数来进行扩展,构建非线性SVM。这时,优化问题变成了一个凸二次规划的对偶问题,而不是非凸问题。通过求解对偶问题,可以得到最优解。
- 对偶问题是通过对原始问题中的拉格朗日乘子进行求解得到的。对于线性可分的SVM,原始问题和对偶问题是等价的,即它们具有相同的最优解。但是,在引入核函数后,原始问题变为非凸问题,而对偶问题仍然是凸二次规划问题,并且可以通过合适的核函数进行求解。
- 因此,在线性可分的情况下,SVM的优化问题是一个凸二次规划问题;而在引入核函数构建非线性SVM时,优化问题变为凸二次规划的对偶问题。
4.1 拉格朗日乘子法:
整体思路是通过引入拉格朗日乘子,可将有 d 个变量与 k 个约束条件的最优化问题转化为具有 d+k 个变量的无约束优化问题求解。
如果高数上讲的拉格朗日乘子法忘完了,请先浏览这篇博客:优化-拉格朗日乘子法 - 知乎
浅浅总结一下,对约束条件分情况讨论,约束条件一般分为两种——等式约束和不等式约束。
4.1.1 等式约束
4.1.2 不等式约束
4.2 对偶问题:
拉格朗日里面有个对偶问题,这里需要提一下,不然后面公式推导会有问题。我们通俗的讲就是这样的:
4.3 求解线性可分的SVM的步骤:
构造拉格朗日函数:根据SVM的优化目标和约束条件,构造拉格朗日函数。拉格朗日函数由原始问题的目标函数和约束条件组成,通过引入拉格朗日乘子来表示约束条件。(这样做的好处是,我们可以使用拉格朗日乘子来表示约束条件,并将原始问题转化为一个包含拉格朗日乘子的优化问题。)
强对偶性转化:利用强对偶性质,将原始问题转化为对拉格朗日乘子进行求解的问题。这意味着最优解可以通过求解对偶问题得到。(通过转化为对偶问题,我们可以更方便地求解优化问题。对偶问题的求解通常更高效,因为它的变量维度与训练样本数量相关,而不是特征的维度。)
使用序列最小最优化(Sequential Minimal Optimization,SMO)算法求解对偶问题:SMO算法是一种常用的求解SVM对偶问题的方法。它通过选取一对拉格朗日乘子,并固定其他拉格朗日乘子来最小化对偶问题的目标函数。这个步骤是迭代进行的,直到满足收敛条件。(这种逐步优化的方法使得求解过程更加高效,并且能够处理大规模数据集。)
计算权重和偏置:在求解对偶问题后,可以根据最优的拉格朗日乘子计算权重向量和偏置项。权重向量可以用于定义最大分割超平面,而偏置项则用于划分数据点的类别。
得到最大分割超平面。
4.4 核函数与软间隔:
这部分是SVM的精髓。我们之前推导的SMO算法等都是在线性约束条件下的,但是面对非线性问题以及一些线性不可分的问题的时候,我们还是需要核函数映射一下的,把非线性问题转化为线性问题求解。
例如:左下图中线性不可分,这种情况的解决方法就是:将二维线性不可分样本映射到高维空间中,让样本点在高维空间线性可分,比如:
4.4.1 核函数引入原因(解决线性不可分):
非线性问题的求解大概分两步:
- 把非线性问题在高维空间转化为线性问题
- 找到对应的核函数,在求解时把高维的问题在低维下求解
对于在有限维度向量空间中线性不可分的样本,我们将其映射到更高维度的向量空间里,再通过间隔最大化的方式,学习得到支持向量机,就是非线性SVM。
4.4.2 核函数的作用:
也就是核技巧允许我们在算法中使用核函数的形式,而无需直接计算高维特征空间的内积。
4.4.3 常见的核函数:
4.4.4 软间隔:
软间隔是允许SVM存在分错的样本,但是要控制其错误数量尽可能的少。所以软间隔SVM引入了一个描述分错样本数量的损失,加入了目标函数共同进行优化。
SVM因为支持向量的选取的原因,很容易受噪声干扰,在现实中很容易因为部分样本导致支持向量间距过窄。这个时候我们引入软间隔,允许部分样本在间隔内。两者对比图如下。
软间隔SVM的原理推导同硬间隔SVM,只是在目标函数中多了一个正则项。对于软间隔SVM,在间隔内的点同样可以影响超平面的位置,所以也属于支持向量。
4.4.5 求解非线性可分的SVM的步骤:
使用核函数求解非线性支持向量机(SVM)的步骤可以总结如下:
数据准备:准备训练数据集,包括输入特征和对应的类别标签。
核函数选择:根据问题的性质和数据的分布,选择适合的核函数,如线性核函数、多项式核函数、高斯径向基函数核等。
核矩阵计算:通过核函数计算训练集中样本两两之间的核函数值,构成一个核矩阵。核矩阵的每个元素表示两个样本在高维特征空间中的内积。
优化问题建立:将核矩阵代入SVM的对偶问题中,构建优化问题。优化问题的目标是最大化间隔,并且满足一定的约束条件。
求解优化问题:使用相应的优化算法(如SMO算法)求解对偶问题,得到最优的拉格朗日乘子。
计算权重和偏置:根据最优的拉格朗日乘子,计算权重向量和偏置项,用于定义最大分割超平面。
预测新样本:对于新的样本,将其通过核函数映射到高维特征空间,然后使用计算得到的权重向量和偏置项进行分类预测。
4.5 松弛变量和惩罚因子:
如果使用核函数向高维空间映射后,问题仍然是线性不可分的,那怎么办?
在支持向量机(SVM)中,为了处理线性不可分的情况,引入了松弛变量和惩罚因子。
松弛变量是一组非负变量,用于允许一些样本点位于错误的一侧或在间隔边界上。它们允许在一定程度上违反线性分割规则,以处理线性不可分的情况。每个样本点都会对应一个松弛变量,用于衡量其违反分类规则的程度。
惩罚因子是一个超参数(通常表示为C),用于控制松弛变量的权重。它决定了对误分类的惩罚程度。较大的惩罚因子会使模型更加关注分类的准确性,对错误分类的容忍程度较低;而较小的惩罚因子则会更加容忍错误分类,允许更多的样本点违反分类规则。
通过引入松弛变量和惩罚因子,SVM的优化目标可以重新定义为最小化分类误差和松弛变量的总和,同时尽量使间隔最大化。这可以通过解决一个带有约束的优化问题来实现。
在支持向量机(SVM)的参数设置中,通常使用惩罚因子 C 控制训练误差和模型复杂度之间的权衡。这个惩罚因子 C 就是用来调整松弛变量的,它控制了对误分类样本的惩罚程度,从而影响了最终决策边界的位置。
松弛变量本身并不是一个独立的参数,而是由惩罚因子 C 控制的。因此,在设置 SVM 模型时,通过调整 C 的值来间接地控制松弛变量的影响,从而实现对模型的调优。
具体的请参考SVM(支持向量机)原理及数学推导全过程详解(附MATLAB程序) - 知乎
SVM(六):带松弛变量的SVM数学模型_松弛变量怎么加-CSDN博客
5 SVM的优缺点
优点:
- 有严格的数学理论支持,可解释性强,不依靠统计方法,从而简化了通常的分类和回归问题;
- 能找出对任务至关重要的关键样本(即:支持向量);
- 采用核技巧之后,可以处理非线性分类/回归任务;
- 最终决策函数只由少数的支持向量所确定,计算的复杂性取决于支持向量的数目,而不是样本空间的维数,这在某种意义上避免了“维数灾难”。
缺点:
- 训练时间长。当采用SMO算法时,由于每次都需要挑选一对参数,因此时间复杂度O(N^2),其中N为训练样本的数量;
- 当采用核技巧时,如果需要存储核矩阵,则空间复杂度为O(N^2);
- 模型预测时,预测时间与支持向量的个数成正比。当支持向量的数量较大时,预测计算复杂度较高。
因此支持向量机目前只适合小批量样本的任务,无法适应百万甚至上亿样本的任务。
6 实战(使用支持向量机(SVM)进行线性和非线性分类任务):
6.1 目的:
使用支持向量机(SVM)进行线性和非线性分类任务。
6.2 步骤:
导入需要的库:
numpy
、matplotlib
、datasets
、StandardScaler
等。使用
datasets.load_iris()
加载鸢尾花数据集,并选择只使用前两个特征。对特征进行标准化处理,使用
StandardScaler
将特征按照均值为0、方差为1的方式进行缩放。创建一个线性SVM模型,并使用标准化后的特征进行训练。
定义了一个名为
plot_decision_boundary
的函数,用于绘制决策边界。函数内部使用网格化的方式生成决策边界的预测结果,并使用contourf
函数进行可视化。绘制线性SVM模型的决策边界和样本点的散点图。
定义了一个名为
plot_svc_decision_boundary
的函数,用于绘制SVM模型的决策边界和间隔边界。函数内部除了绘制决策边界外,还绘制了间隔边界(即支持向量)的两条直线。绘制线性SVM模型的决策边界、间隔边界和样本点的散点图。
生成一个非线性可分的数据集,并绘制散点图。
使用SVM中的多项式核函数构建一个非线性SVM模型,其中核函数采用RBF(径向基函数)核。
绘制非线性SVM模型的决策边界和样本点的散点图。
6.3 代码:
import numpy as np
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.preprocessing import StandardScaler#用于数据预处理的类
iris = datasets.load_iris()#鸢尾花(Iris)数据集
X = iris.data
y = iris.target
X = X[y<2,:2]#只使用前两个特征
y = y[y<2]#只保留了两个类别的样本
standardScaler = StandardScaler()
standardScaler.fit(X)
X_standard = standardScaler.transform(X)#将特征按照均值为0、方差为1的方式进行缩放,消除特征之间的量纲差异
# SVM线性分割
from sklearn.svm import LinearSVC # Support Vector Classifier
svc = LinearSVC(C=1e9) # 参数C表示正则化参数,其中C的值越大,表示模型对错误分类的惩罚越大,即越倾向于拟合训练数据
svc.fit(X_standard, y)#训练,拟合
def plot_decision_boundary(model, axis):#绘制决策边界
x0, x1 = np.meshgrid(
np.linspace(axis[0], axis[1], int((axis[1]-axis[0])*100)).reshape(-1, 1),
np.linspace(axis[2], axis[3], int((axis[3]-axis[2])*100)).reshape(-1, 1),)
X_new = np.c_[x0.ravel(), x1.ravel()]
y_predict = model.predict(X_new)
zz = y_predict.reshape(x0.shape)
from matplotlib.colors import ListedColormap
custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
plt.contourf(x0, x1, zz, cmap=custom_cmap)
plot_decision_boundary(svc, axis=[-3, 3, -3, 3])
## 需要标准化之后的
plt.scatter(X_standard[y==0,0], X_standard[y==0,1], color='red')
plt.scatter(X_standard[y==1,0], X_standard[y==1,1], color='blue')
plt.show()
# 绘制margin
def plot_svc_decision_boundary(model, axis):#绘制SVM模型的决策边界和间隔边界
x0, x1 = np.meshgrid(
np.linspace(axis[0], axis[1], int((axis[1]-axis[0])*100)).reshape(-1, 1),
np.linspace(axis[2], axis[3], int((axis[3]-axis[2])*100)).reshape(-1, 1),)
X_new = np.c_[x0.ravel(), x1.ravel()]
y_predict = model.predict(X_new)
zz = y_predict.reshape(x0.shape)
from matplotlib.colors import ListedColormap
custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
plt.contourf(x0, x1, zz, cmap=custom_cmap)
# 增加margin的那两根直线
w = model.coef_[0]
b = model.intercept_[0]
# w0*x0 + w1*x1 + b = 0
# => x1 = -w0/w1 * x0 - b/w1 纵轴
plot_x = np.linspace(axis[0], axis[1], 200)
up_y = -w[0]/w[1] * plot_x - b/w[1] + 1/w[1]
down_y = -w[0]/w[1] * plot_x - b/w[1] - 1/w[1]
up_index = (up_y >= axis[2]) & (up_y <= axis[3])
down_index = (down_y >= axis[2]) & (down_y <= axis[3])
plt.plot(plot_x[up_index], up_y[up_index], color='black')
plt.plot(plot_x[down_index], down_y[down_index], color='black')
# hard svm margin 两个直线里面没有样本
plot_svc_decision_boundary(svc, axis=[-3, 3, -3, 3])
plt.scatter(X_standard[y==0,0], X_standard[y==0,1], color='red')
plt.scatter(X_standard[y==1,0], X_standard[y==1,1], color='blue')
plt.show()
# SVM非线性分割
X, y = datasets.make_moons(noise=0.15,random_state=666)#使用Scikit-learn的datasets模块中的make_moons函数生成一个非线性可分的数据集。参数noise表示添加的噪声水平
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show()
# 使用SVC中的多项式核函数
from sklearn.preprocessing import PolynomialFeatures#用于生成多项式特征的预处理类
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline#它允许将多个预处理步骤和估计器(模型)组合在一起,形成一个完整的工作流程
def plot_decision_boundary(model, axis):#决策边界
x0, x1 = np.meshgrid(
np.linspace(axis[0], axis[1], int((axis[1]-axis[0])*100)).reshape(-1, 1),
np.linspace(axis[2], axis[3], int((axis[3]-axis[2])*100)).reshape(-1, 1),)
X_new = np.c_[x0.ravel(), x1.ravel()]
y_predict = model.predict(X_new)
zz = y_predict.reshape(x0.shape)
from matplotlib.colors import ListedColormap
custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
plt.contourf(x0, x1, zz, cmap=custom_cmap)
def RBFKernelSVC(gamma =1.0):
return Pipeline([
("std_scaler", StandardScaler()),#特征标准化
("rbfSVC", SVC(kernel='rbf', gamma=gamma)),#核函数为RBF核(径向基函数核)。参数gamma用于控制核函数的宽度,较小的gamma值会导致较宽的RBF核,而较大的gamma值会导致较窄的RBF核。
])
rbf_svc = RBFKernelSVC()
print(rbf_svc.fit(X, y))
plot_decision_boundary(rbf_svc,axis=[-1.5, 2.5, -1, 1.5])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show()
线性SVM:
非线性SVM: