目录
一、算法概述
1.1 算法简介
给定训练样本集,
。支持向量机基于训练集D在样本空间中找到一个划分超平面,将不同类别的样本分开。支持向量机(SVM) 的基本思想就是想要找到一个划分超平面,以 “最好地” 区分这两类点,以至如果以后有了新的点也能做出很好的分类。因此支持向量机适用于二分类模型。
1.2 间隔与支持向量
在样本空间中,划分超平面可以通过线性方程来描述:
其中为法向量,决定了超平面的方向;b为位移项,决定了超平面与原点之间的距离。样本空间中任意点x到超平面(w,b)的距离表示为
假设超平面(w,b)能将训练样本正确分类,即对于,
如下图所示,距离超平面最近的这几个训练样本使上式成立,则被称为“支持向量”。
两个异类支持向量到超平面的距离之和为,被称为“间隔”。
具有“最大间隔”的划分超平面所产生的分类结果是最鲁棒的,对未见示例的泛化能力最强。想要找到具有“最大间隔”的超平面,即找到满足条件的参数w和b,使得γ最大,仅需最大化,这等价于最小化
支持向量与间隔
1.3 算法流程
(1)收集数据
(2)准备数据:需要数值型数据
(3)分析数据:有助于可视化分割超平面
(4)训练算法:SVM的大部分时间都源自训练,该过程主要实现两个参数的调优
(5)测试算法
(6)使用算法:解决分类问题
1.4 对偶问题
对最小化公式使用拉格朗日乘子法可得到其"对偶问题".即对公式中每条约束添加拉格朗日乘子
≥0,则该问题的拉格朗日函数可写为
其中.令L(w,b,α)对w和b的偏导为0可得
,
。代入到L(w,b,α)中可将w和b消去,得到对偶问题
解出α后,求出w和b即可得到模型
对于任意样本
,总有
=0或
若
=0,则该样本将不会再上式模型求和公式中出现,也就不会对f(x)有任何影响
若
>0,则必有
,所对应的样本点位于最大间隔边界上,是一个支持向量。
这显示出支持向量机的一个重要性质:训练完成后,大部分的训练样本都不需要保留,最终模型仅与支持向量有关。
二、算法实现
2.1 实现SVM分类器
定义一个名为LinearSVM的类,用于实现线性支持向量机算法。其中包含类的初始化方法、fit方法和predict方法。
在类的初始化方法中,根据公式设置学习率、正则化参数和迭代次数等参数。
fit方法,用于训练模型。首先获取输入数据的样本数和特征数,然后将标签y转换为-1和1。接着初始化权重w和偏置b为0。然后进行迭代,遍历每个样本,检查分类条件。如果满足条件,即样本正确分类且在正确的间隔之外(),则根据公式
仅通过正则项更新权重;否则,当样本错误分类或在间隔内(
),则根据公式
,
同时更新权重和偏置。
predict方法,用于预测输入数据X的标签。计算线性输出,然后返回其符号作为预测结果。
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)
2.2 实现结果可视化
定义一个名为plot_hyperplane的函数,用于绘制数据集和分类超平面。首先绘制散点图,然后获取坐标轴的范围,生成网格点,计算网格点的Z值,最后绘制等高线。
#结果可视化
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()
2.3 准备数据 训练模型
使用sklearn.datasets中的make_blobs函数生成一个包含100个样本、2个中心点的数据集。将标签y调整为-1和1,然后创建一个LinearSVM实例,调用fit方法进行训练。再调用plot_hyperplane函数,可视化训练结果。
#数据准备和模型训练
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
2.4 实验结果截图
SVM(含学习率、正则化参数)
2.5 调整参数C优化分类结果
参数C控制了错误分类训练示例的惩罚。参数C越大,则告诉SVM要对所有的例子进行正确的分类。
当C比较小时,模型对错误分类的惩罚较小,比较松弛,样本点之间的间隔就比较大,可能产生欠拟合的情况
当C比较大时,模型对错误分类的惩罚较大,两组数据之间的间隔就小,容易产生过拟合的情况
C值越大,越不容易放弃那些离群点;C值越小,越不重视那些离群点。
#调整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
2.6 实验结果截图(可调整参数C)
当C=20时,SVM分类结果如下图所示
参数C=20
当C=100时,SVM分类结果如下图所示
参数C=100
当C=200时,SVM分类结果如下图所示
参数C=200
2.7 整体代码
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
三、总结分析
3.1 实验中出现的问题
问题一:在定义LinearSVM
类时,__init__
方法的名称写错了,导致运行时在尝试访问LinearSVM
对象的n_iters
属性时,发现该对象并没有这个属性。
问题二:在调用plot_hyperplane
函数时,没有使用类的实例名svm
来调用。
3.2 实验小结
本次实验我们完成了对于支持向量机SVM分类器的基本实现。SVM 适合中小型数据样本、非线性、高维的分类问题。SVM是一种基于边界的算法,它依赖于少数支持向量,因此对于小样本数据集具有较好的泛化能力。SVM使用核函数将输入数据映射到高维空间,从而可以解决非线性问题。常用的核函数包括线性核、多项式核和径向基函数(RBF)核等。但是对于大规模数据集,SVM的计算复杂度随着样本数量的增加而增加。同时,当SVM在处理含有缺失数据的样本时,分类结果可能不佳。
3.3 参考书籍
《机器学习》 周志华
《机器学习实战》 Peter Harrington