1-数据清洗与采样策略
1. 为什么数据质量往往比模型复杂度更重要
1.1 机器学习的根本约束
机器学习的核心假设是训练数据
一个粗略的量化关系:
- 数据质量:影响模型能够学到的上限( Bayes Error Rate 附近)
- 模型复杂度:影响模型能否逼近这个上限
- 数据规模:在固定质量下,规模越大,模型越能覆盖真实分布
当数据质量低下时,增加模型复杂度和训练数据规模只能放大噪声,而非学到真正有用的模式。
1.2 错误数据对训练的伤害
以二分类为例,设训练样本中有一小部分标签是随机错误的(label noise)。若不加处理:
- SGD 场景:模型会在这些错误样本上浪费宝贵的梯度更新,导致整体收敛速度变慢
- 深度网络场景:由于深度网络强大的拟合能力,模型会记忆这些错误标签,在正确标签的样本上泛化性能反而下降
- 对比学习场景:错误标签导致正样本对构造错误,模型学到的是错误样本之间的关系而非语义关系
经验上常见(empirical common practice):在真实工业数据中,5%10% 的标签噪声率并不罕见。若不加处理,最终模型性能可能下降 515 个百分点。
2. 数据质量问题详解
2.1 噪声标签(Noisy Labels)
来源
- 人工标注错误:标注员疲劳或专业知识不足,尤其在细粒度分类中(如不同鸟类的区分)
- 自动标注的遗留问题:用规则或弱模型自动标注后未做人工校验(如用关键词匹配做情感分类)
- 标签模糊性:样本本身可能属于多个类别,但被迫分配单一标签
对训练的影响
对于随机标签噪声(symmetric noise),模型学到的条件概率
处理方法
| 方法 | 原理 | 适用场景 |
|---|---|---|
| Label Smoothing | 将硬标签软化为 | 轻度噪声( |
| Co-teaching | 两个网络互相过滤对方认为是噪声的样本 | 中度噪声( |
| 置信度学习(Confident Learning) | 通过模型预测置信度识别可能的噪声样本 | 可与任何训练流程结合 |
| 数据重标注 | 对被识别为噪声的样本重新标注 | 噪声比例低、但每条数据价值高 |
2.2 重复样本(Duplicate Samples)
来源
- 爬虫重复抓取:同一页面被多次存入数据库
- 数据拼接错误:不同来源的数据合并时出现完全相同的行
- 数据增强副作用:某些流水线对同一原始样本做了数据增强后多次加入训练集,但记录方式导致视为不同样本
对训练的伤害
重复样本的危害不来自”样本本身”,而来自其对采样分布的扭曲:
- 若某样本重复 100 次,则在随机梯度采样(SGD)中该样本被选中的概率是其他样本的 100 倍
- 这相当于对这类样本做了隐性加权,使模型过度拟合到重复样本的模式
- 在极端情况下,重复样本可能导致模型只学会识别少数”被重复”的模式,而忽略真正具有判别性的特征
检测方法
| 方法 | 实现 | 适用场景 |
|---|---|---|
| 精确哈希 | 对完整样本计算 MD5/SHA 哈希,完全相同的视为重复 | 结构化数据(数值/类别特征) |
| 近似最近邻 | 对 embedding 向量建立ANN索引,找出 cosine similarity > 0.99 的样本对 | 非结构化数据(文本/图像 embedding) |
| MinHash / SimHash | 用于大规模网页去重,LSH(局部敏感哈希)族 | 十亿级网页去重 |
2.3 脏数据(Dirty Data)
类型
- 格式错误:如数值字段出现文本、日期字段格式不统一
- 范围异常:如年龄字段出现
999、-1,或评论长度为 0 - 注入攻击:如爬取的文本中包含 HTML 标签、SQL 注入片段、特殊控制字符
- 编码错误:如文本以 UTF-8 读取后出现乱码(
�)
对训练的影响
脏数据进入训练后,模型的梯度更新会受到污染。在深度网络中,脏数据可能导致激活值出现极端值(outlier),进而影响 batch normalization 的统计量估计,或在 attention 计算中引入噪声。
2.4 长尾分布(Long-Tail Distribution)
定义
长尾分布指少数类别(head classes)拥有极大量的样本,而多数类别(tail classes)只有少量样本。这种分布在自然语言处理(话题分类)和计算机视觉(细粒度识别)中普遍存在。
若不加以处理,模型会在 head classes 上表现良好,在 tail classes 上几乎无法工作。
量化指标
其中
2.5 类别不平衡(Class Imbalance)
长尾分布是类别不平衡的一种形式,但两者有细微区别:
- 类别不平衡:各类别样本数量不均,可能有明确的头部和尾部,但不一定是”长尾”
- 长尾分布:头部与尾部之间的差距极大,呈幂律分布特征
3. 数据清洗流程
3.1 四阶段清洗框架
工业级数据清洗通常分为四个阶段,由浅入深:
原始数据 → 规则过滤 → 统计过滤 → 模型过滤 → 人工抽检
3.2 规则过滤(Rule-based Filtering)
规则过滤是最快速、成本最低的清洗手段,适用于明确的”坏样本”定义。
常用规则:
| 规则类型 | 示例 | 作用 |
|---|---|---|
| 长度过滤 | 文本长度 | 去除过短/过长的无意义样本 |
| 格式过滤 | 包含 HTML 标签、特殊控制字符(\x00) | 去除注入攻击和编码错误 |
| 词汇过滤 | 包含”undefined”、“null”、“NaN” 等占位符 | 去除未填充的占位符内容 |
| 来源过滤 | 来自已知质量差的站点/用户 | 去除可信度低的来源 |
| 时间过滤 | 超出合理时间范围的样本(如未来日期) | 去除时间戳错误数据 |
特点:速度快,可并行处理;但只能过滤明确的”坏样本”,无法处理”有问题但不明确”的边界样本。
3.3 统计过滤(Statistical Filtering)
统计过滤基于样本在某些统计量上的异常来识别潜在问题。
方法 1:分布检验
对关键数值特征(如文本长度、token 数量、图片尺寸)计算分布。若某样本的数值落在分布的极端尾端(如超出
方法 2:TF-IDF 异常检测
对文本数据,计算每个样本的 TF-IDF 向量与整体 centroids 的距离。若某样本的最近邻距离异常大(> 某阈值),可能是脏数据或非目标语言。
方法 3:Embedding 异常检测
对文本/图像训练 embedding,然后做 UMAP/t-SNE 可视化,检查是否存在离群点(outlier)。也可用 Isolation Forest 等无监督方法检测 embedding 空间中的离群点。
3.4 模型过滤(Model-based Filtering)
模型过滤使用轻量级的可训练模型来识别”低质量”样本。
方法 1:语言模型困惑度过滤
用语言模型对文本样本计算困惑度(perplexity)。困惑度异常高的样本(如
方法 2:置信度过滤(Confident Learning)
以交叉验证方式训练模型,对每个样本记录模型预测的置信度。若某样本的标签与模型高置信度预测不符,标记为潜在噪声标签。
# Confident Learning 的简化实现
for sample in dataset:
pred_label, pred_confidence = model.predict(sample)
if pred_confidence > threshold and pred_label != sample.label:
# 可能是标签噪声
noise_candidates.append(sample)方法 3:文本质量分类器
训练一个文本质量分类器(如”高质量新闻 vs 低质量新闻”),对每个样本预测质量分数,过滤掉分数低于阈值的样本。
3.5 人工抽检(Manual Inspection)
所有自动化清洗后,必须进行人工抽检作为最终质量把关。抽检方式:
- 随机抽检:从清洗后的数据中随机抽取
条,人工评估是否符合预期 - 分层抽检:从不同类别、不同质量区间各抽取一定比例,确保各层都经过评估
- 边界样本抽检:重点检查被规则过滤、统计过滤、模型过滤标记为”可疑”的样本,确认过滤是否准确
4. 采样策略
4.1 随机采样(Random Sampling)
从数据集
在 i.i.d. 假设下,随机采样使得每个样本被选中的概率均为
优点:实现简单;在大数据集 i.i.d. 场景下是标准做法。
缺点:在类别不平衡场景下,mini-batch 中 head classes 出现频率远高于 tail classes,tail classes 的梯度更新机会极少。
4.2 分层采样(Stratified Sampling)
分层采样保证每个 mini-batch 中各类别样本的比例与整体数据集一致:
- 将数据集按类别标签划分为
个子集 - 在每个子集
中独立随机抽取 个样本
这与真实数据分布
优点:解决了 head/tail 不平衡问题;每个 epoch 中 tail classes 有稳定的梯度更新机会。
缺点:在极端不平衡(
4.3 重采样(Resampling)
当 tail classes 样本过少时,可以对 tail classes 做过采样(oversampling),或对 head classes 做欠采样(undersampling),使各类别样本数均衡。
过采样(Oversampling)
对 tail classes 的样本重复采样,使每个类的样本数达到
问题:过采样会导致模型对 tail classes 过拟合——因为同一个样本被多次看到,模型记忆了这些样本而非学习到泛化特征。
改进:SMOTE(Synthetic Minority Over-sampling Technique):对 tail classes 的样本在特征空间做插值,生成合成样本,而非简单重复。
欠采样(Undersampling)
从 head classes 中随机抽取
问题:可能丢失 head classes 中有价值的样本;若
4.4 Class-Balanced Sampling(类别平衡采样)
Class-balanced sampling 的核心思想不是让 batch 内各类别比例均衡,而是让模型在每个 epoch 中对每个类别都有大致相等的梯度更新次数。
具体做法:
- 计算每个类别的采样权重:
(或 的归一化形式) - 按照权重
采样类别 ,再在该类别内随机采样样本
这相当于将均匀采样转变为”类别级别”的加权采样,保证 tail classes 的梯度更新机会。
4.5 Hard Example Mining(困难样本挖掘)
Hard example mining 的核心思想是:模型最需要学习的不是”已经会的简单样本”,而是”当前还不会的困难样本”。
在线困难样本挖掘(OHEM,Online Hard Example Mining)
在每个 mini-batch 中,先让模型对所有候选样本做前向传播,计算 loss,然后将 loss 最大的
为什么 OHEM 有效
模型在简单样本上的 loss 很小 → 梯度也小 → 对参数更新贡献有限。困难样本的 loss 大 → 梯度大 → 对参数更新贡献更多。OHEM 相当于在每个 batch 中优先处理困难样本,加速收敛。
但注意:OHEM 增加了训练的计算成本(需要先跑一遍前向传播算 loss);且如果困难样本中含有噪声标签,过度关注这些样本可能导致过拟合到噪声。
5. 非均匀采样与偏差问题
5.1 经验风险最小化在非均匀采样下的偏差
设从真实分布
其中
5.2 Importance Sampling / Reweighting 的基本思想
Importance sampling 的核心思想是:如果采样分布
设样本
则加权经验风险为
当
问题:真实分布
5.3 为什么 class-balanced sampling 本质上是 importance reweighting
设类别
当各类的真实频率
6. 数据增强为什么能提升泛化
6.1 数据增强的本质
数据增强(Data Augmentation)在不增加真实样本的情况下,通过对已有样本施加语义保留的变换,扩大有效训练集。具体来说,对于一个样本
理想情况下,
6.2 为什么增强能提升泛化
理论视角:增强相当于在样本级引入了随机噪声,模型在训练时会学到对这些变换的不变性(invariance)。例如:
- 图像水平翻转增强 → 模型学到的表示不受水平翻转影响(空间位置不变性)
- 颜色抖动增强 → 模型不过度依赖特定颜色(颜色不相关性)
- 随机掩码增强(BERT style)→ 模型学会从部分信息恢复完整语义(遮蔽不相关性)
这些不变性正是模型在新数据上泛化的基础——真实世界的测试数据也会经历各种变换干扰,模型若已学会忽略这些干扰,自然泛化更好。
经验观察:在 CIFAR-10 上,不使用数据增强的错误率约为
7. 数据集泄漏的避免
7.1 泄漏的定义与危害
数据集泄漏(data leakage)指训练数据中包含了本不应该被模型看到的信息,导致模型在训练和测试时性能指标虚高,但实际部署时性能骤降。
典型案例
案例 1:验证集信息泄漏到训练集
若训练集和验证集在构造时来自同一时间窗口,而测试集来自不同时间窗口(如时间序列预测),则模型学到的是时间相关性而非真实规律。
案例 2:特征中包含未来信息
某特征
案例 3:归一化统计量泄漏
若归一化参数(均值、方差)在计算时包含了测试集的数据,则测试集的归一化实际上使用了”来自未来的信息”。正确做法是:归一化参数仅从训练集计算,再应用于验证集和测试集。
7.2 如何避免泄漏
| 检查项 | 操作 |
|---|---|
| 数据分割是否按时间/来源正确分层 | 确认训练集和测试集来自不同时间段/不同来源 |
| 特征构造是否有”未来信息” | 审查每个特征的公式,确认只使用了当前时刻可用的信息 |
| 归一化参数是否仅从训练集计算 | 将数据分割后,先用训练集 fit 归一化参数,再 transform 验证/测试集 |
| 数据增强是否在 train 模式下进行 | 推理时的数据增强应与训练时一致,不能在 transform 中使用 label 信息 |
| pipeline 中是否有”用测试集选模型”的操作 | 所有超参数选择应在验证集上进行,而非测试集 |
8. 去重与近重复检测的意义
8.1 为什么去重不只是”省数据”
如第 2.2 节所述,重复样本的危害不在于”数据量虚高”,而在于其对采样分布的扭曲。设想一个场景:
- 数据集有 10000 个独特样本,其中某 100 个样本各被重复了 100 次
- 总数据集大小看似 100000,实际独特样本只有 10000
- 随机采样概率中,这 100 个重复样本的概率是其他样本的 100 倍
- 模型在训练时过度拟合到这 100 个”被重复”的模式
去重后,模型需要学会区分真正不同的样本,泛化到新的测试数据时才更鲁棒。
8.2 近重复检测(Near-Duplicate Detection)
完全精确的重复(exact duplicate)只是问题的一小部分。更大、更难处理的问题是近重复(near-duplicate)——内容高度相似但不完全相同,如同一篇文章的稍作改写版本。
方法:MinHash + LSH
MinHash 是一种近似集合相似度算法,用于在大规模语料中高效找出 Jaccard 相似度
- 对每个文档计算 shingle(token n-gram)集合
- 对所有 shingle 计算 MinHash 签名
- 用 LSH(Locality Sensitive Hashing)对 MinHash 签名分区,使相似文档大概率落入同一桶
- 在桶内精确计算 Jaccard 相似度
参数选择:shingle 大小通常为
9. 工程流程
9.1 数据管线设计
一个完整的数据管线分为四个模块:
原始数据 → 清洗模块 → 采样模块 → 训练模块
清洗模块(数据清洗管线的输出)
- 输出:经过四阶段清洗后的干净数据集 + 清洗报告
- 报告内容:各阶段过滤掉多少样本(占比),每类过滤的典型样例
采样模块(训练时的采样策略)
- 输出:带采样权重的 mini-batch
- 关键点:采样权重应与模型训练解耦——采样模块提供 batch,训练模块只负责前向/反向,不要在训练循环中混合采样逻辑
训练模块(与数据管线的交互)
- 训练前:接收清洗后的数据集 + 采样配置
- 训练中:从 dataloader 持续获取 batch
- 训练后:输出训练日志 + 模型 checkpoint
9.2 如何检查标签分布、样本长度分布、异常值
标签分布检查
from collections import Counter
label_counts = Counter([sample['label'] for sample in dataset])
for label, count in sorted(label_counts.items(), key=lambda x: -x[1]):
print(f"Label {label}: {count} ({count/len(dataset)*100:.2f}%)")- 检查各类的样本数差距是否在合理范围(
) - 检查是否有类只有极少量样本(如
个),这类样本无法支撑模型学习,标注为”长尾问题类”
样本长度分布检查
lengths = [len(sample['text'].split()) for sample in dataset]
import numpy as np
lengths_mean = np.mean(lengths)
lengths_std = np.std(lengths)
lengths_p50 = np.median(lengths)
lengths_p95 = np.percentile(lengths, 95)
lengths_p99 = np.percentile(lengths, 99)
# 可视化:histogram of lengths- 检查长度分布是否呈长尾(若 p99 远超 p95,说明有极端长样本)
- 决定是否截断(truncation):若 p99 长度是 p95 的 10 倍,可将超过 p95 的样本截断或过滤
异常值检测
import pandas as pd
features = pd.DataFrame([sample['features'] for sample in dataset])
# 检查每列的统计量
print(features.describe())
# 找出异常值(超出 3 sigma 的样本)
for col in features.columns:
mu, sigma = features[col].mean(), features[col].std()
outliers = features[abs(features[col] - mu) > 3*sigma]
if len(outliers) > 0:
print(f"Column {col}: {len(outliers)} outliers")9.3 训练前数据审计
在正式训练前,应完成以下审计步骤:
| 审计项 | 目标 | 工具 |
|---|---|---|
| 样本总量 | 确认数据规模符合预期(如 | len(dataset) |
| 标签分布 | 确认无极端不平衡(头尾比 | Counter |
| 长度分布 | 确认无极端长样本(截断或过滤) | np.percentile |
| 去重检查 | 确认无大量重复样本(重复率 | hash / annoy |
| 近重复检查 | 确认无大量近重复文档(相似度 | MinHash + LSH |
| 缺失值检查 | 确认各字段无大量缺失(缺失率 | pandas.isnull |
| 脏数据检查 | 确认无 HTML 标签、乱码、异常字符 | 正则表达式过滤 |
| 训练/验证分割 | 确认分割比例正确(通常 | 验证标签分布一致性 |
| 数据来源追溯 | 确认训练/验证/测试数据来源不同 | metadata 检查 |
10. 数据上线前检查清单
训练开始前,必须确认以下所有项目已完成:
- 标签分布审计:打印所有类别的样本数,检查头尾比是否在可接受范围
- 去重完成:精确哈希去重完成,近重复检测(如有必要)完成,重复率
- 脏数据过滤:HTML 标签、控制字符、乱码已被过滤;正则规则已覆盖已知脏数据类型
- 长度截断/过滤:超长样本已截断或过滤;截断长度已确认(如 tokenizer max length = 512)
- 缺失值处理:所有必要字段的缺失值已处理(过滤或填补);已记录填补策略
- 数据集分割:训练/验证/测试分割已完成;分割比例已记录;分割时使用了相同的采样策略(分层分割)
- 归一化参数:如使用归一化,参数(均值/方差/词频等)仅从训练集计算,不涉及验证/测试集
- 数据来源验证:训练集和测试集来自不同的物理来源/时间窗口,无跨集污染
- 数据审计报告:已完成完整审计报告,记录了过滤各阶段的样本数和过滤原因
- 种子可复现:数据加载 pipeline 在相同种子下可复现相同的数据顺序
11. 常见数据问题与处理方式对照表
| 数据问题 | 诊断方法 | 处理方式 |
|---|---|---|
| 噪声标签(轻度 | 交叉验证置信度分析;人工抽检 | Label Smoothing;置信度过滤 |
| 噪声标签(中度 | Confident Learning | Co-teaching;数据重标注 |
| 重复样本 | MD5 哈希精确匹配 | 直接去重 |
| 近重复文档 | MinHash + LSH(相似度阈值 0.8~0.95) | 去重或合并 |
| 脏数据(HTML/乱码/特殊字符) | 正则表达式匹配 | 规则过滤 |
| 极端长尾分布 | 类别样本数统计 | 类别平衡采样;Focal Loss;过采样 |
| 缺失特征值 | pandas.isnull() 检查 | 删除该特征;均值填补;标记为特殊值 |
| 分布外样本(Out-of-distribution) | embedding 空间异常检测; perplexity 检测 | 过滤该样本 |
| 时间序列数据中包含未来信息 | 特征审计(检查特征定义) | 重新构造特征,移除未来信息 |
| 训练/测试集来源重叠 | metadata 追溯检查 | 重新分割数据,确保来源不重叠 |
| 标注模糊性(多标签) | 人工抽检;标签共现分析 | 使用多标签分类;将模糊标签标记为 unsure |
| 不平衡数据集 | 类别样本数统计 | 类别平衡采样;class weight;Focal Loss |
| 注入攻击内容(XSS/SQL injection) | 安全扫描(OWASP 规则) | 过滤或转义 |
| 非目标语言 | 语言检测(langdetect) | 过滤目标语言外的样本 |
| 低质量文本(自动生成/模板化) | 语言模型困惑度;perplexity 检测 | 阈值过滤;语言质量分类器 |