数据预处理:数据清洗、归一化与标准化

做模型优化,数据预处理是绕不开的第一关。说实话,很多新手上来就调网络结构、调学习率,结果模型死活不收敛。我见过太多这种情况了——最后查下来,八成是数据没处理好。

我个人习惯把数据预处理拆成三层:清洗、归一化、标准化。每层解决不同的问题。今天咱们就把这三层彻底讲透。

先说数据清洗

数据清洗,说白了就是去脏数据。脏数据长什么样?我给你列几种常见的:

  • 缺失值——某个特征列里一堆 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、逻辑回归
稳健标准化 含异常值的数据 无固定范围 低 树模型、集成方法

归一化与标准化,选哪个?

说实话,没有绝对正确的答案。我个人习惯遵循两条原则:

  1. 如果特征有物理上下界(比如像素 0~255、经纬度 -90~90),用归一化。
  2. 如果特征无界或分布未知,用标准化,因为标准化对异常值更鲁棒。

另外有一种折中方案:如果你既想保留分布形态,又想压缩尺度,可以用分位数归一化(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)
数据预处理核心流程 原始数据 数据清洗 缺失值处理 异常值检测 重复数据去重 归一化 (Min-Max) 标准化 (Z-score) 稳健标准化 预处理完成 → 模型训练 注意:清洗是前提,尺度变换是后续;三者顺序不可颠倒。

数据预处理没有银弹。不同任务、不同数据分布,选不同方法。我建议你每次做实验时,把几种预处理都跑一遍,看验证集结果说话——实践才是检验真理的唯一标准。