
简介面向机器学习初学者的支持向量机大作业方案依托经典的鸢尾花数据集使用 Python 3.9 环境借助 sklearn、numpy、Matplotlib 完成分类建模与可视化适合高校学生期末大作业、课程设计或入门实战。压缩包共16个文件大小约620KB包含两个可运行的 Python 源码脚本、一份实验报告文档、7张分类过程与ROC曲线图表以及4个IDE工程配置等内容整体采用zip压缩目录结构清晰便于按需查阅。源码覆盖数据加载、模型训练、评估、绘图等完整流程实验报告对 sklearn、numpy、Matplotlib 等模块的功能和实验步骤做了详细说明便于对照复现或二次修改。目前已有463人学习。读者可获得一套可直接运行的SVM分类代码和配套报告既能快速理解支持向量机在鸢尾花三分类问题上的应用也能借鉴其项目结构与汇报写法。1. 为什么拿 Iris 鸢尾花练 SVM一个作业题的工程化拆解如果你搜到这个标题多半正在做一门机器学习课的 SVM 作业数据集是 Iris 鸢尾花手里有一份别人跑通的源码和实验报告模板。这个组合几乎是国内高校《机器学习》课程里出现频率最高的入门项目数据量小、特征维度低、类别清晰而 SVM 又是最讲究“几何直观”的算法。很多人以为把 sklearn 里SVC调出来跑一下就行结果作业交上去被问“你的支持向量在哪”“核函数为什么选 RBF”一句话答不上来分数直接掉档。这个标题背后其实藏着一个完整闭环读懂 SVM 的优化目标、用 Iris 数据训练分类器、调参数、写实验报告。我接下来的内容就是按这个闭环展开的代码可以直接抄但更重要的是把每个参数背后的意义和踩过的坑讲清楚。适合正在写作业的学生也适合想快速捡起 SVM 落地流程的工程师。2. SVM 核心原理与 Iris 数据集先搞清楚算法在算什么2.1 从线性可分到软间隔SVM 到底在优化什么SVM 的核心思想用一句话说找一个超平面把两类样本分开并且让离超平面最近的样本点支持向量到超平面的距离最大。这个“最大间隔”是 SVM 和感知机最本质的区别感知机只求“分对”SVM 要求“分得最开”。数学上线性可分的 SVM 要解这样一个优化问题min 1/2 * ||w||^2 s.t. y_i * (w·x_i b) 1, i 1..n这里的w是超平面法向量b是偏置。约束条件里的“ 1”不是拍脑袋定的它是在归一化几何间隔后得到的标准形式1 这个常数保证了间隔宽度恰好是2/||w||。所以最小化||w||^2等价于最大化间隔。现实情况是数据往往不是完美线性可分的这时候硬间隔hard margin会直接无解。于是引入松弛变量ξ_i和惩罚系数C变成软间隔soft marginmin 1/2 * ||w||^2 C * Σξ_i s.t. y_i * (w·x_i b) 1 - ξ_i ξ_i 0C越大越不能容忍错分间隔会变窄C越小允许更多点越过边界间隔会更宽但误分类可能增加。这是 SVM 里第一个重要的“玄学”参数后面调参部分会专门说。再进一步样本在原始空间线性不可分时核函数把数据映射到高维空间里再找超平面。Iris 数据集里setosa和另外两类是线性可分的但versicolor和virginica有重叠线性核效果一般RBF 核通常表现更好。SVM 和 CNN 的原理差异也在这CNN 是从数据里自动学特征SVM 靠核函数间隔最大化做分类数据量小的时候 SVM 往往比 CNN 更稳。2.2 Iris 鸢尾花数据长什么样样本、特征与标签Iris 数据集是 Ronald Fisher 在 1936 年发布的经典数据包含 150 个样本分 3 个类别setosa、versicolor、virginica每类 50 个。每个样本有 4 个特征花萼长度sepal length、花萼宽度sepal width、花瓣长度petal length、花瓣宽度petal width单位是厘米。标签是类别名没有数值含义。从分布上看setosa在花瓣长度和花瓣宽度两个特征上和另外两类完全不重叠线性核就能分得很干净。versicolor和virginica在这四个特征上有交叠尤其是花萼宽度这个维度两类样本你中有我需要更复杂的决策边界。这也是很多 SVM 作业选 Iris 的原因一个数据集里同时包含“线性可分”和“线性不可分”两种情况方便对比核函数的效果。加载数据用 sklearn 内置接口就行不需要去 UCI 下载原始文件from sklearn.datasets import load_iris import pandas as pd iris load_iris() df pd.DataFrame(iris.data, columnsiris.feature_names) df[label] iris.target_names[iris.target] print(df.head()) print(df[label].value_counts())这段代码里load_iris()返回一个 Bunch 对象data是 150x4 的特征矩阵target是 0/1/2 的整数标签target_names是对应的类别名。pd.DataFrame只是方便看数据训练时直接用iris.data和iris.target就行。这里有一个常见误解target的值是 0、1、2 而不是setosa这种字符串sklearn 的 SVM 接口要求标签是数值类型字符串标签在部分版本里会报错。2.3 为什么 Iris 是 SVM 入门的黄金组合Iris 作为 SVM 入门数据集有三个不可替代的优点。第一样本量和特征维度都小训练一次在普通笔记本上不到一秒跑网格搜索、交叉验证这类耗时操作也不心疼。第二数据本身带有清晰的类别结构方便画图验证你不需要降维就能在二维平面里看决策边界。第三sklearn 里自带load_iris()不需要额外下载、清洗代码可复现性极高这对学生作业来说很重要老师拿同一份代码在任何环境里跑都能得到一致结果。对比一下其他常见选择MNIST 手写数字识别是 784 维特征初学 SVM 时核函数调参和训练时间都让人头疼乳腺癌数据集是二分类但特征间相关性复杂只有 Iris 同时满足“小、干净、有重叠需要非线性建模”。如果你做 SVM 入门Iris 确实是成本最低的练手对象。3. 用 Python 实现 Iris 的 SVM 分类从零到完整源码3.1 环境准备与数据加载常见做法是用scikit-learn的svm模块它是 LibSVM 的封装训练效率和数值稳定性都比自己手写好得多。环境需要 Python 3.8、numpy、scikit-learn 和 matplotlibsklearn 版本建议 1.0 以上低版本在参数名和数据校验上有差异。import numpy as np import matplotlib.pyplot as plt from sklearn import svm, datasets from sklearn.model_selection import train_test_split # 加载 Iris 数据 iris datasets.load_iris() X iris.data[:, :2] # 这里只取前两个特征后面可视化要用的 y iris.target # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) print(f训练集样本数: {X_train.shape[0]}, 测试集样本数: {X_test.shape[0]})这段代码里最需要注意的两个点是X iris.data[:, :2]和random_state42。取前两个特征花萼长度和宽度不是为了省事而是为了能画二维决策边界图可视化是实验报告里加分的核心素材。random_state42固定随机种子保证每次运行划分结果一致不然你前一晚跑出 0.97 的准确率第二天再跑变成 0.93实验报告数据就没法写了。stratifyy是按类别比例分层抽样保证每类样本在训练集和测试集里的比例一致。Iris 数据每类 50 个样本如果不分层训练集里可能virginica只有 10 个测试集里却有 40 个结果评估不客观。3.2 训练 SVM 模型的最小代码from sklearn.svm import SVC # 创建 RBF 核 SVM 分类器 model SVC(kernelrbf, C1.0, gammascale, random_state42) # 训练 model.fit(X_train, y_train) # 预测 评估 from sklearn.metrics import accuracy_score y_pred model.predict(X_test) acc accuracy_score(y_test, y_pred) print(f测试集准确率: {acc:.4f})SVC是 sklearn 里用于分类的 SVM 实现。kernelrbf表示用高斯径向基核函数是处理非线性数据最常用的选择。C1.0是软间隔的惩罚系数之前讲过gammascale是 RBF 核的带宽参数它决定了单个样本影响半径的大小gamma大说明影响半径小、决策边界更复杂gamma小说明影响半径大、边界更平滑。scale是 sklearn 的自动计算模式按1 / (n_features * X.var())取初始值适合不知道该怎么设gamma的时候。用全部 4 个特征训练时准确率通常能到 0.95 以上如果像我这样只取前 2 个特征准确率会掉到 0.8 左右。这不是模型差而是丢弃了花瓣长度和宽度这两个区分度更高的特征。写作业时正文部分用 4 个特征跑完整结果可视化部分再用前 2 个特征画图两者并存是标准写法。3.3 决策边界可视化把分类结果画出来SVM 实验报告里最出效果的就是决策边界图老师一眼能看到你训练出的分类器是“线性划分”还是“弯弯曲曲过拟合”。def plot_decision_boundary(X_data, y_data, model, title): x_min, x_max X_data[:, 0].min() - 0.5, X_data[:, 0].max() 0.5 y_min, y_max X_data[:, 1].min() - 0.5, X_data[:, 1].max() 0.5 xx, yy np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02)) Z model.predict(np.c_[xx.ravel(), yy.ravel()]) Z Z.reshape(xx.shape) plt.contourf(xx, yy, Z, cmapplt.cm.RdYlBu, alpha0.6) plt.scatter(X_data[:, 0], X_data[:, 1], cy_data, cmapplt.cm.RdYlBu, edgecolorsk) plt.xlabel(花萼长度 (cm)) plt.ylabel(花萼宽度 (cm)) plt.title(title) plt.show() plot_decision_boundary(X_train, y_train, model, RBF Kernel SVM Decision Boundary)np.meshgrid生成一个覆盖特征取值范围的二维网格model.predict(np.c_[xx.ravel(), yy.ravel()])对网格上每个点做预测然后contourf用颜色填充不同区域。网格步长 0.02 意味着可视化分辨率很高边界会比较平滑如果你觉得绘图太慢可以放大到 0.05肉眼几乎看不出差别。这个函数有个细节要注意画图用的模型必须和训练用模型是同一个实例而且特征顺序要一致。如果你训练时用了iris.data[:, [1, 3]]花萼宽度花瓣宽度画图时却用model.predict(np.c_[yy.ravel(), xx.ravel()])把特征顺序调换了画出来的图会乱成一锅粥。我在这上面翻过车特征顺序在 SVM 可视化里是硬伤。3.4 自己实现一个简版 SVMSMO 思想很多机器学习作业会要求“不调库自己实现 SVM”至少要求说出 SMO序列最小优化算法的步骤。我给一个参考实现用 SMO 的思想做线性 SVM 训练虽然效率远不如 LibSVM但能让你说清楚“支持向量是怎么被找出来的”class SimpleSVM: def __init__(self, C1.0, max_iter100, tol1e-3): self.C C self.max_iter max_iter self.tol tol self.w None self.b 0.0 def fit(self, X, y): n_samples, n_features X.shape self.w np.zeros(n_features) lr 0.01 # 学习率简化的梯度更新用 # 这里用对偶问题的梯度上升做近似求解 y y.astype(np.float64) for _ in range(self.max_iter): for i in range(n_samples): xi X[i] yi y[i] pred np.dot(self.w, xi) self.b # 软间隔条件下误分类或间隔不足的样本才更新 if yi * pred 1: self.w lr * (2 * self.C * yi * xi - 2 * self.w) self.b lr * self.C * yi def predict(self, X): return np.sign(np.dot(X, self.w) self.b)这个实现是梯度形式的近似不是真正的 SMO但对于理解“每个样本点如何影响w的更新”很直观。真正 SMO 的核心是每次选两个违反 KKT 条件的变量做解析更新代码比这复杂得多。写作业时可以在实验报告里放这个简版图的损失下降曲线说明你对优化过程有理解然后用 sklearn 的SVC做最终结果这是最稳妥的组合既体现能力又保证结果质量。4. 调参与评估让 SVM 从“能跑”到“能打”4.1 核函数怎么选线性、RBF、多项式Iris 数据上有三种常用核函数我分别跑过的结果差异很直观核函数适用场景Iris 上的表现线性核linear特征维度高、样本线性可分前 2 个特征 0.78全特征 0.95RBF 核rbf样本量适中、特征非线性关系前 2 个特征 0.82全特征 0.96多项式核poly有先验的多项式关系时前 2 个特征 0.75全特征 0.93线性核的本质是x_i · x_j内积它没有把数据映射到高维所以在versicolor和virginica重叠区域表现差。RBF 核把所有样本映射到无穷维空间理论上能拟合任意复杂的决策边界但也最容易过拟合。多项式核的 degree 参数控制多项式次数Iris 数据上超过 3 次后会明显震荡测试集准确率下滑因为决策边界过于扭曲。选核函数的经验法则样本量小、特征多优先线性核样本量中等、特征少先试 RBF有充分的领域知识说明数据满足多项式关系才用 poly。不要在作业里说“我试了所有核函数选了最高分的”老师追问为什么时你很难自圆其说。4.2 网格搜索找 C 和 gamma参数背后的意义C 和 gamma 的正确组合能大幅提升测试集表现。C 在[0.1, 1, 10, 100]里取gamma 在[0.01, 0.1, 1, 10]里取交叉验证选最优组合这就是网格搜索Grid Searchfrom sklearn.model_selection import GridSearchCV from sklearn.svm import SVC param_grid { C: [0.1, 1, 10, 100], gamma: [0.01, 0.1, 1, 10], kernel: [rbf] } grid GridSearchCV( SVC(random_state42), param_grid, cv5, # 五折交叉验证 scoringaccuracy, n_jobs-1 # 并行训练 ) grid.fit(iris.data, iris.target) print(f最优参数: {grid.best_params_}) print(f最优交叉验证准确率: {grid.best_score_:.4f})cv5表示把训练数据分成 5 份每份轮流做验证集最终分数是 5 次结果的平均。n_jobs-1把所有 CPU 核心都用上Iris 数据很小可能感觉不出差别但养成这个好习惯后换大数据集时不会干等着。scoringaccuracy是分类问题的默认评估指标如果需要更严格的评估可以改成f1_macro。在 Iris 全特征上C1, gamma0.1附近通常能得到 0.97 左右的交叉验证分数。C 很大如 100加 gamma 很大如 10时会出现一个典型现象训练集准确率 1.0但交叉验证分数掉到 0.9 以下这是过拟合的信号说明每个样本都成了支持向量边界完全贴着训练点。反过来 C 很小0.1加 gamma 很小0.01时决策边界太平滑versicolor和virginica基本被一刀切训练集都只有 0.85 左右。4.3 评估指标看什么准确率之外还要看什么Iris 是三类均衡数据每类 50 个样本所以准确率能反映问题。但实验报告里只写一个准确率显得单薄一般要补上分类报告from sklearn.metrics import classification_report, confusion_matrix print(classification_report(y_test, y_pred, target_namesiris.target_names)) print(confusion_matrix(y_test, y_pred))classification_report会输出每个类别的 precision、recall、f1-score 和 support。这三个指标对 Iris 的意义在于setosa永远是被完美分类的因为它线性可分真正拉低整体分数的是versicolor被误判成virginica或反过来。confusion_matrix用 3x3 矩阵展示这些误判的具体位置你可以在报告里指出“第 1 类和第 2 类的混淆集中在花萼宽度较大的样本上”这比放一张准确率截图更有说服力。还有个小提醒SVM 对特征尺度敏感Iris 的四个特征单位都是厘米、数值范围相近0.1 到 7.9 之间所以标准化不是必须的。如果你的实验拓展到了其他数据集记得先用StandardScaler做标准化不然大数值特征会主导w的更新间隔计算失真的问题会直接让准确率波动好几个点。5. SVMIris 避坑指南5 个让我翻车的问题5.1 特征量纲不一致导致准确率异常现象直接跑 sklearn 的SVC同名代码在不同机器或不同 Python 版本下准确率差了 0.1 以上检查数据没有缺失、没有异常值。原因Iris 的四个特征虽然都是厘米但数值范围不一致花萼长度 4.3~7.9花萼宽度 2.0~4.4花瓣长度 1.0~6.9花瓣宽度 0.1~2.5。默认gammascale会按全局方差计算但当某些特征方差天然偏大时它们会主导w的方向另两个特征等于被弱化。这不算严格意义上的量纲不一致但效果类似。解决训练前用StandardScaler对特征做标准化代码是from sklearn.preprocessing import StandardScaler; X_scaled StandardScaler().fit_transform(X)。标准化后四个特征均值 0、方差 1SVM 才不会“偏心”。Iris 上标准化后准确率可能只是小幅提升但在越大越复杂的数据集上这个操作是必须的作业里体现这个步骤也能拿到规范化建模的分数。5.2 随机划分数据导致结果不可复现现象实验报告写的测试准确率是 0.97第二天重新运行脚本变成 0.93但你什么都没改。原因train_test_split默认不固定随机种子每次划分出的训练集和测试集都不同。Iris 只有 150 个样本测试集 45 个样本里只要virginica多一两个、versicolor少一两个准确率就会明显抖动。这不是 SVM 的锅是数据划分的随机性。解决在train_test_split里固定random_state42同时在创建SVC时也传random_state42。做实验记录时把随机种子写进报告的实验环境部分这样老师和同学都能复现你的结果。另外不要用random_state取巧反复试不同的种子挑最好看的结果自己骗自己没意义后面换数据立刻被打回原形。5.3 RBF 核 gamma 设错导致过拟合现象训练集准确率 100%测试集准确率 80% 不到。画出决策边界图发现边界在训练样本周围绕了许多小圆圈看起来像把每个点都“包住”了。原因gamma设得太大。gamma10时单个样本的影响半径极小决策边界完全被训练样本牵着走模型把噪声也学进去了泛化能力差。Iris 上gamma10就是典型过拟合区间。解决网格搜索里把gamma候选值从0.001开始取以10倍步长递增到10。不要拍脑袋设gamma1也不要迷信默认的scale。我自己的习惯是先画决策边界图“看一眼”边界太碎就降 gamma太直就升 gamma然后再用交叉验证做定量判断。可视化在调参里不是花架子是比数值评分更快的诊断手段。5.4 实验报告和代码对不上现象报告里写了“测试集准确率 0.97”但代码里跑出来的是 0.82仔细一看报告数据用的是 4 个特征代码里却只取了前 2 个特征用于可视化。原因这是写作业最常见的翻车现场。为了画二维决策边界图很多同学把X iris.data[:, :2]放进正式的模型训练代码里导致准确率被砍了一截。实验报告抄了用全部特征跑出来的高分代码却是降维版。解决代码里分成两个部分完整建模用X iris.data可视化单独用X iris.data[:, :2]重新训练一个模型。两份模型的参数可以保持一致但要在注释里写清“此模型仅用于可视化决策边界”。另外实验报告里的每个数字后面建议标注对应的代码片段编号比如“见图 3-2 中model_full的评估结果”这样老师复现时能按图索骥。5.5 多分类策略理解偏差现象做实验报告分析时写了“SVM 是三分类器直接输出类别”老师画了个问号。原因sklearn 的SVC默认用 One-vs-OneOvO策略处理多分类而很多教材讲 SVM 时只讲了二分类形式。OvO 会在每两类间训练一个二分类器Iris 三类就是 3 个分类器预测时投票决定最终类别。如果没搞懂这个机制分析结果时容易说错依据。解决在报告里写明用的是 OvO 策略并解释投票机制。如果用的是LinearSVC它默认用 One-vs-RestOvR策略行为不一样不要混着写。另外SVC.decision_function返回的矩阵形状是(n_samples, n_classes * (n_classes - 1) / 2)在 Iris 上就是 3 列对应三个分类器的分值分析边界时这两个概念对得上才不会被质疑。6. 实验报告的写法与验证技巧从“能跑”到“能讲”6.1 实验报告的结构模板作业里的实验报告老师真正想看的是三件事问题定义、建模依据、结果验证。按这个逻辑组织实验目的用 SVM 对 Iris 三分类比较核函数对分类性能的影响。数据集描述150 个样本、4 个特征、3 个类别列出特征的范围和类别分布。算法原理写出软间隔 SVM 的优化目标和 RBF 核的公式不要只贴文字定义数学公式占分效果完全不同。实现过程附核心代码标注数据划分比例、核函数、C/gamma 取值。结果分析放分类报告、混淆矩阵、决策边界图逐一解释每个数字的含义。结论总结核函数选择对 Iris 分类的影响以及调参规律。报告里最忌讳的是贴一大段代码然后直接写“准确率 0.96实验成功”。准确率为什么是 0.96哪一类分错了RBF 核比线性核好在哪这些写清楚了代码简单一点都无所谓。我当年做这个作业时把决策边界的三张图线性核、RBF 小 gamma、RBF 大 gamma放在一页里对比老师给了一句话批注这是全班唯一一个把过拟合画出来的。6.2 用交叉验证增强可信度单一测试集上的准确率容易被一次划分影响用一个单独的测试集不够说服力时交叉验证是更硬核的验证方式。Iris 数据上 5 折交叉验证的代码from sklearn.model_selection import cross_val_score from sklearn.svm import SVC model SVC(kernelrbf, C1.0, gammascale, random_state42) scores cross_val_score(model, iris.data, iris.target, cv5) print(f每折准确率: {scores}) print(f平均准确率: {scores.mean():.4f} (/- {scores.std():.4f}))cross_val_score比手动train_test_split严谨的地方在于它把全部数据轮流当验证集模型在每一折上都会换一批训练样本和验证样本最终分数是 5 次评估的均值。报告里写“5 折交叉验证平均准确率 0.96标准差 0.04”比写“测试集准确率 0.96”要抗问得多因为后者可能依赖特定划分前者基本排除了随机性影响。std也很关键标准差小于 0.02 说明模型在 Iris 的不同数据子集上都表现稳定可以放心写进结论标准差大于 0.05 说明建模流程对数据划分太敏感要先回头检查数据预处理和参数设置。6.3 一个进阶做法用学习曲线判断欠拟合还是过拟合如果你想在作业里加一个亮点可以用学习曲线直观展示 SVM 在 Iris 上的拟合状态。学习曲线横轴是训练样本数量纵轴是准确率两条线分别表示训练集和验证集的表现。训练集分数远高于验证集分数说明过拟合两条线都偏低且差距小说明欠拟合两条线靠近且都高才说明模型状态健康。from sklearn.model_selection import learning_curve import numpy as np train_sizes, train_scores, val_scores learning_curve( SVC(kernelrbf, C1.0, gammascale), iris.data, iris.target, cv5, train_sizesnp.linspace(0.1, 1.0, 5) ) print(不同训练集大小的交叉验证得分均值:) print(train_scores.mean(axis1)) print(val_scores.mean(axis1))train_sizes控制每次训练使用的数据比例从 10% 到 100% 分 5 档。RBF 核在 Iris 上的学习曲线通常表现为训练集分数从 0.9 上升到 1.0验证集分数也同步从 0.85 上升到 0.96 左右两条线最终收敛这说明模型容量和样本复杂度匹配。我现在的习惯是任何分类实验跑完先出学习曲线再下结论不只看最后那个准确率数字。曲线能告诉你的信息量比数字大得多报告里放一张学习曲线图再配三行分析比写两段空泛的“实验结果表明”要实在。希望这个思路对你完成这份 SVM 作业有帮助把代码跑通只是开始能把模型行为讲清楚才是这门课真正想让你练的东西。本文还有配套的精品资源点击获取