简介:面向机器学习课程设计或期末大作业场景,这份资源基于经典Iris鸢尾花数据集完成SVM分类项目,提供可直接运行的Python源码与配套实验报告。项目使用Python 3.9 IDLE环境,借助sklearn构建分类模型、numpy处理数组、Matplotlib绘制结果图,适合需要快速上手支持向量机并完成实验文档的新手参考。压缩包共16个文件,包含2个py脚本、1份doc实验报告、7张png结果图表,以及若干xml/iml工程配置与.gitignore文件,整体仅620KB,结构紧凑,便于查看源码与实验产出的对应关系。目前已有463人学习/下载。资源价值在于既有两份可直接运行的核心Python脚本,也有实验报告和ROC曲线、花卉分类对比图等可视化输出,能够帮助读者理解SVM在鸢尾花数据上的训练、预测与评估流程,同时也为撰写课程报告提供素材和排版参照。
1. 拿 Iris 做 SVM 作业,为什么一半人交上去只是“能跑”
Iris 鸢尾花数据集几乎是每门机器学习课的固定开场:150 条样本、4 个特征、3 个类别,简单到很多人觉得“能跑就行”。但真正交上去的 SVM 作业,分数差距几乎全在细节里:数据有没有标准化、C 和 gamma 是不是照抄默认值、三类问题有没有真的按三类来训、实验报告里的图和代码能不能对上。这篇笔记按“数学直觉 → 数据预处理 → 最小可交代码 → 调参和可视化 → 常见翻车点 → 交作业前的验证技巧”来写,新手能照着把源码和实验报告一次性补齐,熟手可以直接把它整理成自己第一个能复用的 SVM 分类模板。适合正在做《机器学习》课后作业、想补一个严谨实验流程,以及准备把 SVM 当起点再往项目里做的同学。
2. 先把 SVM 的数学直觉立住:间隔、支持向量和软间隔 C
2.1 支持向量是“贴边的样本”,间隔是它到决策边界的最小距离
SVM 和其他分类器最大的不同,是它不只考虑“分对还是分错”,而是想找一个离两类样本都足够远的超平面。这个“远”用几何间隔衡量:假设决策边界是 w·x + b = 0,样本 x_i 到边界的距离是 |w·x_i + b| / ||w||。SVM 的目标是让所有训练样本里离边界最近的那个样本的距离尽量大,同时保证分类正确。写成优化问题就是 min 1/2||w||²,约束条件是 y_i(w·x_i + b) ≥ 1,对每个样本都成立。
在 Iris 上用前两个特征(花萼长、花萼宽)画散点图,setosa 明显聚在一头,versicolor 和 virginica 有一部分重叠。如果只拿 setosa 和 versicolor 做二分类,一个线性边界就能隔开,但边界放在哪儿有很多种取法。SVM 选的是在正确分类的前提下向两侧扩展一个“安全距离”,让这个最小距离尽可能大。被安全距离恰好挡住的样本就是支持向量,它们才是真正决定边界位置的样本,其他样本对边界没有直接影响。
这一点的直接价值在于:支持向量个数能反映模型的复杂程度。线性核在 Iris 上做一个二分类时,通常只会挑出十几到二十几个支持向量;换到 RBF 核且 gamma 偏大时,支持向量可能会翻倍,边界在线条上跟着样本绕弯。所以读模型输出时不要只看准确率,把 len(model.support_vectors_) 打出来,这个数越大,说明模型越复杂,越要怀疑过拟合。周志华《机器学习》把这一节定名为“间隔与支持向量”,一句话概括就是,SVM 学到的不是一条随便能分开的分界线,而是一条由最难分类的样本“顶住”的分界线。
这个数学直觉在作业里的落点,其实不是让你手推拉格朗日对偶,而是帮你解释两个现象:为什么支持向量少的模型往往更稳,为什么 C 和 gamma 动一下边界就会大幅变形。实验报告里如果能把“支持向量数”作为一个观察指标写进结果分析,整个报告的完成度马上不一样。很多同学交上来的源码里连打印支持向量的语句都没有,老师一眼就能看出是照基础教程跑通就结束了。
2.2 软间隔和 C:Iris 里的 versicolor 和 virginica 本来就有重叠
实际 Iris 数据里,versicolor 和 virginica 在花萼长宽两个维度上明显交叠,严格可分做不到。硬间隔要求所有样本到边界至少有余量,这个无解条件会让优化问题不可行。解决办法是引入松弛变量 ξ_i,允许样本以一定代价越过边界,目标函数变成 min 1/2||w||² + C·Σξ_i,这就是软间隔。软间隔不是改 kernel 能替代的——C 管的是“容忍度”,kernel 管的是“边界形状”,两个参数该分开调。
C 的实际手感是:C 越大,模型越不容忍错误,边界会尽量贴住训练样本,训练集准确率很好看,但测试集可能掉下来;C 越小,边界越平滑,允许个别样本越界,泛化通常更稳。C 趋近无穷大时,软间隔就退回成硬间隔。Iris 这种小样本上,C 在 1 附近通常就够用;你可以在作业里把 C 设成 [0.01, 0.1, 1, 10, 100] 跑一轮,把训练集和测试集准确率画成两条折线,观察它们在哪个区间开始分叉。
这个分叉点就是实验报告里最有说服力的素材。我见过太多实验报告只写“测试集准确率 96%”,完全不提用了什么 C、做了哪几次对比。把 C 和准确率的关系画出来,哪怕只解释一句“C 大于 10 后训练集继续上涨而测试集回落,呈现过拟合趋势”,这个实验报告的完整度就高了一截。注意折线图要标注随机种子,否则换个种子曲线就变,答辩时会被问为什么复现不出来。
提示:C 不是越大越好,也不是越小越好。搜参之前先固定 kernel,别把 kernel、C、gamma 一起随手改,否则你根本不知道是哪个参数造成的准确率波动。
2.3 核函数选择:线性核和 RBF 在 Iris 上差多少
核函数解决非线性边界:样本在当前维度分不开,映射到更高维空间后也许能用线性超平面分开。显式的特征映射计算量太大,所以用核函数在两个样本的内积里隐式完成。RBF 是最常用的一种,形式是 K(x_i, x_j) = exp(-γ||x_i - x_j||²),γ 控制单条“高斯山头”的宽度:γ 越小山头越宽,边界越平缓;γ 越大山头越尖,边界越曲折。
但 Iris 不是非线性很强的数据,核函数的选择并不直接影响能不能分开。标准化后用默认参数,线性核和 RBF 的测试准确率都在 95% 上下波动,真正拉开差异的是支持向量个数、训练耗时和边界形状。实验报告里可以固定 C 和 random_state,只替换 kernel,输出一张三行对比表。
| 核函数 | 测试准确率(参考区间) | 支持向量数(参考区间) | 决策边界特点 |
|---|---|---|---|
| linear | 0.95-0.98 | 较少,约 15-30 | 平直超平面,两两类别间的边界是直线 |
| poly | 0.93-0.97 | 中等 | 边界带曲率,degree 影响弯折程度 |
| rbf | 0.95-0.98 | 视 gamma 而定,可多可少 | gamma 小时接近线性,gamma 大时绕样本画圈 |
这张表不需要每个数字精确,关键是“支持向量数”这一列能反映边界复杂程度。对 Iris 的结论通常可以写成:线性核已经足够,RBF 的优势要等数据具备明显非线性结构时才体现。这个结论本身就是核函数选型的一次判断练习,比直接说“因为要调 gamma 所以不用 RBF”更像做过实验的样子。
写报告的方法部分时,把 kernel、C、gamma 的取值范围和搜索步长列清楚。源码里也要把这些参数作为变量,不要写在 fit 那一行里到处复制。后面第 4 章会给出一个可复现的网格搜索脚本,把这份对比表落到数据上。如果你用的是《机器学习》西瓜书配套练习或吴恩达机器学习作业的思路,这套“先固定简单模型、再逐项放开超参”的顺序也是通用的。
3. 数据准备到最小可交代码:Iris 的划分、标准化和线性 SVM
3.1 读 Iris 并划分训练 / 测试集:stratify 分层采样是必须项
sklearn 里读 Iris 的方式很固定:load_iris() 返回的 data 是 150×4 的 float 数组,target 是 0/1/2,target_names 对应 setosa、versicolor、virginica。很多作业源码第一步就把标签打出来确认类别顺序,这是好习惯。但在划分时常见一个错误:直接 train_test_split(X, y, test_size=0.3) 一拆了事,不写 stratify。
150 条样本里每类只有 50 条,随机划分不分层,某类在训练集里可能只剩二十几条。Iris 各类分布完全均匀,分层采样和随机采样多数情况下差距不大,但测试集只有 45 条,一旦某类在测试集里缺少数条,准确率波动就会被放大。所以更稳的做法是 stratify=y,让训练集和测试集里三类比例一致,这是作业里“实验流程规范”这一项最容易被扣分也最容易补上的点。
from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split iris = load_iris() X, y = iris.data, iris.target 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, X_test.shape) # (105, 4) (45, 4) print(y_train.min(), y_train.max()) # 0 和 2,三类都在逻辑说明:train_test_split 是 sklearn 的切分接口,test_size=0.3 表示测试集 45 条,stratify=y 按原始标签比例做分层采样,random_state=42 固定随机种子,保证再次运行得到同一份切分。print 两行是自检:第一行确认样本数,第二行确认切分后类别范围没变。参数说明:y_train.min() 和 y_train.max() 在 Iris 的 0/1/2 编码下分别是 0 和 2,如果打印出来只有 0 和 1,说明要么加载错了数据集,要么在导入时就过滤了类别。
如果你想把作业做得更完整,可以在切分前顺手看每个类别的样本数:np.bincount(y) 会输出 [50 50 50]。这个三行输出放在实验报告开头,能让老师一眼看到数据理解是到位的。吴恩达机器学习作业里的做法也是先按类别统计数据分布再进入建模阶段,这套流程在 Iris 上同样适用。另外,sklearn 新版里 load_iris(as_frame=True) 会返回 pandas DataFrame,如果你的作业要求展示特征统计表,用这个入口更方便,但训练时记得取 .values 转回 numpy 数组。
3.2 标准化:为什么必须用 StandardScaler 而不是 MinMaxScaler
SVM 的决策边界由样本到超平面的距离决定,距离计算依赖特征尺度。Iris 四个特征单位都是厘米,数值范围差异不算大,但花萼长 4.3-7.9,花瓣宽 0.1-2.5,比值超过 20 倍。不标准化会让 SVM 默认“数值大的特征更重要”,把主要权重押在花萼长和花瓣长上,而实际上花瓣宽对类别的区分度也很关键。这是 SVM 这类几何模型和树模型最大的区别,树模型不敏感,SVM 敏感,作业里说出这一句就说明你能解释模型原理。
StandardScaler 让每个特征均值为 0、方差为 1,公式是 z = (x - mean) / std。MinMaxScaler 把值压缩到 [0,1]。两者在 Iris 上都能用,差别在极端值的处理:MinMax 会把离群点压缩到很窄区间,StandardScaler 对离群点容忍度更高。作业里选 StandardScaler 的主要理由是它的统计更好解释,而且在实验报告里可以说“每个特征被变换到均值 0 方差 1”,比“压缩到 0-1”更贴近 SVM 的距离推导。
再强调一遍 scaler 的 fit/transform 顺序:scaler 只能 fit 在训练集上,再用同样参数 transform 测试集。如果先对全量 X fit 再切分,测试集的均值和方差就泄漏进了训练流程,属于实验流程不严谨。下面的代码先 fit_transform 训练集,再只 transform 测试集,顺序不要反。
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) print(X_train_scaled.mean(axis=0)) # 约 [0, 0, 0, 0] print(X_train_scaled.std(axis=0)) # 约 [1, 1, 1, 1]逻辑说明:fit_transform 在训练集上计算均值和标准差并完成变换,transform 只应用这组统计量。打印均值和标准差是为了自证标准化生效,实验报告里保留这两行输出,能直接回应“你为什么不直接跑原始数据”这个追问。参数说明:StandardScaler 默认 with_mean=True、with_std=True,对稠密矩阵直接用默认值;只有输入是稀疏矩阵时才需要改 with_mean=False,Iris 用不到。
还有一个容易忽略的点:标准化要放在切分之后。先切分再 fit scaler,和先 fit scaler 再切分,结果几乎一样,但前者才是严格流程。如果你嫌写起来麻烦,后面第 4 章的 Pipeline 方案会把这一步自动包进去,交叉验证的每一折内部只会用该折训练数据计算 scaler 参数,这是最不容易被答辩问倒的组织方式。
3.3 训练线性 SVM 的最小代码块及关键参数解读
线性 SVM 是这份作业的主体。很多同学一上来就上 RBF,其实先跑通线性核,把预测结果、支持向量、分类报告都看一遍,再决定要不要换核,才是正常的实验节奏。代码不用多,能说明“训练-预测-评估”闭环就行。
from sklearn.svm import SVC from sklearn.metrics import accuracy_score, classification_report model = SVC(kernel='linear', C=1.0, random_state=42) model.fit(X_train_scaled, y_train) y_pred = model.predict(X_test_scaled) print("准确率:", accuracy_score(y_test, y_pred)) print(classification_report(y_test, y_pred, target_names=iris.target_names)) print("支持向量个数:", len(model.support_vectors_))逻辑说明:SVC 是 sklearn 的 C-SVM 实现,kernel 指定核函数,C 是软间隔惩罚系数,random_state 固定随机种子。fit 之后模型记录 support_vectors_ 和 dual_coef_,predict 对新样本做类别推断。classification_report 输出每类的精确率、召回率、F1 和样本数,是实验报告里比单一准确率更有信息量的指标。支持向量个数直接打印出来,和第 2.1 节的讨论对应,用于判断边界复杂程度。
参数说明:decision_function_shape 在多分类时控制 one-vs-rest 还是 one-vs-one,sklearn 的 SVC 默认就是 ovr,作业里不需要显式写;如果你在旧版本或新版本之间出现结果对不上,优先检查这一项。准确率的参考区间在 0.94-1.0 之间,和切分与随机种子相关。如果低于 0.9,先检查标准化和 stratify,而不是盲目调大 C。C 从 1 提 到 1000 也许能把训练集准确率从 97% 拉到 100%,但测试集可能从 97% 掉到 93%,这组实验数据本身就是报告里的过拟合论据。
这里有一个多分类的细节经常被忽略:SVC 内部对多分类默认使用 one-vs-rest,也就是把三个类别分别和其余两类对比,训练三个二分类器。Iris 只有 150 条样本,这种策略完全够用。如果想在报告里探讨 one-vs-one 和 one-vs-rest 的差异,可以显式传 decision_function_shape='ovo' 跑一次对比,但结论通常是准确率差别甚微,反而增加训练次数。作业源码里保留默认即可,报告里写清楚用的是哪条路径。
4. 从调参到可视化:核函数、gamma、网格搜索和决策边界
4.1 在 Iris 上对比 linear / poly / rbf:不要只看准确率
把三种核放在同一个脚本里对比,是实验报告里常见的一节。用循环而不是复制三次代码,能减少把某个参数改错的概率。打印项里除了准确率,还要有支持向量数和训练耗时,这两个指标对结论的支撑作用比准确率本身更强。准确率在 95% 和 98% 之间的差别,放在 45 条测试集上可能只有一两个样本之差,但支持向量数的差异能说明模型结构完全不同。
import time for kernel in ['linear', 'poly', 'rbf']: t0 = time.time() model = SVC(kernel=kernel, C=1.0, random_state=42) model.fit(X_train_scaled, y_train) acc = model.score(X_test_scaled, y_test) sv = len(model.support_vectors_) print(f"{kernel:8s} acc={acc:.3f} sv={sv:3d} 耗时={(time.time()-t0)*1000:.1f}ms")逻辑说明:for 循环把 kernel 当作唯一变量,C 和随机种子全部固定,保证对比条件一致,支持向量数用 len(model.support_vectors_) 取得。训练耗时用 time.time() 差值,用于说明核函数复杂度差异。production。参数说明:poly 核默认 degree=3,Iris 上 poly 并不比 linear 好,如果实验报告想压缩篇幅,可以把 poly 去掉,只保留 linear 和 rbf 的对比。这里的 f-string 里 :8s 是对齐格式,:3d 是三位整数对齐,纯粹为了让终端输出整洁。
对比结果可能出现的参考区间是:linear 准确率约 0.95-0.98,poly 约 0.93-0.97,rbf 约 0.95-0.98;支持向量数 linear 最少,rbf 因 gamma 默认值而异。这个结果说明一个容易被忽略的事实:在特征区分度足够且特征维度不高的情况下,无脑上 RBF 并不会带来收益。实验报告的结论可以写“线性核在 Iris 上性能与 RBF 接近,因支持向量更少、可解释性更强,选线性核作为最终模型”。这句话比“我选了 RBF 因为它是默认的”更像做实验得出的判断。
4.2 网格搜索找 C 和 gamma:在小样本上如何避免把验证集“搜穿”
网格搜索在 Iris 这种小数据上很常见,但有两个隐患:一是参数网格给得过大,搜出的最优组合很可能是在某个特定划分上过拟合;二是忽略了标准化应该放进 Pipeline。这两个问题都会让 GridSearchCV 的输出看起来很好,但测试集复现不出来。Iris 的测试集只有 45 条,一个参数组合在这 45 条上多猜对两条,准确率就高了 4 个百分点,这种差距没有统计学意义,报告里要会用交叉验证分数来中和。
from sklearn.model_selection import GridSearchCV from sklearn.pipeline import Pipeline pipe = Pipeline([ ('scaler', StandardScaler()), ('svm', SVC(random_state=42)) ]) param_grid = { 'svm__kernel': ['linear', 'rbf'], 'svm__C': [0.1, 1, 10], 'svm__gamma': [0.01, 0.1, 1] } grid = GridSearchCV(pipe, param_grid, cv=5, scoring='accuracy', n_jobs=-1) grid.fit(X_train, y_train) print("最优参数:", grid.best_params_) print("交叉验证最优分数:", grid.best_score_) print("测试集分数:", grid.score(X_test, y_test))逻辑说明:Pipeline 把 StandardScaler 和 SVC 串成一个整体,GridSearchCV 在每一折交叉验证中只在该折训练数据上 fit scaler,避免标准化泄漏验证集信息。svm__kernel 这种写法指定的是 Pipeline 里名为 svm 的步骤的参数。cv=5 在 105 条训练数据上每折约 21 条验证,搜索过程会评估 18 组参数、每组建 5 个模型,总共 90 次 fit,Iris 上完全跑得动。参数说明:n_jobs=-1 让多核并行,小数据上差别不大,但写法规范;scoring='accuracy' 在多分类下就是正确率均值,不涉及 precision 的平均方式问题。
这里要提醒一个常见的坑:网格里同时放了 linear 和 rbf,但 linear 核根本不需要 gamma,GridSearchCV 会自动跳过 linear 下的 gamma 参数,不会报错。如果你在 sklearn 1.0 和 1.3 两个版本里跑出不同结果,先检查环境,两个版本之间 SVC 的默认 gamma 计算方式有过调整。作业里把这些环境信息写进实验报告最后,能省很多答辩时的麻烦。网格搜索结果里如果最优参数是 C=10、gamma=0.01 这类边界值,说明你的网格范围没覆盖到真正的合理区间,应该把 C 往更大方向扩展再搜一轮。
4.3 画决策边界图:把支持向量标出来,报告才有“图”
Iris 是四维数据,不能直接可视化全部特征空间。常见做法是选两个特征组合训练一个简化模型,在二维平面上画决策区域。区分度最高的是 petal length 和 petal width,画出来的边界清晰,支持向量也能看出是“顶在边上”的。实验报告里放这样一张图,比放十行 print 输出更有说服力,也是“源码+实验报告”这个模板里最能出彩的部分。
import numpy as np import matplotlib.pyplot as plt X2 = iris.data[:, [2, 3]] # petal length, petal width X2_train, X2_test, y2_train, y2_test = train_test_split( X2, y, test_size=0.3, stratify=y, random_state=42) model2 = SVC(kernel='linear', C=1.0, random_state=42) model2.fit(X2_train, y2_train) x_min, x_max = X2[:, 0].min()-0.5, X2[:, 0].max()+0.5 y_min, y_max = X2[:, 1].min()-0.5, X2[:, 1].max()+0.5 xx, yy = np.meshgrid(np.linspace(x_min, x_max, 300), np.linspace(y_min, y_max, 300)) Z = model2.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape) plt.contourf(xx, yy, Z, alpha=0.3) plt.scatter(X2_train[:, 0], X2_train[:, 1], c=y2_train, edgecolor='k') plt.scatter(model2.support_vectors_[:, 0], model2.support_vectors_[:, 1], s=100, facecolors='none', edgecolors='red', label='SVs') plt.xlabel('petal length (cm)') plt.ylabel('petal width (cm)') plt.legend() plt.show()逻辑说明:X2 取的是 iris.data 的两个列索引,画图用 2D 数据训练另一个模型,目的是在平面坐标上逐点预测。np.meshgrid 在 [最小值-0.5, 最大值+0.5] 区间生成 300×300 的网格坐标,np.c_ 把两组坐标拼接成 (90000, 2) 的数组,一次 predict 得到每个网格点的类别。plt.contourf 用半透明色块画出三个决策区域,支持向量用红色空心圆标出。参数说明:300 是网格分辨率,数值越大边界越平滑、计算量越大;facecolors='none' 画空心圆,是为了让支持向量不遮住样本点。
注意图注。这张图只用两个特征训练了一个简化模型,和用四个特征训练的最终模型不是同一个。报告里写“基于 petal length 和 petal width 训练二维 SVM 用于可视化”比较好。如果直接写“SVM 决策边界图”,答辩时可能被指出“模型只用了两个特征”。想验证这一点也很简单:把 model2 的支持向量和 model(四维模型)的支持向量对比,两者选中样本往往不同。可视化只是展示工具,不是最终模型的边界,这一点在报告里写清楚,反而显得你理解透彻。
5. SVM 作业常见问题与避坑:5 个典型翻车点
5.1 没标准化,准确率莫名低了 10 个点
现象:同一份数据,换台电脑或换个同学复现,准确率从 0.97 掉到 0.87,代码看起来一模一样。
原因:没做 StandardScaler,或者把 scaler 在切分后的训练集和测试集上重复 fit 了。SVM 是几何模型,特征尺度直接影响距离计算,量纲大的特征会主导边界位置,模型在原始数据上会把大部分权重押在数值大的特征上,把区分度高的中小量纲特征忽略掉。
解决:把标准化放进 Pipeline,和 SVC 挂在一起,不要写成两个松散变量。自查时打印 X_train_scaled.mean(axis=0) 和 std,均值接近 0、方差接近 1 才算生效。如果这一步没问题,再检查是不是随机种子没固定,两种原因都要在报告里交代清楚。
5.2 混淆矩阵几乎全 0,只有对角线有数
现象:classification_report 输出里只有 setosa 一行分数正常,versicolor 和 virginica 行全是 0 或者重复同一行,测试集准确率看起来还挺高。
原因:检查 y 在导入后有没有被赋值为二分类。常见写法 y = (y == 2).astype(int) 把三分类压成了两类,后面忘了改;或者训练时传的 y 是 DataFrame 里某一列,但列名和数据内容对不上。
解决:训练前打印 np.unique(y_train) 和 np.unique(y_test),确认两者都包含 0、1、2 三个值。这两行代码能排除掉一大类“三类问题当成二类处理”的案例。如果测试集准确率高但混淆矩阵只有对角线,再加一行 print(len(np.unique(y_train))),等于 3 才继续往下跑。
5.3 gamma 设成 1,训练集 100% 测试集 85%
现象:RBF 核在训练集上分数拉满,测试集掉了很多,数据量本身又不大,调参空间看起来很小。此时的决策边界图往往是锯齿状,支持向量数量占了训练集的一半以上。
原因:gamma 控制 RBF 的局部性,gamma 越大越容易在每个训练点附近画圈,模型在记样本而不是学规律。Iris 这种 105 条训练样本的数据,RBF gamma 超过 1 之后几乎必然过拟合。
解决:把 gamma 设成 [0.01, 0.1, 1, 10] 跑网格搜索,观察训练集和测试集分数曲线。实验报告里画出这两条线,直接对应你做出的超参选择:选 gamma=0.1 是取了训练集和测试集的平衡点,而不是只看最高的一次准确率。
5.4 两次运行结果不一样,复现不出报告里的数字
现象:昨天跑出的测试准确率 0.96,今晚重跑变成 0.93,代码一个字没动,连随机种子都写在里面了。
原因:train_test_split 里固定了 random_state,但 SVC 里的 random_state 没固定;或者切分时 stratify 写错了位置。还有可能是 sklearn 版本变化,不同版本之间 SVC 的默认 gamma 计算方式、decision_function_shape 的默认值都有过调整。
解决:在 train_test_split 和 SVC 里都显式写 random_state=42。报告最后加一段“实验环境”,写清楚 Python 和 scikit-learn 版本。源码里把版本号打印出来再跑,比在报告里手写版本号靠谱,因为手写容易抄错。
5.5 决策边界图画得一团糟,边界线从数据中间穿过去
现象:二维散点图里,决策区域和实际样本分布错位,边界线横穿某类样本,区域颜色和点的颜色对不上。看代码逻辑没毛病,但图就是不对。
原因:画网格时用了整个数据集的范围,但模型只用训练集拟合;或者模型用了标准化后的数据,画图却用原始坐标。还有一个常见原因:网格点预测时传错了特征列顺序,把 petal_length 和 petal_width 的位置交换了。
解决:训练数据和画图数据必须完全一致。如果训练时用了 StandardScaler,画图前也要对网格点用同一个 scaler.transform 再 predict。用 meshgrid 之前先打印 X2.shape,确认这是二维数组;再打印 scaler 的参数,确认训练和图用的是同一组统计量。
6. 交作业前做三个验证:随机种子、交叉验证分数、模型持久化
源码和实验报告都写完,先别急着交,做三个快速验证,每一项都不超过五分钟。
第一个验证是固定随机种子。把 train_test_split 和 SVC 的 random_state 都写成同一个值,重跑一遍脚本,确认准确率、支持向量数、混淆矩阵三个输出和第一次完全一致,不一致就说明还有随机源没固定。
第二个验证是比较交叉验证分数和测试集分数。GridSearchCV 的 best_score_ 是交叉验证均值,单独跑一次测试集得到 test score,两者差距如果在 3 个百分点以内,说明模型对数据划分不敏感;如果差距超过 5 个百分点,说明当前切分里可能碰上了比较特殊的样本,需要回看测试集类别分布,而不是急着改参数。
第三个验证是保存模型和预测结果,给答辩前的自己留一颗后悔药:
import joblib joblib.dump(model, "iris_svm_linear_C1_seed42.joblib") loaded = joblib.load("iris_svm_linear_C1_seed42.joblib") print(loaded.score(X_test_scaled, y_test))逻辑说明:joblib 是 sklearn 官方推荐的模型持久化工具,文件名里带上模型类型、C 值和随机种子,方便答辩现场直接加载复现。加载后重新算一次 score,验证保存过程中没有量纲或参数丢失。参数说明:如果模型里包含 StandardScaler,建议整个 Pipeline 一起 dump,而不是只存 SVC,否则加载后还要手动拼回 scaler。
实验报告的结构也值得按这个顺序排:数据说明和划分方式、特征标准化前后的统计对比、线性 SVM 基线结果、核函数和超参对比、最终模型参数、交叉验证与测试集分数、可视化图、实验环境。每张图下面写一句结论,每个表格后面说明选择依据。做到了这些,源码和实验报告就不再是两个分离的交付物,而是一套能让别人照着复现出同一份结果的完整方案。我习惯在自己电脑上把代码完整重跑一遍,然后把训练日志原样贴进报告附录,截图都不需要了。希望帮到你。
本文还有配套的精品资源,点击获取