机器学习期末大作业避坑指南:从数据清洗到模型对比的完整闭环
简介南邮通达学院《机器学习》期末大作业资源内含一份完整的系统设计报告面向需要完成医疗保健数据驱动系统设计任务的同学。报告以膀胱癌诊断为核心案例严格遵循作业要求系统阐述了数据收集、数据处理包含特征提取、模型选择与具体任务以及实验评估设计三大步骤脉络清晰、格式规范。内容涉及机器学习在医学影像、尿脱落细胞学、卡介苗治疗预后评估和术后复发预测等真实场景的应用不仅解释了有监督学习如ANN、逻辑回归和无监督学习的基本原理还列举了多项研究数据如ANN将MRI诊断准确率提升至95%、CT分级准确度达97%等并给出了系统示意图的设计建议。资源仅包含1个docx文档压缩包大小29KB下载后可直接阅读与编辑省去自己构思框架的时间。目前已有1021人学习特别适合南京邮电大学通达学院选修《机器学习》课程、正在准备期末大作业的学生参考借鉴。1. 通达学院机器学习期末大作业别把“跑通代码”当成“完成作业”通达学院的机器学习期末大作业每年都有一批同学卡在同一个地方代码在教程里跑通了、模型也训练完了、报告交上去了分数却远低于预期。原因是大多数同学把“跑通了别人的代码”当成了“完成了自己的项目”。期末大作业真正考的不是你会不会调参而是你能不能围绕一个数据集讲完一个完整的问题闭环——数据怎么来的、特征怎么构造、模型为什么这么选、结论凭什么站得住。这篇笔记就照着这个闭环来拆从选题到答辩每一步给出能直接复现的做法和参数再把老师一眼能看穿的坑挑明。适合两类人一类是还没定题、想少走弯路的新手另一类是代码已经跑完、但报告和答辩还讲不清楚的老手。2. 选题与数据准备先用一个能讲完整故事的数据集锁定方向选题是整个大作业里最影响体验的一步。很多同学一开始就扎进某个花哨的深度学习框架想着图像分类、情感分析看起来很厉害结果数据下载不动、预处理做不完、模型训练要跑几个小时最后报告只能堆截图。课程大作业的评分逻辑通常不是“谁的模型准”而是“谁把问题讲完整了”。一个经典的结构化二分类任务往往比一个半吊子的图像任务更容易拿高分。2.1 选题的三条边界可下载、可复现、可收尾我一般会给准备做作业的同学三个筛选条件。第一数据要能一次下载完最好是一个 CSV 文件而不是需要爬虫、需要解压、需要拼接的多文件数据集。第二基线要能在一台普通笔记本上几分钟内跑完这意味着数据量最好在几万行以内特征维度不超过几十个。第三故事要能收尾也就是说你能用一句话说清楚“我要预测什么、对谁有用”。最符合这三条的数据集类型是金融营销、用户流失、信用风险这类表格数据。以银行营销数据集为例每一行是一个客户在一次营销活动中的记录目标字段是客户有没有认购定期存款。这个题目天然包含类别不平衡、特征工程、业务解释这几个课程核心考点而且字段都是可以直接写进报告的业务变量比如年龄、婚姻状况、教育水平、上次联系间隔天数、联系次数等。相比之下如果选一个“预测房价”的题目特征之间高度线性相关反而讲不出太多模型对比的故事。选定数据集之后下一步就是把它处理成能喂给模型的样子。2.2 数据清洗与特征工程字段处理决定了报告能不能写满公开的银行营销数据集拿到手之后直接训练会遇到几个问题里面有重复记录有些分类字段存在“unknown”取值目标列是 yes/no 字符串。直接删除带 unknown 的行会损失信息但把这些值单独保留成一类反而能让模型学出“缺失本身也有规律”。下面的代码是清洗阶段最常见的操作import pandas as pd df pd.read_csv(bank_marketing.csv, sep;) # 去重同一客户在营销系统里被重复记录是常见脏数据 df df.drop_duplicates() # poutcome 里的 unknown 单独保成一类不直接删行 df[poutcome] df[poutcome].replace(unknown, unknown_class) # 分类字段转哑变量drop_first 防止产生完全共线列 cat_cols [job, marital, education, default, housing, loan, contact, month, poutcome] df pd.get_dummies(df, columnscat_cols, drop_firstTrue) # 目标列转为 0/1 df[y] (df[y] yes).astype(int)这段代码里有三个关键决策。drop_duplicates()去重是为了防止同一个客户同时出现在训练集和测试集里这一点在后面的避坑章节会展开。replace(unknown, unknown_class)是把未知类别当成一种真实分布而不是简单删除因为“未知”在营销数据里往往意味着某种渠道特征。drop_firstTrue对逻辑回归尤其重要它去掉每个分类字段的第一个虚拟变量避免设计矩阵出现完全多重共线性否则逻辑回归的系数解释会变得不稳定。做完这些之后还要看数值字段的分布。duration最近一次联系的通话时长通常是最强的预测特征但它有一个业务陷阱通话时长只有在营销结束后才知道如果用它做预测等于在“结果已经发生之后”预测结果。课程报告里如果不讨论这个字段的因果含义答辩时基本会被追问到。常见的处理方式是保留它但明确讨论或者干脆先跑一版不含 duration 的模型做对照。2.3 划分训练/验证/测试随机种子与分层抽样是作业的“后悔药”数据划分的顺序是作业里最常见的隐藏分水岭。错误的做法是先用全量数据做标准化、做哑变量然后再划训练集和测试集正确的做法是先划开再让任何预处理只学习训练集的信息。另一个容易被忽略的点是随机种子。不设random_state每次运行划分结果都不一样后面调参得到的分数根本没法复现写报告的时候自己都说不清哪个结果对应哪次运行。from sklearn.model_selection import train_test_split X df.drop(y, axis1) y df[y] # 第一刀先切出测试集保证它完全没参与后续任何 fit X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) # 第二刀从训练集里切验证集用于调参 X_train, X_val, y_train, y_val train_test_split( X_train, y_train, test_size0.25, random_state42, stratifyy_train )stratifyy是分层抽样参数在类别不平衡时保证训练集和测试集里正负样本的比例与全量数据一致。如果不加这个参数随机划分有可能让测试集里正样本比例跟训练集相差很多最终分数忽高忽低成了别人口中的“玄学”。random_state42不是固定要求但一旦定下来就不要改因为后面网格搜索、模型初始化都要沿用同一个随机种子整套结果才能复现。验证集比例取 0.25 是常见做法因为它是从已切出的训练集里再切实际占全量数据的比例是 0.2×0.250.05 乘上训练集整体上训练、验证、测试大约是 60:20:20。到这一步数据集已经可以进入建模了。但很多同学会在这里直接跳到模型训练跳过了最该做的一步先跑一个最简单、最不可解释的基线记住它的分数。否则后面模型调好了你也不知道好在哪里。3. 基线模型把逻辑回归和随机森林跑成可复现的对照模型部分最容易犯的错误是一上来就上 XGBoost、LightGBM跑出一个很高的分数然后报告里写“因为 XGBoost 效果好所以我选了它”。这句话在答辩时几乎站不住脚因为老师会接着问“那逻辑回归多少分随机森林多少分你的数据有什么特性导致树模型必然更好”如果答不上来分数就会打折扣。正确顺序是先用逻辑回归建立一个基线再用随机森林做对比最后才考虑集成模型。3.1 Pipeline把预处理和模型绑成一个整体建模的第一个动作是定义 Pipeline。很多同学喜欢先手动做标准化再拿去训练这样做的风险在于每次交叉验证时标准化器都是在全量训练集上拟合的逻辑上已经包含了数据泄漏的影子。用 Pipeline 可以把标准化、模型训练绑成一个整体交叉验证时每个折都会重新拟合标准化器这是最稳妥的做法。from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression pipe_lr Pipeline([ (scaler, StandardScaler()), (clf, LogisticRegression(max_iter1000, class_weightbalanced)) ]) pipe_lr.fit(X_train, y_train)max_iter1000是为了防止逻辑回归在特征维度较高时达到迭代上限而报警。class_weightbalanced是类别不平衡处理的第一道防线它让模型自动把少数类的权重调高。课程作业里用这个参数比手动过采样更省事而且不会改变训练集的样本分布报告里也更容易解释。跑完这个基线用验证集算一下 ROC-AUC记住这个数字它就是后面所有调参的参照物。3.2 逻辑回归基线的参数与第一次成绩逻辑回归在这个数据集上通常能跑到 0.90 以上的 ROC-AUC前提是包含 duration 字段如果排除 duration会降到 0.80 左右。这两个数字本身就是报告里很好的分析素材。逻辑回归的优势在于系数可解释你可以直接把系数输出成表格看哪些字段正向影响认购。这一步不要追求最高分而是追求“我理解我的模型”。import numpy as np coefs pd.DataFrame({ feature: X_train.columns, coef: pipe_lr.named_steps[clf].coef_[0] }).sort_values(coef, ascendingFalse) print(coefs.head(10))这里用pipe_lr.named_steps[clf]取出管道里的逻辑回归模型拿到系数后按绝对值排序。需要注意的是因为管道里做了标准化系数是在标准化尺度上的不能直接解读成“原始字段每增加一个单位对数几率增加多少”只能看方向和相对大小。报告里如果写到系数解释必须先说明这一点否则就是严谨性漏洞。3.3 随机森林对比特征重要性与过拟合信号随机森林是课程作业里的“安全牌”它对特征尺度不敏感、不需要标准化、对非线性关系有天然的拟合能力。它的缺点是黑盒所以要用特征重要性来打开它。特征重要性分数表示每个特征在整个森林中被用作分裂点时带来的不纯度下降总量值越大说明模型越依赖它。from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier( n_estimators200, max_depth8, min_samples_split5, class_weightbalanced, random_state42, n_jobs-1 ) rf.fit(X_train, y_train) importance pd.DataFrame({ feature: X_train.columns, importance: rf.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance.head(10))n_estimators200是树的数量再往上加收益很小但训练时间线性增长。max_depth8限制单棵树深度是防止过拟合最有效的参数。min_samples_split5要求内部节点至少有 5 个样本才继续分裂进一步抑制树往极端方向生长。n_jobs-1意思是使用所有 CPU 核心。随机森林在这个数据集上的 ROC-AUC 通常比逻辑回归略高或持平如果高出一大截要怀疑是不是数据泄漏或者验证集划分出了问题。到这里基线和对照模型都有了。下一步才进入调参环节。但调参不是漫无目的地试而是带着问题去搜索树深度对过拟合的影响是什么、类别权重该不该开、用哪个指标做评分。这些决策最后都要写进报告成为老师判断“你是真做了还是抄了代码”的依据。4. 调参与模型对比用网格搜索给报告造一条“证据链”调参是作业里最容易投入产出倒挂的部分。很多同学拿 GridSearchCV 一次性搜索十几个参数跑了半小时出结果然后只抄一个 best_params_ 到报告里。这种做法既浪费时间又没法回答老师“为什么这些参数最优”。正确的调参路径是先判断模型缺什么再针对性地设置搜索网格最后把每一次搜索的分数变化记录下来这才是报告里真正值钱的东西。4.1 网格搜索的边界先树深度、再正则化、最后类别权重网格搜索不是参数越多越好。参数一多组合数爆炸训练时间成倍增长而且容易过拟合验证集。我一般遵循三个原则第一每次只搜一个维度的两组到三组取值第二评分指标用 ROC-AUC 而不是 accuracy第三交叉验证折数固定为 5不要为了省时间改成 3。下面是针对随机森林的搜索示例注意搜索的对象是一个新定义的管道from sklearn.model_selection import GridSearchCV from sklearn.metrics import roc_auc_score pipe_rf Pipeline([ (clf, RandomForestClassifier(random_state42, n_jobs-1)) ]) param_grid { clf__n_estimators: [200, 300], clf__max_depth: [6, 8, None], clf__min_samples_split: [2, 5], clf__class_weight: [balanced, None] } grid GridSearchCV( pipe_rf, param_grid, scoringroc_auc, cv5, verbose1 ) grid.fit(X_train, y_train) print(grid.best_params_) print(grid.best_score_)参数名里的clf__前缀对应管道中名为clf的组件双下划线是 sklearn 里嵌套参数的标准写法。scoringroc_auc是关键因为默认的 accuracy 在类别不平衡时会虚高正样本占 11% 的话全预测负类也有 89% 的准确率但它一点用都没有。cv5表示五折交叉验证每个参数组合都要训练 5 个模型所以网格规模要克制。跑完之后如果best_params_里class_weight选中了balanced说明类别不平衡确实影响了模型如果选中None说明这个数据集上加权没有带来提升这个结论也可以写进报告。4.2 类别不平衡别让 accuracy 骗了你课堂作业中很多同学会在报告里写“模型准确率 91%”看上去很高但测试集里正样本比例只有 11%全预测“不认购”也能到 89%。所以课程作业里必须同时报告 ROC-AUC、精确率、召回率、F1而不是只挑一个好看的数字。ROC-AUC 对类别不平衡不那么敏感它衡量的是模型把正样本排在负样本前面的能力更适合作为这个题目的主指标。精确率和召回率要放在一起看因为营销场景里你联系 1000 个预测会认购的客户最终真正认购的有多少这是业务上更真实的成本收益问题。下面这组代码计算并输出测试集上的完整指标from sklearn.metrics import classification_report, roc_auc_score y_pred_prob grid.predict_proba(X_test)[:, 1] y_pred grid.predict(X_test) print(ROC-AUC:, roc_auc_score(y_test, y_pred_prob)) print(classification_report(y_test, y_pred, target_names[no, yes]))predict_proba(X_test)[:, 1]取的是正类的预测概率ROC-AUC 需要的是概率而不是硬分类结果。classification_report会一次性输出每个类别的精确率、召回率、F1 和样本数写报告时直接复制这段输出比手画表格更严谨。如果发现召回率很低比如正类的召回率只有 0.2说明模型把绝大多数真实认购客户漏掉了这时候再去调 class_weight 或者换阈值。4.3 模型对比表报告里最值钱也最能抗问的部分模型对比表是课程报告里最该认真做的一张表。它不一定要展示最好的成绩而是要展示“我做过哪些尝试、每一步提升来自哪里”。一张合格的对比表至少包含四行逻辑回归基线、随机森林默认参数、随机森林调参后、类别权重调整后。每一行都要写清楚验证集 ROC-AUC 和测试集 ROC-AUC两者的差距能直接反映过拟合程度。模型验证集 ROC-AUC测试集 ROC-AUC备注逻辑回归基线0.9120.905含 duration 字段随机森林默认参数0.9240.898出现过拟合信号随机森林调参后0.9210.909限制深度后测试集提升调参后排除 duration0.8310.818字段因果性讨论注意看第二行到第三行的变化验证集分数下降了但测试集分数反而上升了这说明限制树深度抑制了过拟合。这种“验证集降、测试集升”的现象在报告里解释清楚比单纯追求最高分更能体现建模能力。如果只写一个最终结果老师看不到你的思考过程分数自然上不去。到这里模型的训练、评价和对比都做完了。接下来要面对的问题是这套流程里有太多地方可能出错而且有些错误不会让程序报错只会让分数虚高。第 5 章把这些错误集中拆开。5. 期末作业避坑指南五个让老师一眼识破的翻车现场课程作业里最糟糕的情况不是模型分数低而是代码和报告里存在数据泄漏、随机种子不固定、重复数据混入测试集这类问题。这些问题通常不会让程序报错甚至会给出一个漂亮的分数但在答辩时一问就露馅。下面五条是出现频率最高的踩坑记录每条都按“现象、原因、解决”来写。5.1 StandardScaler 泄漏先 fit 全集还是先 fit 训练集现象训练集和测试集的标准差几乎一致测试集 ROC-AUC 比验证集还高模型在报告里“好得不像真的”。原因用StandardScaler().fit_transform(X)对全量数据做了标准化然后才划分训练集和测试集。标准化器在计算均值和方差时已经看过了测试集的数据相当于测试集信息提前进入了训练流程。这在课堂上就是数据泄漏的典型例子属于老师必查的坑。解决把标准化放进 Pipeline或者严格按照“先划分、再 fit 训练集、再 transform 测试集”的顺序。# 错误写法先对全量做标准化再划分 scaler StandardScaler() X_scaled scaler.fit_transform(X) X_train, X_test train_test_split(X_scaled, y, test_size0.2, random_state42) # 正确写法划分之后scaler 只 fit 训练集 scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test)两种写法的分数差异通常很小但第二种才经得起追问。如果答辩时老师问“你的标准化是怎么做的”回答“在划分之后 fit 训练集”是标准答案回答“先 fit 全量再划分”基本等于承认泄漏。5.2 random_state 不固定同一份代码为什么会跑出两个成绩现象同一个 notebook 从头再跑一遍测试集 ROC-AUC 从 0.91 变成了 0.89而且每次都不一样。原因train_test_split、RandomForestClassifier、GridSearchCV内部都有随机过程。如果不固定随机种子每次运行时数据划分不同、树的分裂点不同、网格搜索的训练折不同结果自然无法复现。作业报告里如果写“模型 AUC 0.91”但代码里一个random_state都没设老师复跑一次对不上整个报告可信度都会下降。解决在所有引入随机性的地方固定random_state42。划分数据时设、初始化模型时设、网格搜索的管道组件里也要设。常见做法是定义一个全局变量SEED 42然后在所有相关位置传入。这样一个实验跑完过几天重跑还能得到一模一样的数字。写报告的时候这个种子值本身不需要解释但它保证了你报告里的表格不是“只出现过一次的运气”。5.3 重复数据没去重测试集里混进了训练样本现象测试集分数量然高但仔细检查发现某些样本和训练集中的记录完全相同特征一致、目标也一致。原因原始数据里同一个客户在同一次活动中有重复记录清洗阶段没有做drop_duplicates()划分数据时一部分重复样本被分到了训练集另一部分被分到了测试集。模型相当于“背下了”训练集里的样本测试时碰到一模一样的内容直接输出正确答案虚高得分。解决在划分之前做全量去重并结合业务含义判断。去重时只看特征列还是看全部列需要根据数据实际情况决定。对于营销数据同一客户在同一天同一渠道的记录一般只保留一条这个判断写进报告里也是数据清洗的工作量证明。5.4 报告里的“太完美”曲线越光滑越可疑现象报告里的 ROC 曲线几乎是一条完美弧线精确率-召回率曲线没有任何抖动或者训练集准确率 99%、测试集 85%两者差距巨大。原因第一种情况是论文里常见的人工美化痕迹第二种情况是典型的过拟合信号说明模型把训练集的特征记住了没有泛化能力。课程作业里很多同学喜欢把图做得“好看”但老师看过的图比我们做过的多得多曲线太过完美反而会被重点检查。解决训练集和测试集的指标都要写出来让差距自然呈现。如果过拟合严重优先做两件事降低树模型的max_depth、增加min_samples_split对线性模型则增加正则化强度。图要保留真实曲线而不是只挑好看的部分。特征重要性图里如果出现某个特征占比超过 0.5也要警惕是不是这个特征本身包含了未来信息。5.5 答辩第一问“为什么选这个模型”背不下来的动机现象答辩被问“逻辑回归和随机森林你为什么不选那个”时只能回答“因为随机森林分数高”。原因选模型只看了最终分数没有从数据特性出发思考。逻辑回归适合需要系数解释的场景随机森林适合特征与目标关系非线性、特征尺度不一的场景。如果回答不出这两者的差异就说明建模过程缺少思考环节。解决在报告里写一段“模型选型依据”套路是先说明数据是什么类型、有多少特征、是否存在非线性关系再说明逻辑回归适合用来做基线因为它可解释、训练快然后说明随机森林能捕捉交互作用但可能过拟合最后测试集上的对比印证了哪个更适用。这段话不用很长但能让老师看到选择背后的逻辑链比背十个模型的参数有用得多。6. 把作业做成能讲的作品报告结构、图表与答辩自测报告是大作业的最后一步但也是最容易拉分的一步。老师不可能一行行读代码他主要通过报告里的文字、图表、结果表来判断你做没做、懂没懂。所以报告不是代码的附件而是作品的说明书。结构上建议按“业务问题、数据、方法、结果、讨论”五个部分组织每一部分都要回答“为什么”而不是只堆“做了什么”。图表方面三个图基本够用特征相关性或特征重要性图、ROC 曲线、训练集和测试集分数对比柱状图。表格方面第 4 章的模型对比表一定要放它是最能抗追问的部分。注意 ROC 曲线不要单独画一张光秃秃的图要标注 AUC 数值最好把逻辑回归和随机森林两条曲线画在一起一眼能看出哪个模型更优。所有图的坐标轴要有标签、曲线要有图例这属于最基本的专业感不需要额外技巧。答辩前的自测我习惯用五个问题来复盘第一为什么选这个数据集它对谁有用第二哪个特征对预测最重要为什么第三逻辑回归和随机森林的差异在你的数据上表现为什么第四测试集上的结果有没有可能存在数据泄漏第五如果只能用一个指标向业务方汇报你选哪个、为什么这五个问题全部能用报告里的内容回答出来答辩基本不会冷场。如果某个问题答不上来说明报告对应章节还需要补而不是临场发挥能解决的。我自己每次做完一个课程项目都会把整套代码用固定种子从头跑一遍确认每个数字都对得上再开始写报告。这个习惯救过我很多次因为作业里的翻车往往不是算法不行而是过程经不起追问。这篇笔记里提到的坑几乎都是往届同学踩过的希望帮到你。本文还有配套的精品资源点击获取