一、支持向量机基本介绍
支持向量机(Support Vector Machine,SVM)是一种有监督学习模型,可用于分类和回归问题的广义线性分类器,其核心思想是在特征空间中寻找一个最优超平面来最大化不同类别样本之间的间隔,从而实现高效分类。
1.1 线性可分
对一个混合数据集,若可以画出一条直线(二维)将不同类别的数据点划分到两侧,则称这样的数据为线性可分。将数据集分隔开来的直线称为分隔超平面(separating hyperplane),也就是分类的决策边界。对于二维平面,分隔超平面是直线,如果数据集是三维的,那么此时的分隔超平面就是平面,也称为超平面(hyperplane),高维情况依此类推,即对于N维的数据集,超平面为一个用于划分数据集的N-1维的子空间对象。
如果不存在直线或平面将数据集划分到两侧,则这样的数据为线性不可分。
以下只对二分类问题进行讨论。
1.2 最大间隔超平面
对于同个数据集,能够划分该数据集的直线(即分隔超平面)可能有多条(如下图所示),我们希望找到使划分结果最优的那条直线,即最佳分界线。那么,用什么来衡量它们的划分效果呢?
在SVM中,我们希望找到离分隔超平面最近的点(这类点称为支持向量),确保它们离分隔超平面的距离尽可能远,而两类样本的支持向量到分隔面的距离之和称为间隔,即我们希望间隔尽可能地大。因此在线性可分的二分类问题中,求解目标就是寻找能将两类样本完全正确划分、且间隔最大的超平面,即求解最大间隔超平面。
1.3 超平面与间隔的数学定义
1. 设超平面方程为 ,其中 w 是权重向量,b是偏置;
2. 样本点 到超平面的距离为:
3. 对于线性可分数据,类别标签 ∈ {+1,-1},满足
此时,间隔可表示为:
推导:两类样本中离超平面最近的点满足
,故最近距离为
,总间隔为该值的两倍。
1.4 硬间隔和软间隔
硬间隔:完全线性可分,即要求存在一个超平面,能将所有样本点完全正确划分为两类,且样本与超平面的间隔至少为1。
软间隔:当数据线性不可分时,允许部分样本分类错误,通过引入松弛变量和惩罚参数平衡分类准确性与间隔大小。
二、对偶问题
在优化理论中,每个原始问题 (Primal Problem)都存在一个对应的对偶问题 (Dual Problem),对偶问题具有以下特点:
1.目标函数转换:原始问题的最小化转为对偶问题的最大化,反之亦然;
2.约束条件转换:原始问题的约束条件转化为对偶问题的变量约束;
3.强对偶性:当原始问题为凸优化且满足 Slater 条件时,对偶问题的最优解等于原始问题的最优解;
4计算优势:对偶问题在某些情况下更容易求解(如引入核函数),且能自然引入样本点间的内积运算。
三、硬间隔问题求解
2.1 优化目标:最大化间隔
1. 正确分类所有数据点:
①
,
;
②
,
;
所以若保证严格正确分类,则有
恒成立。
2. 目标转化为求解 w 和 b,使得间隔 最大,等价于最小化
(简化优化计算)
3. 最终优化问题可表示为:,受制于
2.2 支持向量与最大间隔的关系
在最大间隔超平面的求解中,满足 的样本点称为支持向量。
关键性质:
1. 最大间隔超平面仅由支持向量决定,与其他样本无关,即非支持向量不影响超平面;
2. 支持向量的数量通常远少于总样本数,降低了模型复杂度;
3. 最小化 ,目标函数为凸函数,存在唯一全局最优解。
2.3 对偶问题
1. 原始问题:,受制于
;
2. 引入拉格朗日乘子 ,构造拉格朗日函数:
3. 对原始变量求偏导并令其为零:
① 对 w 求偏导:
② 对 b 求偏导:
4.代入拉格朗日函数消元:
将 代入L,并利用
化简,得到对偶问题:
,受制于
,∀i,
5.KKT条件,最优解需满足:
① 原问题可行性:; ② 对偶可行性:
③ ,若
,则
(对应支持向量)
假设 为上述拉格朗日函数的最优解,若满足
,则原问题的最优解为:
,
四、软间隔问题求解
3.1 优化目标:平衡间隔与分类误差
1. 在数据线性不可分时,软间隔SVM允许部分样本违反 ,为避免噪声的存在导致问题无解,引入松弛变量
,令
,
显然,当 足够大时,分类后的样本点就可以满足
。
2. 求解 w 和 b,使得间隔 最大,即最小化
,同时要避免
取太大的值,因此在目标函数中加入惩罚项C,得到
3. 最终优化问题表示:,受制于
:最大化间隔 (控制模型复杂度) ;
:最小化分类误差 (惩罚违反
的样本) ;
C权衡间隔大小与分类误差:C → +∞ 趋近硬间隔,C → 0 允许大量错分。
3.2 支持向量新定义
在软间隔中,支持向量包括三类样本:
1. 边界上的样本:满足 且
(对应
)
2. 间隔内的正确分类样本:满足 且
(对应
)
3. 错分样本:满足 且
(对应
)
关键性质:软间隔超平面的位置由所有支持向量共同决定,而非仅由边界上的样本决定。错分样本和间隔内样本通过调整 的值影响超平面,使模型在复杂数据中仍保持鲁棒性。
3.3 对偶问题
1. 原始问题:,受制于
2. 引入拉格朗日乘子 和
,构造拉格朗日函数:
其中, 对应约束
;
对应约束
。
3. 对原始变量求偏导并令其为零:
① 对 w 求偏导:
② 对 b 求偏导:
③ 对 ξ 求偏导:
由于 ,可得
;又因为
,因此有
。
4. 代入拉格朗日函数消元:
将 代入L,并利用
和
化简,得到对偶问题:
,受制于
和
。
5. KKT条件,最优解需满足:
① 原问题可行性: ,
;
② 对偶可行性: ,
;
③ ,
假设 为上述拉格朗日函数的最优解,若满足
,则原问题的最优解为:
,
①
:样本对超平面无影响,为非支持向量,不参与计算 b;
②
:此时
,则
;
③
,此时
,但
未知 ,无法求解 b
通常取多个满足
的支持向量计算出多个 b 的值,然后取平均值作为解。
五、核函数
核函数(Kernel Function)是机器学习中用于解决非线性分类问题的核心概念,其本质是通过隐式地将低维空间中的数据映射到高维特征空间,从而在高维空间中实现线性可分。
5.1 核函数的定义
核函数是一个函数 K(x , z) ,它接受两个低维空间中的输入向量 x 和 z ,输出它们在高维特征空间中的内积,即 K(x , z) = ⟨ ϕ(x) , ϕ(z) ⟩ ;
其中 ϕ(·) 是从原始空间到高维特征空间的映射函数, ⟨ · , · ⟩ 表示内积运算。
核函数的核心思想是避免维度灾难:直接计算高维空间的内积可能面临维度爆炸 (如原始维度n,映射后维度可能达 ) ,而核函数可通过低维计算间接得到高位内积,避免显示映射。
5.2 常见核函数
核函数类型 | 数学表达式 | 特点 |
---|---|---|
线性核 | | 等价于无映射,直接计算内积 |
多项式核 | | d 为多项式次数, |
高斯核 | | |
sigmoid 核 | | 形式类似神经网络激活函数 |
六、示例:垃圾邮件过滤器
6.1 实验要求
使用SVM建立自己的垃圾邮件过滤器。首先需要将每个邮件x变成一个n维的特征向量,并训练一个分类器来分类给定的电子邮件x是否属于垃圾邮件(y=1)或者非垃圾邮件(y=0)。
数据集:emailSample1.txt, vocab.txt, spamTrain.mat, spamTest.mat
6.2 邮件预处理与特征提取
邮件数据不能直接用于机器学习模型,需要转换为数值特征。
处理邮件内容,先进行文本标准化:将邮件文本转换为小写,移除HTML标签;特殊内容替换:将URL替换为httpaddr,邮箱替换为emailaddr等;最后进行分词并过滤:使用正则表达式分割文本并去除无效字符。
vocab.txt 包含约1899个垃圾邮件中常见的单词,每个单词对应一个唯一索引,因此特征向量是一个1899维的向量,每个位置对应词汇表中的一个单词,出现为1,未出现为0。
6.3 模型训练与可视化
线性核在文本分类中表现良好,且计算效率高,因此实验采用线性核的SVM,并设置C = 0.1表示较小的正则化强度。
由于原始特征向量是1899维的,无法直接可视化,因此使用PCA降维到 2 维,降维后要对二维数据重新进行SVM模型训练,并且参数和核函数的选择要与原模型一致;之后生成二维平面上的密集点 (网格) ,用于评估模型决策函数,并计算每个样本点道决策边界的有符号距离;利用 pyplot 函数将决策边界与数据点可视化。
6.4 模型评估与预测
在训练集上训练SVM分类器,模型学习到1899维特征空间中的最优超平面;对训练集和测试集分别进行预测 (模型评估指标为准确率) ,返回训练集和测试集的准确率。
对单封邮件 (即文本 emailSample1.txt) 进行预处理和特征提取,再用训练好的SVM模型对该邮件进行预测,返回预测结果。
6.5 代码实现
import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn.metrics import accuracy_score
import re
from scipy.io import loadmat
#设置字体,解决负号问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
#读取词汇表
def get_vocab_list():
vocab_dict = {}
with open('vocab.txt', 'r') as f:
for line in f:
idx, word = line.strip().split('\t')
vocab_dict[word] = int(idx)
return vocab_dict
#处理邮件内容,提取特征
def process_email(email_contents):
vocab_list = get_vocab_list()
word_indices = []
#转换为小写
email_contents = email_contents.lower()
#移除HTML标签
email_contents = re.sub(r'<[^<>]+>', ' ', email_contents)
#处理URLs
email_contents = re.sub(r'[http|https]://[^\s]*', 'httpaddr', email_contents)
#处理电子邮件地址
email_contents = re.sub(r'[^\s]+@[^\s]+', 'emailaddr', email_contents)
#处理货币符号
email_contents = re.sub(r'[$]+', 'dollar', email_contents)
#分词
tokens = re.split(r'[ @$/#.-:&*+=\[\]?!(){},''">_<;%\n\r]', email_contents)
#处理每个词
for token in tokens:
#移除非字母数字字符
token = re.sub('[^a-zA-Z0-9]', '', token)
#词干提取,这里简化处理,实际应用中可以使用nltk.stem.PorterStemmer
#检查是否在词汇表中
if len(token) < 1:
continue
if token in vocab_list:
word_indices.append(vocab_list[token])
return word_indices
#创建特征向量
def email_features(word_indices):
vocab_list = get_vocab_list()
n = len(vocab_list)
x = np.zeros((n, 1))
for idx in word_indices:
x[idx] = 1
return x
#读取并处理邮件样本
def read_email_sample(file_path):
with open(file_path, 'r') as f:
email_contents = f.read()
word_indices = process_email(email_contents)
features = email_features(word_indices)
return features
#加载数据集
def load_data():
#加载训练和测试数据
data = loadmat('spamTrain.mat')
X_train = data['X']
y_train = data['y'].ravel()
data_test = loadmat('spamTest.mat')
X_test = data_test['Xtest']
y_test = data_test['ytest'].ravel()
return X_train, y_train, X_test, y_test
#训练SVM模型
def train_svm(X, y):
#使用线性核的SVM
clf = svm.SVC(kernel='linear', C=0.1)
clf.fit(X, y)
return clf
#可视化决策边界(二维简化版)
def visualize_decision_boundary(X, y, model, title):
#使用PCA降维以便可视化
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
X_2d = pca.fit_transform(X)
#训练二维数据的SVM模型
clf_2d = svm.SVC(kernel='linear', C=0.1)
clf_2d.fit(X_2d, y)
#创建网格以绘制决策边界
h = 0.02
x_min, x_max = X_2d[:, 0].min() - 1, X_2d[:, 0].max() + 1
y_min, y_max = X_2d[:, 1].min() - 1, X_2d[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
Z = clf_2d.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# 绘制决策边界和间隔边界
plt.contour(xx, yy, Z, levels=[-1, 0, 1], linestyles=['--', '-', '--'], colors='k')
#绘制散点,根据类别设置颜色和标签
non_spam_idx = np.where(y == 0)
spam_idx = np.where(y == 1)
plt.scatter(X_2d[non_spam_idx, 0], X_2d[non_spam_idx, 1], c='red', label='非垃圾邮件', edgecolors='k')
plt.scatter(X_2d[spam_idx, 0], X_2d[spam_idx, 1], c='yellow', label='垃圾邮件', edgecolors='k')
plt.title(title)
plt.xlabel('主成分 1')
plt.ylabel('主成分 2')
plt.legend()
plt.show()
#主函数
def main():
#加载数据
X_train, y_train, X_test, y_test = load_data()
#训练SVM模型
model = train_svm(X_train, y_train)
#预测并评估
y_pred_train = model.predict(X_train)
y_pred_test = model.predict(X_test)
print(f"训练集准确率: {accuracy_score(y_train, y_pred_train):.4f}")
print(f"测试集准确率: {accuracy_score(y_test, y_pred_test):.4f}")
#可视化决策边界
visualize_decision_boundary(X_train, y_train, model, '垃圾邮件分类决策边界')
#测试单个邮件样本
email_sample = read_email_sample('emailSample1.txt').reshape(1, -1)
prediction = model.predict(email_sample)
print(f"邮件样本预测结果: {'垃圾邮件' if prediction[0] == 1 else '非垃圾邮件'}")
if __name__ == "__main__":
main()
运行测试结果截图:
实验结果分析:
在训练集上评估可以检查模型学习能力,在测试集上评估可以检查模型泛化能力;训练集准确率接近100%,说明模型对训练数据拟合良好,测试集准确率略低,但仍很高,说明模型泛化能力强,两者差距小,表明过拟合风险低。
七、实验小结
SVM可应用于多种场景,例如文本分类 (如垃圾邮件过滤) 、图像识别 (如手写识别、物体检测) 、生物信息学 (如基因序列分类、蛋白质结构预测) 等。
优点:
① 当特征维度高于样本数时仍有效;
② 可利用核函数的灵活性处理非线性问题;
③ 仅依赖支持向量,对噪声和离群点不敏感;
④ 决策边界由少量样本 (支持向量) 决定,模型具有稀疏性,存储效率高;
⑤ 泛化错误率低,计算开销不大,结果易解释。
缺点 :
① 对参数调节和核函数的选择敏感;
② 原始分类器不加修改仅适用于处理二分类问题;
③ 对特征缩放敏感 (需先标准化或归一化数据) 。
收获: 针对硬间隔和软间隔两种情况,需要分别对问题的优化目标进行讨论,并且要注意约束条件也要作出相应的变化;对于线性不可分的数据,可利用核函数将其隐式映射,进行内积运算求解;无论是惩罚参数还是核参数 ,都与分类器的性能息息相关,因此要结合交叉验证对参数进行合理的调节。