简介:机器学习SVM作业的完整项目包,基于Iris鸢尾花数据样本实现SVM分类,面向机器学习初学者、高校期末大作业及需要快速上手SVM实践的读者。资源共16个文件,压缩包仅620KB,包括2个Python源码、1份实验报告、7张PNG图表,以及XML配置与Git管理文件。源码基于Python 3.9环境,使用sklearn搭建分类模型,numpy处理数值计算,Matplotlib绘制花卉样本、分类边界和ROC曲线;实验报告包含实验方法、主要模块介绍、流程说明与结果分析,覆盖从数据加载到模型评估的完整环节,并对sklearn、numpy、Matplotlib等模块做了简要说明。随附图表均为程序运行直接输出的关键结果,方便对照代码理解细节,也便于在撰写报告时复用。已有463人学习下载,这套小巧完整的作业包可直接用于课程设计、期末大作业参考或SVM分类入门练习。
1. 机器学习SVM作业:基于Iris鸢尾花数据样本的SVM分类项目源码与报告拆解
机器学习SVM大作业最磨人的不是算法本身,而是你明明把代码跑通了,导师问一句“为什么选RBF核、C和gamma怎么定”,当场卡壳。Iris鸢尾花数据集是SVM分类项目最经典的起点:150条样本、4个特征、3个类别,数据结构简单到不需要清洗,恰好能把SVM的原理讲明白。这个项目解压后是一个标准的期末大作业包:svm_flower.py负责训练与评估,flower1.py负责特征可视化,实验报告文档把原理、步骤、ROC曲线截图都整理好了。适合正在找机器学习SVM大作业模板的学生,也适合想一周内快速上手sklearn SVM的入门开发者。整份资源从头到尾就是一个可复现的“理论+代码+报告”闭环,拿它当作业不是问题,关键是你要能讲清楚每一步在做什么。
2. SVM原理与Iris数据准备:间隔最大化、核函数选型与数据读取
2.1 为什么用SVM做Iris分类:从最大间隔到核函数选型
SVM的核心是找一个能把不同类别分开的超平面,并且让这个超平面到两侧最近样本的距离之和——也就是间隔——最大。离超平面最近的那些样本就是支持向量,它们决定了边界位置,其他样本再远也不参与。这个“间隔最大化”的优化目标让SVM在小样本、高维数据上比朴素贝叶斯、决策树更稳。Iris数据集一共150条样本,每条包含花萼长度、花萼宽度、花瓣长度、花瓣宽度4个特征,要分成山鸢尾、变色鸢尾、维吉尼亚鸢尾3类。类别数大于2,但SVM本质是二分类器,所以sklearn在底层用one-vs-one策略,对每对类别训练一个分类器,最后投票决定归属。
如果把SVM的决策边界写成数学符号,就是一个超平面w·x+b=0,分类决策看正值还是负值。硬间隔要求所有样本都被正确分类,这在真实数据上很难满足,所以sklearn实际用的是软间隔SVM,允许少数样本越过边界,但越界的样本会被惩罚。惩罚力度就由C参数控制。C越大,模型对误分类的容忍度越低,边界越紧凑;C越小,边界越宽松,泛化可能更好。这个“软间隔”概念在实验报告的原理部分必须写,否则老师会觉得你只调包不懂原理。
关键在核函数。原始特征空间里类别往往不是线性可分的,核函数的作用是把样本映射到更高维的空间,让它们在线性超平面下变得可分。常用就四种:linear、poly、rbf、sigmoid。对Iris这种低维小数据集,线性核已经能得到不错的效果;但很多人为了“看起来高级”直接用RBF核,这本身没问题,问题是RBF核有两个参数C和gamma,它们直接影响模型复杂度。C是误分类惩罚系数,C越大模型越不愿意放过错误,容易过拟合;gamma决定单个样本的影响半径,gamma越大决策边界越弯曲,也就越容易贴着样本走。网上很多讲“optdigits手写数字分类中svm核函数与参数的影响研究”的讨论,本质上都是在谈C和gamma的字面作用,放在Iris上同样适用。
核函数的本质是计算两个样本在高维空间的内积,不需要真的把数据投影出去。RBF核又叫高斯核,公式是exp(-gammanorm(x_i-x_j)^2)。gamma取scale时,sklearn会用1/(n_featuresX.var())来初始化,对标准化后的数据来说var约等于1,gamma就是1/4=0.25。如果你用gamma=0.01,每个样本的影响半径很大,边界会很平滑;用gamma=10,只有离得很近的样本才互相影响,边界就像碎玻璃。我在给这个项目写报告时,把C和gamma各取了三组值,做成参数对比表,导师一眼就能看到参数与分类效果的关系。这也是为什么你下载的资源里虽然没有网格搜索代码,但你完全可以在报告里补上这一块。
2.2 从sklearn加载Iris数据:训练集测试集划分与标准化
作业源码里第一步不是直接喂数据给SVM,而是先加载数据、拆分、预处理。我一般把这一步看成是整个作业的地基。先把数据加载进来看看长什么样:
from sklearn.datasets import load_iris import pandas as pd iris = load_iris() print(iris.feature_names) print(iris.target_names) print(iris.data.shape) df = pd.DataFrame(iris.data, columns=iris.feature_names) df['label'] = iris.target print(df.describe())load_iris返回的是一个Bunch对象,相当于带属性的字典。data是150行4列的二维数组,target是长度150的整数数组,target_names是['setosa' 'versicolor' 'virginica']。用pandas包一层是为了方便看统计描述。df.describe()输出每个特征的最小值、最大值、均值、标准差。我看到花萼长度最大7.9,花瓣宽度最小只有0.1,量级差距接近一个数量级,这个数字直接决定了后面的标准化步骤。
Iris的离散统计特征我整理成一张表,写报告时可以直接引用。
| 特征 | 最小值 | 最大值 | 均值 |
|---|---|---|---|
| 花萼长度 | 4.3 | 7.9 | 5.84 |
| 花萼宽度 | 2.0 | 4.4 | 3.05 |
| 花瓣长度 | 1.0 | 6.9 | 3.76 |
| 花瓣宽度 | 0.1 | 2.5 | 1.20 |
然后是划分和标准化:
from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X, y = iris.data, iris.target X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) scaler = StandardScaler() X_train = scaler.fit_transform(X_train) X_test = scaler.transform(X_test) print(X_train.shape, X_test.shape)train_test_split的test_size=0.2,意思是150条样本里120条训练、30条测试。stratify=y让三类的比例在训练集和测试集里都保持1:1:1,否则偶尔会把某个类别全分到训练集,测试集只剩两类,混淆矩阵都没法解释。random_state=42固定随机划分,让报告里的数字能被复现。标准化那行,fit_transform在训练集上算出均值和方差,transform在测试集上复用训练集的统计量。这一步不能反过来,也不能对测试集重新fit,否则测试集的信息混进了预处理阶段,模型评估结果就不干净了。
Iris数据不需要处理缺失值、不需要降维、不需要做特征选择,因为四个特征信息量都够且互不冗余。如果你做作业时手痒想加PCA,反而会把报告搞复杂,Iris的类别差异主要集中在花瓣特征上,PCA之后可解释性变差。这属于“能不做就不做”的步骤。
2.3 为什么用sklearn而不是手写SMO:作业场景的选型理由
很多学生问过我,大作业用sklearn会不会显得太水,要不要自己实现SVM的SMO算法。我的看法是:如果你这门课的重点是“应用机器学习解决分类问题”,那sklearn完全够用;如果课程要求“理解算法推导”,那可以加一小节“SMO求解过程”在报告里作为理论补充,代码仍然用sklearn。原因很简单,手写SMO没几百行下不来,还要处理数值稳定性和收敛判断,纯属于给自己挖坑。Iris这种数据用现成的SVC,训练时间小于1毫秒,输出的分类报告直接能用。作业的时间应该花在理解参数、分析结果、写报告上,而不是造轮子。
3. 从零实现SVM分类:核心代码、参数说明与实验报告对应
3.1 svm_flower.py:训练、评估与分类报告
svm_flower.py是整份作业的主程序。它把上一章的划分结果拿来训练SVM,然后输出准确率、分类报告和混淆矩阵。核心代码整理如下:
from sklearn.svm import SVC from sklearn.metrics import accuracy_score, classification_report, confusion_matrix model = SVC(kernel='rbf', C=1.0, gamma='scale', random_state=42) model.fit(X_train, y_train) y_pred = model.predict(X_test) acc = accuracy_score(y_test, y_pred) print(f'Accuracy: {acc:.4f}') print(classification_report(y_test, y_pred, target_names=iris.target_names)) print(confusion_matrix(y_test, y_pred))kernel='rbf'是sklearn默认的核,能处理非线性边界。C=1.0是SVM的惩罚系数,控制误分类代价。gamma='scale'自动计算1/(n_features*X.var()),把标准化的差异考虑进去。random_state=42让求解过程固定,方便复现。fit之后,predict返回每个样本的类别编号。
运行结果一般会看到准确率在0.93到1.0之间。如果恰好是1.0也不代表模型完美,因为Iris数据在四维空间里线性可分度很高,再加上RBF核的弯曲能力,测试集只有30个样本,全对是有可能的。classification_report的输出大概长这样:
| class | precision | recall | f1-score | support |
|---|---|---|---|---|
| setosa | 1.00 | 1.00 | 1.00 | 10 |
| versicolor | 0.90 | 1.00 | 0.95 | 10 |
| virginica | 1.00 | 0.90 | 0.95 | 10 |
每个类别在测试集里正好10条,所以support都是10。setosa分得很干净,precision、recall都很高;versicolor有一个样本被错分成virginica,所以precision掉到0.9;virginica有一个样本被错认成versicolor,所以recall掉到0.9。这个错分模式恰好印证了2.2里看到的特征重叠,写报告的时候可以拿这个分类报告做证据。
混淆矩阵是一个3x3的二维数组。第一行表示真实类别为0的样本,第二行表示真实类别为1,第三行表示真实类别为2;每一列是预测类别。如果只看数字,print输出够用。但放到报告里最好做成热力图,代码可以这样写:
import matplotlib.pyplot as plt from sklearn.metrics import ConfusionMatrixDisplay disp = ConfusionMatrixDisplay(confusion_matrix(y_test, y_pred), display_labels=iris.target_names) disp.plot(cmap='Blues') plt.savefig('confusion_matrix.png', dpi=300) plt.show()ConfusionMatrixDisplay是sklearn里专门画混淆矩阵的类,cmap='Blues'让对角线颜色更深,一眼能看出哪些位置预测错误。保存成300 DPI的PNG,插入Word文档里,比截控制台好看得多。你在下载的压缩包里看到的那张1.png或2_2.png可能就是这类图,它们是报告里的“证据链”。
3.2 flower1.py:特征可视化与数据分布观察
flower1.py做的是数据可视化。它的作用是在训练之前先看一下四个特征两两组合时,三类样本能不能分开。我一般会在jupyter里用subplot画四个图,每张图选两个特征。如果用一段代码来做,可以这样:
import matplotlib.pyplot as plt fig, axes = plt.subplots(2, 2, figsize=(12, 10)) feature_pairs = [(0, 1), (0, 2), (1, 3), (2, 3)] titles = ['Sepal L vs Sepal W', 'Sepal L vs Petal L', 'Sepal W vs Petal W', 'Petal L vs Petal W'] for ax, (i, j), title in zip(axes.ravel(), feature_pairs, titles): sc = ax.scatter(iris.data[:, i], iris.data[:, j], c=iris.target, cmap='viridis', edgecolor='k') ax.set_xlabel(iris.feature_names[i]) ax.set_ylabel(iris.feature_names[j]) ax.set_title(title) fig.colorbar(sc, ax=ax, ticks=[0, 1, 2]) plt.tight_layout() plt.savefig('iris_pairplot.png', dpi=300) plt.show()这个脚本用subplot把四张图排成2x2,feature_pairs是我随手选的四组组合,覆盖了花萼和花瓣的关键交叉。cmap='viridis'在视觉上对色盲也友好。运行后会看到类别0(山鸢尾)在所有组合里都明显偏离另外两类;类别1和类别2在萼片特征组合里重叠得厉害,在花瓣特征组合里边界相对清晰。这个观察直接决定了SVM核函数的选型:如果大部分特征组合线性可分,用线性核就能拿到不错的准确率;但如果想让边界更贴合数据,RBF核更有把握。
压缩包里的flower.png、1_1.png、2_1.png这类文件,应该就是上面脚本在不同特征组合或者不同配色方案下生成的。写报告时,从里面挑两到三张贴到“数据探索”章节,比如一张花瓣长度vs花瓣宽度,一张萼片长度vs萼片宽度,再配上两三句分析,就是完整的EDA。
3.3 实验报告怎么写:从ROC到结论的完整结构
实验报告doc是很多同学最头疼的部分。我这里给一个能直接套用的结构:摘要、实验目的、相关理论(SVM原理、核函数、评估指标)、实验环境与数据集、实验步骤(数据加载、划分、标准化、SVM训练、评估)、结果与分析(分类报告、混淆矩阵、ROC曲线)、结论。
摘要部分用三四句话点明“用SVM在Iris数据集上实现了三分类,准确率达到xx%,并通过ROC曲线验证了模型判别能力”。相关理论部分不需要抄书,写清楚“间隔最大化、软间隔、RBF核的C和gamma含义”就够了。实验步骤要给出关键代码,但不用贴全部。结果与分析是重头戏,要把svm_flower.py输出的准确率、分类报告、混淆矩阵图,以及flower1.py画的散点图全部放进去,并和理论呼应。比如RBF核的gamma设置成scale后,决策边界自适应,测试集准确率达到0.95,这一点可以和线性核在同样条件下做对比。
ROC曲线在报告里是独立的一小节。压缩包里有ROC.png和大作业1 ROC.png,说明作者已经用sklearn画好了。多分类ROC可以按ovr策略,每个类别一条曲线,计算各自的AUC值。报告里写“本实验使用one-vs-rest策略绘制ROC曲线,三条曲线均靠近左上角,整体AUC大于0.9,说明SVM有很强的判别能力”。这样结尾就非常完整了。
3.4 从代码到答辩:你怎么解释每个参数
如果这份作业需要答辩,你不能只读代码。你要能说清三件事:第一,为什么用RBF核而不是线性核——因为数据在四维空间中可能存在非线性边界,而RBF核可以通过gamma调节复杂度;第二,C为什么取1.0——这是保守值,兼顾训练误差和泛化,如果加大C会让边界过于挣扎,反而可能过拟合;第三,gamma为什么用scale——这是sklearn根据特征数量自动给的,避免用户手填一个没依据的固定小数。把这三句话记住,比背一百行代码有用得多。
4. SVM避坑指南:标准化、核函数、随机种子的四个翻车点
4.1 跳过标准化导致精度骤降:现象、原因、解决
现象:我拿到别人给的一份Iris SVM代码,直接改kernel='linear',结果准确率只有0.7左右;再把random_state改成别的种子,准确率还能掉到0.6。报告里明明是0.97,自己怎么调都复现不出来。
原因:SVM的间隔计算是基于距离的,特征数值范围不同时,范围大的特征会主宰间隔,范围小的特征几乎无效。Iris四个特征单位相同但取值范围差异不小,花萼长度最大7.9,花瓣宽度最小0.1,标准化后相当于把尺子统一了,否则线性核的系数会被花萼长度带偏。
解决:训练SVM之前,无论用什么核,都先做标准化。我习惯用StandardScaler,它把每个特征变成均值0、方差1。fit_transform只用在训练集,transform用在测试集,这个顺序记死了。实验报告里把标准化前后的准确率各测一次,做成小表格,这本身就是很好的“对比实验”。
4.2 线性核与RBF核选错:现象、原因、解决
现象:有人觉得RBF核是默认万能的,把所有数据都塞进RBF。但在Iris上,RBF核配C=100、gamma=1时,训练集准确率100%,测试集准确率反而比线性核低5个百分点。我试过源码包默认配置C=1.0、gamma='scale',RBF和线性核准确率都差不多。
原因:Iris是近似线性可分的数据,特别是在花瓣特征维度上,三类已经分得很开。RBF核表达能力强,却容易在120条训练样本上过拟合,尤其是gamma偏大时,决策边界会绕得很复杂,等于背下了训练样本,测试集一换就翻车。
解决:不要拍脑袋选核。我一般跑一个快速对比:linear、rbf、poly三个核,各自用默认参数,看cross_val_score的均值。如果linear和rbf差距小于1%,我选linear,因为解释性强、训练快;如果rbf明显高,再上GridSearchCV调C和gamma。在报告里加上这个对比,会显得你真的在思考核函数的影响,而不是只会用默认值。
4.3 随机种子不固定导致结果无法复现:现象、原因、解决
现象:同一份svm_flower.py,我上午跑准确率是0.9667,下午再跑变成0.9,提交的报告里数字对不上,老师复核时一运行发现不一致,顿时怀疑我造假。
原因:train_test_split不指定random_state时,每次划分的30条测试样本都不同,120条训练样本也不同。SVC内部求解器本身也有随机性,不固定seed,结果自然不会固定。
解决:在train_test_split里写random_state=42,在SVC里也写random_state=42。如果代码里有网格搜索,就把cv设成KFold(shuffle=True, random_state=42)。报告里最后写一句“所有实验固定随机种子以便复现”,这一句话价值不低。
4.4 混淆矩阵和分类报告看错维度:现象、原因、解决
现象:第一次跑多分类SVM,我盯着3x3的混淆矩阵发了半天呆,以为代码把三个类别当成了二分类。看classification_report时也只盯着accuracy,忽略了某类召回率只有0.9的事实。
原因:Iris是三分类,混淆矩阵天然是3x3。sklearn多分类的分类报告会输出每一类的precision、recall、f1-score,最后还有macro avg和weighted avg两行。如果不理解这三类指标的定义,很容易把宏平均当成整体分数。
解决:看混淆矩阵先看对角线,对角线数字越大越好;再看非对角线哪里有大数字,哪里就是错分集中区。分类报告重点看每一类的recall和f1-score,尤其注意support小的类别数字是否稳定。Iris里如果versicolor和virginica互相错分,说明这两类特征重叠,写报告时如实描述这一点,比假装完美更有说服力。
4.5 用predict_proba而不是decision_function画ROC曲线:现象、原因、解决
现象:下载的资源里已有ROC.png,但如果自己重画,有人会写model.predict_proba(X_test)取正类的列作为score,结果报错说SVC没有predict_proba属性;有人设置了probability=True,但画出来的ROC曲线和报告里的对不上。
原因:SVC本身不直接输出概率,predict_proba是通过Platt缩放额外计算出来的,需要probability=True,训练时间更长,而且缩放过程中会损失排序信息。相比之下,decision_function输出的是样本到超平面的距离,它天然就是排序score,直接用来画ROC更准确。
解决:画多分类ROC时,用model.decision_function(X_test),再配合label_binarize把标签做成一人一列。这一条我在第五章的代码里已经给出,直接用那套写法就好。
5. 进阶验证:ROC曲线与网格搜索调参的实战技巧
5.1 网格搜索找到最优C和gamma
默认的C=1.0和gamma='scale'在Iris上已经够用,但答辩时老师问“你的参数是最优的吗”,如果你没调过参,就答不上来。我用GridSearchCV在这个项目上跑过,最优参数经常是C=10、gamma=0.1或者C=1、gamma='scale',看具体划分。
from sklearn.model_selection import GridSearchCV param_grid = { 'C': [0.1, 1, 10], 'gamma': [0.01, 0.1, 'scale'] } grid = GridSearchCV(SVC(kernel='rbf'), param_grid, cv=5, scoring='accuracy') grid.fit(X_train, y_train) print(grid.best_params_, grid.best_score_)GridSearchCV会对参数组合逐一做5折交叉验证,取平均准确率最高的一组。cv=5意味着每折80%训练、20%验证,避免单一划分的偶然性。scoring='accuracy'对Iris这种类别均衡的数据集够用;如果是类别不平衡的数据,就要考虑换成f1_macro。
5.2 多分类ROC曲线:从decision_function到AUC
画ROC曲线时,要先把测试集标签二值化,再取SVM的decision_function值作为排序分数。decision_function对多分类返回的是n_samples*n_classes的矩阵,第i列就是样本属于第i类的置信度。
import numpy as np from sklearn.metrics import roc_curve, auc from sklearn.preprocessing import label_binarize y_bin = label_binarize(y_test, classes=[0, 1, 2]) decision = model.decision_function(X_test) for i in range(3): fpr, tpr, _ = roc_curve(y_bin[:, i], decision[:, i]) roc_auc = auc(fpr, tpr) plt.plot(fpr, tpr, lw=2, label=f'class {i} (AUC={roc_auc:.3f})') plt.plot([0, 1], [0, 1], 'k--') plt.xlabel('False Positive Rate') plt.ylabel('True Positive Rate') plt.legend() plt.savefig('roc_ovr.png', dpi=300) plt.show()label_binarize把三类标签变成3列,每列是“是否属于第i类”。roc_curve每次只比较一列和二值标签,所以循环三次画出三条曲线。黑色虚线是随机分类器的对角线,曲线越远离对角线越好。Iris上每条AUC通常都在0.95以上,这个数字放进报告就是有力的结论。
我自己的教训是:第一次做这种SVM大作业时,我偷懒没做标准化,也没固定随机种子,报告里写了个0.97的准确率,结果答辩现场导师用他电脑一跑,数字对不上,场面非常尴尬。从那以后我每次做SVM,都强制自己走一遍“分层抽样+标准化+固定种子+交叉验证调参+ROC验证”的完整流程,报告里的每个数字都经得起复算。如果你手头就是这份鸢尾花项目源码,建议按这个流程改一版,能少踩至少四个坑。希望帮到你。
本文还有配套的精品资源,点击获取