一、支持向量机算法介绍
1、支持向量机概念
支持向量机(Support Vector Machine,简称 SVM)是一种监督学习算法,常用于分类和回归分析。它的基本思想是在特征空间中找到一个最优的超平面,以将不同类别的样本分开。
在支持向量机中,我们试图找到一个超平面,使得不同类别的样本点尽可能地远离该超平面,同时使得同一类别的样本点尽可能地靠近它。这样的超平面被称为最大间隔超平面,具有较好的泛化能力。
为了实现最大间隔超平面的求解,支持向量机引入了支持向量的概念。支持向量是离超平面最近的样本点,它们决定了超平面的位置和方向。只有这些支持向量对最终的分类结果起作用,其他样本点并不影响。
在线性可分的情况下,支持向量机通过求解一个凸二次规划问题来确定最大间隔超平面。而在线性不可分的情况下,支持向量机通过引入松弛变量和惩罚项,允许一些样本点被错误分类或位于超平面附近。
除了线性支持向量机,还存在非线性支持向量机。非线性支持向量机通过引入核函数,将样本从原始的特征空间映射到一个高维的特征空间,使得原本线性不可分的问题变为线性可分的问题。
2、支持向量机的优缺点
优点:泛化错误率低,计算开销不大,结果易解释
缺点:对参数调节和核函数的选择敏感,原始分类器不加修改仅适用于处理二类问题
适用数据类型:数值型和标称型数据
二、支持向量机算法推导
1、最大间隔与分类
从图中我们可以发现:能够将正样本和负样本分开的线有很多条,但是最好的往往只有一个,便是图中”正中间” , 容忍性好, 鲁棒性高, 泛化能力最强的红线
可以发现最好的分隔线能够使得位于决策边界上的样本点(也叫做支持向量)离分割线的最大间隔最大
在不同的空间维度当中,SVM的形式也不是唯一的,在多维空间中SVM就变成一个超平面来分隔开正负样本了
现在我们假设有训练数据:
它们线性可分且当且仅当:
我们对这个不等式进行转化:
现在假设分别有一个和一个样本分别位于两边的决策边界上,接着我们计算样本点到达超平面之间的距离:
因此分类间隔为:
我们接下来的任务就是要寻找适合的参数w和b使得分类间隔最大:
2、对偶问题
既然现在我们要在一个约束条件下去求得
此时我们可以建立拉格朗日函数来进行求解
因此我们引入拉格朗日乘子得到拉格朗日函数:
我们通过这个函数和约束关系可能并不能直接求得参数w和b的值,但是我们可以求解出的值和与参数w和b的关系,以此来间接求出参数w和b的值
现在我们对拉格朗日函数分别对参数求偏导:
将的到的w和b的值代回到拉格朗日函数中:
依据对偶问题我们可以得到:
因此最终的要求的模型为: 或者:
3、二次规划
现在我们取一个比较简单的小例子使用二次规划的方法来求解参数w和b:
4、SMO优化算法
上个小节我们使用的二次规划方法仅仅使用于数据量比较小的样本数据集,对于大样本数据集如果仍使用二次规划方法,计算量太大;因此我们使用SMO优化算法:
对于:
我们的基本思路是:
- 第一步:选取一对需更新的变量和 .
- 第二步:固定和 以外的参数, 求解对偶问题更新和
仅考虑和 时,对偶问题的约束变为:
偏移项b:通过支持向量来确定
SMO算法的流程:
5、核函数
在上述线性回归例子当中,正负样本是处于一个比较好区分的分布,但如果对于下面这个数据集来说:
在二维平面上就找不到一条线来分隔正负样本了,这个时候我们就需要通过一些方法,将二维的数据映射到高维空间,如下图所示:
可以发现当映射到高维空间之后,我们就有机会通过一个超平面来分隔正负样本。数据集从低维到高维的转换就是通过核函数来实现的:
常用核函数有:
6、软间隔
当我们在实际生活中使用SVM算法的时候经常会出现下面这种数据集的情况:
可以看到左上角有一个噪声点,所谓噪声点指那些与其他观测值不一致或异常的数据点,因为这个噪声点的出现,我们如果硬要将正负样本分隔开的话,就会出现下列情况:
很明显,这条分隔线显然是不如上面那条的,因此我们需要通过软间隔来实现,所谓软间隔是指允许在某些情况下出现错误分类,即允许一些数据点位于超平面的错误一侧。软间隔的目标是在保持最大间隔的同时,尽量减少错误分类的数量;
因此我们需要引入一个松弛变量:
三、支持向量机算法实现
1、SVM的一般流程
-
收集数据:从数据集中收集有标签的训练样本,并对数据进行预处理和特征提取。
-
准备数据:将数据集分成训练集和测试集,并对数据进行归一化或标准化处理。
-
训练模型:使用训练集训练SVM模型,选择SVM核函数、正则化参数等超参数,并使用交叉验证等技术进行模型选择和调优。
-
测试模型:使用测试集评估模型性能,计算出准确率、召回率、F1值等指标,并可视化预测结果。
-
应用模型:使用训练好的SVM模型进行分类或回归预测,对新的未知样本进行分类或回归预测。
-
调整模型:根据实际应用场景和用户反馈对模型进行调整和改进。
2、SVM的实现
数据集准备
本次使用的数据集是从吴恩达练习中拿的
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from scipy.io import loadmat
from sklearn import svm
# 加载数据
raw_data3 = loadmat('data\ex6data3.mat')
X3, y3 = raw_data3['X'], raw_data3['y'].ravel() # 确保 y3 是一维数组
data3 = pd.DataFrame(raw_data3['X'], columns=['X1', 'X2'])
data3['y'] = y3
def plot_data(ax, X, y):
'''绘制数据集的散点图'''
positive = data3[data3['y'] == 1]
negative = data3[data3['y'] == 0]
ax.scatter(positive['X1'], positive['X2'], s=20, marker='x', label='Positive', c='black')
ax.scatter(negative['X1'], negative['X2'], s=20, marker='o', label='Negative', c='y')
ax.set_xlabel('x1')
ax.set_ylabel('x2')
ax.legend()
# 创建图和轴
fig, ax = plt.subplots()
plot_data(ax, X3, y3)
plt.show()
训练并绘制决策边界
本次训练使用的是内置核rbf
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from scipy.io import loadmat
from sklearn import svm
# 加载数据
raw_data = loadmat('data\ex6data3.mat')
X, y = raw_data['X'], raw_data['y'].ravel() # 确保 y 是一维数组
data = pd.DataFrame(raw_data['X'], columns=['X1', 'X2'])
data['y'] = y
def plot_data(X, y, ax):
'''绘制数据集的散点图'''
positive = data[data['y'] == 1]
negative = data[data['y'] == 0]
ax.scatter(positive['X1'], positive['X2'], s=20, marker='x', label='Positive', c='black')
ax.scatter(negative['X1'], negative['X2'], s=20, marker='o', label='Negative', c='y')
ax.set_xlabel('x1')
ax.set_ylabel('x2')
ax.legend()
def plot_boundary(ax, clf, X):
'''绘制决策边界'''
x_min, x_max = X[:, 0].min() * 1.2, X[:, 0].max() * 1.1
y_min, y_max = X[:, 1].min() * 1.1, X[:, 1].max() * 1.1
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 500), np.linspace(y_min, y_max, 500))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
ax.contour(xx, yy, Z)
sigma = 0.1
gamma = np.power(sigma, -2)
Cs = [1, 50, 100]
# 对每个 C 值绘制决策边界
for C in Cs:
clf = svm.SVC(C=C, kernel='rbf', gamma=gamma)
model = clf.fit(X, y)
fig, ax = plt.subplots()
plot_data(X, y, ax)
plot_boundary(ax, model, X)
ax.set_title('SVM Decision Boundary with C = {}'.format(C))
plt.show()
训练结果
结果分析
对比上面三组结果发现,当C越小时,分隔两组数据的决策边界就越松驰,间隔就稍微大一些,有一些正样本点也划分为负样本点;随着C的增大,划分越来越细致,基本上能够分类得很明确。像线性可分例子中的一样,C越大就容易产生过拟合的情况,C越小容易产生欠拟合的情况。
3、SVM实现垃圾邮件分类
加载数据集和词汇表
# 加载词汇表
with open('data/vocab.txt', 'r') as f:
vocab = [line.strip().split('\t')[1] for line in f]
# 加载训练集和测试集
train_data = loadmat('data/spamTrain.mat')
test_data = loadmat('data/spamTest.mat')
X_train = train_data['X']
y_train = train_data['y'].ravel()
X_test = test_data['Xtest']
y_test = test_data['ytest'].ravel()
特征提取
def email_to_features(email, vocab):
features = np.zeros(len(vocab))
for index in email:
features[index-1] = 1
return features
# 对训练集和测试集进行特征提取
X_train_features = np.array([email_to_features(email, vocab) for email in X_train])
X_test_features = np.array([email_to_features(email, vocab) for email in X_test])
模型构建和训练
from sklearn.svm import SVC
# 创建并训练SVM分类器
svm = SVC(kernel='linear')
svm.fit(X_train_features, y_train)
模型评估
from sklearn.metrics import accuracy_score
# 预测训练集和测试集
train_predictions = svm.predict(X_train_features)
test_predictions = svm.predict(X_test_features)
# 计算精度
train_accuracy = accuracy_score(y_train, train_predictions)
test_accuracy = accuracy_score(y_test, test_predictions)
print("训练集精度:", train_accuracy)
print("测试集精度:", test_accuracy)
运行结果
四、实验小结
支持向量机的目标是找到一个超平面,将不同类别的样本正确分隔开。这个超平面被定义为具有最大边界(最大间隔)的决策边界。SVM通过选择支持向量(离超平面最近的训练样本点)来进行分类。
线性可分与线性不可分:在线性可分的情况下,SVM使用线性核函数构建一个线性分类器。在线性不可分的情况下,可以使用非线性核函数(如多项式核函数、高斯径向基核函数)将数据映射到高维空间,使其在新的空间中线性可分。
正则化参数C:SVM中的正则化参数C用于控制模型的复杂度。较小的C值会导致更强的正则化,可能导致欠拟合;较大的C值会导致较少的正则化,可能导致过拟合。通过交叉验证或网格搜索来选择最优的C值。
核函数选择:SVM可以使用不同的核函数来处理非线性问题。常用的核函数包括多项式核函数和高斯径向基核函数。选择适当的核函数取决于数据的特征和分布。
特征工程与数据预处理:在SVM中,特征工程和数据预处理对模型性能至关重要。可以使用特征选择、特征转换和标准化等技术来提高分类器的准确性。
参数调优:除了正则化参数C之外,还有其他参数需要进行调优,如核函数的参数(如多项式核函数的degree、高斯径向基核函数的gamma等)。通过交叉验证来选择最佳的参数组合。
非线性SVM扩展:除了二分类问题,SVM还可以扩展到多分类问题(如one-vs-rest和one-vs-one方法)和回归问题(支持向量回归,SVR)。
总之,支持向量机是一种强大且灵活的算法,具有较好的泛化能力。通过调整参数、选择合适的核函数和进行有效的特征工程,可以提高SVM模型的性能并获得更准确的预测。