目录
1 、支持向量机介绍
支持向量机(support vector machine,SVM)是有监督学习中最有影响力的机器学习算法之一,该算法的诞生可追溯至上世纪 60 年代, 前苏联学者 Vapnik 在解决模式识别问题时提出这种算法模型,此后经过几十年的发展直至 1995 年, SVM 算法才真正的完善起来,其典型应用是解决手写字符识别问题。
SVM 是一种非常优雅的算法,有着非常完善的数学理论基础,其预测效果,在众多机器学习模型中“出类拔萃”。在深度学习没有普及之前,“支持向量机”可以称的上是传统机器学习中的“霸主”。
支持向量机是一种二分类模型,其基本模型定义为特征空间上的间隔最大的线性分类器,其学习策略便是间隔最大化,最终可转化为一个凸二次规划问题的求解。支持向量机的学习算法是求解凸二次规划的最优化算法。
基础的SVM算法是一个二分类算法,至于多分类任务,可以通过多次使用SVM进行解决。
2 、最大间隔超平面
支持向量(Support Vectors):
在SVM中,支持向量是指那些离决策边界(也称为超平面)最近的点。这些点对于确定决策边界的位置至关重要,因为决策边界是由这些点确定的。换句话说,如果我们稍微移动这些点中的任何一个,决策边界都会改变。而其他远离决策边界的点对决策边界的位置没有影响,因此它们被称为“非支持向量”。
在二维空间中,决策边界是一条直线;在三维空间中,它是一个平面;在更高维度中,我们称之为超平面。这些支持向量位于超平面的两侧,距离超平面最近的这些点到超平面的距离被称为“间隔”。
最大间隔超平面(Maximum Margin Hyperplane):
SVM的目标是找到一个能够将数据点正确分类并且使得间隔最大化的超平面。这个间隔是指支持向量到超平面的距离。为什么要最大化这个间隔呢?因为间隔越大,分类器的泛化能力就越强,即对新数据的分类准确性就越高。
为了找到这个最大间隔超平面,SVM使用了优化算法。具体来说,它试图找到一个超平面,使得该超平面与最近的支持向量之间的距离最大化。这个优化问题可以转化为一个二次规划问题,并可以通过求解该问题的对偶形式来找到最优解。
在找到这个最大间隔超平面后,我们就可以用它来对新的数据进行分类了。具体地,如果一个点位于超平面的一侧,我们就将其分类为某一类;如果它位于另一侧,我们就将其分类为另一类。
3、线性不可分
然而,上面的方法只适用于线性可分的情况。对于线性不可分的数据集,我们无法找到这样一种直线,将不同类型的样本分割开来,SVM的方法好像就不适用了。
但是Vapnik提出了一种观点,我们所认为的线性不可分,只是在当前维度下线性不可分,别不代表它在高维空间中线性不可分。比如有一组样本在二维空间线性不可分,但是在三维空间中,我们是有可能找到这样一条直线将其分隔开来的,Vapnik还认为,当维数趋于无穷时,一定存在这样一条线,可以将不同类型的样本分割开来。
针对样本不是完全能够划分开的情况,可以允许支持向量机在一些样本上出错,为此要引入“软间隔”的概念。此外,即便恰好找到了某个核函数使训练集在特征空间中线性可分,也很难断定这个貌似线性可分的结果是不是由于过拟合所造成的。缓解这一问题的办法是使用软间隔的方法,即我们允许最终找到的超平面发生少量的分类错误,只要它能够保证将大多数样本分类正确就可以了。
4、核函数
当数据非线性可分时,可以通过引入核函数将数据映射到高维空间,使得数据在高维空间中线性可分。核函数事先在低维上计算,而将实质上的分类效果表现在了高维上。
在实际中,我们会经常遇到线性不可分的样例,此时,我们的常用做法是把样例特征映射到高维空间中去,但如果凡是遇到线性不可分的样例,一律映射到高维空间,那么这个维度大小是会高到可怕的,此时就需要使用核函数。核函数虽然也是将特征进行从低维到高维的转换,但核函数会先在低维上进行计算,而将实质上的分类效果表现在高维上,避免了直接在高维空间中的复杂计算。
如下图所示的两类数据,这样的数据本身是线性不可分的,当我们将二维平面的坐标值映射一个三维空间中,映射后的结果可以很明显
5、案例
5.1 SVM分类器
class LinearSVM:
def __init__(self,learning_rate=0.0001,lambda_param=0.1,n_iters=1000):
self.learning_rate=learning_rate#学习率
self.lambda_param=lambda_param#正则化参数
self.n_iters=n_iters#迭代次数
self.w=None
self.b=None
def fit(self,X,y):#fit方法用于模型训练
n_samples,n_features=X.shape
y_ =np.where(y<=0,-1,1)
#初始化权重和偏置为0
self.w=np.zeros(n_features)
self.b=0
for _ in range(self.n_iters):
for idx,x_i in enumerate(X):
condition = y_[idx]*(np.dot(x_i,self.w)-self.b)>=1 #遍历每个样本,检查分类条件
if condition:
self.w-=self.learning_rate*(2*self.lambda_param*self.w)#如果样本被正确分类且在正确的间隔外,仅通过正则项更行权重(防止过拟合)
else:
self.w-=self.learning_rate*(2*self.lambda_param*self.w-np.dot(x_i,y_[idx]))#样本被错误分类或者在间隔内,则权重更新包括误差项,同时更新偏置,使样本在未来被正确分类
self.b-=self.learning_rate*y_[idx]
def predict(self,X):
Linear_output=np.dot(X,self.w)-self.b
return np.sign(Linear_output)
5.2 模型训练
def fit(self, X, y):
# 获取数据点的数量和特征数量
n_samples, n_features = X.shape
# 将标签二值化,0转换为-1,其他保持不变
y_ = np.where(y <= 0, -1, 1)
# 初始化权重向量为零向量,偏置项为0
self.w = np.zeros(n_features)
self.b = 0
# 迭代n_iters次
for _ in range(self.n_iters):
# 对每一个数据点进行更新
for idx, x_i in enumerate(X):
# 计算当前数据点的间隔条件
condition = y_[idx] * (np.dot(x_i, self.w) - self.b) >= 1
if condition:
# 如果数据点正确分类且在间隔外,仅通过正则项更新权重
self.w -= self.learning_rate * (2 * self.lambda_param * self.w)
else:
# 如果数据点错误分类或在间隔内,权重更新包括误差项和正则项
self.w -= self.learning_rate * (2 * self.lambda_param * self.w - np.dot(x_i, y_[idx]))
self.b -= self.learning_rate * y_[idx]
5.3 可视化
# 可视化结果
def plot_hyperplane(X, y, w, b):
# 绘制数据点,使用viridis颜色映射
plt.scatter(X[:, 0], X[:, 1], marker='o', c=y, s=100, edgecolors='k', cmap='viridis')
# 绘制决策边界
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
# 创建用于绘制决策边界的网格数据
xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
# 计算决策边界的值
Z = (np.dot(xy, w) - b).reshape(XX.shape)
# 绘制决策边界
ax.contour(XX, YY, Z, colors='k', levels=[-1, 0, 1], alpha=0.5, linestyles=['--', '-', '--'])
# 显示图像
plt.show()
# 使用训练好的模型绘制超平面
plot_hyperplane(X, y, svm.w, svm.b)
完整代码:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
class LinearSVM:
def __init__(self,learning_rate=0.0001,lambda_param=0.1,n_iters=1000):
self.learning_rate=learning_rate#学习率
self.lambda_param=lambda_param#正则化参数
self.n_iters=n_iters#迭代次数
self.w=None
self.b=None
def fit(self,X,y):#fit方法用于模型训练
n_samples,n_features=X.shape
y_ =np.where(y<=0,-1,1)
#初始化权重和偏置为0
self.w=np.zeros(n_features)
self.b=0
for _ in range(self.n_iters):
for idx,x_i in enumerate(X):
condition = y_[idx]*(np.dot(x_i,self.w)-self.b)>=1 #遍历每个样本,检查分类条件
if condition:
self.w-=self.learning_rate*(2*self.lambda_param*self.w)#如果样本被正确分类且在正确的间隔外,仅通过正则项更行权重(防止过拟合)
else:
self.w-=self.learning_rate*(2*self.lambda_param*self.w-np.dot(x_i,y_[idx]))#样本被错误分类或者在间隔内,则权重更新包括误差项,同时更新偏置,使样本在未来被正确分类
self.b-=self.learning_rate*y_[idx]
def predict(self,X):
Linear_output=np.dot(X,self.w)-self.b
return np.sign(Linear_output)
#结果可视化
def plot_hyperplane(X,y,w,b):
plt.scatter(X[:,0],X[:,1],marker='o',c=y,s=100,edgecolors='k',cmap='winter')
ax=plt.gca()
xlim=ax.get_xlim()
ylim=ax.get_ylim()
xx=np.linspace(xlim[0],xlim[1],30)
yy=np.linspace(ylim[0],ylim[1],30)
YY,XX=np.meshgrid(yy,xx)
xy=np.vstack([XX.ravel(),YY.ravel()]).T
Z=(np.dot(xy,w)-b).reshape(XX.shape)
ax.contour(XX,YY,Z,colors='k',levels=[-1,0,1],alpha=0.5,linestyles=['--','-','--'])
plt.show()
#数据准备和模型训练
X,y=datasets.make_blobs(n_samples=100,centers=2,random_state=6)
y=np.where(y==0,-1,1)#调整标签为-1和1
svm0=LinearSVM()
svm0.fit(X,y)
plot_hyperplane(X,y,svm0.w,svm0.b) # type: ignore
#调整C参数看不同的效果
class LinearSVM1:
def __init__(self,C=1.0,learning_rate=0.0001,n_iters=1000):
self.C=C
self.learning_rate=learning_rate
self.n_iters=n_iters
self.w=None
self.b=None
def fit(self,X,y):
n_samples,n_features=X.shape
y_ =np.where(y<=0,-1,1)
#初始化权重和偏置为0
self.w=np.zeros(n_features)
self.b=0
for _ in range(self.n_iters):
for idx,x_i in enumerate(X):
condition = y_[idx]*(np.dot(x_i,self.w)-self.b)>=1 #遍历每个样本,检查分类条件
if condition:
self.w-=self.learning_rate*(2*self.w)#如果样本被正确分类且在正确的间隔外,仅通过正则项更行权重(防止过拟合)
else:
self.w-=self.learning_rate*((2*self.w)-self.C*np.dot(x_i,y_[idx]))#样本被错误分类或者在间隔内,则权重更新包括误差项,同时更新偏置,使样本在未来被正确分类
self.b-=self.learning_rate*self.C*y_[idx]
def predict(self,X):
Linear_output=np.dot(X,self.w)-self.b
return np.sign(Linear_output)
#数据准备和模型训练
X,y=datasets.make_blobs(n_samples=100,centers=2,random_state=6)
y=np.where(y==0,-1,1)#调整标签为-1和1
svm1=LinearSVM1(C=200)
svm1.fit(X,y)
plot_hyperplane(X,y,svm1.w,svm1.b) # type: ignore
结果:
6、总结
优点:
- 高效性:在高维空间中依然有效。它可以有效地处理具有大量特征的数据集,并且不易受到维度灾难的影响。
- 泛化能力强:SVM通过最大化分类间隔来构建最优的决策边界,从而在训练数据之外的新数据上表现出很好的泛化能力。对于未知数据的预测效果往往较好。
- 鲁棒性:SVM在构建模型时只关注支持向量,即位于决策边界附近的样本点。对于噪声和异常值有一定的容忍能力。
- 处理线性和非线性问题:SVM在处理线性可分和线性不可分的数据时都能够取得良好的效果。通过使用核函数,SVM可以将数据映射到高维特征空间,从而处理非线性问题。
缺点:
- 对参数敏感:SVM的性能受核函数、惩罚系数等参数影响较大。选择不当的参数值可能导致模型性能下降。此外,选择合适的核函数也是一个挑战,不同的数据集可能需要不同的核函数。
- 计算复杂度高:对于大规模数据集,SVM的训练时间较长。当处理大规模数据集时,训练时间和内存消耗可能会变得非常高,甚至难以承受。
- 对缺失数据敏感:SVM对于缺失数据比较敏感。如果数据中存在缺失值,需要进行额外的处理,如填充缺失值或者使用特定的方法处理缺失数据,否则可能影响模型的性能。