1. 先搞清楚模型学不好八成是数据的锅模型训练到一半不收敛损失曲线像心电图一样上下乱跳第一反应是调学习率、换优化器、加正则项折腾一整夜最后发现训练集里有三百多条重复样本还有几十个缺失值被填成了 0。这种经历我遇到过不止一次相信做过真实项目的人也都懂。数据预处理这件事说它是建模流程里最枯燥的一环大概没人反对但它同时也是最能拉开水平差距的一环。很多人对数据预处理的理解还停留在“把缺失值填一填、把数据归一化一下”这个层面实际上它覆盖的范围要宽得多从原始数据落地的第一秒钟开始到最终喂进模型的那份干净张量为止中间所有的清洗、转换、降维、增强、切分全都算在内。它的目标只有一个——让模型看到的数据尽可能贴近它假设的数据分布。模型本身是一堆矩阵乘法和非线性激活它不会替你判断某个特征是噪声还是信号也不会区分“这个 0 是真的 0”还是“这个 0 是因为没采集到”。这篇文章适合三类人看。第一类是刚入门机器学习、跟着教程跑通了 demo 但一上真实数据就翻车的朋友第二类是做过几个项目但预处理流程全靠直觉、想系统梳理一遍的从业者第三类是做图像、文本、时序这些非结构化数据想看看别人家在清洗和增强上都踩过什么坑的工程师。不管你是哪一类下面的内容都是可以直接抄作业的代码和参数我都会给全。我个人习惯把整个预处理拆成四块来看清洗、转换、降维、增强。这四块不是严格的流水线顺序很多时候是交叉进行的。比如做图像训练数据预处理的时候你可能先要清洗掉损坏的图片文件再做尺寸统一和像素归一化接着用 PCA 看一眼数据分布有没有异常簇最后才上增强策略。顺序搞反了代价可能是几小时的重复计算也可能是模型指标莫名其妙掉几个点。1.1 为什么“垃圾进垃圾出”在深度学习时代依然成立有人觉得现在模型参数量都上亿了容错能力强脏数据扔进去它自己也能学。这个想法在数据量足够大、噪声足够随机的前提下确实有一定道理——大规模预训练模型对随机噪声的鲁棒性比传统模型强不少。但问题在于真实场景里的脏数据往往不是随机噪声而是系统性偏差。举个例子某次做用户行为预测训练集里有一批样本的“停留时长”字段全是 -1这是埋点上报失败时的默认值。如果直接把它当数值特征喂进去模型会学到一个荒谬的规律“停留时长为 -1 的用户转化率特别高”。为什么因为上报失败往往发生在网络环境差的时候而网络差的用户恰好是某类特定人群。模型学到的是这个隐藏的相关性不是真实的因果。上线之后只要埋点恢复正常这个特征就彻底失效了。这就是系统性脏数据的可怕之处它不会让模型报错只会让模型悄悄地学错。而数据清洗的核心价值就是把这些隐藏的“编码错误”找出来还原成缺失值或者直接剔除。判断标准很简单这个值是不是业务上真实可能出现的不是那它就是脏的。再说缺失值。很多人上来就df.fillna(0)这是最省事也最容易埋雷的做法。0 在数值上有意义填进去等于告诉模型“这个样本的这一维就是 0”而实际上它是“未知”。正确做法是要么填均值/中位数改变分布但至少不引入虚假语义要么加一个缺失指示列is_null让模型自己学要么用模型预测填补KNN、迭代填补。三种方法各有适用场景后面章节会详细讲。1.2 预处理在不同数据类型上的差异有多大这个问题必须单独拎出来讲因为太多人把表格数据的经验直接搬到图像和文本上结果就是白忙活。数据类型清洗重点转换重点降维思路增强手段结构化表格缺失值、异常值、重复行标准化、编码、分箱PCA、特征重要性筛选通常不需要可做过采样图像损坏文件、尺寸不一、重复图归一化、Resize、色彩空间PCA像素级、自编码器翻转、裁剪、色彩抖动、Cutout文本乱码、HTML 标签、超长截断分词、词表构建、padding词向量降维、句向量聚类同义替换、回译、EDA时序时间戳对齐、重复采样点滑窗、差分、归一化奇异值分解、小波加噪、时间扭曲、缩放这张表是我自己在多个项目里总结出来的你可以当成一个 checklist 用。注意图像那一行的“重复图”经常被忽略——爬虫抓来的数据集里同一张图换个文件名出现三五次是常态放在训练集里顶多算冗余要是同时出现在训练集和验证集里那你的验证指标就是假的。这个坑后面会专门讲。看这张表还能发现一个规律结构化数据偏重“修”非结构化数据偏重“变”。表格数据里每个字段都有明确语义你只能小心翼翼地修补它而图像和文本本身冗余度高你反而可以大胆地变换、裁剪、替换模型照样能学到东西。理解了这个差异你在面对新数据集时就知道该往哪个方向使劲了。1.3 一份合格预处理流程的判断标准怎么知道自己做的预处理够不够我一般用三条标准自检。第一条可复现。整个流程必须能被封装成一个函数或者 Pipeline给定相同的原始数据任何人跑一遍都能得到完全相同的结果。所有涉及随机的步骤比如划分数据集、随机增强必须固定随机种子。这条听起来是废话但我见过太多人是在 notebook 里一个单元格一个单元格地手动改数据第二天自己都复现不出来。第二条无泄漏。训练集上算出来的统计量均值、方差、最大最小值、类别编码映射表绝对不能用到验证集和测试集上。正确顺序是先用训练集fit再对验证测试集transform。scikit-learn 的 Pipeline 天然帮你做这件事自己手写的话特别容易搞错。第三条可解释。每一步操作之后数据变成了什么样你心里要有数。做完归一化特征的取值范围应该是 [0,1] 或者均值 0 方差 1做完独热编码列数应该等于类别数或者类别数减一。这些都可以用断言检查。我习惯在流程里加几行assert跑通了心里才踏实。重要提示预处理流程一旦确定并开始跑实验就不要在中途偷偷改。每改一次都应该重新跑一遍完整的实验记录。我踩过的最大坑就是为了“顺手优化一下”某个填充策略结果前后两组实验的指标根本不可比白白浪费两天。2. 数据清洗把脏东西挡在模型门外清洗是预处理里最耗时间但最不显眼的部分。一个真实项目里我大概会花掉整个建模周期 40% 到 50% 的时间在这一步。听起来夸张但只要你亲手处理过几万行带各种奇葩问题的真实数据就知道这个比例很正常。2.1 缺失值处理三种策略的适用边界先说结论没有一种填充方法在所有场景下都是最优的选哪种取决于缺失机制。缺失机制分三类。完全随机缺失MCAR比如问卷里有人随机漏填了几道题随机缺失MAR比如高收入人群更不愿意填收入字段缺失和已观测变量相关非随机缺失MNAR比如抑郁量表里最严重的患者反而不填。前两种可以用统计方法填第三种填什么都可能引入偏差最好加缺失指示变量。具体操作上我常用这三招简单统计量填充数值列填中位数类别列填众数或单独一个Unknown类别。优点是快、无偏在 MCAR 下缺点是把方差压小了。适合缺失率低于 5% 的情况。模型预测填充用 KNN 或者迭代式填补IterativeImputer利用其他特征来预测缺失值。适合缺失率 5% 到 30% 的情况计算成本高一些但效果好。缺失指示 统计量填充既填值又加一列 0/1 标记。适合怀疑缺失本身带信息的场景比如“这个字段缺失说明用户没完成某流程”。缺失率超过 50% 的列我的建议是直接删掉除非你有强业务理由保留。硬填进去模型大概率学到的是噪声。2.2 异常值检测3σ、IQR、孤立森林怎么选异常值不等于错误值。有些异常值是真实发生的重要事件比如大促当天的销量删掉反而损失信息有些异常值是录入错误比如年龄填了 200 岁必须处理。所以第一步永远是判断这个异常值在业务上是否合理。检测方法上我一般这样选方法适用条件优点缺点3σ 法则数据近似正态分布计算极快对偏态分布误判严重IQR 四分位距任意分布尤其偏态稳健不受极端值影响边界系数 1.5 需要按场景调孤立森林多维数据、样本量大能捕捉多维组合异常需要调参结果不易解释DBSCAN 聚类有明显密度结构能发现局部异常高维下效果差实践中最常用的是 IQR公式很简单Q1 减去 1.5 倍 IQR 是下界Q3 加上 1.5 倍 IQR 是上界。但我一般会把系数从 1.5 调到 3因为 1.5 在业务数据上太激进了正常的长尾都会被判成异常。处理方式上不要无脑删除。我通常分三步走先标记出来看一眼分布判断是录入错误还是真实极值录入错误就修正或置为缺失值真实极值就做截断winsorize把它压到 1% 和 99% 分位点上既保留样本又不让它带偏模型。2.3 重复值与一致性校验最容易被跳过的一步重复值检测听起来简单df.drop_duplicates()一行就完了但真实场景里远没这么轻松。字符串类型的字段北京和北京市和 北京 带空格在程序眼里是三个不同的值日期字段2024-01-01和2024/1/1也是两回事。我的做法是先做一轮规范化再查重字符串统一去空格、统一大小写、统一全半角日期统一格式化类别字段建立映射表把同义值合并。这一步做完之后再 drop_duplicates效果会好很多。一致性校验则是检查字段之间的逻辑约束。比如“结束时间必须晚于开始时间”、“订单金额必须等于单价乘数量允许浮点误差”、“年龄字段不能超过 120”。这类断言最好写成代码每次数据更新都跑一遍比人工抽查靠谱一百倍。实操心得一致性校验失败的样本不要急着删先按失败类型分组统计。如果某一类失败集中在某个时间段或者某个渠道那很可能是上游采集逻辑出了问题这时候修上游比修数据重要得多。2.4 图像与文本数据的清洗要点图像数据的清洗有几个固定动作。第一损坏文件检测用 PIL 打开一遍捕获异常损坏的直接记录到黑名单第二尺寸和通道统一有的是 RGB 三通道有的是灰度单通道需要统一否则 dataloader 会报错第三重复图检测可以用感知哈希pHash算指纹相似度超过阈值的判为重复这个比直接比 MD5 更靠谱因为重新压缩过的图 MD5 肯定不一样但内容完全相同。文本数据这边清洗的重点是去噪和规范化。HTML 标签、URL、邮箱、连续空格、控制字符这些都要清掉全角转半角、繁体转简体如果下游模型是简体语料训练的也要做。遇到表情符号怎么办看任务。情感分析里表情符号是强信号必须保留并转成文字描述而做主题分类的时候表情符号基本是噪声删掉更干净。另外提醒一句清洗规则一定要写在一个独立的配置文件里不要散落在代码中。文本清洗规则往往需要反复调整集中管理改起来才不痛苦。3. 数据转换让特征变成模型能吃的形状清洗解决的是“对不对”的问题转换解决的是“合不合适”的问题。同一份干净数据转换方式不同模型表现可能差出十几个点。这一章讲几个最关键的转换决策。3.1 归一化与标准化搞混这两个的人太多了先把定义说清楚。归一化Normalization也叫 Min-Max Scaling把数据线性映射到 [0,1]公式是(x - min) / (max - min)。标准化Standardization也叫 Z-score把数据变成均值 0、方差 1公式是(x - mean) / std。什么时候用哪个我的判断逻辑是数据分布接近正态、算法对均值方差敏感逻辑回归、SVM、PCA、神经网络→ 用标准化数据有明确上下界、需要压缩到固定区间图像像素、某些距离度量→ 用归一化数据有极端离群值 → 两个都别直接用先处理异常值或者用鲁棒标准化减去中位数除以 IQR有个细节很多人不知道树模型XGBoost、LightGBM、随机森林不需要做归一化或标准化。因为树模型的分裂点是基于特征取值排序找的单调变换不改变排序所以做了也白做。你要是看到一个教程给 XGBoost 的特征做标准化基本可以判断作者是在套模板。3.2 类别特征编码独热、标签、目标编码的取舍类别编码是另一个重灾区。三种主流方法各有各的适用条件。独热编码One-Hot每个类别一个二进制列无先后关系最安全。缺点是类别数一多维度就爆炸几百个类别直接让特征维度翻几百倍。经验阈值是类别数低于 15 用独热超过就要考虑别的方案。标签编码Label Encoding把类别映射成 0,1,2,3…… 优点是省空间缺点是人为引入了顺序关系。如果你用的是线性模型或者神经网络模型会以为 2 比 1 大、3 比 2 大这是错的。所以标签编码只适合树模型或者类别本身有顺序比如“低/中/高”。目标编码Target Encoding用该类别的目标均值来替代类别值。这个方法威力大但极其容易泄漏必须配合交叉验证和平滑使用。平滑的意思是类别样本数少的时候让编码值向全局均值靠拢公式大致是(n * 类别均值 m * 全局均值) / (n m)其中 m 是平滑系数一般取 10 到 20。注意目标编码一定要在划分好训练验证集之后再算而且训练集内部要用 K 折的方式算否则同一个样本的目标值会被自己“看到”验证指标会虚高得离谱。这个坑我踩过当时验证 AUC 0.95上线之后掉到 0.6排查了半天才想起来是编码泄漏。3.3 分布变换偏态数据的处理手段很多数值特征天然是右偏的比如收入、用户停留时长、商品销量。这种分布直接喂给线性模型或者神经网络效果通常不好因为极端大值会主导梯度。常用的修正手段有三种对数变换log(x 1)最常用适合跨度好几个数量级的数据。加 1 是为了处理 0 值。Box-Cox 变换自动找一个最优的幂次参数 λ让数据尽量接近正态。要求数据全为正。Yeo-Johnson 变换Box-Cox 的推广版允许数据包含 0 和负数。我的习惯是先画一张直方图看偏度偏度绝对值大于 1 就做变换变换后再画一次确认。做完记得保存变换参数推理阶段新来的数据要用同一套参数处理。3.4 图像数据的几何与像素变换图像这边几何变换和像素变换是两件不同的事容易混。几何变换包括 resize、裁剪、旋转、翻转改变的是空间结构像素变换包括归一化、标准化、色彩空间转换RGB 转灰度、HSV改变的是数值表示。顺序上有讲究先几何后像素。因为 resize 会改变像素值插值算法会平均邻域像素如果你先做了归一化再 resize归一化就白做了。尺寸统一上不要直接暴力拉伸会变形。常见做法是短边缩放到目标尺寸然后中心裁剪或者保持长宽比缩放后 padding 补边。前者会丢边缘信息后者会引入黑边看任务选。分类任务一般用中心裁剪检测和分割任务必须用 padding不然标注框会错位。4. 数据降维与特征选择砍掉冗余留下信号维度不是越高越好。特征维度远大于样本量的时候模型几乎必然过拟合这就是所谓的维数灾难。降维和特征选择是两条不同的路降维是把原始特征变换成一组新的低维特征会改变语义特征选择是从原始特征里挑出一部分保留语义不变。4.1 PCA 的使用边界与常见误用PCA 是最经典的线性降维方法原理是找方差最大的几个正交方向作为新坐标轴。用法很简单sklearn.decomposition.PCA(n_components0.95)就能保留 95% 的方差。但 PCA 有几个前提经常被忽略。第一它假设主成分方向是线性的遇到非线性流形结构比如瑞士卷数据集就无能为力第二它对尺度敏感必须先标准化否则量纲大的特征会主导主成分第三降维后的特征是原始特征的线性组合可解释性会丢失如果业务方要求“必须能解释每个特征的含义”PCA 就不合适。还有个隐蔽的坑PCA 的fit只能在训练集上做然后transform验证测试集。我见过有人图省事在全量数据上 fit结果测试集信息泄漏进训练过程指标虚高。4.2 LDA、t-SNE、UMAP 分别解决什么问题这三个经常被放在一起讨论但其实它们的目标完全不同。**LDA线性判别分析**是有监督的降维目标是让不同类别的样本在低维空间里尽量分开。适合分类任务的特征压缩降维后维度最多是“类别数减一”。它和 PCA 的区别就是PCA 看方差LDA 看类别可分性。t-SNE是纯可视化工具把高维数据投到 2D 或 3D 平面上看聚类结构。它的优点是局部结构保持得极好缺点有三个计算慢、不能对新样本做变换没有transform方法、参数敏感perplexity 设不同值出来的图完全不一样。所以不要用 t-SNE 的结果做下游建模的输入特征它就是个看图工具。UMAP是 t-SNE 的改进版速度快得多而且支持对新样本做变换全局结构也保持得更好一些。如果要降维之后还要继续建模UMAP 比 t-SNE 合适。4.3 基于模型的特征重要性筛选过滤式方法方差阈值、卡方检验、互信息速度快但忽略了特征之间的交互包裹式方法递归特征消除效果好但计算量大我日常最常用的是嵌入式方法——直接用树模型的特征重要性来筛。具体做法是先训一个 LightGBM 或者随机森林取出feature_importances_然后有两种筛法。一种是按重要性排序取前 K 个K 靠交叉验证确定另一种是设一个阈值重要性低于阈值的全部删掉。我一般用后者阈值取 0.001 到 0.01 之间。一个注意事项树模型的重要性对高基数类别特征有偏好比如用户 ID 这种看到某个 ID 类特征重要性排第一要警惕很可能是过拟合的信号不是真的有用。4.4 降维的代价什么时候不该降降维不是必须的有时候不做反而更好。判断依据有三条。一是信噪比本来就高。如果特征都是精心设计的业务指标每个都有明确含义降维反而会引入噪声。二是下游模型本身有正则化能力。像 LightGBM、带 L2 的线性模型对冗余特征有一定容忍度。三是样本量足够大。维数灾难的核心矛盾是“维度相对样本量过高”如果样本量是维度的几十倍降维的收益就很有限了。我个人的经验是特征数在 100 以内、样本量上万的情况下优先做特征选择而不是降维特征数千以上、样本量几千的情况下先降维再看效果。5. 数据增强与样本平衡把小数据集用出大效果数据不够是常态。增强的本质是在不改变标签语义的前提下扩大样本的有效多样性。这里的关键词是“有效”——无脑增强出来的样本如果和真实分布差太远反而是有害的。5.1 图像增强的主流手段与真实坑点图像增强手段我按强度分三档。轻量级水平翻转、小角度旋转±15 度内、小范围平移。这三样几乎无脑用风险极低适合大多数分类任务。中量级随机裁剪、缩放、色彩抖动亮度、对比度、饱和度微调、Cutout随机遮挡一块区域。色彩抖动在做医学影像的时候要小心因为颜色本身可能就是诊断依据Cutout 的遮挡面积不要超过 25%否则可能把关键目标遮住。重量级Mixup、CutMix、RandAugment、AutoAugment。这些属于策略级增强效果显著但需要调参而且必须配合足够长的训练轮次不然模型还没学到东西就被增强了。一个真实坑点做医疗影像的时候我见过有人给皮肤镜图像做水平翻转增强。问题是皮肤病变往往有特定的不对称性模式翻转之后这个模式就反了相当于给模型灌了错误的标签。所以增强策略一定要结合数据本身的物理含义来定不能照搬 ImageNet 那一套。5.2 文本增强的几种可行做法文本增强比图像难因为文字是离散的改一个字符可能整个语义就变了。相对安全的做法有这么几种。同义词替换随机挑几个非关键词用同义词词典或者 WordNet 替换。要注意有些词在特定领域有特殊含义比如“阳性”在医学语境下不能随便换成“积极”。回译把句子翻译成另一种语言再翻译回来能得到语义相近但表达不同的句子。这个方法效果好但需要翻译模型成本高一些。随机插入与交换随机插入一个不影响语义的词或者交换相邻词的位置。改动幅度小风险低适合做数据扩增的补充手段。大模型改写现在用大语言模型做改写是很成熟的做法给一句 prompt 让它用不同说法重写同一个意思。效果不错但要注意控制生成的内容不能改变原意最好人工抽检一批。顺序上文本增强一般放在分词之前做因为增强操作是针对原始文本的。5.3 类别不平衡过采样、欠采样与代价敏感类别不平衡几乎每个真实项目都会遇到。假设正样本占 1%模型什么都不学全预测负类准确率就有 99%但这个模型毫无用处。处理思路有这么几条。重采样。过采样是复制少数类样本缺点是容易过拟合欠采样是删掉多数类样本缺点是丢信息。实践中效果比较好的折中是SMOTE及其变体它通过在少数类样本之间做插值来合成新样本而不是简单复制。但 SMOTE 在高维数据上效果会退化而且对类别边界模糊的数据会合成出错误的样本。用之前最好先降维。代价敏感学习。不改变数据分布而是在损失函数里给少数类更高的权重。class_weightbalanced一行就能搞定简单粗暴但有效。这是我最常用的方法因为不改数据、无额外风险。阈值调整。模型输出的概率不变改变判定阈值来平衡精确率和召回率。这个适合已经训好模型、只需要调整业务策略的场景。评价指标也要跟着换。不平衡场景下不要看准确率看 F1、AUC-ROC 或者 AUC-PR。样本极度不平衡的时候 AUC-PR 比 AUC-ROC 更能反映真实性能。6. 实操全流程从原始表格到可训练数据集前面讲的都是原理和判断逻辑这一章给一个可以直接跑的完整实现。以结构化表格数据为例用的是 pandas 加 scikit-learn。6.1 流程设计与目录结构我习惯把预处理拆成三个独立模块clean.py负责清洗transform.py负责转换pipeline.py把两者串成 sklearn 的 Pipeline。配置文件单独放一个config.yaml所有阈值、参数都从配置读代码里不写死数字。目录大概长这样project/ ├── config/ │ └── preprocess.yaml ├── src/ │ ├── clean.py │ ├── transform.py │ └── pipeline.py ├── data/ │ ├── raw/ │ ├── interim/ │ └── processed/ └── tests/ └── test_pipeline.py分成 raw、interim、processed 三层是刻意的。raw 目录只读永远不修改interim 放清洗后的中间结果processed 放最终可以直接喂模型的数据。这样任何一步出了问题都能回溯到上一层的状态不用从头重跑。6.2 核心代码实现先看清洗部分import pandas as pd import numpy as np def clean_dataframe(df, config): df df.copy() # 1. 字符串规范化 str_cols df.select_dtypes(include[object]).columns for col in str_cols: df[col] df[col].astype(str).str.strip().str.lower() df[col] df[col].replace({nan: np.nan, : np.nan}) # 2. 去重 before len(df) df df.drop_duplicates(subsetconfig[dedup_keys], keepfirst) print(f去重: {before} - {len(df)}) # 3. 缺失率过高的列直接删 missing_ratio df.isnull().mean() drop_cols missing_ratio[missing_ratio config[max_missing_ratio]].index df df.drop(columnsdrop_cols) print(f删除高缺失列: {list(drop_cols)}) # 4. 数值列异常值截断winsorize num_cols df.select_dtypes(include[np.number]).columns for col in num_cols: low, high df[col].quantile([0.01, 0.99]) df[col] df[col].clip(low, high) # 5. 一致性校验 if start_time in df.columns and end_time in df.columns: mask df[end_time] df[start_time] assert mask.sum() 0, f发现 {mask.sum()} 条时间倒置记录 return df几个要点说明一下。drop_duplicates的subset参数很关键通常只用业务主键或者几个核心字段做判重不要用全部列否则只要有一列不同就算作两条去重等于没做。winsorize 用的是 1% 和 99% 分位点这个值在数据量小的时候要放宽到 5% 和 95%不然会把太多正常值压平。再看转换部分这里用 sklearn 的 ColumnTransformer 把不同类型列的处理串起来from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.decomposition import PCA def build_preprocessor(num_cols, cat_cols, config): num_pipe Pipeline([ (imputer, SimpleImputer(strategymedian)), (scaler, StandardScaler()), ]) cat_pipe Pipeline([ (imputer, SimpleImputer(strategyconstant, fill_valuemissing)), (encoder, OneHotEncoder(handle_unknownignore, sparse_outputFalse)), ]) preprocessor ColumnTransformer([ (num, num_pipe, num_cols), (cat, cat_pipe, cat_cols), ], remainderdrop) return preprocessorhandle_unknownignore这行很重要。推理阶段如果遇到训练时没见过的类别这个参数会让它编码成全 0 向量而不是直接报错。生产环境里这个参数基本是必加的。6.3 划分数据集与防泄漏检查划分和拟合的顺序是防泄漏的核心。正确写法是这样from sklearn.model_selection import train_test_split # 第一步划分 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) # 第二步只在训练集上 fit preprocessor build_preprocessor(num_cols, cat_cols, config) X_train_t preprocessor.fit_transform(X_train) X_test_t preprocessor.transform(X_test) # 注意是 transform 不是 fit_transform # 第三步断言检查 assert X_train_t.shape[1] X_test_t.shape[1], 维度不一致 assert not np.isnan(X_train_t).any(), 训练集仍有缺失值stratifyy保证划分后各类别比例一致类别不平衡的时候必须加。第三步的断言看着多余但真的救过我几次——有一次类别编码器在测试集上遇到了新类别虽然设了handle_unknown但某个中间环节还是产生了 NaN靠这个断言当场发现了。如果要进一步防泄漏可以在训练集内部再做一次 K 折用于目标编码这类高风险操作。sklearn 的KFold配合手写循环就行注意每一折内部单独计算编码映射表。6.4 一个完整的处理清单把整条链路串起来大概是这样读取原始数据记录原始行数和列数字符串规范化、类型转换按业务主键去重删除高缺失率列填充剩余缺失值异常值检测与截断一致性断言校验划分训练/验证/测试集在训练集上 fit 所有转换器依次 transform 三个集合保存转换器对象到磁盘用 joblib推理时加载复用第 10 步经常被忘。训练时的 StandardScaler 参数如果不保存推理阶段重新算一遍前后标准就不一致了模型输出会莫名其妙地飘。用joblib.dump(preprocessor, preprocessor.pkl)存下来服务端加载即可。7. 常见问题与排查技巧实录这一章是我这些年攒下来的一些实战问题和处理经验按问题类型整理成速查表遇到对应症状可以对照排查。7.1 常见问题速查表症状可能原因排查方法解决方向训练损失震荡不收敛特征未标准化量纲差异大打印各特征均值方差加 StandardScaler验证指标远高于线上数据泄漏检查是否在全量数据上 fit严格划分后 fit模型只预测多数类类别严重不平衡看混淆矩阵加 class_weight训练集表现好测试差过拟合或重复样本跨集检查两集交集去重后再划分推理结果与训练不符转换器参数未保存对比前后统计量序列化 preprocessor特征重要性集中在 ID 类高基数特征过拟合看特征基数删掉或用目标编码图像训练报维度错误通道数不统一遍历检查 shape统一转 RGB显存爆掉未做尺寸统一或 batch 过大打印单样本 shaperesize 调 batch这张表可以直接贴在手边排查的时候按症状找原因比盲猜快很多。7.2 几个踩过的坑和对应解法第一个坑验证集和训练集有重复样本。这个在图像任务里特别常见。爬来的数据集里同一张图可能来自不同来源文件名不同但内容相同。我当时用 MD5 去重结果漏掉了一批经过压缩的重复图验证集指标虚高 8 个点。后来改用感知哈希才彻底解决。表格数据同理如果主键不唯一但业务上算是同一条记录也要按业务键去重。第二个坑填充值选错导致模型学偏。有个项目里用户年龄缺失同事直接填了 0。训练完之后模型显示“年龄越小转化率越高”业务方觉得发现了新洞察差点拿去做策略。实际上是因为填 0 的那批用户本身行为就特殊。改成填中位数并加缺失指示列之后这个虚假规律就消失了。第三个坑增强策略和数据性质冲突。前面提过的医学影像翻转问题。不止翻转垂直翻转在自然图像上就很少用因为天空和地面是有方向的翻了之后不符合物理常识。增强策略每加一项都应该想清楚“这个变换之后样本在现实中还可能存在吗”。第四个坑降维降过头。有次为了加速训练把 200 维特征 PCA 降到 10 维模型 AUC 掉了 5 个点。回头一看解释方差比只有 0.7说明丢了 30% 的信息。后来改成保留 95% 方差维度大概是 60 维效果就基本无损了。教训是n_components设成整数之前先看看解释方差曲线别拍脑袋。第五个坑pipeline 里的随机性没固定。随机森林、SMOTE、数据划分都有随机性任何一处没设random_state实验结果就不可复现。我的做法是在项目根目录定义全局随机种子常量所有涉及随机的调用都引用它这样改种子只需要改一个地方。7.3 一些提高效率的个人习惯最后分享几个让预处理工作变轻松的习惯。数据先看再动。拿到新数据集第一件事不是写代码是打开看一眼。df.head()、df.describe()、df.info()三连再画几个直方图和箱线图。花十分钟看数据能省下后面好几小时的调试。我见过太多人上来就写一堆处理逻辑跑完才发现某个字段根本就是空的。每一步都留痕。数据行数变化、列数变化、关键统计量每做一步操作就打印一次。这些日志在出问题的时候就是线索。我更推荐把日志写进文件而不是只在终端看。写测试。预处理代码是那种“错了也不报错”的代码最需要测试。我一般会写几个针对性的测试用例给一条已知的输入断言输出符合预期给一条含缺失值的输入断言输出不含 NaN给一条含未知类别的输入断言不抛异常。这几个测试跑通了心里的底就实了。别追求一步到位。预处理是个迭代过程。第一版能跑通就行然后根据模型表现回头调。指望一开始就把所有情况考虑周全结果往往是陷在细节里出不来。数据预处理这活儿做久了会发现它其实很考验对业务的理解。同样一份数据懂业务的人知道哪些异常值是真实信号哪些是采集错误知道缺失值背后藏着什么行为模式知道增强策略该怎么定才不违背常识。这些判断没有标准答案只能靠在项目里一次次试错慢慢积累。我自己到现在每接一个新数据集还是会先花半天时间跟它“混熟”然后再动手写代码。