动手学机器学习支持向量机
原文:https://github.com/ForceInjection/hands-on-ML/blob/main/nju_software/%E5%8A%A8%E6%89%8B%E5%AD%A6%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E6%94%AF%E6%8C%81%E5%90%91%E9%87%8F%E6%9C%BA%E7%AE%97%E6%B3%95.md
代码:https://github.com/ForceInjection/hands-on-ML/blob/main/nju_software/svm.ipynb
一、支持向量机基础概念
(一)什么是支持向量机
支持向量机(Support Vector Machine,简称 SVM)是一种监督学习算法,主要用于分类和回归任务。其核心思想是通过寻找一个最优的超平面,将不同类别的数据点尽可能宽地分开,这个超平面在高维空间中起到分类决策边界的作用。
在分类问题中,距离超平面最近的几个数据点被称为支持向量,它们对于确定超平面的位置和方向起着关键作用。以一个简单的二维线性可分数据集为例,假设数据集中有两类样本点,分别用不同颜色表示,SVM 的目标就是找到一条直线(在高维空间中则是超平面),使得这条直线能够将两类样本点完全分开,并且距离这条直线最近的样本点(支持向量)到直线的距离最大化。
与其他算法相比,如决策树容易受到数据噪声的影响,神经网络在小样本数据集上可能容易过拟合,而 SVM 则通过最大化间隔的方式,提升了模型在未知数据上的泛化能力,尤其适用于数据维度较高但样本数量相对较少的情况。
(二)数学基础铺垫
在深入理解 SVM 的原理之前,需要回顾一些相关的数学基础知识。线性代数中,向量的内积是描述两个向量之间关系的重要运算,它能够反映出向量之间的夹角大小,这对于确定数据点在超平面上的投影位置具有关键作用。
向量的范数则用于衡量向量的长度或大小,在 SVM 中,用于表示数据点到超平面的距离。优化理论方面,凸优化问题是一类重要的优化问题,其特点是目标函数是凸函数,且可行域是凸集,这类问题具有唯一的全局最优解,便于求解。拉格朗日乘子法是求解带约束优化问题的有效方法,通过引入拉格朗日乘子,将带约束的优化问题转化为无约束的优化问题,从而方便求解。这些数学基础将在后续推导 SVM 的目标函数和求解过程中发挥重要作用。
二、硬间隔最大化与线性可分 SVM
(一)线性可分问题的正式定义与判断标准
线性可分问题是指存在一个超平面能够将不同类别的数据点完全分开的情况。
形式化地,给定一个数据集 ,其中 是输入特征向量, 是对应的类别标签,若存在一个超平面 ,使得对于所有的 ,都有 ,则称该问题为线性可分问题。
判断一个数据集是否线性可分,可以通过检查是否存在这样的超平面满足上述条件,或者利用一些几何方法和工具,如计算数据点之间的线性可分性指标等。
(二)硬间隔最大化的思想与目标函数推导
硬间隔最大化是针对线性可分问题提出的核心思想,其目标是在确保所有数据点都被正确分类的前提下,找到一个具有最大间隔的超平面。间隔的大小由支持向量到超平面的距离决定,最大化间隔意味着提高模型的泛化能力,使得模型在未知数据上具有更好的分类性能。
为了实现这一目标,需要构建相应的优化问题。具体地,我们希望通过调整超平面的参数 和 ,使得对于所有数据点,满足 ,同时最大化间隔 $ \frac2}{||} $。
为了便于优化,通常将最大化间隔问题转化为最小化 $ \frac1}{2}||^2 $ 的问题,这是一个典型的凸优化问题,并且带有不等式约束条件。
(三)求解硬间隔最大化问题
为了解决带有不等式约束的优化问题,可以采用拉格朗日乘子法。首先,构建拉格朗日函数:
其中, 是拉格朗日乘子,用于衡量每个约束条件的重要性。通过对拉格朗日函数分别对 、 和 求偏导并令其等于零,可以得到最优解的条件,即 KKT 条件。
解这些方程可以得到最优的 、 和 ,从而确定最优超平面。其中,只有支持向量对应的 大于零,这表明支持向量在确定超平面中起到了关键作用,而非支持向量的数据点对超平面的位置没有影响。通过求解得到的最优超平面,可以对新的数据点进行分类预测。
三、软间隔最大化与非线性可分 SVM
(一)非线性可分问题的现实场景与挑战
在实际应用中,数据往往是复杂且不完美的,很少存在完全线性可分的情况。数据中可能包含噪声、异常点或者由于数据本身的复杂性导致无法通过一个简单的超平面进行完美划分。例如,在图像识别中,由于光照变化、遮挡等因素,同一类物体的图像可能在特征空间中分布较为分散,难以用线性边界分开;在文本分类中,由于语言的多样性和歧义性,不同类别文本的特征也可能存在重叠。
面对这些非线性可分的问题,硬间隔最大化策略将无法适用,因为不存在一个超平面能够将所有数据点正确分类,此时需要引入软间隔最大化的概念来应对这些挑战。
(二)软间隔最大化的概念引入与合理性和目标函数构建
软间隔最大化放宽了硬间隔最大化的要求,允许部分数据点位于间隔带内甚至被错误分类,但同时通过引入一个惩罚机制来控制误分类的程度。
具体来说,对于每个数据点,引入一个松弛变量 ,表示该数据点到超平面的距离不足部分,即当数据点被正确分类但靠近超平面时,或者被错误分类时, 会大于零。目标函数则在原来硬间隔最大化的目标函数基础上,增加了一个惩罚项,以平衡间隔最大化和误分类的惩罚。新的目标函数形式为:
其中, 是惩罚参数,用于控制对误分类样本的惩罚程度。当 较大时,对误分类的惩罚更严厉,模型会尽量减少误分类,但可能导致过拟合;当 较小时,模型对误分类的容忍度较高,可能倾向于找到一个更简单的超平面,从而提高泛化能力。因此,选择合适的 值对于模型的性能至关重要,通常需要通过交叉验证等方法来确定。
(三)软间隔 SVM 的求解方法
软间隔 SVM 的求解方法与硬间隔 SVM 类似,同样可以使用拉格朗日乘子法将原问题转化为对偶问题进行求解。构建拉格朗日函数:
其中, 和 是拉格朗日乘子。通过对拉格朗日函数分别对 、、、 和 求偏导并令其等于零,得到 KKT 条件,进而求解得到最优解。
与硬间隔 SVM 不同的是,在软间隔 SVM 中,部分数据点的 可能会超过零,但受到 的限制,这使得模型在间隔最大化和误分类惩罚之间达到平衡。通过求解得到的最优超平面,可以在一定程度上容忍数据中的噪声和异常点,提高模型在实际应用中的鲁棒性和泛化能力。
四、核函数与非线性 SVM
(一)核函数的基本思想与作用
尽管通过软间隔最大化可以处理一定程度的非线性可分问题,但对于一些复杂的非线性数据分布,仅仅依靠在原始特征空间中寻找线性超平面可能仍然无法取得满意的分类效果。
核函数的引入为解决这一问题提供了有力的工具。核函数的基本思想是通过将数据从原始输入空间映射到一个更高维的特征空间,使得在原始空间中非线性可分的数据在高维特征空间中变得线性可分,从而可以应用线性 SVM 的方法进行分类。
这种映射通常是非线性的,但通过核函数,我们可以在不显式计算高维特征空间中的坐标的情况下,高效地计算数据点在高维空间中的内积,从而大大降低了计算复杂度。
(二)常见核函数的类型与特点
多项式核函数 :多项式核函数的形式为 ,其中 是一个常数项, 是多项式的次数。多项式核函数能够捕捉数据点之间的非线性关系,并且通过调整 和 的值,可以控制映射后的高维空间的复杂度。当 且 时,多项式核函数退化为线性核函数。较大的 值可以增加模型的复杂度,提高对复杂数据分布的拟合能力,但也可能导致过拟合。 高斯核函数(RBF 核) :高斯核函数是最常用的核函数之一,其形式为 ,其中 是高斯核的带宽参数。高斯核函数通过计算数据点之间的欧氏距离的平方的指数衰减,将数据映射到无限维的空间中。高斯核函数具有良好的局部性,当 较小时,数据点在高维空间中的分布较为集中,模型对局部特征较为敏感;当 较大时,数据点在高维空间中的分布较为分散,模型对全局特征的考虑更多。选择合适的 值对于高斯核函数的性能至关重要。 其他核函数 :除了多项式核函数和高斯核函数外,还有一些其他的核函数,如 sigmoid 核函数 ,其中 和 是参数。sigmoid 核函数在神经网络等领域也有广泛应用,但其在 SVM 中的性能可能不如前两种核函数稳定,在实际应用中需要谨慎选择。
(三)核函数的选取策略与实践技巧
在实际应用中,选择合适的核函数及其参数对于 SVM 的性能有着至关重要的影响。以下是一些常见的核函数选取策略与实践技巧:
根据数据特点选择核函数:对于具有明显线性可分特征的数据,可以选择线性核函数;对于具有非线性关系但结构相对简单(如存在多项式关系)的数据,可以尝试多项式核函数;而对于复杂的非线性数据分布,尤其是数据在局部区域具有相似性特征时,高斯核函数通常是较好的选择。 参数选择与调优:对于选定的核函数,需要对其参数进行合理的选择和调优。例如,在多项式核函数中,需要确定 和 的值;在高斯核函数中,需要确定 的值;在 sigmoid 核函数中,需要确定 和 的值。通常可以采用交叉验证的方法,在训练集上通过多次试验不同的参数组合,选择使得模型在验证集上性能最佳的参数组合。 核函数的组合与自定义:在某些情况下,单一的核函数可能无法很好地描述数据的特征,可以通过组合多个核函数或者定义自定义核函数来提高模型的性能。例如,将高斯核函数和多项式核函数相结合,或者根据数据的特定结构设计特殊的核函数。但需要注意的是,组合或自定义核函数需要满足核函数的 Mercer条件,以确保对应的核矩阵是半正定的。
五、支持向量机的实践操作
(一)数据预处理步骤
数据标准化 :由于 SVM 对特征的尺度较为敏感,特别是对于基于距离计算的核函数(如高斯核函数),不同尺度的特征可能导致模型性能的显著差异。因此,在应用 SVM 之前,通常需要对数据进行标准化处理,使得每个特征的均值为 0,方差为 1。常见的标准化方法包括 Z-score 标准化,其公式为 ,其中 是原始特征值, 是该特征的均值, 是该特征的标准差。通过标准化,可以使不同特征在相同的尺度上进行比较和计算,提高模型的稳定性和准确性。 数据集划分 :为了评估模型的性能和进行参数调优,需要将数据集划分为训练集、验证集和测试集。通常,可以按照一定的比例(如 7:2:1 或 8:1:1 等)随机划分数据集。训练集用于训练 SVM 模型,验证集用于在参数调优过程中评估模型的性能,选择最优的参数组合,测试集则用于对最终训练好的模型进行独立的性能测试,以评估模型在未知数据上的泛化能力。在划分数据集时,需要注意保持数据分布的一致性,避免因划分不当导致模型性能的偏差。
(二)使用机器学习库(如 Scikit-learn)实现 SVM
Scikit-learn 中 SVM 模块的介绍 : Scikit-learn是一个功能强大的开源机器学习库,提供了丰富的机器学习算法实现,其中包括SVM。在Scikit-learn中,SVM 主要通过svm模块中的SVC(用于分类任务)和SVR(用于回归任务)类来实现。这些类提供了简单易用的接口,用户可以通过设置不同的参数来选择核函数、调整模型的超参数等。代码示例 :以下是一个使用 Scikit-learn实现SVM分类任务的代码示例:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineimport matplotlib.pyplot as pltimport seaborn as snsimport numpy as npimport pandas as pdfrom sklearn import datasetsfrom sklearn.model_selection import train_test_splitfrom sklearn.preprocessing import StandardScalerfrom sklearn.svm import SVCfrom sklearn.metrics import accuracy_score, classification_report, confusion_matrix, ConfusionMatrixDisplayfrom sklearn.decomposition import PCAfrom matplotlib import rcParams# 配置 matplotlib 使用字体rcParams['font.sans-serif'] = ['Heiti TC']rcParams['axes.unicode_minus'] = False # 解决负号显示问题# 加载数据集iris = datasets.load_iris()X = iris.datay = iris.targettarget_names = iris.target_names# 数据集划分X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)# ================== 数据可视化 ==================# 1. 特征分布箱线图(使用原始数据)plt.figure(figsize=(12, 6))df_train = pd.DataFrame(X_train, columns=iris.feature_names)df_train["species"] = [target_names[y] for y in y_train]melt_df = df_train.melt(id_vars="species", var_name="features", value_name="value")plt.title("Feature Distributions by Species (Training Set)")sns.boxplot(x="features", y="value", hue="species", data=melt_df)plt.xticks(rotation=45)plt.tight_layout()plt.show()# 数据标准化scaler = StandardScaler()X_train = scaler.fit_transform(X_train)X_test = scaler.transform(X_test)# ================== 模型训练 ==================model = SVC(kernel="rbf", C=1.0, gamma="scale", random_state=42)model.fit(X_train, y_train)y_pred = model.predict(X_test)# ================== 模型评估可视化 ==================# 1. 混淆矩阵plt.figure(figsize=(8, 6))ConfusionMatrixDisplay.from_predictions(y_test, y_pred, display_labels=target_names, cmap="Blues")plt.title("混淆矩阵")plt.show()# 2. 分类结果PCA可视化pca = PCA(n_components=2)X_train_pca = pca.fit_transform(X_train)X_test_pca = pca.transform(X_test)# 创建决策边界网格x_min, x_max = X_test_pca[:, 0].min() - 1, X_test_pca[:, 0].max() + 1y_min, y_max = X_test_pca[:, 1].min() - 1, X_test_pca[:, 1].max() + 1xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02))# 预测网格点类别(使用原始模型)Z = model.predict(pca.inverse_transform(np.c_[xx.ravel(), yy.ravel()]))Z = Z.reshape(xx.shape)plt.figure(figsize=(10, 8))plt.contourf(xx, yy, Z, alpha=0.8, cmap=plt.cm.Paired)scatter = plt.scatter(X_test_pca[:, 0],X_test_pca[:, 1],c=y_test,edgecolors="k",cmap=plt.cm.Paired,s=80,label="True Labels",)# 标记错误分类点errors = np.where(y_pred != y_test)plt.scatter(X_test_pca[errors, 0],X_test_pca[errors, 1],s=100,edgecolors="red",facecolors="none",linewidths=2,label="Misclassified",)plt.xlabel("First Principal Component")plt.ylabel("Second Principal Component")plt.title("Classification Results with Decision Boundaries (PCA Projection)")plt.legend()plt.colorbar(scatter, ticks=[0, 1, 2], label="Species")plt.show()# 输出性能指标print("\n" + "="*40)print("Model Performance Metrics:")print("="*40)print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")print("\nClassification Report:")print(classification_report(y_test, y_pred, target_names=target_names))
在上述代码中,首先加载了鸢尾花数据集,并将其划分为训练集和测试集。然后对数据进行了标准化处理,接着创建了一个使用高斯核函数的 SVM 分类模型,并设置了惩罚参数 和高斯核函数的带宽参数 (自动根据数据的方差计算合适的带宽值)。通过调用 fit 方法在训练集上训练模型,使用 predict 方法在测试集上进行预测,最后通过 accuracy_score 和 classification_report 函数评估模型的性能,输出准确率和分类报告,包括精确率、召回率和 F1 值等指标。 3. 模型训练过程中的注意事项 :在模型训练过程中,需要注意一些问题以确保模型的正常训练和性能提升。首先,要确保数据预处理步骤的正确性,包括数据标准化、数据集划分等,避免因数据问题导致模型训练异常或性能下降。其次,对于大规模数据集,SVM 的训练时间可能会较长,可以通过调整参数(如设置 max_iter 参数限制最大迭代次数)或者使用一些优化技巧(如使用随机梯度下降法进行优化)来加快训练速度。此外,在训练过程中,可以通过监控模型的收敛情况(如查看损失函数值的变化)来判断模型是否已经收敛,如果模型没有收敛,可能需要调整参数或者增加训练的迭代次数。
(三)模型评估与调优
分类任务中的评估指标 :在分类任务中,常用的评估指标包括准确率、精确率、召回率和 F1值等。
准确率是指分类正确的样本数占总样本数的比例,反映了模型的整体分类准确程度; 精确率是指预测为正类的样本中实际为正类的比例,侧重于衡量模型对正类样本的预测准确性; 召回率是指实际为正类的样本中被正确预测为正类的比例,侧重于衡量模型对正类样本的召回能力; F1值则是精确率和召回率的调和平均数,综合考虑了两者的表现。通过这些指标,可以从不同的角度评估模型的分类性能,发现模型的优势和不足之处。
MSE)、平均绝对误差(MAE)和 R 方(R²)等。均方误差是预测值与真实值之差的平方的均值; 平均绝对误差是预测值与真实值之差的绝对值的均值,这两个指标都反映了模型预测误差的大小,值越小表示模型的预测性能越好; R 方则是对模型拟合优度的度量,取值范围在 [0,1] 之间,值越接近 1 表示模型对数据的拟合效果越好,解释了因变量的方差比例。
网格搜索是一种穷举搜索方法,它在指定的参数范围内生成所有可能的参数组合,并对每种组合进行训练和评估,最终选择性能最佳的参数组合。 交叉验证则是将数据集划分为多个子集(称为折),在每次迭代中使用其中一个折作为验证集,其余折作为训练集,对模型进行训练和评估,通过多次迭代取平均性能指标,以减少数据划分对模型性能评估的影响,提高参数选择的可靠性。 在 Scikit-learn中,可以通过GridSearchCV类来实现网格搜索和交叉验证的结合,自动搜索最优参数组合并进行模型训练和评估。
六、支持向量机的实际应用案例分析
(一)文本分类
文本数据的向量化方法 :文本数据是一种典型的非结构化数据,无法直接输入到 SVM模型中进行处理,需要将其转换为数值型的向量形式。常见的文本向量化方法包括词袋模型(Bag of Words,简称BoW)和 TF-IDF(Term Frequency-Inverse Document Frequency)。
词袋模型的基本思想是将文本表示为一个词汇表中的词的出现次数的向量,忽略了词的顺序和语法信息。 TF-IDF 则在词袋模型的基础上,对词的出现次数进行加权,权衡了词在文档中的重要性和在语料库中的普遍性。具体来说, TF-IDF的计算公式为 ,其中, 表示词 在文档 中的出现频率, 表示词 的逆文档频率, 是文档总数, 是包含词 的文档数。通过TF-IDF向量化,可以将文本数据转换为适合SVM处理的数值型特征向量。
在文本分类任务中,首先需要收集和整理文本数据集,对文本进行预处理,包括去除停用词、标点符号、数字等噪声信息,进行词干提取或词形还原等操作,以降低词汇的维度和复杂度。 然后,采用词袋模型或 TF-IDF方法对文本数据进行向量化处理,得到数值型的特征矩阵。接着,将特征矩阵划分为训练集和测试集,并对训练集进行标准化处理(如将特征值缩放到 [0,1]或标准化为均值为 0,方差为 1 的分布)。之后,选择合适的核函数(如线性核函数、高斯核函数等)和参数,创建 SVM模型,并在训练集上进行训练。在训练过程中,可以使用交叉验证和网格搜索等方法对模型的参数进行调优,选择最优的参数组合以提高模型的分类性能。 最后,在测试集上对训练好的模型进行性能评估,输出准确率、精确率、召回率和 F1 值等指标,并对分类结果进行分析和解读,了解模型对不同类别文本的分类效果,发现可能存在的问题并进行相应的改进。
首先, SVM对于高维稀疏数据具有良好的处理能力,而文本向量化后的特征维度通常较高且呈现稀疏性,SVM能够在这种数据环境下有效地进行分类。其次, SVM的泛化能力较强,通过最大化间隔的原则,能够在有限的训练数据上学习到较为鲁棒的分类决策边界,对于未知的文本数据具有较好的分类效果。然而, SVM在文本分类中也面临一些挑战。例如,文本数据的向量化过程可能会导致特征维度爆炸,特别是在处理大规模文本数据集时,特征矩阵的维度可能非常高,这会增加模型的计算复杂度和存储开销,需要采用一些特征选择或降维技术(如卡方检验、互信息、主成分分析等)来减少特征维度。此外, SVM是一种基于实例的学习算法,其分类决策依赖于支持向量,对于大规模数据集,支持向量的数量可能会较多,影响模型的预测速度,需要在模型的准确性和效率之间进行权衡。
七、支持向量机的优缺点与局限性
(一)优点总结
泛化能力较强 : SVM通过最大化间隔的原则,在有限的训练数据上能够学习到较为鲁棒的分类决策边界,对于未知数据具有较好的泛化能力,尤其适用于小样本、高维数据的情况,在许多实际应用中取得了较好的分类效果。适用于高维空间数据 : SVM的核技巧能够将数据映射到高维空间,使得原本在低维空间中非线性可分的数据在高维空间中变得线性可分,这对于处理复杂的非线性数据分布非常有效,如图像、文本等高维数据。独特的数学理论基础与优化方法 : SVM基于凸优化问题的求解,具有唯一的全局最优解,保证了模型的稳定性和可靠性。同时,拉格朗日乘子法等优化方法为SVM的求解提供了坚实的数学基础和有效的计算手段。
(二)缺点与局限性分析
对参数选择较为敏感 : SVM的性能在很大程度上依赖于核函数的类型及其参数的选择,以及惩罚参数 的设置。不合适的参数可能导致模型过拟合或欠拟合,影响模型的分类性能。例如,在高斯核函数中,若带宽参数 选择过小,模型可能会对噪声数据过于敏感,导致过拟合;若 选择过大,模型可能会过于平滑,无法捕捉到数据中的细节特征,导致欠拟合。在大规模数据集上的训练时间较长 : SVM的训练时间复杂度通常与训练样本数量的平方或立方成正比,对于大规模数据集,训练时间可能会非常长,计算资源消耗较大。虽然一些改进的算法(如序列最小优化算法 SMO)能够在一定程度上提高SVM的训练效率,但对于超大规模数据集,仍然难以满足实时性要求。模型结果的可解释性相对较弱 :与其他一些机器学习算法(如决策树)相比, SVM的模型结果较为抽象,难以直观地解释分类决策的依据和过程。这在一些需要对模型决策进行详细解释的应用场景中可能成为一个局限性。