目录
一.介绍
1.1 支持向量机
SVM(支持向量机)是一种常见的监督学习算法,用于分类和回归问题。在分类问题中,SVM通过在特征空间中找到一个最优的超平面来将不同类别的数据分隔开。在回归问题中,SVM通过寻找一个最优的超平面来拟合数据点,使得尽可能多的数据点位于超平面的边界上。
SVM的核心思想是将原始特征映射到一个高维特征空间,并在该空间中找到一个最优的超平面。为了找到最优的超平面,SVM采用了两个关键概念:间隔和支持向量。间隔表示超平面到最近的训练样本的距离,SVM的目标是找到具有最大间隔的超平面。支持向量是离超平面最近的训练样本点,它们对于定义超平面起到关键作用。
SVM算法可以使用不同的核函数,如线性核、多项式核和高斯核,来处理非线性的分类问题。这些核函数可以将数据从原始特征空间映射到一个更高维的特征空间,在新的特征空间中进行线性分类或回归。
1.2线性、硬间隔、软间隔和非线性
硬间隔指的就是完全分类准确,不能存在分类错误的情况。软间隔,就是允许一定量的样本分类错误。
1. 3线性可分
对于一个数据集合可以画一条直线将两组数据点分开,这样的数据成为线性可分,如下图所示:
- 分割超平面:将上述数据集分隔开来的直线成为分隔超平面。对于二维平面来说,分隔超平面就是一条直线。对于三维及三维以上的数据来说,分隔数据的是个平面,称为超平面,也就是分类的决策边界。
- 间隔:点到分割面的距离,称为点相对于分割面的间隔。数据集所有点到分隔面的最小间隔的2倍,称为分类器或数据集的间隔。论文中提到的间隔多指这个间隔。SVM分类器就是要找最大的数据集间隔。
- 支持向量:离分隔超平面最近的那些点。
SVM所做的工作就是找这样个超平面,能够将两个不同类别的样本划分开来,但是这种平面是不唯一的,即可能存在无数个超平面都可以将两种样本分开,
1.4最大间隔
划分超平面可以定义为一个线性方程:
其中:
- 是一个法向量,决定了超平面的方向
- 为训练样本
- 为位移项,决定了超平面与原点之间的距离
只要确定了法向量 和位移,就可以唯一地确定一个划分超平面。划分超平面和它两侧的边际超平面上任意一点的距离为
则最大间隔为:
既
1.5对偶问题
1.5.1拉格朗日乘数法是等式约束优化问题
等式约束
我们有目标函数和约束条件:
拉格朗日函数:
分别对的,的偏导置零,将变量转化为条件。目标其实就是求 的极值。
不等式约束
我们有目标函数和约束条件:
拉格朗日函数:
约束条件转化为KKT条件:
问题转化为在KKT条件下求解的最小值。
1.5.2核函数
核函数的出现旨在处理那些在原始特征空间中线性不可分的问题,即无法通过一个线性超平面来完美分割的数据。核函数通过将原始特征映射到一个高维空间,使得在新的空间中数据变得线性可分。这种映射使得支持向量机(SVM)在高维空间中能够更灵活地找到一个线性超平面,从而在原始特征空间中解决非线性问题。
核函数的作用在于计算两个样本点在高维空间中的内积,而无需显式计算映射后的数据。这节省了计算成本,使得SVM在高维空间中的计算变得可行。常用的核函数包括线性核、多项式核、径向基函数(RBF)核等,它们分别对应不同的特征映射方式。
几种常见的核函数:
线性核函数(Linear Kernel):
线性核函数对原始特征进行线性组合,适用于线性可分的情况。虽然它不引入额外的复杂性,但在处理非线性问题上有限制。
多项式核函数(Polynomial Kernel):
多项式核函数引入了多项式特征映射,其中d是多项式的次数,c是常数。它可以处理一定程度的非线性问题,通过调整次数d可以增加模型的复杂性。
径向基函数核(RBF Kernel / Gaussian Kernel):
RBF核函数基于样本点之间的距离,将数据映射到无限维的空间。它在处理非线性问题上非常强大,通过调整参数σ(标准差)可以控制映射的宽度。
二.实验
1.计算fx
遍历数据集中的每个样本,计算其与当前样本的内积,然后乘以对应的权重系数和目标值,最后将所有结果求和并加上偏置项b。最终返回计算得到的fx2值。
import random
import matplotlib.pyplot as plt
import numpy as np
#加载数据集
from sklearn.datasets import load_iris
#计算当前类别
def Fx(N,a,target,dataset,b,i):
fx2 = 0
for k in range(N):
fx2 += a[k] * target[k] * np.matmul(dataset[i], dataset[k].T)
fx2 += b
return fx2
2.核函数
计算第i个样本和第j个样本的内积
#计算核函数
def Kernel(dataset, i, j):
result = np.matmul(dataset[i], dataset[j].T)
return result
3.随机生成j
在0到N-1的范围内随机选择一个不等于i的整数j。为了实现这个功能,函数使用了一个while循环,不断地生成一个随机整数j,直到找到一个满足条件的j值为止。
#随机的 j 值
def random_j(N, i):
while True:
#选择一个随机的 j 值,确保 j != i
j = random.choice(range(N))
if j != i:
return j
4.拉格朗日乘子的上下界
计算拉格朗日乘子的上下界
def get_L_H(target, Alpha,C,i, j):
#L: 拉格朗日乘子的下界
#H: 拉格朗日乘子的上界
L, H = 0, 0
# 不同类别,计算 L 和 H
if target[i] != target[j]:
L = max([0, Alpha[j] - Alpha[i]])
H = min([C, C + Alpha[j] -Alpha[i]])
else:
# 同一类别,计算 L 和 H
L = max([0, Alpha[j] + Alpha[i] - C])
H = min([C, Alpha[i] + Alpha[j]])
return L, H
5.限制alpha_j
将alpha_j限制在范围[L, H]内
#过滤
def filter(L, H, alpha_j):
# 将拉格朗日乘子 alpha_j 限制在范围 [L, H] 内。
if alpha_j < L:
alpha_j = L
if alpha_j > H:
alpha_j = H
return alpha_j
6.smo
通过迭代更新拉格朗日乘子(Alpha)和截距项(b),以找到最佳的超平面来划分数据集。
def SMO(iter_max,N,target,toler,Alpha,C,dataset,b,w):
iter = 0
while iter < iter_max:
change_num = 0
for i in range(N):
#当前预测类别
Fx_i = Fx(N,Alpha,target,dataset,b,i)
#计算误差
Ex_i = Fx_i - target[i]
#确定是否符合KKT条件,不符合就进行更新
if target[i] * Ex_i < -toler and Alpha[i] <C or target[
i] * Ex_i > toler and Alpha[i] > 0:
j = random_j(N,i)
print('i:{},j:{}'.format(i, j))
Fx_j = Fx(N,Alpha,target,dataset,b,j)
Ex_j = Fx_j - target[j]
alpha_i = Alpha[i]
alpha_j = Alpha[j]
L, H = get_L_H(target, Alpha,C,i, j)
if L == H:
print('L == H')
continue
eta = Kernel(dataset,i, i) + Kernel(dataset,j, j) - 2 * Kernel(dataset,i, j)
if eta <= 0:
print('eta <= 0')
continue
#更新a_j
Alpha[j] += target[j] * (Ex_i - Ex_j) / eta
Alpha[j] = filter(L, H, Alpha[j])
if abs(Alpha[j] - alpha_j) < 0.00001:
print('alpha够精确了')
continue
#更新alpha[i]
Alpha[i] += target[i] * target[j] * (alpha_j - Alpha[j])
#更新b
b1 = b - Ex_i - target[i] * Kernel(dataset,i, i) * (Alpha[i] - alpha_i) - target[
j] * Kernel(dataset,i, j) * (Alpha[j] - alpha_j)
b2 = b - Ex_j - target[i] *Kernel(dataset,i, j) * (Alpha[i] - alpha_i) - target[
j] * Kernel(dataset,j, j) * (Alpha[j] - alpha_j)
if 0 < Alpha[i] < C:
b = b1
elif 0 < Alpha[j] < C:
b = b2
else:
b = (b1 + b2) / 2.0
print(Alpha[i], Alpha[j])
change_num += 1
if change_num == 0:
iter += 1
else:
iter = 0
for i in range(N):
w += target[i] *Alpha[i] * dataset[i]
return Alpha,w,b
7.图像显示
绘制决策边界并显示图像。
def display(Alpha,dataset,target,w,b):
svm_point = []
for i in range(100):
if Alpha[i] > 0:# 如果Alpha值大于0,表示该点是支持向量
print('第{}个是支持向量'.format(i), dataset[i], target[i])
svm_point.append(i)
x_point = np.array([i[0] for i in dataset])
y_point = np.array([i[1] for i in dataset])
x = np.linspace(4, 6, 5) # 在x轴上生成5个等间距的点
y = -(w[0] * x + b) / w[1]# 根据权重w和偏置b计算对应的y值
plt.scatter(x_point[:50], y_point[:50], color='red')# 绘制前50个点,颜色为红色
plt.scatter(x_point[-50:], y_point[-50:], color='blue') # 绘制后50个点,颜色为蓝色
support_vector = np.array([dataset[i] for i in svm_point])# 获取支持向量点的坐标
plt.scatter(support_vector[:, 0], support_vector[:, 1], color='black')# 绘制支持向量点,颜色为黑色
plt.plot(x, y)
plt.show()
10.预测
def predict(w,b,X):
linear_output = np.dot(X, w) - b
return np.sign(linear_output[0])
11结果
11.1数据集
dataset, label = load_iris()['data'][5:105, :2], load_iris()['target'][:100]
target= np.array([1 if i == 1 else -1 for i in label])
print(dataset)
print(label)
11.2 计算计算Alpha,w,b
通过设定好的一些参数以及数据集,计算Alpha,w,b
N, M = len(dataset), len(dataset[0])
C = 100
#容错率
toler = 0.01
b = 0
Alpha = np.zeros(N)
#最大迭代次数
iter_max = 40
w = np.zeros(M)
#计算Alpha,w,b
Alpha,w,b=SMO(iter_max,N,target,toler,Alpha,C,dataset,b,w)
print()
print(Alpha)
print(w)
print(b)
11.3 预测
单个测试集预测
X=load_iris()['data'][100:101, :2]
print(X)
print("预测结果是{}".format(predict(w,b,X)))
11.4图像
显示图像
display(Alpha,dataset,target,w,b)
支持向量
绘图
三.结论
可以从图像中看出支持向量的选取还是有误差的,在蓝色点中较远的点也被选入了支持向量中,因此还是需要对代码进行改进和调整。在此次实验中,了解了其核心理论和关键概念,包括最大间隔与分类、对偶问题的引入,为了处理非线性问题,学习了核函数的应用,熟悉了svm分类的实现过程,核函数的选择对于模型的性能至关重要,可以根据问题的特点选择合适的核函数类型。也感受到了svm的优缺点。
优点:
- 可用于线性/非线性分类,也可以用于回归,泛化错误率低,也就是说具有良好的学习能力,且学到的结果具有很好的推广性。
- 可以解决小样本情况下的机器学习问题,可以解决高维问题,可以避免神经网络结构选择和局部极小点问题。
- SVM是最好的现成的分类器,现成是指不加修改可直接使用。并且能够得到较低的错误率,SVM可以对训练集之外的数据点做很好的分类决策。
缺点
- 对参数调节和和函数的选择敏感。