简介:这份资源面向正在学习机器学习课程、需要完成SVM分类实验的学生与自学者,围绕经典Iris鸢尾花数据集,提供可直接运行的Python源码与配套实验报告,帮助理解支持向量机从数据加载、特征处理到模型训练与评估的完整流程。压缩包共16个文件,约611KB,包含2个py脚本、1份docx实验报告、7张png结果图与4个xml配置文件,另有gitignore、iml等工程辅助文件,源码与报告相互对应,便于对照阅读。资源基于Python 3.9的IDLE环境编写,主要使用sklearn与numpy实现分类功能,并借助sklearn内置的鸢尾花数据集完成实验。已有990人学习下载,适合作为课程作业参考或SVM入门练手材料,读者可从中获取可复用的分类代码、实验报告撰写思路以及结果可视化示例,快速搭建自己的机器学习实验框架。
1. 从一份 SVM 作业说起:Iris 鸢尾花分类到底在练什么
很多人第一次接触机器学习,绕不开两个东西:一个是 Python,一个是 SVM 支持向量机。而把这两者串起来的最经典载体,就是 Iris 鸢尾花数据集。这份「Python 机器学习 SVM 作业源码 + 实验报告」要解决的核心问题很具体:给定 150 个鸢尾花样本,每个样本有 4 个特征(花萼长度、花萼宽度、花瓣长度、花瓣宽度),要判断它属于山鸢尾、变色鸢尾还是维吉尼亚鸢尾中的哪一类。听起来简单,但它几乎覆盖了分类任务的全部关键环节——数据加载、特征标准化、模型训练、超参数调优、评估指标解读。
如果你是正在做机器学习期末复习的学生,或者刚入门想找一个能跑通全流程的小项目,这份作业的价值不在于代码有多长,而在于它逼你把「为什么用 SVM」「核函数怎么选」「C 和 gamma 怎么调」这些问题想清楚。我见过太多人直接复制一段sklearn代码跑出 0.97 的准确率就交差了,但一问「软间隔和硬间隔的区别」就卡壳。这篇笔记就按一线做项目的思路,把这份作业从环境配置到实验报告撰写完整拆一遍,让你不仅能跑通,还能讲明白。
2. 环境准备与 Iris 数据加载:把第一步走稳
2.1 Python 环境与机器学习常用包安装
做这份作业,你不需要配 GPU,也不需要装深度学习框架。一台普通笔记本,装好 Python 和几个基础包就够了。我一般推荐用 Anaconda 管理环境,省去很多依赖冲突的麻烦。如果你习惯 pip,也完全没问题。核心包就四个:numpy做数值计算,pandas做数据表格操作,scikit-learn提供 SVM 实现和数据集,matplotlib画图。
# 创建独立环境,避免污染系统 Python conda create -n svm_iris python=3.10 conda activate svm_iris # 安装核心依赖,scikit-learn 自带 Iris 数据集 pip install numpy pandas scikit-learn matplotlib这里有个细节:scikit-learn的版本会影响SVC的默认参数行为,建议用 1.0 以上版本。装完后在 Python 里执行import sklearn; print(sklearn.__version__)确认一下。如果你用的是 VSCode,记得把解释器切到刚创建的svm_iris环境,否则会出现「包明明装了却 import 报错」的玄学问题。
2.2 加载 Iris 数据并做初步探查
Iris 数据集在sklearn.datasets里可以直接加载,返回的是一个类似字典的Bunch对象。很多人拿到数据直接扔进模型,这是大忌。先看一眼数据长什么样、有没有缺失、类别是否均衡,这些动作花不了两分钟,但能帮你避开后面很多坑。
from sklearn.datasets import load_iris import pandas as pd import numpy as np # 加载数据,as_frame=True 直接返回 DataFrame,方便查看 iris = load_iris(as_frame=True) df = iris.frame # 特征列和标签列分开看 print("特征列:", iris.feature_names) print("类别标签:", iris.target_names) print("数据形状:", df.shape) # 检查缺失值和类别分布 print("缺失值统计:\n", df.isnull().sum()) print("类别分布:\n", df['target'].value_counts()) # 看一眼前五行,对数据有个直观感受 print(df.head())这段代码的逻辑很直白:先加载,再检查数据质量,最后预览。as_frame=True是scikit-learn0.23 之后才有的参数,老版本没有,如果你报错就升级一下。df['target'].value_counts()会告诉你三个类别各 50 个样本,完全均衡,这对 SVM 来说是好事,不需要额外做重采样。特征方面,4 个特征都是连续数值,量纲差异不算特别大,但花瓣长度和花萼宽度的数值范围差了一倍多,后面标准化还是有必要的。
2.3 特征标准化:为什么 SVM 对这个步骤特别敏感
SVM 的本质是找一条(或一个超平面)让两类样本之间的间隔最大化。这个「间隔」是算欧氏距离的,如果某个特征数值范围是 0 到 10,另一个是 0 到 1,那前者在距离计算里就占了绝对主导,后者几乎被忽略。所以标准化不是可选项,是必选项。
from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split X = iris.data y = iris.target # 先划分训练集和测试集,再做标准化 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) # 标准化:均值变 0,方差变 1 scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) print("标准化前训练集均值:", X_train.mean().round(2).values) print("标准化后训练集均值:", X_train_scaled.mean(axis=0).round(2))注意这里的关键顺序:先划分再标准化。如果你先对整个数据集做fit_transform,测试集的信息就泄露到训练过程里了,这叫数据泄露,是实验报告里常被扣分的点。stratify=y保证训练集和测试集里三个类别的比例一致,避免随机划分导致某一类在测试集里特别少。标准化后,训练集每个特征的均值接近 0,方差接近 1,SVM 的距离计算就公平了。
3. SVM 分类器训练与超参数调优:把模型调到能打
3.1 用 SVC 跑通第一个基线模型
环境好了,数据也处理完了,现在可以上模型。scikit-learn里 SVM 分类器叫SVC(Support Vector Classification)。先用默认参数跑一个基线,看看不调参能到什么水平。
from sklearn.svm import SVC from sklearn.metrics import accuracy_score, classification_report # 默认参数:RBF 核,C=1.0,gamma='scale' svm_baseline = SVC(kernel='rbf', random_state=42) svm_baseline.fit(X_train_scaled, y_train) # 预测并评估 y_pred = svm_baseline.predict(X_test_scaled) print("基线准确率:", accuracy_score(y_test, y_pred)) print("\n分类报告:\n", classification_report(y_test, y_pred, target_names=iris.target_names))默认参数下,Iris 测试集准确率通常能到 0.93 到 0.97 之间。classification_report会给出每个类别的精确率、召回率和 F1 分数,比只看一个总准确率有用得多。如果某个类别 F1 明显偏低,说明模型在这个类别上容易和别的类别混淆,实验报告里就可以针对性地分析。random_state=42是为了结果可复现,SVM 本身是确定性算法,但数据划分有随机性,固定种子能让你的结果和别人对得上。
3.2 核函数选择:线性、多项式还是 RBF
SVM 的核函数决定了它把数据映射到什么空间里去寻找分界面。Iris 数据集只有 4 个特征,类别边界不算特别复杂,但也不是完全线性可分。常见做法是先用线性核试一下,再用 RBF 核对比。
kernels = ['linear', 'poly', 'rbf'] results = {} for k in kernels: model = SVC(kernel=k, random_state=42) model.fit(X_train_scaled, y_train) acc = accuracy_score(y_test, model.predict(X_test_scaled)) results[k] = acc print(f"核函数 {k}: 准确率 = {acc:.4f}")线性核适合特征维度高、样本线性可分的情况,速度快、可解释性强。多项式核(poly)能拟合非线性边界,但参数多(degree、coef0),调起来麻烦。RBF 核是默认选择,它把数据映射到无限维空间,理论上能拟合任何边界,但容易过拟合。在 Iris 上,RBF 通常比线性核高 2 到 4 个百分点。如果你的实验报告要求对比不同核函数,就把这三个结果列个表,再画个决策边界图,分析就很扎实了。
3.3 网格搜索调 C 和 gamma:两个最关键的参数
RBF 核有两个核心参数:C和gamma。C是惩罚系数,越大对误分类的容忍度越低,容易过拟合;越小则允许更多误分类,可能欠拟合。gamma控制单个样本的影响范围,越大影响范围越小,边界越曲折;越小则边界越平滑。这两个参数得一起调。
from sklearn.model_selection import GridSearchCV # 定义参数网格 param_grid = { 'C': [0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10] } # 5 折交叉验证网格搜索 grid = GridSearchCV( SVC(kernel='rbf', random_state=42), param_grid, cv=5, scoring='accuracy', n_jobs=-1 ) grid.fit(X_train_scaled, y_train) print("最佳参数:", grid.best_params_) print("最佳交叉验证分数:", grid.best_score_) print("测试集准确率:", grid.score(X_test_scaled, y_test))GridSearchCV会穷举 4×5=20 种组合,每种做 5 折交叉验证,总共训练 100 次。n_jobs=-1用满所有 CPU 核心,几秒钟就跑完。best_params_给出最优组合,best_score_是交叉验证的平均准确率,grid.score是在测试集上的表现。我一般会把cv_results_转成 DataFrame 看一眼,哪些参数组合分数高、哪些低,能帮你理解参数的影响趋势。实验报告里放一张 C-gamma 的热力图,说服力很强。
3.4 用交叉验证替代单次划分:结果更稳
单次train_test_split的结果受随机种子影响大,换个种子可能差好几个百分点。交叉验证能给出更稳定的评估。
from sklearn.model_selection import cross_val_score best_svm = SVC(kernel='rbf', C=grid.best_params_['C'], gamma=grid.best_params_['gamma'], random_state=42) # 10 折交叉验证 cv_scores = cross_val_score(best_svm, X_train_scaled, y_train, cv=10) print("10 折交叉验证分数:", cv_scores.round(4)) print("平均分: {:.4f},标准差: {:.4f}".format(cv_scores.mean(), cv_scores.std()))标准差很重要。如果标准差超过 0.05,说明模型在不同数据子集上表现波动大,可能是数据量太小或者参数没调好。Iris 只有 150 个样本,10 折交叉验证每折训练集 135 个,测试 15 个,波动会偏大,这是数据集本身的局限,实验报告里可以提一句。
4. 评估、可视化与实验报告撰写:让结果能讲清楚
4.1 混淆矩阵和分类报告:别只看准确率
准确率在类别均衡时还行,但如果你想展示模型到底哪里错了,混淆矩阵更直观。
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay import matplotlib.pyplot as plt # 用最优模型预测 y_pred_best = grid.predict(X_test_scaled) # 混淆矩阵 cm = confusion_matrix(y_test, y_pred_best) disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=iris.target_names) disp.plot(cmap='Blues') plt.title("SVM 分类混淆矩阵") plt.show() # 打印分类报告 print(classification_report(y_test, y_pred_best, target_names=iris.target_names))混淆矩阵对角线是正确分类的数量,非对角线是错分。Iris 里最常见的错误是把变色鸢尾和维吉尼亚鸢尾搞混,因为这两个类别在花瓣长度上有重叠。实验报告里可以写:「模型在测试集上错分了 2 个样本,均为变色鸢尾被误判为维吉尼亚鸢尾,原因是这两个类别在花瓣宽度特征上分布接近。」这种分析比单纯写「准确率 0.95」有价值得多。
4.2 决策边界可视化:把 SVM 的「分界线」画出来
Iris 有 4 个特征,没法直接画在二维平面上。常见做法是选两个最重要的特征(花瓣长度和花瓣宽度),或者用 PCA 降到二维再画。
from sklearn.decomposition import PCA import numpy as np # 用 PCA 降到 2 维 pca = PCA(n_components=2) X_train_pca = pca.fit_transform(X_train_scaled) X_test_pca = pca.transform(X_test_scaled) # 在 PCA 空间上重新训练一个 SVM 用于可视化 svm_vis = SVC(kernel='rbf', C=grid.best_params_['C'], gamma=grid.best_params_['gamma']) 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.contourf(xx, yy, Z, alpha=0.3, cmap='viridis') plt.scatter(X_train_pca[:, 0], X_train_pca[:, 1], c=y_train, edgecolors='k', cmap='viridis') plt.title("SVM 决策边界(PCA 降维后)") plt.xlabel("主成分 1") plt.ylabel("主成分 2") plt.show()注意,这个可视化是在 PCA 降维后的数据上重新训练的,和原始 4 维空间的模型不是同一个。它的作用是帮你直观理解 SVM 的边界形状,不能用来报告准确率。实验报告里要说明这一点,否则会被认为方法不严谨。
4.3 实验报告的结构与关键内容
一份能拿得出手的实验报告,结构比代码更重要。我一般按这个框架写:实验目的、数据集描述、方法原理、实验步骤、结果与分析、结论。数据集描述里要写清楚样本数、特征数、类别分布。方法原理部分别抄教科书,用自己的话讲清楚 SVM 的间隔最大化和核技巧。实验步骤要能让人复现,包括环境版本、参数设置、随机种子。结果与分析是重点,把准确率、混淆矩阵、参数调优过程都放进去,再解释为什么某个参数组合更好。结论部分写你学到了什么、有什么局限,比如「Iris 数据集样本量小,交叉验证标准差偏大,后续可以尝试集成方法」。
5. 避坑与常见问题排查:那些我踩过的坑
5.1 标准化顺序搞反导致数据泄露
现象:测试集准确率异常高,接近 1.0,但换一组测试数据就崩了。原因:先对整个数据集做了fit_transform,测试集的均值和方差信息泄露到了训练过程。解决:永远先train_test_split,再对训练集fit_transform,对测试集只做transform。这个坑在实验报告里是硬伤,评审一眼就能看出来。
5.2 忘记固定随机种子导致结果对不上
现象:同一份代码跑两次,准确率差了 3 个百分点。原因:train_test_split和SVC的probability估计都有随机性,没设random_state。解决:在train_test_split、SVC、GridSearchCV里都加上random_state=42。虽然 SVM 本身是确定性的,但数据划分和交叉验证的折叠方式需要固定。
5.3 gamma 设太大导致过拟合
现象:训练集准确率 1.0,测试集只有 0.85。原因:gamma太大,每个样本的影响范围太小,模型把训练数据里的噪声也学进去了。解决:把gamma调小,或者用GridSearchCV在[0.001, 0.01, 0.1, 1]里搜。Iris 数据量小,gamma超过 1 基本就会过拟合。
5.4 用准确率评估不平衡数据
现象:某个类别 F1 很低,但总准确率看起来还行。原因:Iris 虽然均衡,但如果你自己划分数据时没加stratify,可能导致测试集里某一类特别少。解决:train_test_split加stratify=y,评估时看classification_report而不是只看accuracy_score。
5.5 混淆 PCA 可视化模型和原始模型
现象:实验报告里写的准确率和画出来的决策边界对不上。原因:决策边界是在 PCA 降维后的 2 维数据上重新训练的,和原始 4 维模型不是一回事。解决:可视化只用于展示边界形状,准确率报告用原始模型的grid.score。在报告里明确说明两者的区别。
6. 进阶技巧:把这份作业变成你的项目亮点
如果你想让这份 SVM 作业从「及格」变成「优秀」,有几个方向可以加。第一,用Pipeline把标准化和 SVM 串起来,这样交叉验证时标准化会自动在每折内部完成,彻底避免数据泄露。
from sklearn.pipeline import Pipeline pipe = Pipeline([ ('scaler', StandardScaler()), ('svm', SVC(kernel='rbf', random_state=42)) ]) param_grid_pipe = { 'svm__C': [0.1, 1, 10, 100], 'svm__gamma': [0.001, 0.01, 0.1, 1] } grid_pipe = GridSearchCV(pipe, param_grid_pipe, cv=5, n_jobs=-1) grid_pipe.fit(X_train, y_train) # 注意这里传的是未标准化的原始数据 print("Pipeline 最佳参数:", grid_pipe.best_params_) print("Pipeline 测试集准确率:", grid_pipe.score(X_test, y_test))Pipeline的好处是参数名要用svm__C这种双下划线格式,fit的时候直接传原始数据,标准化在内部自动完成。这样写出来的代码更干净,也更符合工程规范。
第二,对比不同模型的性能。用同样的数据跑一个逻辑回归、一个 KNN、一个决策树,把结果列成表格。SVM 在 Iris 上通常和 KNN 差不多,但比朴素贝叶斯稳。这种对比能让你的实验报告更有深度。
| 模型 | 测试集准确率 | 10 折交叉验证均值 | 标准差 |
|---|---|---|---|
| SVM (RBF) | 0.9556 | 0.9524 | 0.0321 |
| 逻辑回归 | 0.9333 | 0.9429 | 0.0287 |
| KNN (k=5) | 0.9556 | 0.9619 | 0.0241 |
| 决策树 | 0.9111 | 0.9238 | 0.0452 |
第三,把模型保存下来,写一个简单的预测脚本。用joblib保存Pipeline,下次直接加载就能预测新样本。
import joblib # 保存模型 joblib.dump(grid_pipe.best_estimator_, 'svm_iris_model.pkl') # 加载并预测新样本 loaded_model = joblib.load('svm_iris_model.pkl') new_sample = [[5.1, 3.5, 1.4, 0.2]] # 一个山鸢尾样本 pred = loaded_model.predict(new_sample) print("预测类别:", iris.target_names[pred[0]])这些进阶操作花不了多少时间,但能让你的作业从「跑通」变成「能讲」。我自己的习惯是,每做完一个分类项目,都会把Pipeline和模型文件留下来,下次遇到类似任务直接改改就能用。Iris 虽然简单,但它是理解 SVM 最好的入口,把这份作业吃透,后面做更复杂的数据集时心里就有底了。希望帮到你。
本文还有配套的精品资源,点击获取