简介这份资源是面向高校学生与机器学习初学者的SVM分类实践作业包围绕经典Iris鸢尾花数据集展开帮助读者理解支持向量机在多分类任务中的建模流程与调参思路适合用作课程设计、期末大作业或入门练手项目。压缩包共16个文件约620KB以7张png结果图、4个xml配置、2个py源码脚本为主另附doc实验报告、iml工程文件与gitignore等源码含注释报告记录实验过程与结论便于对照理解。目前已有227人学习下载。读者可从中获得完整的SVM分类实现代码、数据预处理与可视化结果、实验报告撰写参考以及可直接部署运行的工程结构快速完成从数据加载到模型评估的全流程复现。1. 从一份 SVM 作业说起Iris 鸢尾花分类到底在练什么很多人第一次接触机器学习是从一份「Python机器学习SVM作业」开始的。数据集是经典的 Iris 鸢尾花150 个样本、4 个特征、3 个类别任务是用支持向量机做分类。听起来简单但真正动手时你会发现卡住新手的从来不是 SVM 的数学推导而是几个很具体的问题数据怎么读、标签怎么编码、核函数选哪个、C 和 gamma 怎么调、为什么训练集 100% 测试集却只有 60%。这份作业的价值不在于「跑出一个准确率」而在于它逼你把机器学习的完整流程走一遍加载数据、划分训练测试集、特征标准化、模型训练、预测评估、结果可视化。Iris 数据量小、特征少、类别可分性好非常适合用来验证你对流程的理解是否完整。如果你连 Iris 都跑不通换到真实业务数据只会更乱。这篇文章就按一线做项目的顺序把这份作业从环境配置到实验报告该写什么完整拆一遍新手能照着复现熟手能看到参数边界和常见翻车点。2. 动手之前环境、数据与 SVM 的选型逻辑2.1 用 scikit-learn 跑通 Iris 的最小环境做这份作业不需要复杂的环境。Python 3.8 以上、scikit-learn、numpy、matplotlib、pandas 这几个库就够了。我一般用 conda 建一个独立环境避免和系统里的包打架。如果你还在纠结 python安装教程记住一条装完 Python 后直接用 pip 装库不要手动去官网一个个下 whl 文件。# 创建独立环境避免依赖冲突 conda create -n svm_iris python3.10 -y conda activate svm_iris # 安装核心库 pip install scikit-learn numpy matplotlib pandas装完后验证一下版本scikit-learn 不同版本在 SVM 的默认参数上有细微差别实验报告里最好写清楚你用的版本。import sklearn import numpy as np print(sklearn:, sklearn.__version__) print(numpy:, np.__version__)参数说明conda create -n指定环境名-y跳过确认。pip 安装时如果网络慢可以加国内镜像源但不要混用 conda 和 pip 装同一个包容易出现版本冲突。这一步的坑在于有些人用系统自带的 Python权限不够导致装到用户目录后面 VSCode 找不到解释器。如果你用 VSCode记得在右下角切换到你刚建的环境。2.2 Iris 数据集的结构与三种类别的可分性Iris 数据集在 scikit-learn 里可以直接加载不需要额外下载。它包含 150 个样本每个样本有 4 个特征花萼长度、花萼宽度、花瓣长度、花瓣宽度单位都是厘米。三个类别分别是 setosa、versicolor、virginica每类 50 个样本。from sklearn.datasets import load_iris iris load_iris() X iris.data # 特征矩阵shape (150, 4) y iris.target # 标签0/1/2 print(特征名:, iris.feature_names) print(类别名:, iris.target_names) print(数据形状:, X.shape) print(类别分布:, np.bincount(y))逻辑说明load_iris()返回的是 Bunch 对象类似字典。X是 150 行 4 列的浮点数组y是 0、1、2 的整数标签。np.bincount(y)用来确认每类是否均衡结果是[50 50 50]说明数据平衡不需要做重采样。这里有个关键认知setosa 和另外两类是线性可分的但 versicolor 和 virginica 在花瓣特征上有重叠。如果你只用前两个特征做二维可视化会发现 setosa 完全分开另外两类混在一起。这直接决定了你后面选什么核函数、预期准确率大概是多少。很多人一上来就追求 100%但在 Iris 上用线性核做到 95% 左右是正常水平强行调到 100% 大概率是过拟合。2.3 线性核、RBF 核怎么选先看数据再定SVM 的核心是找一个超平面把不同类别分开但原始空间里分不开时就需要核函数把数据映射到高维。常见的选择是线性核和 RBF 核。线性核适合特征维度高、样本线性可分的情况RBF 核适合非线性、边界复杂的情况。在 Iris 上我一般先用线性核跑一个基线再用 RBF 核对比。不要一上来就调参先看基线准确率心里有个底。from sklearn.svm import SVC from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 划分训练集和测试集固定随机种子保证可复现 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) # 标准化SVM 对特征尺度敏感这一步不能省 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 线性核基线 svm_linear SVC(kernellinear, C1.0) svm_linear.fit(X_train_scaled, y_train) print(线性核准确率:, svm_linear.score(X_test_scaled, y_test)) # RBF 核基线 svm_rbf SVC(kernelrbf, C1.0, gammascale) svm_rbf.fit(X_train_scaled, y_train) print(RBF核准确率:, svm_rbf.score(X_test_scaled, y_test))参数说明test_size0.3表示 30% 做测试stratifyy保证训练测试集里三类比例一致避免某一类全被分到测试集。StandardScaler做的是 z-score 标准化把每个特征变成均值 0、方差 1。SVM 是基于距离的算法如果不标准化花瓣长度这种数值大的特征会主导距离计算导致模型效果变差。gammascale是 scikit-learn 的默认值等于1 / (n_features * X.var())比手动设固定值更稳。跑完你会发现线性核和 RBF 核在 Iris 上都能到 95% 以上差别不大。但如果你不做标准化线性核可能掉到 90% 以下RBF 核更惨。这就是为什么我把标准化放在训练之前而不是当成可选项。3. 把作业跑完整训练、评估与可视化一条龙3.1 训练集测试集划分与标准化顺序上一节已经提到了划分和标准化这里要把顺序讲透。正确的顺序是先划分训练测试集再在训练集上 fit 标准化器然后用同一个标准化器 transform 测试集。绝对不能先对整个数据集做标准化再划分那样测试集的信息会泄露到训练过程中准确率虚高实验报告里写出来就是硬伤。# 正确做法fit 只在训练集上做 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) # 训练集fit transform X_test_scaled scaler.transform(X_test) # 测试集只 transform # 错误做法不要这样写 # X_scaled scaler.fit_transform(X) # 先对全量数据标准化 # X_train, X_test train_test_split(X_scaled, ...) # 再划分逻辑说明fit_transform会计算训练集的均值和方差并据此转换。transform只用训练集的均值和方差来转换测试集。如果先对全量数据标准化测试集的均值和方差就参与了训练相当于提前偷看了答案。这个坑在作业里很常见很多人跑出 99% 的准确率其实是因为数据泄露。3.2 用 GridSearchCV 调 C 和 gamma 的实操C 和 gamma 是 RBF 核 SVM 最重要的两个参数。C 控制惩罚力度C 越大对误分类容忍度越低容易过拟合C 越小则容忍度高可能欠拟合。gamma 控制单个样本的影响范围gamma 越大影响范围越小容易过拟合gamma 越小影响范围越大可能欠拟合。我一般用 GridSearchCV 做网格搜索配合交叉验证选最优参数。不要手动一个个试效率太低。from sklearn.model_selection import GridSearchCV # 定义参数网格 param_grid { C: [0.1, 1, 10, 100], gamma: [0.001, 0.01, 0.1, 1], kernel: [rbf] } # 5 折交叉验证 grid GridSearchCV( SVC(), param_grid, cv5, scoringaccuracy, n_jobs-1, verbose1 ) grid.fit(X_train_scaled, y_train) print(最优参数:, grid.best_params_) print(最优交叉验证分数:, grid.best_score_) print(测试集准确率:, grid.score(X_test_scaled, y_test))参数说明cv5表示 5 折交叉验证把训练集分成 5 份轮流用 4 份训练、1 份验证。n_jobs-1表示用所有 CPU 核心并行计算。scoringaccuracy是评估指标也可以换成f1_macro。verbose1会打印搜索进度方便观察。跑完后你会看到最优参数通常在C1或C10、gamma0.01或0.1附近。如果最优 C 是 100、gamma 是 1那就要警惕过拟合了测试集准确率可能反而下降。这时候要看交叉验证分数和测试集分数的差距差距大说明模型泛化能力差。3.3 混淆矩阵、分类报告与决策边界可视化准确率只是一个数字要写实验报告还得有混淆矩阵和分类报告。混淆矩阵能看出哪两类容易混分类报告能给出每类的精确率、召回率和 F1。from sklearn.metrics import confusion_matrix, classification_report import matplotlib.pyplot as plt import seaborn as sns # 用最优模型预测 best_model grid.best_estimator_ y_pred best_model.predict(X_test_scaled) # 混淆矩阵 cm confusion_matrix(y_test, y_pred) print(混淆矩阵:\n, cm) # 分类报告 print(classification_report(y_test, y_pred, target_namesiris.target_names)) # 可视化混淆矩阵 plt.figure(figsize(6, 5)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsiris.target_names, yticklabelsiris.target_names) plt.xlabel(预测类别) plt.ylabel(真实类别) plt.title(SVM 分类混淆矩阵) plt.tight_layout() plt.savefig(confusion_matrix.png, dpi150) plt.show()逻辑说明confusion_matrix返回一个 3x3 数组对角线是正确分类的样本数非对角线是误分类。classification_report输出每类的 precision、recall、f1-score 和支持度。在 Iris 上setosa 通常全部正确versicolor 和 virginica 之间可能有 1 到 2 个误判。决策边界可视化稍微复杂一点因为 Iris 有 4 个特征不能直接画二维图。常见做法是只取两个特征比如花瓣长度和花瓣宽度或者用 PCA 降到二维再画。作业里如果要求可视化我建议用 PCA 降维保留的信息更完整。from sklearn.decomposition import PCA # 用 PCA 降到二维 pca PCA(n_components2) X_train_pca pca.fit_transform(X_train_scaled) X_test_pca pca.transform(X_test_scaled) # 在降维后的数据上重新训练一个 SVM 用于可视化 svm_vis SVC(kernelrbf, C1.0, gammascale) svm_vis.fit(X_train_pca, y_train) # 生成网格点 x_min, x_max X_train_pca[:, 0].min() - 1, X_train_pca[:, 0].max() 1 y_min, y_max X_train_pca[:, 1].min() - 1, X_train_pca[:, 1].max() 1 xx, yy np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02)) # 预测网格点类别 Z svm_vis.predict(np.c_[xx.ravel(), yy.ravel()]) Z Z.reshape(xx.shape) # 画决策边界 plt.figure(figsize(8, 6)) plt.contourf(xx, yy, Z, alpha0.3, cmapcoolwarm) plt.scatter(X_train_pca[:, 0], X_train_pca[:, 1], cy_train, cmapcoolwarm, edgecolorsk, label训练集) plt.scatter(X_test_pca[:, 0], X_test_pca[:, 1], cy_test, cmapcoolwarm, marker^, edgecolorsk, label测试集) plt.xlabel(主成分 1) plt.ylabel(主成分 2) plt.title(SVM 决策边界PCA 降维) plt.legend() plt.tight_layout() plt.savefig(decision_boundary.png, dpi150) plt.show()参数说明PCA(n_components2)把 4 维降到 2 维方便可视化。np.meshgrid生成网格点np.c_把两个数组按列拼接。contourf画填充等高线alpha0.3控制透明度。注意这里是在降维后的数据上重新训练了一个 SVM只是为了可视化最终报告的准确率应该用原始 4 维特征的模型。4. 避坑与排查Iris 上做 SVM 最容易翻车的 5 个点4.1 准确率 100% 但交叉验证只有 90%现象训练集准确率 100%测试集 100%但交叉验证分数只有 90% 左右。原因C 设得太大模型对训练样本的误分类零容忍把噪声也学进去了。Iris 数据本身有少量重叠样本强行分开就是过拟合。解决把 C 从 100 降到 1 或 10观察交叉验证分数是否上升。如果训练集和测试集分数差距超过 5%基本可以判定过拟合。用learning_curve画学习曲线也能看出来。4.2 忘记标准化导致 RBF 核效果暴跌现象线性核准确率 95%RBF 核只有 60% 多。原因RBF 核基于欧氏距离如果特征尺度差异大距离计算会被大数值特征主导。Iris 的花瓣长度范围 1 到 7花萼宽度范围 2 到 4不标准化时花瓣长度的影响被放大。解决在训练前加StandardScaler并且严格按「先划分、再 fit 训练集、transform 测试集」的顺序做。标准化后 RBF 核通常能到 95% 以上。4.3 标签编码搞错导致类别顺序混乱现象混淆矩阵里 setosa 和 virginica 混在一起但实际它们很好分。原因手动把标签映射成字符串或打乱顺序导致target_names和实际标签对不上。比如把 0 映射成 virginica1 映射成 setosa。解决直接用load_iris()返回的iris.target和iris.target_names不要手动改。如果必须改用LabelEncoder并记录映射关系。画混淆矩阵时xticklabels和yticklabels的顺序要和标签编码一致。4.4 gamma 默认值在不同版本行为不一致现象同样的代码换台电脑跑出来准确率差很多。原因scikit-learn 早期版本gamma默认是auto等于1 / n_features新版本默认是scale等于1 / (n_features * X.var())。两者在标准化后的数据上差别不大但不标准化时差别明显。解决显式写gammascale或gammaauto不要依赖默认值。实验报告里写清楚 scikit-learn 版本号和 gamma 取值。4.5 用测试集调参导致评估结果虚高现象反复在测试集上试参数最后测试集准确率 99%但换一批数据就崩。原因测试集被当成了验证集用参数选择过程中测试集信息泄露评估结果不再客观。解决调参只用训练集加交叉验证测试集只在最后评估一次。如果数据量小可以用cross_val_score在训练集上做交叉验证选最优参数后再用测试集做最终评估。测试集是「后悔药」不能提前吃。5. 实验报告怎么写才像做过从代码到结论的最后一公里实验报告不是代码的堆砌而是把你做了什么、为什么这么做、结果说明什么讲清楚。我一般按这个结构写实验目的、数据集描述、方法、实验结果、分析与结论。数据集描述里要写清楚样本数、特征数、类别分布最好附一张特征分布的箱线图。方法部分要写清楚用了什么核函数、参数搜索范围、交叉验证折数。实验结果部分放混淆矩阵、分类报告、决策边界图。分析部分要解释为什么 versicolor 和 virginica 容易混可以结合花瓣特征的散点图说明。有一个技巧在报告里加一段「参数敏感性分析」固定 gamma 不变画 C 从 0.01 到 100 的准确率变化曲线再固定 C 不变画 gamma 的变化曲线。这样能直观看出模型对参数的敏感程度比只写「最优参数是 C1, gamma0.01」有说服力得多。# 参数敏感性分析固定 gamma观察 C 的影响 C_values [0.01, 0.1, 1, 10, 100] train_scores [] test_scores [] for C in C_values: svm SVC(kernelrbf, CC, gamma0.01) svm.fit(X_train_scaled, y_train) train_scores.append(svm.score(X_train_scaled, y_train)) test_scores.append(svm.score(X_test_scaled, y_test)) plt.figure(figsize(8, 5)) plt.plot(C_values, train_scores, markero, label训练集准确率) plt.plot(C_values, test_scores, markers, label测试集准确率) plt.xscale(log) plt.xlabel(C 值对数刻度) plt.ylabel(准确率) plt.title(C 值对 SVM 准确率的影响gamma0.01) plt.legend() plt.grid(True, alpha0.3) plt.tight_layout() plt.savefig(c_sensitivity.png, dpi150) plt.show()这段代码跑出来的图通常能看到 C 很小时训练集和测试集都欠拟合C 增大后训练集上升、测试集先升后降。那个「先升后降」的拐点就是比较合适的 C 范围。把这个图放进实验报告比干巴巴写一段文字强得多。最后说一个我自己的习惯每次跑完实验把随机种子、库版本、参数组合、准确率记在一个单独的文本文件里。Iris 作业看起来简单但如果你后面要做更复杂的分类任务这套记录习惯能帮你省下大量「上次那个参数是多少来着」的翻找时间。SVM 在 Iris 上跑通只是起点真正值钱的是你对流程的掌控和对参数的直觉。希望帮到你。本文还有配套的精品资源点击获取