简介:这份资源是面向高校学生与机器学习初学者的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 python=3.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_size=0.3, random_state=42, stratify=y ) # 标准化:SVM 对特征尺度敏感,这一步不能省 scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # 线性核基线 svm_linear = SVC(kernel='linear', C=1.0) svm_linear.fit(X_train_scaled, y_train) print("线性核准确率:", svm_linear.score(X_test_scaled, y_test)) # RBF 核基线 svm_rbf = SVC(kernel='rbf', C=1.0, gamma='scale') svm_rbf.fit(X_train_scaled, y_train) print("RBF核准确率:", svm_rbf.score(X_test_scaled, y_test))参数说明:test_size=0.3表示 30% 做测试,stratify=y保证训练测试集里三类比例一致,避免某一类全被分到测试集。StandardScaler做的是 z-score 标准化,把每个特征变成均值 0、方差 1。SVM 是基于距离的算法,如果不标准化,花瓣长度这种数值大的特征会主导距离计算,导致模型效果变差。gamma='scale'是 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, cv=5, scoring='accuracy', n_jobs=-1, verbose=1 ) grid.fit(X_train_scaled, y_train) print("最优参数:", grid.best_params_) print("最优交叉验证分数:", grid.best_score_) print("测试集准确率:", grid.score(X_test_scaled, y_test))参数说明:cv=5表示 5 折交叉验证,把训练集分成 5 份,轮流用 4 份训练、1 份验证。n_jobs=-1表示用所有 CPU 核心并行计算。scoring='accuracy'是评估指标,也可以换成f1_macro。verbose=1会打印搜索进度,方便观察。
跑完后你会看到最优参数通常在C=1或C=10、gamma=0.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_names=iris.target_names)) # 可视化混淆矩阵 plt.figure(figsize=(6, 5)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=iris.target_names, yticklabels=iris.target_names) plt.xlabel('预测类别') plt.ylabel('真实类别') plt.title('SVM 分类混淆矩阵') plt.tight_layout() plt.savefig('confusion_matrix.png', dpi=150) 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_components=2) X_train_pca = pca.fit_transform(X_train_scaled) X_test_pca = pca.transform(X_test_scaled) # 在降维后的数据上重新训练一个 SVM 用于可视化 svm_vis = SVC(kernel='rbf', C=1.0, gamma='scale') 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, alpha=0.3, cmap='coolwarm') plt.scatter(X_train_pca[:, 0], X_train_pca[:, 1], c=y_train, cmap='coolwarm', edgecolors='k', label='训练集') plt.scatter(X_test_pca[:, 0], X_test_pca[:, 1], c=y_test, cmap='coolwarm', marker='^', edgecolors='k', label='测试集') plt.xlabel('主成分 1') plt.ylabel('主成分 2') plt.title('SVM 决策边界(PCA 降维)') plt.legend() plt.tight_layout() plt.savefig('decision_boundary.png', dpi=150) plt.show()参数说明:PCA(n_components=2)把 4 维降到 2 维,方便可视化。np.meshgrid生成网格点,np.c_把两个数组按列拼接。contourf画填充等高线,alpha=0.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 映射成 virginica,1 映射成 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())。两者在标准化后的数据上差别不大,但不标准化时差别明显。
解决:显式写gamma='scale'或gamma='auto',不要依赖默认值。实验报告里写清楚 scikit-learn 版本号和 gamma 取值。
4.5 用测试集调参导致评估结果虚高
现象:反复在测试集上试参数,最后测试集准确率 99%,但换一批数据就崩。
原因:测试集被当成了验证集用,参数选择过程中测试集信息泄露,评估结果不再客观。
解决:调参只用训练集加交叉验证,测试集只在最后评估一次。如果数据量小,可以用cross_val_score在训练集上做交叉验证,选最优参数后再用测试集做最终评估。测试集是「后悔药」,不能提前吃。
5. 实验报告怎么写才像做过:从代码到结论的最后一公里
实验报告不是代码的堆砌,而是把你做了什么、为什么这么做、结果说明什么讲清楚。我一般按这个结构写:实验目的、数据集描述、方法、实验结果、分析与结论。数据集描述里要写清楚样本数、特征数、类别分布,最好附一张特征分布的箱线图。方法部分要写清楚用了什么核函数、参数搜索范围、交叉验证折数。实验结果部分放混淆矩阵、分类报告、决策边界图。分析部分要解释为什么 versicolor 和 virginica 容易混,可以结合花瓣特征的散点图说明。
有一个技巧:在报告里加一段「参数敏感性分析」,固定 gamma 不变,画 C 从 0.01 到 100 的准确率变化曲线;再固定 C 不变,画 gamma 的变化曲线。这样能直观看出模型对参数的敏感程度,比只写「最优参数是 C=1, gamma=0.01」有说服力得多。
# 参数敏感性分析:固定 gamma,观察 C 的影响 C_values = [0.01, 0.1, 1, 10, 100] train_scores = [] test_scores = [] for C in C_values: svm = SVC(kernel='rbf', C=C, gamma=0.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, marker='o', label='训练集准确率') plt.plot(C_values, test_scores, marker='s', label='测试集准确率') plt.xscale('log') plt.xlabel('C 值(对数刻度)') plt.ylabel('准确率') plt.title('C 值对 SVM 准确率的影响(gamma=0.01)') plt.legend() plt.grid(True, alpha=0.3) plt.tight_layout() plt.savefig('c_sensitivity.png', dpi=150) plt.show()这段代码跑出来的图,通常能看到 C 很小时训练集和测试集都欠拟合,C 增大后训练集上升、测试集先升后降。那个「先升后降」的拐点就是比较合适的 C 范围。把这个图放进实验报告,比干巴巴写一段文字强得多。
最后说一个我自己的习惯:每次跑完实验,把随机种子、库版本、参数组合、准确率记在一个单独的文本文件里。Iris 作业看起来简单,但如果你后面要做更复杂的分类任务,这套记录习惯能帮你省下大量「上次那个参数是多少来着」的翻找时间。SVM 在 Iris 上跑通只是起点,真正值钱的是你对流程的掌控和对参数的直觉。希望帮到你。
本文还有配套的精品资源,点击获取