目录
1.3、 函数间隔(Functional margin)与几何间隔 (Geometrical margin)
一.支持向量机简介
支持向量机(Support Vector Machine,简称SVM)是一种广泛应用于分类和回归分析的监督学习算法。其基本原理是通过在特征空间中找到一个最优的超平面,将不同类别的数据点分隔开。
在二分类问题中,SVM的目标是找到一个能够将两类数据点分隔开的超平面,使得两侧距离最近的数据点到超平面的距离(即间隔)最大。这些最靠近超平面的数据点被称为支持向量。超平面的选择不仅要使得间隔最大,还要满足不同类别的数据点被正确分类,即位于超平面两侧的点应被分到不同的类别。
SVM可以通过核函数来处理非线性问题,将数据映射到高维空间,从而找到一个在高维空间中的超平面来完成分类。常用的核函数有线性核、多项式核、径向基核等。SVM在实际应用中表现出色,尤其在数据维度较高、样本数量不是很大的情况下。它对于处理线
性和非线性问题都有很好的效果,是一个强大而灵活的分类算法。
二.算法原理
1.1、什么是支持向量机
支持向量机(support vector machines,SVM)是一种二分类模型,它的目的是寻找一个超平面来对样本进行分割,分割的原则是间隔最大化,最终转化为一个凸二次规划问题来求解。
1.2、一个线性相关的例子
下面举个简单的例子,如下图所示,现在有一个二维平面,平面上有两种不同的数据,分别用圈和叉表示。由于这些数据是线性可分的,所以可以用一条直线将这两类数据分开,这条直线就相当于一个超平面,超平面一边的数据点所对应的y全是 -1 ,另一边所对应的y全是1。
接下来的问题是,如何确定这个超平面呢?从直观上而言,这个超平面应该是最适合分开两类数据的直线。而判定“最适合”的标准就是这条直线离直线两边的数据的间隔最大。所以,得寻找有着最大间隔的超平面。
1.3、 函数间隔(Functional margin)与几何间隔 (Geometrical margin)
在超平面w*x+b=0确定的情况下,|w*x+b|能够表示点x到距离超平面的远近,而通过观察w*x+b的符号与类标记y的符号是否一致可判断分类是否正确,所以,可以用(y*(w*x+b))的正负性来判定或表示分类的正确性。于此,我们便引出了函数间隔(functional margin)的概念。
定义函数间隔为:
而超平面(w,b)关于数据集T中所有样本点(xi,yi)的函数间隔最小值(其中,x是特征,y是结果标签,i表示第i个样本),便为超平面(w, b)关于训练数据集T的函数间隔:
但这样定义的函数间隔有问题,即如果成比例的改变w和b(如将它们改成2w和2b),则函数间隔的值f(x)却变成了原来的2倍(虽然此时超平面没有改变),所以只有函数间隔还远远不够。
事实上,我们可以对法向量w加些约束条件,从而引出真正定义点到超平面的距离--几何间隔(geometrical margin)的概念。
假定对于一个点 x ,令其垂直投影到超平面上的对应点为 x0 ,w 是垂直于超平面的一个向量,为样本x到分类间隔的距离,如下图所示:
可以看出:几何间隔就是函数间隔除以||w||,而且函数间隔y*(wx+b) = y*f(x)实际上就是|f(x)|,只是人为定义的一个间隔度量,而几何间隔|f(x)|/||w||才是直观上的点到超平面的距离。
三、SMO算法的基本思路:
2.1、SMO算法的基本思路:
1.如果所有的变量的解都满足最优化问题的KKT条件 那么这个最优化问题的解就得到了
2.否则选择两个变量 固定其他变量(任意选择两个拉格朗日乘子) 针对这两个变量构建一个二次规划问题
这个二次规划问题关于这两个变量的解应该更加接近二次规划问题的解 使得二次规划问题的目标函数值变得更小
3.此时: 子问题有两个变量 一个是违反KKT条件最严重的一个 一个是由约束条件自动确定的
注意: 选择两个变量实质上只有一个自由变量
因为等式约束:
∑i=2Nαiyi=0
重点:SMO算法实质上包含两个部分
1. 求解两个变量二次规划的解析方法
2.选择变量的启发式方法
2.2、两个变量二次规划的求解方法
假设选择的两个变量是 α1,α2α1,α2 其他αiαi是固定的
带入到凸二次规划的对偶问题中, 可以推导为
为了求解两个变量的二次规划问题, 首先分析约束条件 然后再次约束条件中求极小值
2.3、变量的选择方式
SMO算法在每个子问题中选择两个变量优化 其中至少一个变量是违反KKT条件的
1)第1个变量的选择
选择第1个变量的过程为外层循环 外层循环在训练样本中选取违反KKT条件最严重的样本点
并将其对应的变量作为第1个变量 –> 即:检验训练样本点(xi,yi)(xi,yi)是否满足KKT条件
KKT条件为:
2)第2个变量的选择
选择第2个变量为内层循环 假设在外层循环中以及找到第一个变量α1α1,现在要在内层循环中找到第2个变量,第二个变量的选择标准是
希望能使α2α2有足够的变化
使得alpha2alpha2的取值满足约束条件,由之前推导可得更新alpha2alpha2条件是:满足二次规划问题和根据|E1−E2||E1−E2|更新α2
3)计算阈值b与差值EiEi
每次完成两个变量的优化后 都要重新计算阈值b, 由KKT条件可知
四.代码实现
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
def create_data():
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['label'] = iris.target
df.columns = ['sepal_len', 'sepal_wid', 'petal_len', 'petal_wid', 'label']
print(df)
data = np.array(df.iloc[:100, [0, 1, -1]])
for i in range(len(data)):
if data[i,-1] == 0:
data[i,-1] = -1
return data[:,:2], data[:,-1]
class SVM:
def __init__(self, max_iter=100, kernel='poly'):
self.max_iter = max_iter
self._kernel = kernel
def init_args(self, features, labels):
self.m, self.n = features.shape
self.X = features
self.Y = labels
self.b = 0.0
# 将Ei保存在一个列表里
self.alpha = np.ones(self.m)
self.E = [self._E(i) for i in range(self.m)]
# 松弛变量
self.C = 1.0
def _KKT(self, i):
y_g = self._g(i)*self.Y[i]
if self.alpha[i] == 0:
return y_g >= 1
elif 0 < self.alpha[i] < self.C:
return y_g == 1
else:
return y_g <= 1
# g(x)预测值,输入xi(X[i])
def _g(self, i):
r = self.b
for j in range(self.m):
r += self.alpha[j]*self.Y[j]*self.kernel(self.X[i], self.X[j])
return r
# 核函数
def kernel(self, x1, x2):
if self._kernel == 'linear':
return sum([x1[k]*x2[k] for k in range(self.n)])
elif self._kernel == 'poly':
return (sum([x1[k]*x2[k] for k in range(self.n)]) + 1)**2
return 0
# E(x)为g(x)对输入x的预测值和y的差
def _E(self, i):
return self._g(i) - self.Y[i]
def _init_alpha(self):
# 外层循环首先遍历所有满足0<a<C的样本点,检验是否满足KKT
index_list = [i for i in range(self.m) if 0 < self.alpha[i] < self.C]
# 否则遍历整个训练集
non_satisfy_list = [i for i in range(self.m) if i not in index_list]
index_list.extend(non_satisfy_list)
for i in index_list:
if self._KKT(i):
continue
E1 = self.E[i]
# 如果E2是+,选择最小的;如果E2是负的,选择最大的
if E1 >= 0:
j = min(range(self.m), key=lambda x: self.E[x])
else:
j = max(range(self.m), key=lambda x: self.E[x])
return i, j
def _compare(self, _alpha, L, H):
if _alpha > H:
return H
elif _alpha < L:
return L
else:
return _alpha
def fit(self, features, labels):
self.init_args(features, labels)
for t in range(self.max_iter):
# train
i1, i2 = self._init_alpha()
# 边界
if self.Y[i1] == self.Y[i2]:
L = max(0, self.alpha[i1]+self.alpha[i2]-self.C)
H = min(self.C, self.alpha[i1]+self.alpha[i2])
else:
L = max(0, self.alpha[i2]-self.alpha[i1])
H = min(self.C, self.C+self.alpha[i2]-self.alpha[i1])
E1 = self.E[i1]
E2 = self.E[i2]
# eta=K11+K22-2K12
eta = self.kernel(self.X[i1], self.X[i1]) + self.kernel(self.X[i2], self.X[i2]) - 2*self.kernel(self.X[i1], self.X[i2])
if eta <= 0:
# print('eta <= 0')
continue
alpha2_new_unc = self.alpha[i2] + self.Y[i2] * (E2 - E1) / eta
alpha2_new = self._compare(alpha2_new_unc, L, H)
alpha1_new = self.alpha[i1] + self.Y[i1] * self.Y[i2] * (self.alpha[i2] - alpha2_new)
b1_new = -E1 - self.Y[i1] * self.kernel(self.X[i1], self.X[i1]) * (alpha1_new-self.alpha[i1]) - self.Y[i2] * self.kernel(self.X[i2], self.X[i1]) * (alpha2_new-self.alpha[i2])+ self.b
b2_new = -E2 - self.Y[i1] * self.kernel(self.X[i1], self.X[i2]) * (alpha1_new-self.alpha[i1]) - self.Y[i2] * self.kernel(self.X[i2], self.X[i2]) * (alpha2_new-self.alpha[i2])+ self.b
if 0 < alpha1_new < self.C:
b_new = b1_new
elif 0 < alpha2_new < self.C:
b_new = b2_new
else:
# 选择中点
b_new = (b1_new + b2_new) / 2
# 更新参数
self.alpha[i1] = alpha1_new
self.alpha[i2] = alpha2_new
self.b = b_new
self.E[i1] = self._E(i1)
self.E[i2] = self._E(i2)
return 'train done!'
def predict(self, data):
r = self.b
for i in range(self.m):
r += self.alpha[i] * self.Y[i] * self.kernel(data, self.X[i])
return 1 if r > 0 else -1
def score(self, X_test, y_test):
right_count = 0
for i in range(len(X_test)):
result = self.predict(X_test[i])
if result == y_test[i]:
right_count += 1
return right_count / len(X_test)
def _weight(self):
# linear model
yx = self.Y.reshape(-1, 1)*self.X
self.w = np.dot(yx.T, self.alpha)
return self.w
X, y = create_data()
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)
svm = SVM(max_iter=800)
print(svm.fit(X_train, y_train))
print(svm.score(X_train, y_train))
print(svm.score(X_test, y_test))
五.结果展示
数据分布展示:
六.总结
在这次实验中,我使用支持向量机(SVM)算法对鸢尾花数据集进行了分类任务,主要比较了在有无松弛因子的情况下分类效果的差异,并深入了解了SVM的原理和参数设置。
支持向量机是一种用于分类和回归任务的强大算法,其核心思想是寻找最优超平面来分隔不同类别的样本。在实验中,我分别训练了两个SVM模型,一个未加入松弛因子,另一个加入了松弛因子。
松弛因子的作用是允许部分样本位于超平面错误的一侧,从而增强模型的容错能力,使其能够更好地处理噪声和异常值。通过调整松弛因子参数,我分析了模型在不同容错程度下的表现。本次实验分别设置了松弛因子参数为0(无松弛因子)和101(大松弛因子),采用了默认的学习率和迭代次数(0.0001和2000)。此外,我们对数据进行了归一化处理,以确保所有特征具有相似的尺度,从而避免某些特征对模型训练产生过大影响。
在数据集划分方面,我使用了train_test_split函数,将数据集按70%的训练集和30%的测试集进行划分,确保模型在训练和测试阶段均有足够的数据支持。
实验结果显示,适当引入松弛因子可以提升模型的鲁棒性,使其更好地应对噪声和异常值。松弛因子的选择应根据数据集的特性和任务需求进行调整:较小的松弛因子适用于数据清晰的情况,而较大的松弛因子则适用于复杂数据和噪声较多的情况。这次实验让我对支持向量机的应用和参数调整有了更深的理解。