资讯动态

数据挖掘实验手册:可复现流程、数据泄漏防控与验收标准

发布时间:2026/10/25 21:07:39 来源:尧图企业网站定制
1. 这套实验手册真正要解决的问题从能跑通到结论站得住《HNU数据挖掘实验手册》这个名字听起来像一份课程配套讲义但凡是真正动手带过实验的人都知道它的重点从来不是把教材上的算法翻译成 Python 代码而是要把一群基础参差不齐的人约束到同一条可复现、可对比、可验收的轨道上来。这份手册代号 HNU是某课程组内部使用的一套数据挖掘实验流程文件共八个实验模块覆盖数据预处理、关联规则、分类、聚类、模型评估与可解释性分析。它服务的对象很明确已经学过一点 Python、看得懂 pandas 基本操作但还没独立完成过一次完整数据挖掘流程的人。我带过几轮这套实验最常见的场景是这样的某个同学跑出一个 0.96 的准确率兴冲冲来问我是不是可以写进报告我让他把随机种子固定、把划分方式换一下、把预处理步骤挪到正确的顺序上同一个模型立刻掉到 0.78。问题不在于他的代码写错了而在于他没有意识到在数据挖掘里跑通和结论成立之间隔着大量约定。手册存在的意义就是把这些约定写成硬性条款而不是靠每个人自己悟。所以这份手册的设计逻辑可以概括成一句话能跑通只是及格线能复现、能解释、能对比才是目的。它规定了统一的目录结构、统一的评估口径、统一的随机种子、统一的报告模板甚至在每一章末尾附了验收清单让交上来的作业能被快速核对。下面我把这套手册的结构、每个模块的设计意图、以及带做过程中反复踩到的坑完整拆一遍如果你也在做类似的实验体系建设或者想自己照着走一遍完整的数据挖掘流程可以直接拿去改。2. 八个实验的骨架是怎么排的以及为什么这样排2.1 从数据本身就是脏的开始而不是从算法开始很多数据挖掘课程的第一个实验直接上决策树理由是决策树直观、好讲。我试过这种排法效果很差。原因很现实新手拿到真实数据的第一反应不是建模而是这一列怎么有 30% 是空的如果第一个实验里就让他们一边处理缺失值一边调模型注意力会被彻底撕碎最后两头都没学明白。HNU 手册把第一个实验完全留给数据清洗并且刻意选了一份脏得很典型的数据集有缺失、有重复、有量纲混乱的字段、有时间格式不统一的问题还有几个故意混进去的异常点。实验目标只有三条能把缺失值和异常值处理干净、能说清每一步处理对数据分布的影响、能输出一份清洗前后的对照表。这个实验不涉及任何建模但它建立了一个心理预期——数据挖掘里八成的时间花在数据上这是常态不是你的能力问题。排在这个位置的第二个理由是它天然引出后续所有实验的公共基础设施。清洗实验里写出来的通用函数、读写脚本、目录约定后面七个实验全部复用。我在手册里把这一节写成工具层明确告诉使用者这一节产出的代码不是一次性作业它是后面所有实验的底座写的时候要像写自己的常用工具库一样对待。2.2 关联规则放在分类之前是为了先建立评价指标的概念中间三个实验的顺序是关联规则、分类、聚类。关联规则排在分类前面看起来有点反直觉因为分类更主流。但关联规则有一个独特优势它的评价指标支持度、置信度、提升度是可以手算验证的。当规则数量只有几十条时学员完全可以拿着小规模数据用纸笔算一遍然后和代码输出对照。这个对照过程极其重要。后面分类实验里讲精确率、召回率、F1讲 ROC 和 AUC这些指标的计算复杂度让人很难有直觉。但如果他在关联规则那一节已经经历过我的代码算出来的支撑度0.032和我手算的 32/1000 完全一致这种验证体验后面看到任何指标第一反应都会是我先验算一下而不是代码跑出来什么就是什么。顺带说一句这个排法还有个副作用关联规则实验的数据量小、可视化结果直观可以画规则网络图或热力图在第二个实验就能出图对维持节奏很有帮助。2.3 聚类放在分类之后让无标签的价值显现出来聚类排在分类之后是为了制造一个认知落差。做完分类实验人会习惯性地想找标签、想算准确率这时候给一份完全没有标签的数据很多人第一反应是没法评价好坏。手册在聚类实验开头直接抛出这句话然后引出内部评价指标轮廓系数、Calinski-Harabasz、Davies-Bouldin和外部评价指标如果恰好有真实标签用 ARI、NMI 对照。这个落差教学的效果比我预想的好。学员在被迫放弃准确率这个拐杖之后对聚类结果稳定性的敏感度会明显提高——他们会主动去看不同随机初始化下的结果差异会去琢磨 K 值到底怎么定。这个主动追问的状态正是我希望在最后两个实验模型评估与可解释性里保持的。最后一个模块是综合验收把前面所有环节串起来给定一份没见过的数据从清洗到建模到评估到结论完整跑一遍出一份不超过 6 页的报告。这一节的评分权重最高因为它考的是流程完整性而不是某个模型的分数。3. 环境与数据管线的统一约定不统一后面全是口水仗3.1 版本锁定的具体做法与为什么必须做我吃过最直接的一次亏两个学员用同样的代码一个输出 0.83一个输出 0.71。排查了两小时最后发现一个人的 scikit-learn 是 0.24另一个是 1.1LogisticRegression的默认penalty参数和求解器行为都变了。更隐蔽的是 KMeans 的n_init新版默认值从 10 改成了auto聚类结果会有细微差异而一旦涉及轮廓系数比较这点差异足以让最优 K 值的结论翻转。手册的做法是强制提交一份环境自检脚本的输出作为实验一的必交附件# environment_check.py import sys, platform import numpy as np, pandas as pd, sklearn import matplotlib, scipy import mlxtend print(python :, sys.version.split()[0]) print(os :, platform.platform()) print(numpy :, np.__version__) print(pandas :, pd.__version__) print(sklearn:, sklearn.__version__) print(scipy :, scipy.__version__) print(mpl :, matplotlib.__version__) print(mlxtend:, mlxtend.__version__)比脚本更重要的是约定一个主版本手册里写死 Python 3.10 scikit-learn 1.3.x pandas 2.x所有示例代码和预期输出都基于这套版本验证过。不要求所有人升级到最新但要求你报出来的问题必须附上这份自检输出否则不受理。这一条把大量我这报错了的无效沟通直接砍掉了。提示如果你的实验环境是共享服务器或机房统一镜像把这份自检脚本的输出结果作为镜像版本号的一部分记录下来比记我们用的是 Anaconda有用得多。3.2 数据集分发三种方案各有各的坑数据怎么发到学员手里看起来是小事实际影响很大。手册里对比过三种方案最后选的是第三种方案做法优点主要问题直接给 CSV打包下载简单直接文件被随意改名、缺失行被 Excel 打开后破坏编码给下载脚本运行脚本自动拉取数据源统一依赖外部地址可用性出错时无法排查本地数据包 校验和附带 md5 清单可验证完整性需要额外一行校验命令第三种方案的关键在于那份 md5 清单。它解决了一个很隐蔽的问题有人在 Excel 里打开 CSV另存为的时候把 UTF-8 编码改成 GBK或者把00123这样的编号字段的前导零吃掉甚至把某几行的空值变成了空字符串。这些改动在本地看起来毫无异常但他跑出来的结果和别人对不上排查成本极高。有了校验和md5sum -c checksums.txt一行就能定位。编码问题还有一层手册统一要求所有 CSV 用 UTF-8 无 BOM所有读取代码显式写encodingutf-8不要依赖默认值。Windows 上 pandas 的默认编码在某些版本下是 GBK 系同一份代码换台机器就报UnicodeDecodeError这类问题在实验初期出现的频率高得惊人。3.3 目录结构和命名为了能被别人打开手册规定了固定的提交结构这部分我态度很强硬hnu-lab03/ ├── README.md # 实验目的、数据来源、运行方式 ├── requirements.txt # 依赖清单 ├── data/ │ ├── raw/ # 原始数据只读不许改 │ └── processed/ # 清洗后输出 ├── notebooks/ │ └── 01_eda.ipynb │ └── 02_model.ipynb ├── src/ │ └── utils.py # 公共函数 └── outputs/ ├── figures/ └── metrics.json # 关键指标落盘这份结构的核心是两条规则raw 目录只读任何清洗结果都写到 processed关键指标必须落盘成 json不能只存在于 notebook 的输出单元格里。第二条规则救过很多人——notebook 重启后输出全清如果指标只留在内存里那份报告里的数字就成了无法追溯来源的数字。metrics.json的格式手册也给了模板要求至少包含模型名、超参数、随机种子、划分方式、每个评价指标的均值与标准差。这份文件的另一个用途是横向汇总——收作业时用一个脚本扫描所有 json直接生成全班对比表谁的结果异常一眼就能看出来。4. 分模块的设计意图每个实验的钩子和坑4.1 数据预处理缺失值处理和异常值处理的先后顺序之争这个争论在每一届都会出现先补缺失还是先剔异常手册里没有直接给答案而是让学员用同一份数据跑两种顺序把结果记下来对比。因为答案本身取决于数据生成机制而意识到有顺序问题比记住一个顺序有价值得多。我自己带做时的经验是先处理异常值再处理缺失值在多数场景下更稳。原因是如果先填补缺失填补值本身可能是被异常值污染过的统计量比如用均值填补而均值被几个极端值拉到离谱的位置等于把污染扩散到原本完好的行。但这个结论不绝对如果缺失比例很高而异常值判断依据本身依赖完整数据那就得反过来。具体到操作层面手册规定了几条硬要求。缺失比例超过 40% 的列必须显式说明删除或保留的理由不能默默 drop。用均值填补时必须同时说明该列分布是否近似对称偏态分布用中位数。异常值检测至少用两种方法交叉验证比如 IQR 和 Z-score两者不一致的样本要单独列出并人工看几条。# 缺失率统计必须落到报告里 miss df.isna().mean().sort_values(ascendingFalse) report miss[miss 0].to_frame(missing_ratio) report[dtype] df[report.index].dtypes print(report) # IQR 与 Z-score 交叉标记 q1, q3 df[amount].quantile([0.25, 0.75]) iqr q3 - q1 flag_iqr (df[amount] q1 - 1.5 * iqr) | (df[amount] q3 1.5 * iqr) z (df[amount] - df[amount].mean()) / df[amount].std(ddof0) flag_z z.abs() 3 print(仅IQR命中:, (flag_iqr ~flag_z).sum(), 仅Z命中:, (~flag_iqr flag_z).sum(), 共同命中:, (flag_iqr flag_z).sum())还有一个坑反复出现df.dropna(inplaceTrue)之后忘了重置索引后续按位置索引取行时全部错位一格整个分析结论跑偏。手册里直接禁止在原始 DataFrame 上用inplaceTrue一律用显式赋值。4.2 关联规则支持度阈值不是拍脑袋定的关联规则实验最容易糊弄因为调低支持度总能出结果规则一多看起来就很丰富。手册在这个实验里加了一道强制要求必须先算出支持度对应的绝对条数再定阈值。from mlxtend.frequent_patterns import apriori, association_rules n len(df_bool) min_sup 0.02 # 相对支持度 print(绝对条数 , int(min_sup * n)) freq apriori(df_bool, min_supportmin_sup, use_colnamesTrue, max_len3) rules association_rules(freq, metriclift, min_threshold1.2) # 三重过滤置信度 提升度 绝对支持数 rules rules[(rules[confidence] 0.5) (rules[lift] 1.5)] rules[abs_support] (rules[support] * n).round().astype(int) rules rules[rules[abs_support] 30] print(len(rules)) print(rules.sort_values(lift, ascendingFalse).head(10))道理很简单如果一份数据只有 800 条记录min_support0.01意味着 8 条记录就构成一条频繁项集。8 条记录支撑的规则提升度再高也没有任何实际意义它就是噪声。手册要求把绝对条数写在报告里并把至少 30 条记录支撑作为经验下限达不到的规则一律不进结论。另一个必须讲清的点是提升度的含义。很多报告写提升度为 2.1说明两者强相关这不够。正确表述是同时出现的概率是独立假设下期望概率的 2.1 倍。手册要求每条进入结论的规则都按这个格式重写一遍逼着使用者真的理解这个数字而不是把它当成一个越大越好的评分。还要注意max_len参数。不限制长度的话项集数量会指数级膨胀中等规模数据上跑十几分钟甚至吃满内存。手册建议先max_len2跑通看结果再逐步放宽到 3并且每次调整都记录耗时。这个小步试探的习惯在后面的所有实验里都适用。4.3 分类实验数据泄漏在这个环节最容易发生分类实验是整本手册里我投入精力最多的一节因为数据泄漏几乎每一届都会出现而且表现形式极其隐蔽。最典型的三种第一种先对全量数据做标准化再用交叉验证。StandardScaler().fit_transform(X)这一步用到了全部样本的均值和方差测试折的信息已经渗进训练过程。数据量小的时候这会让指标虚高好几个百分点。第二种先做 SMOTE 过采样再划分数据集。合成样本可能是由训练集和测试集的近邻共同生成的测试集被提前看到了。第三种用全量数据做特征筛选比如按与目标的相关性挑前 10 个特征然后交叉验证。筛选过程用了标签泄漏同样存在。手册给出的标准写法是把所有会学习参数的步骤塞进 Pipeline让交叉验证自己控制边界from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.impute import SimpleImputer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import StratifiedKFold, cross_val_score pipe Pipeline([ (imputer, SimpleImputer(strategymedian)), (scaler, StandardScaler()), (clf, LogisticRegression(max_iter2000, class_weightbalanced, random_state42)), ]) cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score(pipe, X, y, cvcv, scoringf1_macro) print(fF1-macro {scores.mean():.4f} ± {scores.std():.4f})为什么用f1_macro而不是准确率因为手册里选的数据集类别分布是不均衡的多数类占七成左右一个全预测多数类的模型准确率就有 0.7非常有迷惑性。用 macro-F1 能把这个假象直接打回原形。这个设计是刻意的我希望学员在第一次做分类时就被不均衡数据骗一次然后在报告里写清楚为什么我的准确率看起来不错但模型没用这个反思比任何讲解都深刻。同时要求必须报出一个基线。基线分两层多数类基线全预测最多的那类和简单模型基线逻辑回归或决策树默认参数。任何复杂模型如果没超过基线就不许写效果良好。这条规则让很多堆砌模型的报告现了原形。还有一个细节要注意StratifiedKFold在最小类样本数少于折数时会直接报错手册要求在划分前先y.value_counts()检查一遍样本过少的类要么合并要么换用KFold并说明理由。4.4 聚类实验K 值不会自己浮出来聚类实验的验收标准写得很具体必须给出至少两种 K 值选择依据并且两种依据的结论不一致时要解释原因。最常用的两种是肘部法看簇内平方和随 K 的下降曲线拐点和轮廓系数。问题是这两者经常给出不同答案尤其是在簇形状不是球形的时候。手册不回避这个矛盾反而把它当成教学点让学员把两条曲线画在同一张图上然后写一段分析说明我最终选了哪个 K为什么。from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score import numpy as np inertia, sil [], [] for k in range(2, 11): km KMeans(n_clustersk, n_init10, random_state42).fit(Xs) inertia.append(km.inertia_) sil.append(silhouette_score(Xs, km.labels_)) for k, i, s in zip(range(2, 11), inertia, sil): print(fk{k:2d} inertia{i:10.2f} silhouette{s:.4f})这里有个必须写进手册的坑silhouette_score在 K2 时往往偏高因为它衡量的是簇间分离度/簇内紧密度簇越少越容易分离。所以单看轮廓系数最大值经常选出 K2而实际业务上可能需要更细的划分。手册要求补充一个业务可解释性判断——每个簇挑 3 到 5 个代表性样本用人话描述这个簇是什么描述不出来就说明这个 K 不合适。另一个反复出现的问题是标准化与聚类指标的相互影响。如果各特征量纲差异大又没有标准化欧氏距离会被数值大的特征主导轮廓系数算出来再好也没有意义。手册把这个检查作为强制项聚类前必须打印各特征的标准差差异超过一个数量级就必须标准化。5. 评分与验收怎么判断一份报告是真做过还是抄来的5.1 结果可复现性的三个硬指标判断报告真实性我不看图表漂不漂亮只看三件事。第一同一随机种子两次运行结果完全一致。这是最基本要求浮动超过 1e-6 就说明流程里有未固定的随机源比如 DataLoader 的多进程、某些初始化、pandas 的采样顺序。手册要求把np.random.seed、random.seed、模型random_state、train_test_split的种子全部统一成一个写在config.py里的常量。第二关键指标的落盘文件存在且与报告数字一致。这一步会淘汰掉相当一部分报告——有人是手动把数字抄进报告的抄错了或者改了模型忘了改数字一对就露馅。第三用另一台机器能跑通。手册明确规定可以抽查这一项不作为全员必查但被抽到跑不通会直接按未完成处理。requirements.txt里必须钉版本README.md里必须写清运行顺序。5.2 报告里的假深度长什么样带过几轮之后识别套路的速度会快得惊人。手册的评分细则里专门列了几种看着很深但没内容的写法并明确给出扣分标准典型写法问题验收要求使用了先进的集成学习方法取得了良好的效果无具体模型、无参数、无对比必须写明算法全称、关键超参、对比对象及数值准确率达到 0.95模型性能优异无基线无类别分布说明必须给多数类基线说明类别比例通过特征工程提升了模型表现提升多少做了哪几步逐步消融报每一步的指标变化聚类结果符合预期预期是什么怎么判断的每簇给文字描述 代表性样本参数经过多次调优调了什么范围依据给出搜索空间、最优参数、验证曲线这份表格我贴在了手册附录里每个实验的报告模板开头都重复一次。效果是报告的口水话明显减少虽然篇幅变短了但信息密度上去了。5.3 分数权重怎么分配才不至于唯指标论如果只按模型指标给分必然出现死磕调参、流程一塌糊涂的情况。手册的权重设计是这样的流程完整性 40%、结论合理性 30%、结果可复现性 20%、代码与文档规范 10%。模型绝对分数不占权重只有是否超过基线作为一个门槛项。这个设计的核心考虑是新手做出的模型分数高低很大程度上取决于运气和数据集难度而不取决于能力。同一个人在不同数据集上的 F1 能差 0.3。但流程是否完整、结论是否有依据、结果能否复现这些和数据集难度无关是能稳定反映水平的。把权重压在这些维度上评价才公平。6. 带做过程中最高频的报错与真实处理链路6.1 环境与依赖类报错信息里已经给了答案ModuleNotFoundError: No module named mlxtend这类问题占了我答复量的三成处理链路其实很短先确认报错里的模块名pip 安装对应包再跑一次自检脚本。但真正拖时间的是环境混乱——有人在系统 Python 里装包有人在虚拟环境里装跑的时候用的解释器和装包的解释器不是同一个。手册里给出的排查顺序是先打印sys.executable再pip -V看 pip 属于哪个环境两者路径不一致就是混了。ImportError: cannot import name X from sklearn...基本都是版本不匹配某些 API 在新版里移到了新模块。手册的原则是不改教程代码去迁就旧版本而是统一版本因为迁就旧版本的代码片段会一路复制下去最后整个工程里混着两个时代的写法。ValueError: Setting a random_state has no effect since shuffle is False是个很典型的警告train_test_split忘了设shuffleTrue或者KFold默认不打乱。这个警告本身无害但它暗示了数据顺序可能存在系统性偏倚比如数据按时间排序必须处理。手册要求把这个警告当成错误对待。6.2 数据类报错完整排查链路示范以ValueError: Input contains NaN, infinity or a value too large for dtype(float64)为例这个报错在分类实验里出现频率最高因为决策树和朴素贝叶斯对缺失值的容忍度不同有些人换模型之后就撞上了。手册里给了一条固定的排查链路要求按顺序走完再动手改第一步定位是哪一列。df.isna().sum()看缺失分布np.isinf(df.select_dtypes(number)).sum()看无穷大。第二步检查缺失是原始数据带的还是中间计算产生的——除法分母为零、log 里的零、pct_change的第一行这几种都会凭空造出 NaN 或 inf。第三步判断这一列的缺失比例决定删行、删列还是填补。第四步如果选择填补说明填补策略并记录不要默默用 0 填了事。另一条常见链路是UnicodeDecodeError。顺序是先试utf-8失败试gbk都不行用chardet探测或者直接encodingutf-8, errorsreplace看乱码位置。手册里强调检测到编码问题后不要就地修要回到数据分发环节确认那份 md5 校验——很可能文件在传输过程中被改动过。MemoryError在处理关联规则或大规模 one-hot 编码时出现。手册给的处理顺序是先降数据类型int64→int32float64→float32再把 one-hot 换成稀疏矩阵最后才是分块处理。很多人第一反应是加内存其实降一下精度就解决了。6.3 结果类不对比报错更难查的一类问题最耗时的从来不是报错而是没报错但结果明显不合理。手册列了几个典型症状和对应排查方向形成一张速查表症状最可能的原因排查动作准确率 0.99 以上数据泄漏或目标列混进特征检查特征列是否含标签衍生字段重跑严格划分的 CV每个 K 的轮廓系数都很低0.2未标准化或该数据本无簇结构打印各特征标准差画降维散点图目视确认交叉验证各折差异极大样本量小或类别严重不均衡打印每折的类别分布改用分层抽样特征重要性全是 0特征高度共线或未 fit 完整数据检查共线性换用 permutation importance训练集好、测试集差过拟合或划分方式有偏加正则检查划分是否随机我印象最深的一次是一个学员的模型在测试集上准确率 0.97全班最高。按流程查下来他的特征表里混进了一个叫result_flag的字段——那是原始数据里的结果标记列做清洗时忘了排除。这个坑现在写进了手册的数据清洗一节作为必须检查项的第一条建模前打印一次特征列表逐列问自己这一列在预测发生时是否真的能拿到。凡是依赖未来信息或结果信息的列一律剔除。排查这类问题有一个通用方法我把它叫做从最简开始加复杂度先用一个特征、一个简单模型跑出基线确认流程没问题再逐步加特征、加模型复杂度每加一步记录指标。一旦某一步指标出现不合理的跳变问题就定位在那一步上。这个方法比盯着报错看有效得多因为它把黑箱整体拆成了可验证的小段。7. 手册本身的迭代写给自己看的坑记录这套手册改到第四版最大的变化不是加了新算法而是加了一整节已知问题。里面记的全是真实踩过的坑某个数据集的某一列在特定版本 pandas 下读取类型会变、某个可视化脚本在深色主题下坐标轴标签看不清、某个示例代码在样本量小于折数时会崩。这些问题不写下来每一届都要重新踩一遍。我自己在维护这份手册时的体会是手册的价值不在于它写得多全而在于它把哪些东西写成了不许商量。版本锁定、目录结构、指标落盘、基线对比、绝对支持数下限这五条是硬性的其余全部留白让人自己发挥。留白的部分才是真正学东西的地方如果连缺失值用均值还是中位数都规定死那这个实验就退化成抄写了。最后分享一个我觉得挺管用的小做法每个实验结束后让使用者写一句这个实验里我最不确定的一件事。收上来之后汇总下一版手册的已知问题就从这里长出来。事实证明大家不确定的点高度集中改起来效率很高。这个做法比发问卷问你觉得手册哪里需要改进有效得多因为具体的困惑比笼统的评价有价值。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑