简介:这是一份面向高校机器学习课程期末大作业与课程设计的高分项目合集,包含多组可运行的Python源码与配套实验报告,覆盖决策树、聚类、回归、分类等常见实验题目,代码中带有详细注释,新手也能读懂并快速部署使用。压缩包整体约2.94MB,共15个文件,其中10个py文件为核心实现脚本,2个csv为实验数据,另含txt说明、data数据文件及docx格式的实验报告,结构清晰,便于按章节或题号查阅。当前已有382人浏览学习,适合需要参考完整思路、完成课程作业或提升项目实践能力的学生使用。资源源自西安电子科技大学机器学习大作业,实验报告与代码一一对应,详细注释覆盖数据读取、模型训练和结果可视化等关键环节。整体功能完整、界面简洁,可直接作为期末提交或课程设计的优质模板。
1. 机器学习大作业拿高分:先搞清楚评审在找什么
机器学习大作业不像期末考试,没有标准答案,评分盯的是流程完整度、结果可信度和报告解释力。所谓“项目合集”,就是把你交的东西拆成三份:选对题目、写好代码、把报告做成能照着复现的实验册子。高分项目往往不是模型最深的那个,而是每步都有据可查的那个。很多人把时间全砸在调参上,最后却栽在报告没有对比基线、代码换台机器就跑不动。这篇文章就按冲高分的路线,把项目选题、代码规范、实验报告和常见扣分点一次讲清楚,适合想拿高分、或想把课程项目收进简历的读者。
2. 高分项目怎么选题与搭基线:三类必做项目与数据选型
选对题是投入产出比最高的一步。机器学习的应用流程其实就是数据理解、建模、评估、解释四步,课程大作业不管写成什么样,评审都在这四步上找分数。常见大作业能归成几类:表格分类、回归与时序预测、聚类降维,再往外一点还有文本分类和图像分类。但课程时间有限,评审更在意“把一个题做完整”,而不是“同时开三个题全部烂尾”。所以我一般把“项目合集”当成一个项目库来用,先按难度和兴趣各挑两三个候选题,再用三条标准过滤:数据能不能轻松拿到、baseline 能不能半小时内跑通、题目有没有可解释空间。三条都满足,才值得往下走。
2.1 分类题:从结构化数据到图像,先定难度阶梯
最常见也最稳妥的是结构化表格分类,这类题数据量小、训练快,特别适合撑起“数据清洗 + 特征工程 + 模型对比”的完整流程。我习惯把分类题分成三档。第一档用 sklearn 内置数据集,比如鸢尾花、乳腺癌、手写数字,几分钟跑完,适合只剩一天的情况,但缺点是太常见,开题容易撞车。第二档从公开渠道下载真实业务数据,比如银行营销、电信客户流失、垃圾短信,特征是类别不平衡或缺失值严重,写“机器学习中的数据处理是什么”这个环节时就有真实材料可用。第三档是 Kaggle 上几十万行的大数据,能写最丰富,但也最容易让人陷在调参里出不来,作业反而烂尾。多数高分项目的位置在“第二档任务 + 认真做实验对比”,而不是第三档。
选数据时还有一个容易忽略的硬件问题:如果课程机房的机器只有 4GB 内存,就不要选动辄几百 MB 的特征矩阵。我见过一个小组选了 Kaggle 上几十万行的点击率预测数据,预处理跑了一个通宵,最后报告只能写“由于内存不足,实验未完成”,这等于直接放弃评分点。图像分类这边,MNIST 或 CIFAR-10 的子集都是安全选项。如果课程允许 PyTorch,MNIST 用 LeNet 半小时就能训完,再用 SVM 做 baseline,报告里就有传统算法与深度网络的完整叙事。下面这个分类代码骨架用 load_digits 而不是 MNIST,因为它在课堂演示时加载最快、不依赖外网,适合做作业起点:
from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix # 加载数据,先看形状和类别,这一步写进报告的“数据理解” digits = load_digits() print(digits.data.shape) # (1797, 64),8x8 图像展平 print(digits.target_names) # 类别 0-9 # 分层划分,保证训练/测试里数字类别比例一致 X, y = digits.data, digits.target X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) # baseline:随机森林默认参数,先出一个底线分数 rf = RandomForestClassifier(random_state=42) rf.fit(X_train, y_train) print("test acc:", rf.score(X_test, y_test)) # 输出混淆矩阵,报告里放这张图能直接撑起一页 print(confusion_matrix(y_test, rf.predict(X_test)))这里有两个容易出错的操作。train_test_split 的 stratify 参数表示按类别比例分层划分,如果某个数字本身样本少,不做分层就可能出现训练集里有 0 但测试集没有 0,混淆矩阵画出来缺一行;random_state=42 让划分结果固定,否则每次运行训练集都不一样,报告里的准确率就成了不可复现的数字。RandomForestClassifier(random_state=42) 里的随机种子最好也固定,这棵树虽然在作业里只是 baseline,但后面所有对比实验都要以它为准。baseline 的意义是提供一个最低参照线,之后 SVC、xgboost 这些模型的改进幅度全部由它衬托出来。
如果你是第一次做这类题,建议把代码按“读数据、划分、训练、评测”四步组织。读数据段打印 shape 和类别数量,划分段写明 test_size 和 random_state,训练段用同一个固定种子,评测段输出准确率、F1 和混淆矩阵。把四段都写进报告对应的小节,评审不看完整代码也知道你做了什么。第二个常被忽略的细节是类别分布,打印 y_train 的 value_counts,如果发现某一类比另一类多好几倍,报告里就自然引出一段类别不平衡的讨论,哪怕只是用 class_weight='balanced' 跑一次对比,也说明你想到了这个问题。
2.2 回归与时序题:可解释性比精度更值钱
回归题里,波士顿房价数据集因为有敏感属性争议,已经不适合再作为课程项目,常见替代是加州房价、Kaggle 二手房数据或超市销售额预测。时序题里,电力负荷、气象温度、电商销量都比较安全,而股票预测看起来炫酷但对课程作业是个坑——股价噪声强、特征语义弱,老师追问“你的特征依据是什么”时很难讲清楚。我的选型逻辑是:回归和时序题优先选特征有明确业务含义的数据,因为这类作业的价值不在把 RMSE 压到多低,而在你能解释误差来自哪里。线性回归、随机森林和 XGBoost 的对比在结构化数据上很容易出图表,再配上 SHAP 或 feature_importances_,报告的可信度一下子就能上去。
import pandas as pd from sklearn.datasets import fetch_california_housing from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_squared_error, r2_score housing = fetch_california_housing() X, y = housing.data, housing.target X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) model = RandomForestRegressor(n_estimators=100, random_state=42) model.fit(X_train, y_train) pred = model.predict(X_test) print("RMSE:", mean_squared_error(y_test, pred, squared=False)) print("R2:", r2_score(y_test, pred)) # 特征重要性导出成 csv,报告里做成直方图 importance = pd.Series(model.feature_importances_, index=housing.feature_names) importance.sort_values(ascending=False).to_csv("feature_importance.csv")这里的两个指标要一起看。RMSE 是均方根误差,单位与目标一致,能说明预测偏离真实值多远;R2 是决定系数,表示模型解释了目标方差的百分比。只放 RMSE 会被问“这个数对业务意味着什么”,只放 R2 会被问“离群点是不是被忽略”,两个都放基本能堵住这两个问题。n_estimators=100 是随机森林的起点,作业数据集通常只有几万行,继续加到 300 回报很小,训练时间却翻三倍。feature_importance.csv 导出后不要只贴在报告里,最好画一张排序条形图,并在图注里写一句最明显的结论,比如“MedInc 是预测房价最重要的特征”。就算这是常识,输出它说明你做了归因,而不是只会 print 分数。
如果题目要求必须用深度学习,LSTM 是最常被抄的模型。网上“lstm模型代码”绝大多数是股票预测 demo,结构相同换数据就能跑,但报告只贴代码、不写清输入窗口长度、预测步长和损失函数,评审大概率直接判为代跑。用自己的话说清楚一件事就够了:你是如何把原始序列切成滑窗样本的。滑窗长度太长会让样本数量变少,太短则学不到周期;多试几个窗口长度做对比,报告里写“窗口 24 小时的误差比窗口 6 小时低 8%”,这段话比模型结构值钱得多。
2.3 基线先行:sklearn pipeline 必须先跑通
大作业翻车的一个共性原因是跳过 baseline,上手就训练高级模型,跑了半天发现被线性回归打败,心态崩了不说,报告也写不出对比。我一般强制自己的流程是“先让最朴素模型跑通、打印出指标、存下日志,再考虑改进”。所谓 baseline 不一定是最差模型,而是逻辑上最简单、几乎不可能出错的方案:分类用逻辑回归或决策树,回归用线性回归,图像任务用原始像素加 SVM。它存在的意义是证明你的数据管线没问题,也证明后续模型的提升不是玄学。
from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC pipe = Pipeline([ ("scaler", StandardScaler()), ("svm", SVC(kernel="rbf", C=1.0, gamma="scale", random_state=42)), ]) pipe.fit(X_train, y_train) print("SVM baseline acc:", pipe.score(X_test, y_test))Pipeline 在这里有两个作用。第一,把预处理和模型绑定成一个整体,fit 时只学习训练集的均值方差,predict 时自动对测试集做同样的 transform,从结构上杜绝数据泄漏;第二,和 GridSearchCV 配合时,参数网格可以直接写成 pipe.get_params() 的形式,预处理参数和模型参数可以同时搜索,代码只需要维护一张参数表。gamma="scale" 是 sklearn 对 RBF 核的默认启发式,值会按特征数量自动调整,作业阶段几乎不需要手动指定;C=1.0 是正则强度的起点,后续网格搜索一般从 0.1、1、10 这几个量级里选。注意这里接的是前面分类题的变量,如果重新打开脚本单独运行,必须先把数据加载和划分的代码也保留在同一文件里。
baseline 跑通之后,第一个该做的改进实验不是换模型,而是把特征标准化加上再看一遍效果。很多同学直接跳到网格搜索,却不清楚标准化对 SVM、逻辑回归这类基于距离的模型影响有多大。标准化的对比实验只需要三行代码,却能在报告里贡献一条“预处理对精度的影响”曲线,这是一道送分题,不做非常可惜。下面的第 3 章会讲如何把这份能跑的代码,升级成一份能提交、能复现、能答辩的工程化代码。
3. 把代码写到“可复现”:训练脚本、随机种子与实验记录
代码部分的高分标准不是“能跑”,而是“别人能跑出和你一样的结果”。一份可复现的作业代码在提交时应该满足三件事:执行入口明确、随机性可控、实验结果落盘。做到这三点,答辩时老师按 README 跑一遍能复现你的数字,项目就已经进入“工程合格”的评分档;做不到,算法再花哨也容易被当成黑匣子处理。下面按这三个目标分别给最小实现。
3.1 训练脚本的最小结构:argparse + config + 日志
最常见的翻车现场是老师拿到你的 .py,双击运行,ModuleNotFoundError 或路径报错。这不是算法问题,是入口和配置写得不够严格。我给自己定的纪律是:任何项目保留一个 train.py 作为唯一入口,所有可调参数从命令行或配置文件注入,不在代码里写死文件夹路径。这样不管是自己复盘还是老师复跑,一行命令就能重现实验。下面这个骨架虽然短,但结构是完整的:
import argparse import time def parse_args(): parser = argparse.ArgumentParser(description="ML homework experiment") parser.add_argument("--data", default="data/digits.csv", help="dataset path") parser.add_argument("--model", default="rf", choices=["rf", "svm", "xgb"]) parser.add_argument("--seed", type=int, default=42) parser.add_argument("--out", default="results/", help="output dir") return parser.parse_args() def main(): args = parse_args() t0 = time.time() # 你的训练逻辑写在这里,不要散落在全局变量里 print(f"[INFO] model={args.model}, seed={args.seed}, cost={time.time()-t0:.2f}s") if __name__ == "__main__": main()这套结构的核心是 argparse。它把数据路径、模型名、随机种子全部变成运行时参数,这意味着你可以在命令行里做这样的事:同一份数据,换一个 --seed 跑三遍,把三组数字都记在报告里,证明结果不是一次性运气。--out 参数指定结果目录,实验日志和 CSV 都往这个目录写,避免每次运行覆盖上次记录。我的习惯是把每次实验用的命令行原样保存在 results 下的 run_cmd.txt 里,答辩被问起就能直接调出来看,不需要翻聊天记录。参数维护上,少量参数用 argparse 够用,参数超过十个时换成 yaml 文件管理,方向是一样的:参数不散落在代码里。
3.2 随机种子和划分顺序:让三次运行能跑出同一个数
机器学习实验天然带随机性:数据划分的随机、模型初始化的随机、SGD 的采样顺序随机。如果不固定这些随机源,同一个脚本跑两次得到两个精度,报告里的任何数字都缺乏说服力。相反,固定种子之后跑三次结果几乎一致,报告写“准确率 97.1%”就变成了一个可验证的声明。固定种子相当于给实验买后悔药,以后发现报告里某个数字可疑,还能用同一条命令重新跑一遍来核实。种子问题的坑在于它不是全局设置一次就完事,numpy、random、torch 各有各的随机源,漏掉其中一个在简单模型上可能看不出来,换到神经网络就明显。
import random import numpy as np def set_seed(seed: int = 42): random.seed(seed) np.random.seed(seed) try: import torch torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) except ImportError: pass # 没装 torch 时不影响其他任务逻辑说明:set_seed 统一设置三个随机库的种子,并把 torch 相关的 CPU、GPU 随机源也固定,避免数据加载和网络初始化在不同机器上产生偏差。参数说明:seed 取值在作业里用 42 就够,真正做研究时才需要跑多组种子取均值;你在报告里只需注明种子值和设置函数的调用位置。PyTorch 的 DataLoader 还有一个细节:shuffle=True 时如果不给它显式传入 generator,它内部仍会使用全局随机源,光调用 set_seed 不一定够,最稳妥的写法是给 DataLoader 传一个固定的 torch.Generator。另外,set_seed 要放在任何随机操作之前调用,最好在 import 数据之前就调,而不是在模型初始化前才调。
提示:固定随机种子后,如果两次运行结果仍有明显差异,先检查数据读取顺序是否稳定,再检查是否有并行操作引入了额外随机性。dict 的遍历顺序在部分自定义代码里也可能影响实验结果。
3.3 训练过程落盘:把指标存成 CSV 而不是 print
每个 epoch 的 loss、准确率、F1 只 print 到控制台等于没记录,关掉窗口就再也找不回来了。正确做法是把训练历史逐行写进 CSV,再做可视化。CSV 的好处是既可以用 pandas 读,也可以直接在 Excel 里打开,还能作为报告的附录数据。我常用的记录方式是这样的:
import pandas as pd history = [] # 每次 epoch 或每次实验追加一条 for epoch in range(10): # 这里的 train_loss 和 val_acc 要从真实训练循环里算出来 train_loss = 0.35 - 0.02 * epoch val_acc = 0.70 + 0.03 * epoch history.append({"epoch": epoch, "train_loss": train_loss, "val_acc": val_acc}) df = pd.DataFrame(history) df.to_csv("results/rf_seed42.csv", index=False)实际训练时,train_loss 和 val_acc 要在训练循环里从 dataloader 或验证集计算,这段代码只是把记录逻辑独立出来。关键点是“记录”这一步不能省。有了 CSV 之后,报告里的训练曲线就不要用截图了,直接从这个文件画,图表区间一致、数据真实。epoch 次数在表格数据集上建议取 20 到 50,图像任务 50 到 100,超过这个范围收益很小;结果文件名建议带模型名和种子,如 rf_seed42.csv,避免多组实验互相覆盖。如果答辩时老师要求现场重跑,你只需要让他看这个 CSV 文件的修改时间,证明这是这台机器上刚生成的,而不是从别处找来的。
4. 实验报告按评分点组织:摘要、数据、方法、结果一个都不能少
实验报告本质上是给评审的一份证明文件:证明你读懂了数据,证明你的方法有依据,证明你的结果是真实实验出来的。评审看一份大作业报告的时间通常只有几分钟,动作不外乎三个:先读摘要,再翻图表,最后查参数表。所以报告的结构应该按这三个动作设计,而不是按你写代码的顺序。下面按六个段落展开,每段都有明确的交付目标。
4.1 问题定义与数据理解:写清“为什么选这个题”
摘要控制在 150 字以内,写清三件事就够了:用了什么数据、做了什么任务、最好结果是多少。比如“基于银行营销电话数据集,构建客户定期存款认购预测模型,通过逻辑回归、随机森林与 XGBoost 的对比实验,最优模型在测试集上 F1 达到 91.2%”。如果你用的算法比较多,摘要里只提最有说服力的一个,其余让实验部分去说明。具体数字换成你自己实验跑出来的即可,格式照抄。数据理解段要回答四个问题:数据从哪来、字段含义是什么、样本量有多少、类别是否平衡。这四件事用一段文字加一个字段表就能讲完,不需要长篇大论。
字段表要像论文里的 Table 那样写:字段名、类型、缺失率、处理方式占四列。数据处理那一节被很多同学跳过,但“机器学习中的数据处理是什么”这个问题的答案,恰好就是报告里最容易拿分的一段。标准化、缺失值填充、类别编码、离群点处理,每一行处理都要写“为什么这么做”。比如“age 字段存在 5 个 999 的异常值,视为缺失并填充中位数”,这个粒度就够了。不要把所有处理混成一句“对数据进行了标准化”,那等于没说。表格示例如下:
| 字段 | 类型 | 缺失率 | 处理方式 |
|---|---|---|---|
| age | 数值 | 0% | 标准化 |
| job | 类别 | 2.1% | 众数填充 |
| y | 二分类 | 0% | 无需处理 |
4.2 方法描述与参数表:把“调了什么”写成人话
方法段最常见的错误是大段贴代码。代码对评审没有信息量,他要看的是一张“方法+参数”对照表。模型名加关键参数、训练时间、测试指标,四列一表基本覆盖所有疑问。写参数时要避免两种极端:一个参数不写,或者把所有参数全部抄一遍。只写那些会影响结果的参数,比如 SVM 写核函数、C、gamma,随机森林写 n_estimators、max_depth,神经网络写学习率、batch size、epoch。表格里加一列训练时间能反映你对计算代价的考虑,这在模型对比时尤其重要。
模型的选型理由也要写一句,不是从网上抄的定义,而是基于数据特性的一句话。比如“数据维度较高且可能线性不可分,因此使用 RBF 核 SVM”;“特征间可能存在交互,随机森林对这种结构更鲁棒”。这句话不需要非常严谨,被追问时有你自己的思考就行。方法段末尾要交代评价指标和划分方式,比如“采用 8:2 分层划分,以准确率和加权 F1 作为评价指标”。划分方式决定结果能否横向比较,不写清楚的话,后面所有数字都悬空。参数表的参考样式:
| 模型 | 关键参数 | 训练时间 | 测试准确率 |
|---|---|---|---|
| 逻辑回归 | C=1.0 | 0.2s | 92.3% |
| SVM-RBF | C=10, gamma=0.01 | 1.8s | 96.5% |
| 随机森林 | n_estimators=200, max_depth=10 | 3.2s | 97.1% |
4.3 结果对比与结论:差 0.5% 也要解释原因
结果段用一张总表开头,列出每个模型在测试集上的指标,随后用一张训练曲线图说明收敛情况。总表的格式直接决定评审耐心:模型名、准确率、F1、训练时间,四列足够。这里有个小原则:所有结果必须是同一次划分、同一个测试集上跑出来的,不能模型 A 用在 8:2 划分、模型 B 用在交叉验证上,两种结果放进同一张表是不可比的。如果用了交叉验证,就在表里写 mean±std,例如 96.8%±0.4%,这比单一数字有力得多。
结论段不要说“模型表现良好”,要解释“为什么这个模型最适合这个数据”。哪怕是“SVM 在这个 64 维特征空间里找到的分隔面比随机森林更干净”这种不完全严谨的话,也展示了分析能力。如果两个模型精度只差 0.5%,更需要解释:是随机波动还是确实提升了?你可以写“两者差异小于交叉验证的标准差,因此认为性能相当”,这句话既诚实又能防止被追问。报告最后还可以加一小段“不足与改进”,写“数据量较小导致深度模型优势不明显,后续可以收集更多数据微调预训练模型”,这比硬吹自己的模型好很多。
5. 大作业避坑指南:五个让分数腰斩的典型问题
5.1 数据泄漏:训练集里混进了测试集
现象:训练准确率正常,但测试集分数高得离谱,比如 99.8%;或者老师重跑时,你的分数完全对不上。原因:数据划分之前就做了标准化或缺失值填充,让测试集的信息提前进入了训练过程。比如对整个 X 调用 fit_transform,再从结果里切分训练测试集,这属于教科书级泄漏。解决:把预处理放进 Pipeline,或者严格按“先划分,再对训练集 fit_transform、对测试集只 transform”的顺序写。自查方法是全文搜索 fit(X_all) 和 fit_transform(X_all),出现基本就是泄漏。
5.2 报告贴了代码但没贴结果
现象:实验报告一半篇幅是代码,翻到最后一页没有评估表、没有曲线、没有日志输出。原因:把“代码能跑”当成“实验完成”,结果只存在于控制台窗口,关了就没了。解决:训练脚本必须写 CSV,报告里引用 results 目录下的实际文件。答辩时被问“你这个准确率哪来的”,你的回答应该是“rf_seed42.csv 里的第五行,测试集 10 折平均”,而不是“好像之前跑出来过”。
5.3 画图不标轴、不标单位
现象:图上只有两条彩色折线,没有横轴名、没有纵轴名、没有图例,标题叫“Figure 1”。原因:matplotlib 的默认风格太朴素,而大家普遍觉得“能显示就行”。解决:统一画图模板,每个图至少包含 title、xlabel、ylabel、legend,需要对比时再加 grid,保存 dpi 不低于 150。写一个 plot_results(file) 工具函数,所有实验共用它,风格一致,省去逐个调图的时间。图里曲线描述也要写清楚,比如“实线是训练损失,虚线是验证损失”,不要只丢一张图。
5.4 超参数全靠猜
现象:SVM 参数写成 C=100、gamma=0.001,被问为什么选这个时回答“试出来的”。原因:没有系统搜索,试了两三组就挑了训练集分数最高的,后面报告也无法解释。解决:用 GridSearchCV 跑一个小范围网格,比如 C 取 0.1/1/10,gamma 取 0.01/0.1/1,25 组组合在小数据集上几秒跑完。把网格搜索结果放进报告,至少包含“最优参数”和“搜索范围”两行,这比“试出来的”可信太多。搜索范围不要太大,作业不是竞赛,你能解释清楚为什么搜这几组比搜几百组更有价值。
5.5 全篇只有一个模型、一组默认参数
现象:报告三千字,实验部分只有“随机森林默认参数,准确率 95.3%”,没有对比只有结论。原因:时间不够,或者认为模型越高级分越高,忽略了“对比实验”本身就是评分点。解决:保证至少三个模型参与对比,其中一个是朴素 baseline。逻辑回归、SVM、随机森林是整个机器学习课程里最常涉及的三个算法,覆盖三种不同的建模思路,报告结构天然成立。有余力再补一个网格搜索后的最优模型,对比表和讨论就完整了。
6. 比分再往上提一档:交叉验证与消融实验的写法
6.1 用 K-Fold 替代单次划分
单次 train_test_split 在作业里够用,但有个明显隐患:这次划分恰好容易或恰好难,你的结论就被偶然绑架。课程项目里我一般用 5 折分层交叉验证替代,输出 mean±std,报告的结论立刻从“某个划分下的表现”变成“模型在这份数据上的稳定表现”。实现只要几行:
from sklearn.model_selection import StratifiedKFold, cross_val_score cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) scores = cross_val_score(rf, X, y, cv=cv, scoring="accuracy") print(f"acc: {scores.mean():.4f} ± {scores.std():.4f}")StratifiedKFold 在分类问题里保证每一折的类别比例和全量数据一致,不会出现某一折恰好缺一类的极端情况;shuffle=True 配合 random_state 让折的划分可复现。scores.mean 和 scores.std 对应报告表里的“96.8%±0.4%”,如果两组模型的均值差小于标准差,结论就写“性能相当”,不要硬说某个模型更强。回归题上,把 StratifiedKFold 换成 KFold 即可,逻辑完全一样。
6.2 消融实验:让报告的每个设计都有存在理由
比交叉验证更能拉高印象分的是消融实验。做法很简单:每次从你的方案里去掉一部分,看指标掉多少。比如随机森林方案里有标准化和特征筛选两步,就分别做“去掉标准化”和“去掉特征筛选”两个变体。结果用一张三行表呈现:
| 设置 | 准确率/F1 | 变化 |
|---|---|---|
| 完整方案 | 97.1% | — |
| 去掉标准化 | 95.8% | -1.3% |
| 去掉特征筛选 | 96.4% | -0.7% |
哪个部分去掉后指标掉得最多,哪个就是你的主要贡献点。这张表放在结论前面,比任何文字都说明问题。我当年交大作业时,模型本身只比 baseline 高了两个点,但消融表证明这两个点来自特征筛选,而不是来自运气,最后拿到的分数比同组那些“模型更复杂”的同学都高。这个习惯我一直保留到现在,每次做完实验先问一句“哪个模块真的有用”,希望帮到你。
本文还有配套的精品资源,点击获取