数据清洗与预处理
本笔记记录ML/AI工程中数据清洗(Data Cleaning)与预处理(Preprocessing)的核心方法。在整个机器学习工作流中,数据准备往往占据60%-80%的工作量,其质量直接决定模型的上限。
为什么数据清洗至关重要
业界有句经典的话:Garbage In, Garbage Out。无论模型架构多么先进,如果输入数据本身存在严重的质量问题,模型的表现都不会好。
实际工程中一个很重要的认知是:数据质量 > 模型复杂度。一个简单的线性模型在干净数据上的表现,往往优于一个深度网络在脏数据上的表现。Andrew Ng提出的Data-Centric AI正是这一理念的体现:与其反复调模型结构和超参数,不如把精力放在改善数据质量上。
因此,数据清洗不是可选的"预备步骤",而是ML pipeline中最具杠杆效应的环节。
常见的数据质量问题
在拿到原始数据后,通常需要排查以下几类问题:
- 缺失值(Missing Values):某些字段为空或NaN,常见于日志采集失败、用户未填写等场景。
- 重复数据(Duplicates):同一条记录出现多次,会导致模型对这些样本过拟合。
- 异常值/离群点(Outliers):数值远超正常范围,可能是传感器故障或录入错误。
- 标签噪声(Label Noise):分类任务中标签标注错误,直接损害监督信号的质量。
- 类别不平衡(Class Imbalance):正负样本比例悬殊(如欺诈检测中正样本不到1%),模型容易偏向多数类。
处理这些问题没有万能方案,需要根据具体业务场景判断。下面逐一展开关键技术。
缺失值处理
删除法
最直接的方式,适用于缺失比例较小的情况:
- 删除行(Listwise Deletion):某行存在缺失值就整行丢弃。简单但可能丢失大量数据。
- 删除列:某列缺失率过高(如超过50%),可以考虑直接丢弃该特征。
统计量插补
用该列的统计量填充缺失值:
- 均值插补:适合正态分布的连续特征。\(x_{\text{fill}} = \bar{x}\)
- 中位数插补:对偏态分布更鲁棒。\(x_{\text{fill}} = \text{median}(x)\)
- 众数插补:适合类别型特征。\(x_{\text{fill}} = \text{mode}(x)\)
这些方法简单快速,但忽略了特征之间的相关性。
KNN插补
利用K近邻算法,根据与该样本最相似的K个邻居的值来估计缺失值。能捕捉特征间的局部关系,但计算量随数据规模增长较快。
from sklearn.impute import KNNImputer
imputer = KNNImputer(n_neighbors=5)
X_filled = imputer.fit_transform(X)
模型预测插补
将缺失特征作为目标变量,用其他特征训练一个模型来预测缺失值。常用的方法包括MICE(Multiple Imputation by Chained Equations)和IterativeImputer。这种方法效果通常最好,但计算开销也最大。
异常值检测
IQR方法
基于四分位距的经典方法。计算第一四分位数 \(Q_1\) 和第三四分位数 \(Q_3\),令 \(IQR = Q_3 - Q_1\),则:
超出该范围的点视为异常值。该方法对分布无假设,适用面广。
Z-score方法
假设数据近似正态分布,计算每个样本的标准化分数:
通常将 \(|z| > 3\) 的点视为异常值。简单直观,但对非正态分布的数据效果较差。
Isolation Forest
一种基于树的无监督异常检测算法。核心思想:异常点因为"稀疏",在随机划分空间时更容易被隔离(需要更少的划分次数)。适合高维数据。
from sklearn.ensemble import IsolationForest
clf = IsolationForest(contamination=0.05)
labels = clf.fit_predict(X) # -1表示异常
DBSCAN
密度聚类算法DBSCAN本身不是为异常检测设计的,但它会将低密度区域的点标记为噪声(label=-1),天然可以用于检测离群点。适合空间聚集结构明显的数据。
数据标准化与归一化
不同特征的量纲差异巨大时(如年龄0-100 vs 收入0-1000000),梯度下降类算法的收敛速度会受到严重影响。标准化/归一化就是解决这个问题的。
Min-Max Scaling(归一化)
将数据线性缩放到 \([0, 1]\) 区间:
适用于数据分布有明确边界、且对异常值不敏感的场景(如图像像素值)。缺点是对离群点敏感,一个极端值就会压缩其他所有数据的范围。
Z-score Standardization(标准化)
将数据变换为均值为0、标准差为1的分布:
最常用的方法。适用于大多数ML算法,尤其是SVM、Logistic Regression、Neural Networks等依赖距离或梯度的模型。
Robust Scaling
使用中位数和四分位距代替均值和标准差:
对异常值具有鲁棒性。当数据中存在较多离群点且不想先做异常值剔除时,优先考虑使用。
选择建议
| 场景 | 推荐方法 |
|---|---|
| 数据有明确范围,无异常值 | Min-Max Scaling |
| 通用场景,数据近似正态 | Z-score Standardization |
| 数据含较多异常值 | Robust Scaling |
| 树模型(Decision Tree, XGBoost等) | 通常不需要标准化 |
特征编码
机器学习模型只能处理数值输入,因此类别型特征必须编码为数字。
One-Hot Encoding
将类别变量转换为二进制向量。适合类别数较少且无序的特征(如颜色、城市)。类别数过多时会造成维度爆炸。
Label Encoding
将每个类别映射为一个整数(如 红=0, 绿=1, 蓝=2)。适合有序类别(如学历:小学<中学<大学)。对无序类别使用会引入虚假的大小关系。
Target Encoding
用目标变量的统计量(如均值)替换类别值。适合高基数(high cardinality)类别特征。需要注意数据泄露问题,通常结合交叉验证或加入平滑项来使用。
文本数据预处理
Tokenization(分词)
将原始文本切分为token序列。英文以空格为天然分隔符,中文则需要借助分词工具(如jieba)。在LLM时代,BPE(Byte Pair Encoding)和SentencePiece等subword级别的tokenizer已成为主流。
去停用词(Stop Words Removal)
去除"的"、"是"、"the"、"is"等高频但信息量低的词。在传统NLP(如TF-IDF + SVM)中很有用,但在深度学习和Transformer架构中通常不做去停用词处理,因为注意力机制能自行学习哪些词重要。
词干化与词形还原
- Stemming(词干化):基于规则截断词尾,如 running -> run。速度快但粗糙。
- Lemmatization(词形还原):基于词典还原词的原形,如 better -> good。更准确但更慢。
这两种技术同样主要用于传统NLP pipeline。现代预训练模型的tokenizer已经内置了处理词形变化的能力。
图像数据预处理
Resize
将所有图像统一到相同的空间尺寸,如 \(224 \times 224\)(ResNet标准输入)或 \(384 \times 384\)。这是因为大多数CNN架构要求固定尺寸输入。注意选择合适的插值方法(bilinear、bicubic等)。
Normalize Pixel Values
原始像素值范围为 \([0, 255]\),通常需要归一化到 \([0, 1]\) 或标准化到特定均值和标准差。例如ImageNet预训练模型常用:
# ImageNet标准归一化参数
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
Color Space Conversion
根据任务需要进行颜色空间转换,如RGB转灰度(减少计算量)、RGB转HSV(某些视觉任务中更有效)等。医学影像等领域还会涉及特定的窗宽窗位调整。
数据管道(Data Pipeline)
在实际工程中,数据预处理不应该是一堆散落的脚本,而应该被组织为可复现、可版本化的Pipeline。
ETL思想
ETL(Extract-Transform-Load)是数据工程的经典范式:
- Extract:从数据源(数据库、API、文件系统)提取原始数据。
- Transform:执行清洗、标准化、特征工程等所有预处理步骤。
- Load:将处理后的数据写入目标存储(特征库、训练数据集等)。
sklearn Pipeline
sklearn提供了Pipeline类,可以将多个预处理步骤和模型串联为一个整体,确保训练和推理时使用完全一致的处理流程:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
pipe = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler()),
('model', LogisticRegression())
])
pipe.fit(X_train, y_train)
score = pipe.score(X_test, y_test)
这样做的好处是:调用 pipe.fit() 时,imputer和scaler只在训练集上fit,在测试集上只transform,避免了数据泄露。
可复现性
数据管道的可复现性是工程化ML的基本要求。关键实践包括:
- 固定随机种子:确保数据划分、随机采样等步骤可复现。
- 版本管理:使用DVC(Data Version Control)等工具对数据和pipeline进行版本化。
- 配置分离:将超参数、文件路径等配置项从代码中抽离,便于实验管理。
- 日志记录:记录每次运行的数据统计信息(样本数、缺失率、分布等),方便排查问题。
一个设计良好的Data Pipeline应该是幂等的(idempotent):对相同的输入,无论运行多少次,都产生完全一致的输出。