资讯详情

资讯详情

深度学习中的数据预处理实战指南:缺失值、缩放与编码全解析

我当年第一次跑深度学习模型把原始数据直接塞进模型想着“反正框架会自动处理”结果损失函数直接变成 NaN找了一天错误最后发现是特征里有几列缺失值没处理加上一个特征的量级是几万另一个是零点几梯度直接炸了。从那以后我就明白模型调参固然重要但数据预处理做得不到位后面全是白费功夫。这篇东西就当是我的第三课笔记讲的就是深度学习里的数据预处理。不管你是刚入门的学生还是已经跑过几个项目想系统梳理一下的开发者都应该能从中拿走点能直接用的东西。我会把常见的处理手段、背后的原理、实际操作的坑以及我自己用下来的经验都写出来尽量不说废话。1. 为什么数据预处理在深度学习里这么关键很多人觉得深度学习是“端到端”的给了数据就能出结果所以预处理不重要。这个想法不能说全错但忽略了一个事实模型虽然能自己学特征但它对输入数据的格式、量纲、分布是非常敏感的。1.1 模型训练的本质是对数值做运算神经网络的每一层本质上都是对输入做加权求和、激活函数变换、再传给下一层。整个过程里没有一个环节能理解“性别”这种文本也没有一个环节能天然处理“NaN”这种缺失值。模型能做的只是数学运算你喂给它的必须是干净、统一的数值。我用一个生活类比解释一下你把一堆混着沙子、石子、铁钉的原料直接扔进精密机床机床再高级也会卡刀、崩刃。数据预处理就是清洗、分类、规整这些原料的过程让机床能稳定运转。深度学习框架再强大也替代不了这一步。1.2 数据质量直接决定模型上限有一句话说得很到位Garbage in, garbage out。模型的上限是由数据决定的模型结构只是逼近这个上限的方式。如果数据里充满缺失值、异常值、量纲不一致的噪声那模型再复杂学到的也是数据里的垃圾模式。我之前用某公司的用户行为数据做过一个预测项目原始数据里有一条特征“页面停留时长”理论上应该是大于0的数值但数据里有大量负值还有的是0。如果不处理模型就会学到“停留时长为负的用户转化率最高”这种完全荒谬的规律。这就是预处理的必要性不是锦上添花而是基础工程。1.3 深度学习框架的数据接口给预处理留下的空间现在主流的深度学习框架比如 PyTorch、TensorFlow都提供了 Dataset、DataLoader 这类数据接口。这些接口的设计初衷之一就是让你在数据进入模型之前有一道“加工流水线”。很多人跳过这个环节直接喂原始数据其实等于浪费了框架设计者特意留给你的处理空间。我在实际项目里的经验是框架层面的数据流水线不只是为了效率更是为了让你把预处理逻辑集中管理。否则每个人预处理代码写一摊后面维护起来非常痛苦。这个后面在实操部分我会详细展开。2. 常见的数据问题与处理方式总览开始动手之前先弄清楚我们到底要处理哪些类型的问题。我把日常项目里最常见的数据问题梳理成四类这样后面每讲一个就能对号入座。2.1 数据问题的四个大类问题类型典型表现后果处理手段缺失值某些字段为 None、NaN、空字符串模型无法计算或学到错误模式删除、填充均值/中位数/插值/模型预测异常值数值远超正常范围如年龄200岁拉偏梯度导致训练不稳定截断、分箱、稳健缩放量纲不一致有的特征范围是0-1有的是0-10000大数值特征主导梯度更新归一化、标准化非数值数据文本、类别、日期等模型无法直接计算编码、嵌入化、时间戳转换这四类问题很少单独出现真实数据往往是四者混杂。我的习惯是先做一遍全局的“数据体检”写一段代码把所有特征的缺失率、分布、量纲情况打出来再去决定每一项怎么处理。2.2 “先看后处理”是一条铁律千万不要拿到数据就开始写处理代码。我见过太多人包括当年的我一上来就 fillna(0)结果把整个分布都搞坏了。正确做法是先了解每列数据的类型、缺失比例、分布形态、取值范围甚至看一眼业务含义再决定怎么处理。具体来说我会对每个特征问三个问题这个特征在业务上代表什么这个特征缺失了代表什么是“没有此事”还是“记录遗漏”这个特征和我们要预测的目标之间是什么关系这三个问题想清楚处理方法自然就定了。比如“收入”字段缺失可能意味着用户没填也可能意味着该用户没有收入这两种业务含义的处理方式完全不同。前者可以用全量均值填充后者则应该填0。2.3 规定一个统一的处理顺序数据预处理涉及的操作很多如果不固定顺序后面排查问题会很麻烦。我自己固定了一套顺序基本涵盖了九成以上的场景先做数据清洗去掉重复行、处理明显的格式错误。再处理缺失值先决定行删除还是列删除再对剩余缺失做填充。处理异常值用描述性统计或者 IQR 规则识别并处理。特征编码把非数值类型转换成数值。缩放/归一化让所有特征处于相近的量级。数据集划分按比例切分训练、验证、测试集。这套顺序不是绝对的但大体上是对的。很多操作之间有依赖关系比如先填充缺失才能计算均值来做标准化先编码才能做某些缩放比如对有序类别做归一化所以顺序很重要。3. 缺失值处理不一定非要用0填缺失值可能是最常遇到的脏数据问题。它的处理方式远比想象中复杂因为“缺失”本身可能包含信息也可能纯粹是噪声。3.1 先判断缺失的三种类型从统计学的角度缺失值可以被分成三类处理方式完全不同完全随机缺失缺失与否和其他变量无关纯粹是记录时的偶然。这种情况直接删除缺失的样本或者用简单的均值填充都不会引入太大偏差。随机缺失缺失与否和其他观测变量有关。比如女性更倾向于不填收入那么“收入缺失”这个事件就和“性别”相关。这种情况需要用其他特征来预测缺失值。非随机缺失缺失与否和缺失值本身有关。比如高收入的人故意不填收入。这种情况最难处理简单的填充方法会引入系统性偏差。我们在实际工程里不会做这么细致的统计检验成本太高但至少要建立这个意识直接填0有时候是错的。3.2 四种实用的填充策略在实际项目中我常用的缺失值填充方法按优先级排序如下业务规则填充如果缺失有明确的业务含义比如“未贷款用户没有贷款金额”直接填充一个代表“无”的值通常填0。统计量填充对于数值型特征用均值、中位数或众数填充。均值适合分布对称的数据中位数适合有偏分布众数适合类别特征。插值法对时间序列或者有序数据用线性插值、样条插值填补缺失点。模型预测填充用其他特征作为输入训练一个简单模型KNN、随机森林、线性回归等来预测缺失值。这个方法效果好但代价是需要额外的训练开销。我在项目里最常用的组合是业务规则优先然后是统计量填充模型预测填充只在缺失比例较高且特征间关联性强时使用。补充一点填完之后一定加一个“是否缺失”的指示特征is_missing因为缺失本身往往能提供信息。3.3 什么时候直接删行或删列填充虽然方便但有时候删除反而是更好的选择缺失比例低于5%且样本量足够大直接删除缺失样本对整体分布影响微乎其微。某一列缺失比例超过70%该列信息量太少容易引入噪声建议整列删除。某一行多个关键特征都缺失该样本几乎没有可用信息删除比填充更干净。我用一个实际经验打个样之前处理过一个电商用户数据集其中“会员等级”这一列缺失了82%。我一开始坚持用众数填充后来做特征重要性分析时发现这列几乎没贡献最后直接删掉模型效果反而微涨。这就是“劣质特征不如没有特征”。注意填充缺失值必须在训练集和测试集上分别操作但填充所用的统计量如均值只能用训练集计算得出避免测试集信息泄漏到训练过程中。4. 特征缩放让每个特征在同一个量级上说话特征缩放是我认为整个预处理流程里最容易出效果、却也最容易被忽视的一步。很多初学者跑模型发现 loss 降不下去或者训练过程震荡剧烈十有八九是特征量纲的问题。4.1 为什么量纲不一致会导致训练不稳定神经网络的梯度更新公式里每个参数的梯度大小和它对应的输入特征数值大小成正比。如果一个特征的范围是0-10000另一个是0-1那么前者的梯度贡献会远超后者相当于后者完全被淹没了。梯度下降朝着大数值特征的方向大步走小数值特征几乎不动模型就很难收敛到最优解。这就好比两个人合伙抬一根木头一个人身高两米壮如牛另一个人身高一米二瘦如柴你想让木头保持水平移动几乎不可能。特征缩放就是先把两个人的力量调整到相近的水平。4.2 标准化与归一化的区别和选择最常见的两种缩放方式是标准化Standardization和归一化Normalization。标准化也叫 Z-score 标准化公式是[ x \frac{x - \mu}{\sigma} ]处理后的特征均值是0标准差是1保留了原始分布的形态信息。它适合大多数梯度类模型尤其是数据分布接近高斯分布的时候效果最好。归一化有两种常见形式Min-Max 归一化[ x \frac{x - x_{min}}{x_{max} - x_{min}} ]把数据映射到0到1之间。MaxAbs 归一化除以最大绝对值映射到-1到1之间。归一化适合数据有明确边界、分布比较均匀的情况比如像素值0-255、概率值0-1。我的经验是对于深度学习模型默认推荐标准化Standardization。因为归一化容易受异常值影响最大值和最小值一变整个映射就变了而标准化对异常值相对稳健。如果你做的是图像处理像素归一化到0-1是惯例那另当别论。4.3 对稀疏数据不要用标准化有一类数据是稀疏的比如用 one-hot 编码后的类别特征、或者文本的 TF-IDF 矩阵里面绝大多数是0。这时候用标准化去缩放会让稀疏结构变得稠密白白浪费内存和计算资源而且缩放后的0不再是0破坏了稀疏性。对于这类数据我的做法是保持稀疏或者只做行方向的 L2 归一化。有一种专门针对稀疏特征的缩放方式是只缩放非零元素但实现起来稍显繁琐项目里我用得不多提一下供参考。4.4 缩放必须在数据划分之后进行这可能是特征缩放里最容易犯错的地方我专门提出来强调计算均值和标准差或者最大值、最小值只能用训练集的数据然后把这个“标准”应用到验证集和测试集上。拿标准化举例你先在全部数据上算均值和标准差然后切分数据集。这样做的问题在于测试集的信息已经通过均值和标准差混进了训练过程模型的评估结果就会偏乐观。这在学术上叫数据泄漏data leakage是论文审稿人最喜欢抓的问题之一。正确流程是这样的先把数据切分成训练集、验证集、测试集。在训练集上计算均值和标准差。用训练集的均值和标准差分别对训练集、验证集、测试集做标准化。代码上可以用 sklearn 的 StandardScaler 或 PyTorch 的 transforms 来实现核心逻辑是一致的。5. 类别特征编码把“文字”变成模型能懂的“数字”现实数据里类别特征几乎是无法避免的。用户所在地域、设备类型、商品类目、星期几这些都是类别特征。模型只能做数值运算所以必须把类别转换成数值。但这个“转换”远不像 map 一下那么简单不同类别特征的性质决定了不同的编码方式。5.1 类别特征的两大类定类与定序在动手编码之前先区分两个概念定类特征类别之间没有大小顺序关系。比如颜色、城市、设备品牌。红色和蓝色没有“谁更大”的说法。定序特征类别之间存在顺序关系。比如学历小学初中高中本科研究生、用户等级普通银卡金卡钻石。这两种特征的编码方式完全不同。定序特征可以映射成连续整数本科3研究生4因为模型可以利用顺序信息定类特征如果用整数编码红色1蓝色2绿色3模型就会错误地认为绿色大于蓝色而且差值有实际意义这完全是误导。5.2 定类特征的编码方案对比编码方式做法优点缺点适用场景One-Hot 独热编码每个类别一列命中为1否则为0无顺序误导简单直观类别多时维度爆炸类别数量少如20哈希编码用哈希函数映射到固定维度维度可控可能碰撞丢失可解释性类别特别多内存受限目标编码用该类别对应目标变量的均值编码维度低携带监督信息容易过拟合需要平滑类别多且有强关联性嵌入编码训练密集向量表示最灵活能学到相似度需要训练成本不可解释深度学习模型内部处理拿实际例子说明用户设备类型这个特征一般只有 iOS、Android、Web 等少数几个类别用 One-Hot 编码最直接。但如果你处理的是商品类目几万个类目One-Hot 马上就炸了——几万列又会引入极强的稀疏性。这时候可以先用哈希编码压缩维度或者在模型里直接加嵌入层。5.3 One-Hot 编码的两个实操注意点使用 One-Hot 编码时有两个坑是我踩过之后才真正理解的。第一个坑训练集和测试集可能类别不一致。训练集里出现了 A、B、C 三个类别测试集里冒出一个 D如果你用 pandas 的 get_dummies 分别处理两个数据集列数就会不同模型直接报错。解决办法是先合并两个数据集一起 get_dummies再拆分或者用带固定类别集合的编码器比如 sklearn 的 OneHotEncoder(handle_unknownignore)。第二个坑线性模型里要丢弃一列。One-Hot 编码后如果不丢掉一列特征矩阵会存在多重共线性每一列可以由其他列线性表示。对于树模型无所谓但对于线性回归、逻辑回归这类模型会导致参数估计不稳定。sklearn 的 OneHotEncoder 有个参数 dropfirst 可以处理。5.4 目标编码的正确用法想补充一下目标编码Target Encoding因为它用好了效果立竿见影用坏了会直接过拟合。基本原理就是用某个类别下目标变量的平均值替代这个类别本身。比如“城市A的用户转化率是5%”就把“城市A”这个类别编码成0.05。它的问题是目标信息泄露——你在训练自己的特征时直接用了目标变量的信息。解决办法是交叉验证式的目标编码在每个折内只用该折训练数据计算类别均值应用到该折的验证数据上。我在用户流失预测项目里用过目标编码处理“省份”字段效果比 One-Hot 好很多因为省份类别有30多个One-Hot 太稀疏目标编码则直接把流失率信息压缩进了特征。但不做交叉验证的话模型在训练集上表现极好测试集上崩得稀碎这个教训我一直记着。6. 实操演练完整的数据预处理流程讲了这么多理论我们上手写代码。下面我用一个模拟的电商用户数据集完整示范一遍数据预处理流程。这里的代码以 PyTorch 和 pandas 为主毕竟这是目前深度学习项目里最主流的组合。6.1 模拟数据与任务场景为了演示我先造一个简单的数据集。场景是预测用户是否会在未来30天内复购二分类任务。原始数据包含以下字段user_id用户ID需丢弃age年龄可能存在缺失和异常total_spent累计消费金额量纲大visit_freq月均访问次数量纲小device_type设备类型定类特征member_level会员等级定序特征churned_last_month上个月是否流失缺失值label是否复购目标值import pandas as pd import numpy as np # 模拟数据 np.random.seed(42) n 10000 data { user_id: range(n), age: np.random.randint(18, 60, sizen).astype(float), total_spent: np.random.lognormal(mean5, sigma1.5, sizen), visit_freq: np.random.rand(n) * 20, device_type: np.random.choice([iOS, Android, Web], sizen), member_level: np.random.choice([normal, silver, gold, diamond], sizen), label: np.random.choice([0, 1], sizen, p[0.7, 0.3]) } df pd.DataFrame(data) # 人为制造脏数据 df.loc[df.sample(500, random_state1).index, age] np.nan df.loc[df.sample(300, random_state2).index, total_spent] np.nan df.loc[df.sample(200, random_state3).index, age] np.random.randint(100, 200, size200) df[device_type] df[device_type].astype(object) print(df.info()) print(df.describe())这段代码故意制造了三种脏数据年龄缺失、消费金额缺失、年龄超过100的异常值。6.2 数据清洗与缺失值处理拿到这个数据后按我前面的顺序来。第一步清洗user_id 是纯标识符对预测毫无帮助直接丢弃。df df.drop(user_id, axis1) # 处理年龄缺失 # 业务上可以认为年龄缺失是随机的先用中位数填充 age_median df[age].median() df[age].fillna(age_median, inplaceTrue) # 处理total_spent缺失 # 消费金额分布通常右偏中位数填充比均值更稳健 spent_median df[total_spent].median() df[total_spent].fillna(spent_median, inplaceTrue) # 标记缺失 df[age_missing] df[age].isna().astype(int) df[spent_missing] df[total_spent].isna().astype(int)等一下上面的代码有个顺序问题我先填充了缺失然后又用 isna 检查这时候缺失已经被填充了isna 永远是 False。正确的做法是先记录缺失标记再填充。我修正一下# 记录缺失标记 df[age_missing] df[age].isna().astype(int) df[spent_missing] df[total_spent].isna().astype(int) # 填充缺失 df[age].fillna(df[age].median(), inplaceTrue) df[total_spent].fillna(df[total_spent].median(), inplaceTrue)这个顺序问题很容易被忽略但影响很大。如果先填充再标记你的模型就完全不知道哪些样本本来是有缺失的也就无法利用“缺失事件”本身的信息。6.3 异常值截断接下来处理年龄异常。年龄在100-200之间明显不合理直接删除会损失样本截断更合适。常用的截断方法是用分位数低于5%分位数或高于95%分位数的值被截断到边界值。p_low df[age].quantile(0.05) p_high df[age].quantile(0.95) df[age] df[age].clip(lowerp_low, upperp_high)这里用 5%-95% 分位数截断是经验做法不是唯一标准。如果业务上有明确的合理范围比如年龄必须在18-65之间应该优先用业务边界。我在实际项目里更倾向于先用业务规则业务规则无法覆盖时再用统计分位数。6.4 类别特征编码按照前面讲的device_type 是定类特征用 One-Hot 编码。member_level 是定序特征映射成有序整数。# 定序特征映射 level_map {normal: 0, silver: 1, gold: 2, diamond: 3} df[member_level] df[member_level].map(level_map) # 定类特征 One-Hot df pd.get_dummies(df, columns[device_type], drop_firstTrue)注意代码里的 drop_firstTrue这对应我前面说的“丢弃一列避免共线性”。device_type 本来有三个类别one-hot 后只生成两列写成“是否为iOS”、“是否为Android”如果都是0就代表Web。6.5 特征缩放与数据集划分接下来是最关键的一步先划分后缩放。我用 sklearn 的 train_test_split 把数据切成训练集、验证集、测试集然后只在训练集上拟合 Scaler。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X df.drop(label, axis1) y df[label] # 先划分 X_train, X_temp, y_train, y_temp train_test_split(X, y, test_size0.3, random_state42) X_val, X_test, y_val, y_test train_test_split(X_temp, y_temp, test_size0.5, random_state42) # 后缩放 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_val_scaled scaler.transform(X_val) X_test_scaled scaler.transform(X_test)我自己在一次复盘时发现很多项目的预处理代码是先缩放后切分这在传统机器学习里勉强能忍如果你不关心评估偏差但在深度学习里是硬伤因为训练过程中每次迭代都会用到测试集的信息模型的泛化能力评估就失真了。另外那个 X_temp 是临时变量先把数据切出30%的临时集再在临时集里一半一半切出验证和测试。这样训练:验证:测试就是 70:15:15比例是比较通行的做法。6.6 在 PyTorch 中用 Dataset 封装整个流程预处理的结果如果只是 numpy 数组还得转成 PyTorch 的 Dataset 才能训练。更优雅的做法是把前面的预处理封装成一个自定义 Dataset 类让数据在每次迭代时自动被处理逻辑清晰且不易出错。import torch from torch.utils.data import Dataset, DataLoader class UserDataset(Dataset): def __init__(self, X, y): self.X torch.tensor(X, dtypetorch.float32) self.y torch.tensor(y, dtypetorch.float32) def __len__(self): return len(self.y) def __getitem__(self, idx): return self.X[idx], self.y[idx] train_dataset UserDataset(X_train_scaled, y_train.values) val_dataset UserDataset(X_val_scaled, y_val.values) test_dataset UserDataset(X_test_scaled, y_test.values) train_loader DataLoader(train_dataset, batch_size256, shuffleTrue) val_loader DataLoader(val_dataset, batch_size256, shuffleFalse) test_loader DataLoader(test_dataset, batch_size256, shuffleFalse)这样写的核心好处是数据加载逻辑和处理逻辑解耦。以后要加新的数据增强、特征变换只需要在 Dataset 的__getitem__里加几行不会影响训练主流程。7. 数据增强你这辈子不处理增强模型容量再大也没用数据增强Data Augmentation虽然是一种特殊的预处理但它解决的问题和前面的缺失值、缩放完全不一样。前面是“数据有毛病我们要修正”数据增强是“数据没毛病但不够多我们要造”。7.1 数据增强为什么有效深度神经网络动辄几十万、几百万参数而真实标注数据的获取成本非常高。如果数据量不足以支撑这么大的模型模型就会过拟合。数据增强通过对原始样本做合理的变换生成多个“看起来不同但语义一致”的样本相当于免费扩充了数据集。以图像任务为例一张猫的照片水平翻转一下还是一张猫的照片但像素分布完全不同。模型在这个新样本上学习到的特征就对“猫在图像左侧”这种情况更有鲁棒性。少样本场景下数据增强往往是提升模型效果最直接的手段。7.2 常用数据增强方法速查领域常用增强方法实例图像翻转、旋转、裁剪、颜色扰动、加噪声水平翻转、随机裁切256x256文本同义词替换、回译、随机删除把“开心”替换为“高兴”时间序列加噪声、缩放、时间扭曲、通道置换对信号加高斯噪声幅度0.02表格数据SMOTE 过采样、特征噪声用 KNN 合成少数类样本7.3 做增强要克制不是所有变换都合理数据增强有个前提就是变换后的样本必须保持语义不变。一张 “6” 的数字图片水平翻转后就变成 “9” 了如果拿来训练数字识别模型就是纯纯的负样本干扰。同样在医学影像里做大幅旋转也可能破坏诊断信息。我的经验是数据增强强度要循序渐进先从弱变换开始观察验证集表现。如果增强后的数据让训练集 loss 和验证集 loss 一起升高说明增强强度太大了破坏了原有语义。在实际项目里我用过一套配置能看出点启发from torchvision import transforms train_transform transforms.Compose([ transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees10), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])注意最后一步 Normalize 用了 ImageNet 的均值和标准差这是迁移学习里的惯例。如果你是从零训练自己的模型就需要自己统计数据集的均值和标准差。8. 常见问题与排查技巧实录预处理的坑非常多我把自己踩过的一些坑整理出来希望能帮你少走弯路。8.1 训练 loss 为 NaN 的排查这是深度学习新手最常遇到的问题。我建议按以下顺序排查检查数据里有没有 NaN 或 Inf在模型前向传播前打印一条数据检查一下确认预处理已经把缺失值清干净。检查特征量纲如果特征范围差异过大梯度可能溢出尝试标准化。检查学习率学习率过大可能导致梯度爆炸调小一个数量级再试。检查标签取值回归任务标签过大会让损失函数爆炸尝试对标签做对数变换。我在处理一个价格预测任务时就发现标签“房价”的范围在几十万到几千万之间。模型一开始 loss 是 NaN我查了三天才发现不是模型问题而是没对标签做标准化。把标签做 Log1p 变换后loss 立刻正常了。8.2 训练集和测试集特征不一致的坑这个问题在比赛和实际工程里都很常见。你的特征工程在训练集上做了一堆衍生特征比如从日期提取星期几、从文本提取长度但测试集的特征少了一列或者列的顺序不同模型直接报错。解决方案是统一特征列表。我习惯在特征工程结束后固定一份特征列清单保存成文件后续无论是训练还是推理都严格按这份清单取列。不要用 pandas 的隐式列匹配要显式地选择列。# 保存特征列清单 with open(feature_columns.txt, w) as f: f.write(\n.join(feature_cols))8.3 幂等性问题数据预处理代码必须能重复执行这个坑更隐蔽。很多人的预处理代码是一次性的在 notebook 里从上到下跑一遍数据就被改干净了。但一旦你重新运行某个单元格比如填充逻辑被再次执行就可能出错。典型例子你已经把 age 列的缺失值填成中位数了第二次执行 fillna 就找不到缺失值但你能保证你的代码逻辑考虑了这种情况吗我的习惯是把预处理流程封装成函数并且每一步都做“断言”。比如填充前断言还有缺失值填充后断言没有缺失值。这样代码既是自动化的也是可重复的。def preprocess(df): # 断言处理前存在缺失 assert df[age].isna().sum() 0 df[age].fillna(df[age].median(), inplaceTrue) # 断言处理后没有缺失 assert df[age].isna().sum() 0 return df8.4 数据泄漏导致的“假高分”数据泄漏是预处理环节里最常见、最隐蔽的问题。除了前面提到的缩放时机目标编码、缺失值填充的统计量、特征选择的阈值这些只要是“从全部数据计算再应用到数据上”的步骤都可能引入泄漏。我在做某个竞赛时曾经在验证集上拿到非常好的成绩AUC 0.98上了测试集直接掉到0.75。后来排查发现是目标编码的均值计算用到了全部数据。修正成交叉验证式的编码之后验证集和测试集的表现才趋于一致。给你一个简单的自查方法在预处理代码里凡是出现np.mean(df[col])、df[col].median()、df[col].max()这类全量聚合操作都要问一句这个操作是在训练集内做的还是在全量数据上做的如果是后者大概率存在泄漏。9. 个人实操总结与经验技巧最后分享一些我在实践中积累的经验不成系统但每一条都值一些调试时间。第一点不要在项目一开始就追求完美的预处理方案。先用最简单的处理方式搭起一个 baseline基线模型然后把作为基准记录好再逐步改进预处理方案对比效果。很多人一上来就做复杂的目标编码、多级插补、手工特征结果 baseline 没建后面所有改动都没有参照系根本看不出每一步带来了多少收益。第二点尽可能把预处理代码版本化。我现在的做法是每个预处理操作写一个函数并且用 CSV 记录每次实验用的预处理配置。模型效果差了我能准确知道是哪一步处理的改动导致的。如果所有预处理都在一个巨大的 notebook 里没法追溯出了效果波动你也不知道锅该谁背。第三点很多预处理技巧的效果是和数据量、模型结构强相关的。比如数据量非常小的时候删缺失行比复杂插补更好数据量足够大时模型可能自己学会处理噪声特征过度清洗反而损伤信息。所以不要迷信某一套固定方案拿数据多试。第四点关于自动化。当你的项目从研究走向工程化落地数据预处理就不再是几个人各写各的脚本而是会成为数据流水线的一部分。这个时候预处理逻辑必须是确定性的、可重复的、有监控的。我见过太多模型在训练时表现良好一到线上预测就崩原因就是训练和推理时走了两套不同的预处理代码。这点务必留意。最后再透露一个细节预处理做完之后我总会做一个“抽样人工检查”的步骤。随机抽出几张处理后的样本直接用肉眼看看数据变成什么样了。比如图像是不是被翻转得不成形了表格数据的数值是不是符合预期范围。这一步花不了十分钟但能拦住一大批低级错误。我职业生涯里最贵的教训几乎都是从这些低级错误里学到的。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →