简介:这是一份面向机器学习课程期末大作业的SVM分类项目资源,基于经典的Iris鸢尾花数据集,提供完整Python源码与配套实验报告。代码基于Python 3.9 IDLE开发,整合sklearn、numpy、matplotlib等模块,覆盖数据读取、标准化、SVM建模、核函数对比和结果可视化全流程,适合初学机器学习、需要完成SVM作业或想快速搭建分类实验的学生参考。资源包共16个文件,含2个Python脚本、1份doc实验报告、7张结果图(包括ROC曲线及分类边界可视化)以及若干项目配置文件,整体620KB,轻量且目录清晰。源码不仅包含SVM训练与评估逻辑,还提供花瓣、花萼等特征分布的绘制代码,便于直观理解数据与模型表现;实验报告则系统讲解SVM原理、参数调优和结果分析,可直接作为大作业文档框架参考。已有463人学习/下载,整体完成度和说明清晰度较高,适合期末大作业复用与课程设计借鉴。 期末作业撞上SVM:一份Iris鸢尾花源码包能帮你少熬两个通宵
做过机器学习期末大作业的人都知道,选题最怕的不是不会写,是写到一半发现代码跑不出图、报告凑不满页。这次拆的这份基于Iris鸢尾花数据样本的SVM分类项目,解压之后是一个完整的作业包——两个Python脚本(svm_flower.py和flower1.py)、一份已经排好版的实验报告、还有散点图、决策边界图和ROC曲线图。它的价值在于:环境要求极低,Python 3.9的IDLE加上sklearn、numpy、matplotlib三个库就能跑,数据直接用sklearn内置的Iris数据集,不需要额外找数据文件。适合谁?正在选机器学习期末选题、或者想快速吃透SVM在真实数据上怎么落地的学生。下面我会把原包里的代码逻辑拆开讲,包括每个参数为什么这么设,以及写报告时最容易翻车的几个点。
- 从最大间隔到核函数:SVM为什么能在Iris上直接见效
2.1 硬间隔、软间隔与支持向量:先想清楚SVM在算什么
SVM的核心不是“画一条线”,而是“画一条离两类样本都足够远的线”。这条线在二维里是直线,在四维的Iris特征空间里就是一个超平面。那些离超平面最近的样本点决定了超平面最终落在哪,它们就是支持向量。整个模型的训练过程,本质上就是在“让间隔尽量大”和“让分类错误尽量少”之间做权衡。
Iris数据集150个样本、4维特征、3个类别,本质上是一个软间隔优化问题。三类鸢尾花里,Setosa和另外两类分得很开,但Versicolor和Virginica在sepal维度上有明显重叠。SVM用hinge损失加正则项来平衡“分对更多样本”和“间隔尽量宽”这两个目标,C参数就是这两个目标的权重。C越大,模型越偏向分对训练集里的所有点,代价是间隔变窄、过拟合风险升高;C越小,间隔越宽,对误分类的容忍度也越高。
这里顺带说一个选型问题:为什么这个场景不用CNN。因为Iris是结构化表格数据,样本量只有150个、特征只有4维,SVM直接在低维空间找超平面就够用了。CNN的优势在于自动学特征表示,但那是给图像、文本这类高维非结构化数据用的,拿CNN来跑150条表格数据,不仅没有优势,反而会因为数据量太小而严重过拟合。这也和很多人初学时的困惑一致:SVM和CNN原理不同,适用的数据形态完全不同,不是谁替代谁的关系。
核函数的选择同样关键。svm_flower.py这个项目默认走RBF(径向基)核,因为Iris的数据并不是严格线性可分的,Versicolor和Virginica两类在部分特征维度上有交错。RBF核能把样本映射到高维空间再找超平面,实际效果通常好于线性核。但对应的代价是多了个gamma参数:gamma控制单个样本的影响半径,gamma太小,决策边界过于平滑,容易欠拟合;gamma太大,边界会围着每个样本画圈,直接过拟合。对Iris这种小样本,gamma取默认值通常够用,但要想在报告里展示调参过程,就得手动遍历C和gamma的组合。
| 对比项 | 线性核 LinearSVC | RBF核 SVC(kernel='rbf') |
|---|---|---|
| 假设 | 原始特征空间线性可分 | 允许非线性,映射到高维 |
| 参数 | 只有C | C + gamma |
| 计算开销 | 低 | 中等 |
| 在Iris上的测试精度 | 通常0.90~0.96 | 通常0.93~0.98 |
| 报告亮点 | 参数少,解释简单 | 可以讲核技巧,能展开调参分析 |
我在实际跑这个项目时,会给两种核各跑一遍,然后做个对比表放进报告。导师会觉得你在选型上花过心思,这也是原包里既有svm_flower.py又有flower1.py的可能原因——两个脚本各承担一种实验对比。
2.2 Iris数据集:为什么“看着简单、写报告不简单”
Iris的三个类别是Setosa、Versicolor、Virginica,每类50个样本,共150行数据,特征就4个:sepal length、sepal width、petal length、petal width。用sklearn自带的load_iris()加载后,data是(150,4)的ndarray,target是(150,)的整数标签,0/1/2分别对应三个类别。
| 特征 | 取值范围(cm) | 类别区分度 |
|---|---|---|
| sepal length | 4.3~7.9 | 中等,三个类分布有重叠 |
| sepal width | 2.0~4.4 | 较差,Versicolor与Virginica混在一起 |
| petal length | 1.0~6.9 | 很好,Setosa明显分离 |
| petal width | 0.1~2.5 | 很好,是关键判别特征 |
写实验报告最容易犯的一个错,是把150个样本全部拿去训练,然后拿同一个数据集再算准确率——那是自欺欺人。正确做法是划分训练集和测试集。这个项目用的是sklearn的train_test_split,加stratify参数按类别比例分层抽样,保证每个类别在训练集和测试集里的比例都和原始数据一致。样本量本来就小,如果shuffle之后碰巧把某一类全划进训练集,报告里的准确率会严重失真。
另一个值得写进报告的观察点是:petal length和petal width这两个特征本身就足以把三类分开,因此做二维散点图时,优先选这两个维度而不是sepal的两个维度。这个选择会直接影响可视化效果,后面第4章会专门演示。
- 复现svm_flower.py:数据加载、特征标准化与分类报告全流程
3.1 加载Iris并划分训练集:stratify、random_state与特征标准化
原包里svm_flower.py的第一段逻辑,基本就是标准的数据加载与划分流程。需要注意一个细节:SVM的RBF核依赖样本间的距离计算,如果某个特征量纲特别大,它会主导距离,其他特征就失效了。Iris四个特征的量纲都在厘米级别,差异不大,但为了流程规范,我还是建议加上标准化。
from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 加载sklearn内置的鸢尾花数据集 iris = load_iris() X, y = iris.data, iris.target # 标准化的常见做法:先fit训练集,再transform测试集 scaler = StandardScaler() X = scaler.fit_transform(X) # 划分训练集与测试集,测试集占30% X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, stratify=y, random_state=42 ) print("训练集样本数:", X_train.shape[0]) print("测试集样本数:", X_test.shape[0])逻辑说明:load_iris()返回的是Bunch对象,里面同时包含data、target、feature_names和target_names,不需要自己读CSV文件。train_test_split把150个样本切成105训练、45测试,stratify=y保证三类在两边都保持相同的比例,random_state=42让每次运行结果完全一致。StandardScaler先在整个数据集上fit,再transform,目的是让每个特征都变成均值为0、方差为1的分布。
参数说明:test_size=0.3是常见划分比例,样本量小的时候也可以用0.2,但0.3会让测试集有45个样本,评估指标更稳。random_state=42只是固定随机种子的约定值,取多少都行,但一旦定了就不能改,否则报告里的数字又要重跑。stratify只有在分类问题里才需要,回归问题不需要。
3.2 训练SVC并评估:默认参数下的基础结果
接着往下就是训练和评估部分。SVC默认核函数是rbf,默认C=1.0、gamma='scale',对Iris数据集来说,直接跑出来的测试精度通常在0.95左右,已经足够写进报告。
from sklearn.svm import SVC from sklearn.metrics import classification_report, accuracy_score # 创建RBF核的SVM分类器 model = SVC(kernel='rbf', C=1.0, gamma='scale', random_state=42) # 在训练集上拟合 model.fit(X_train, y_train) # 预测测试集并输出评估指标 y_pred = model.predict(X_test) print("测试集准确率: {:.4f}".format(accuracy_score(y_test, y_pred))) print(classification_report(y_test, y_pred, target_names=iris.target_names))逻辑说明:SVC的训练分两步,fit负责寻找支持向量和超平面参数,predict拿训练好的模型去预测新样本的类别。classification_report会输出每个类别的precision、recall、f1-score和support,这是报告里最能体现评估分析深度的数据。
参数说明:C=1.0是SVM默认的软间隔惩罚系数,Iris样本量小、特征维度低,不需要调大C去强行拟合;gamma='scale'是sklearn的默认策略,会根据特征数量自动计算gamma=1/(n_features * X.var()),对4维特征来说算出来的值比较适中。如果你把这个项目里的kernel改成'linear',训练代码完全不用动,可以顺手做核函数对比实验。
3.3 项目里的两份脚本怎么分工:svm_flower.py与flower1.py
原包解压后有两个Python文件,从命名和配套图片推断,svm_flower.py是完整版主流程,flower1.py更像是一个可视化辅助脚本或者简化版本。我一般会这样组织实验目录:svm_flower.py负责训练、评估和输出classification_report;flower1.py负责读取同一份数据画散点图、决策边界图;最后在报告里用svm_flower.py的结果数据,用flower1.py的图当可视化证据。
这样的分工有个实际好处:训练和画图解耦。调参时只需要改svm_flower.py里的参数再跑一遍,不需要重新生成所有图。项目里的1_1.png、1_2.png、2_1.png、2_2.png就是这两个脚本分别产出的图,对应不同特征组合和不同核函数的输出结果。如果你想在这个基础上改成自己的作业,保持这个“训练脚本+可视化脚本”的目录结构,后面补实验、换数据都会省很多事。
- 把实验结果变成报告素材:散点图、决策边界与多分类ROC
4.1 特征散点图做出“肉眼可见的分类效果”
散点图是报告的第一张图,作用是展示原始数据的分布。关键在于选哪两个特征来画。之前讲过,petal length和petal width的区分度远高于sepal的两个维度。
import matplotlib.pyplot as plt # 取petal length和petal width两个特征画散点图 plt.figure(figsize=(8, 6)) colors = ['red', 'green', 'blue'] labels = iris.target_names for i in range(3): idx = y == i plt.scatter(X[idx, 2], X[idx, 3], c=colors[i], label=labels[i], edgecolor='k', s=50) plt.xlabel('Petal Length (cm)') plt.ylabel('Petal Width (cm)') plt.title('Iris Data Distribution by Petal Features') plt.legend() plt.savefig('flower.png', dpi=150) plt.show()逻辑说明:X[idx, 2]取的是第3个特征petal length,X[idx, 3]取第4个特征petal width,idx是按类别生成的布尔索引,三类样本依次用不同颜色画出。保存时把dpi设成150,放到Word里不会糊。
如果你拿sepal length和sepal width画同一张图,会看到Versicolor和Virginica基本叠在一起,给人“这个数据集很难分”的错觉。用petal画则是三类清晰分开,和后面的分类准确率互相印证。报告里图与数据保持一致,答辩时会更有说服力。
4.2 决策边界二维可视化:只能做投影示意,别把它当真实边界
SVM在4维空间里的真实决策边界是个三维超平面,二维画图只能选两个特征做切片投影。常见做法是生成网格点,把网格点送入模型预测,再用等高线把预测结果画出来。
import numpy as np from matplotlib.colors import ListedColormap def plot_decision_boundary(model, X, y, feature_idx=(2, 3)): x0_min, x0_max = X[:, feature_idx[0]].min() - 0.5, X[:, feature_idx[0]].max() + 0.5 x1_min, x1_max = X[:, feature_idx[1]].min() - 0.5, X[:, feature_idx[1]].max() + 0.5 # 生成网格点,步长0.02用于画平滑边界 xx, yy = np.meshgrid( np.arange(x0_min, x0_max, 0.02), np.arange(x1_min, x1_max, 0.02) ) # 构造二维特征矩阵并预测 Z = model.predict(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape) # 绘制等高线填色图 plt.contourf(xx, yy, Z, alpha=0.3, cmap=ListedColormap(['red', 'green', 'blue'])) plt.scatter(X[:, feature_idx[0]], X[:, feature_idx[1]], c=y, edgecolor='k', s=40) plt.xlabel('Feature ' + str(feature_idx[0])) plt.ylabel('Feature ' + str(feature_idx[1])) plt.savefig('decision_boundary.png', dpi=150) plt.show()注意这里有一个隐藏的坑:训练模型用的是4维特征,画图只给2个维度的输入,np.c_[xx.ravel(), yy.ravel()]构造出的矩阵只有2列,模型会直接报维度不匹配。解决办法是训练时单独用一个2维特征的SVM专门做可视化,或者把网格点补成4维、未画的两个特征统一填训练集均值。原包里2_1.png、2_2.png这类图,大概率是单独训练了一个2维SVM来画的。报告里最好加一句“图中决策边界基于两个特征的可视化投影,与完整四维模型存在差异”,这句话能挡住答辩时的很多追问。
4.3 ROC曲线与多分类处理:报告里的硬通货
原包里有一张ROC图,说明作者把ROC曲线当成了报告的加分项。二分类的ROC大家都会画,但Iris是三分类,sklearn的roc_auc_score默认只支持二分类,需要多走一步。
from sklearn.metrics import roc_curve, auc from sklearn.preprocessing import label_binarize # 将三分类标签转为二分类形式,每一列代表“是否属于某一类” y_test_bin = label_binarize(y_test, classes=[0, 1, 2]) # 获取每个类别的决策函数值 y_score = model.decision_function(X_test) plt.figure(figsize=(8, 6)) for i in range(3): fpr, tpr, _ = roc_curve(y_test_bin[:, i], y_score[:, i]) roc_auc = auc(fpr, tpr) plt.plot(fpr, tpr, label='{} (AUC={:.3f})'.format(iris.target_names[i], roc_auc)) plt.plot([0, 1], [0, 1], 'k--', label='Chance Level') plt.xlabel('False Positive Rate') plt.ylabel('True Positive Rate') plt.title('Multi-class ROC Curves (One-vs-Rest)') plt.legend() plt.savefig('roc_curve.png', dpi=150) plt.show()逻辑说明:多分类ROC用的是One-vs-Rest策略,把三分类拆成三个二分类问题,每个类别算一条ROC曲线。label_binarize把[0,1,2]的标签变成三列01矩阵,decision_function返回的是每个样本到各分类超平面的距离,距离越大表示模型越确信属于这一类别。
参数说明:y_score[:, i]取第i类对应的决策值,和y_test_bin[:, i]一一对应。虚线是随机分类器的基准线,AUC比0.5高得越多说明模型越有效。Iris数据集上三类AUC基本都在0.97以上,这张图放进报告,比单贴一个accuracy有说服力得多。如果换成ovr策略的predict_proba,也能得到概率值,但SVC的predict_proba本身是依赖Platt缩放计算的,在小型数据集上不如decision_function直观。
- 避坑清单:用Iris做SVM的五个翻车现场与排查方法
5.1 同一份代码每次跑出来的准确率都不一样
现象:不改任何代码,重复运行svm_flower.py,测试集准确率一次是0.933,一次是0.978,报告里的数字不知道该填哪个。
原因:train_test_split没有固定random_state,每次运行都会重新随机划分数据集,测试集换了,准确率自然跟着变。
解决:在train_test_split和SVC里都固定random_state=42,同时带上stratify=y,保证划分结果和类别比例都稳定。我现在的习惯是写完代码先检查随机种子有没有固定,再谈跑实验。
5.2 用训练集测出来的准确率是0.98,测试集只有0.85
现象:把全部150条数据拟合模型后回测,准确率接近满分,但划分出测试集再测,掉到0.85。
原因:模型在训练集上过拟合了。这种情况常见于gamma调得过大,RBF核的决策边界围着训练样本画圈,泛化能力变差。
解决:训练集和测试集严格分离,报告里只报测试集指标。如果测试集掉得厉害,把gamma往小了调,比如从默认的scale改成0.01、0.1这样显式的值,再观察准确率变化。
5.3 画决策边界时报维度不匹配错误
现象:训练好的4维SVM,拿两个特征的网格点去predict,直接报shape mismatch错误。
原因:模型的n_features_in_是4,传入的网格矩阵只有2列,sklearn会检查特征数并拒绝预测。
解决:两个方案二选一。要么再训练一个只用两个petal特征的SVM专门用于可视化;要么在画图时把网格点补成4列,另外两列填训练集均值。我一般推荐前者,因为训练2维模型只要一行代码,可视化语义也更清晰。但要在报告里注明这是二维投影模型,不能代表四维分类器的真实边界。
5.4 IDLE环境里plt.show()卡死窗口
现象:在IDLE的shell里跑带plt.show()的脚本,图像窗口弹出来但shell卡住不动,关掉窗口代码才继续执行。
原因:plt.show()默认是阻塞式的。在IDLE这种交互式环境下,事件循环没有被完全托管,表现就是窗口不响应。
解决:脚本里先plt.savefig()保存图片,再用plt.show()做交互查看;或者提交作业时干脆只保留savefig,去掉show。项目里的png文件都是savefig的产物,用保存的图片文件放进报告,清晰度比截图高一档。
5.5 报告里只有准确率,没有分类报告和ROC,答辩被问“怎么证明模型不是蒙对的”
现象:accuracy_score输出一个0.95就结束了,报告里就一行字描述结果,被追问时答不上来。
原因:评估维度太单薄。三分类数据只看整体准确率,看不出每个类别各自的区分情况。
解决:把classification_report里每一类的precision、recall、f1都贴进报告,再补一张多分类ROC图。Iris这份数据集三个类别的support都是15左右,指标本身有意义的,比单独一个准确率有说服力得多。
- 进阶技巧:用网格搜索把C和gamma写成报告里的实验分析
很多人的报告写到“模型准确率为0.95”就结束了,但高分作业通常会多走一步:做一次参数搜索实验,展示C和gamma对结果的影响。下面是给原项目补上网格搜索的最小改动。
from sklearn.model_selection import GridSearchCV from sklearn.svm import SVC # 定义参数搜索范围:C和gamma各取四个值 param_grid = { 'C': [0.1, 1, 10, 100], 'gamma': [0.01, 0.1, 1, 10] } # 用5折交叉验证评估每组参数 grid_search = GridSearchCV( SVC(kernel='rbf'), param_grid, cv=5, scoring='accuracy' ) grid_search.fit(X_train, y_train) print("最佳参数:", grid_search.best_params_) print("交叉验证最佳得分: {:.4f}".format(grid_search.best_score_)) best_model = grid_search.best_estimator_ test_acc = accuracy_score(y_test, best_model.predict(X_test)) print("最优模型在测试集上的准确率: {:.4f}".format(test_acc))GridSearchCV会遍历16组C和gamma的组合,每组用5折交叉验证评估,最后返回交叉验证得分最高的参数组合。best_estimator_是用选中参数在全部训练集上重新拟合的模型,可以直接拿去测测试集。
C取0.1到100跨了三个数量级,覆盖从强正则到弱正则的范围;gamma取0.01到10同理。在这份Iris数据上,最优参数通常落在C=1或C=10、gamma=0.1或1附近,交叉验证得分在0.95以上。如果最优组合落在搜索范围的边界,说明范围不够,还要往外扩一层再搜。
拿到网格搜索结果后,再做一张小表格放进报告的“实验结果与分析”小节,表头是三列:参数组合、5折交叉验证得分、测试集准确率。只列最优的3到5组,不用全部16组。写讨论时对照表格说一句“当C增大时,模型更注重拟合训练集,交叉验证得分先升后降,说明软间隔惩罚系数存在一个合适区间”,这一句比任何现成结论都更能体现实验是你亲手做的。
我在最后一次交作业时,就是把这套资源里的默认SVC换成GridSearchCV版本,把搜索结果和ROC图一起贴进报告,答辩被问到“为什么选RBF核”和“参数怎么确定”时,都能直接指着表格回答。从那以后我每次跑SVM实验都强制走一遍这个流程:先固定随机种子,再跑默认参数拿基线,然后开网格搜索找最优参数,最后把参数表和ROC图一起存进报告素材文件夹,整个流程下来顺了很多,希望帮到你。
本文还有配套的精品资源,点击获取