数据预处理:数据清洗、归一化与标准化
做模型优化,数据预处理是绕不开的第一关。说实话,很多新手上来就调网络结构、调学习率,结果模型死活不收敛。我见过太多这种情况了——最后查下来,八成是数据没处理好。
我个人习惯把数据预处理拆成三层:清洗、归一化、标准化。每层解决不同的问题。今天咱们就把这三层彻底讲透。
先说数据清洗
数据清洗,说白了就是去脏数据。脏数据长什么样?我给你列几种常见的:
- 缺失值——某个特征列里一堆 NaN
- 异常值——某个样本的数值明显偏离正常范围
- 重复数据——同一条样本出现多次
- 格式不统一——时间戳、字符串、类别编码乱七八糟
处理缺失值,我建议先看数量。如果缺失比例低于 5%,直接删掉那几行就行。但如果缺失超过 20%,你就要想想了——删太多会损失信息量。我曾经有个项目,特征缺失率到了 30%,我一口气删了 2 万条样本,结果模型方差直接炸了。后来改成用中位数填充,效果好了不少。
经验法则:
- 缺失 < 5%:直接删除
- 5% ~ 20%:均值/中位数填充,或用 KNN 插值
- > 20%:考虑该特征是否保留,或使用模型预测填充
异常值处理呢?我常用 Z-score 或 IQR 方法。Z-score 超过 3 的,基本就算异常了。但注意,对于非正态分布的数据,IQR 更靠谱。
import numpy as np
def remove_outliers_iqr(data, factor=1.5):
q1 = np.percentile(data, 25)
q3 = np.percentile(data, 75)
iqr = q3 - q1
lower = q1 - factor * iqr
upper = q3 + factor * iqr
return data[(data >= lower) & (data <= upper)]
再说归一化
归一化,也叫 Min-Max 缩放。它的目标是让所有特征都落在 [0, 1] 区间里。为什么要这样做?你想想看,图像任务里像素值都是 0~255,如果模型直接拿原始值算梯度,大数值的特征会主导损失函数,小数值的特征直接被淹没了。
公式很直白:
X_norm = (X - X_min) / (X_max - X_min)
我在做视觉项目时,习惯先做归一化再输入网络。尤其是 ResNet、EfficientNet 这些预训练模型,它们的权重都是在归一化数据上训出来的。输入不归一化,精度直接掉 3~5 个点,不用怀疑。
小提示:归一化的 min 和 max 必须从训练集计算,然后用同样的值去 transform 验证集和测试集。不要用测试集的 min/max 重新算——那是数据泄露,会高估模型性能。
最后讲标准化
标准化和归一化经常被人搞混。标准化把数据变成均值为 0、标准差为 1 的分布,公式是:
X_std = (X - mean) / std
标准化不限制数值范围,它只改变数据的分布形状。适合的场景是什么?比如你的数据有长尾分布、有正负值,或者你打算用 PCA、SVM 这类对尺度敏感的算法,标准化都更合适。
我踩过一个坑:有一次做文本分类,词频特征的取值分布差异很大——停用词出现几百次,关键词只有几次。我一开始用了归一化,模型在验证集上表现很好,但上线后泛化很差。后来换成标准化,情况就稳定了。为什么?因为标准化保留了特征的相对大小关系,而归一化强行把稀疏特征压到 0 附近,信息损失太大。
注意:标准化操作假设数据近似正态分布。如果你的数据分布特别畸形(比如双峰分布),可以考虑先做秩变换(Rank Transform)或对数变换,再做标准化。
表格对比一下,方便你随时查:
| 方法 | 适用场景 | 输出范围 | 对异常值敏感度 | 常用算法 |
|---|---|---|---|---|
| 归一化 Min-Max | 有界特征(像素、地理坐标) | [0, 1] | 高 | CNN、MLP、KNN |
| 标准化 Z-score | 无界特征、近似正态分布 | 无固定范围 | 中等 | SVM、PCA、逻辑回归 |
| 稳健标准化 | 含异常值的数据 | 无固定范围 | 低 | 树模型、集成方法 |
归一化与标准化,选哪个?
说实话,没有绝对正确的答案。我个人习惯遵循两条原则:
- 如果特征有物理上下界(比如像素 0~255、经纬度 -90~90),用归一化。
- 如果特征无界或分布未知,用标准化,因为标准化对异常值更鲁棒。
另外有一种折中方案:如果你既想保留分布形态,又想压缩尺度,可以用分位数归一化(Quantile Normalization)。这个方法我在基因组数据预处理时用过,效果很好,但它会破坏原始数值的可解释性,工业场景慎用。
一个取巧的招式:同时做归一化和标准化,把两者拼接成新的特征?我以前这么试过,在某些项目上确实涨了点精度,但代价是特征维度翻倍。如果你特征本身不多(少于 100 维),可以试试。
代码实现建议
最后给一套可以直接用的代码模板。我建议你在项目里封装成函数,方便复用:
import numpy as np
from sklearn.preprocessing import MinMaxScaler, StandardScaler
class DataPreprocessor:
def __init__(self, method='standard'):
if method == 'standard':
self.scaler = StandardScaler()
elif method == 'minmax':
self.scaler = MinMaxScaler()
elif method == 'robust':
from sklearn.preprocessing import RobustScaler
self.scaler = RobustScaler()
else:
raise ValueError('method must be standard/minmax/robust')
def fit(self, X):
self.scaler.fit(X)
return self
def transform(self, X):
return self.scaler.transform(X)
def fit_transform(self, X):
self.fit(X)
return self.transform(X)
数据预处理没有银弹。不同任务、不同数据分布,选不同方法。我建议你每次做实验时,把几种预处理都跑一遍,看验证集结果说话——实践才是检验真理的唯一标准。