数据清洗工程实践:从 C4 到 FineWeb 的质量革命

系统梳理 LLM 预训练数据清洗的全流程,覆盖去重、质量过滤、毒性检测、PII 脱敏等关键环节,对比主流数据集的清洗策略。

深度解析 数据清洗 预训练数据 FineWeb LLM

“Garbage in, garbage out” 在大模型时代被赋予了新的含义——预训练数据的质量直接决定了模型的能力上限。本文将系统梳理数据清洗的全流程和工程实践。

一、数据清洗的重要性

1.1 质量差距的量化

相同规模的训练数据,不同质量清洗后训练出的模型性能差异惊人:

数据集清洗策略训练后 HellaSwag训练后 ARC
Raw Common Crawl无清洗45.232.1
C4基础清洗58.741.3
RefinedWeb中等清洗65.448.7
FineWeb深度清洗68.152.3
FineWeb-Edu教育质量筛选72.656.8

从 Raw Common Crawl 到 FineWeb-Edu,HellaSwag 提升了 27.4 个百分点——仅通过数据清洗,不改变模型架构和训练方法。

1.2 数据清洗的 ROI

优化手段相对成本性能提升幅度
增大模型参数 2×高(训练+推理成本翻倍)~3-5%
增大训练数据 2×中(训练成本翻倍)~2-4%
提升数据质量低(一次性清洗成本)~5-15%

数据质量是性价比最高的优化手段。

二、数据清洗全流程

2.1 Pipeline 概览

一个完整的数据清洗流水线通常包含以下阶段:

原始数据 (Common Crawl)
  ↓
[1] URL 过滤 ─── 去除已知低质量域名
  ↓
[2] 文本提取 ─── HTML → 纯文本
  ↓
[3] 语言检测 ─── 识别文本语言
  ↓
[4] 基础过滤 ─── 长度、字符比例等规则
  ↓
[5] 去重 ─── 精确去重 + 模糊去重
  ↓
[6] 质量评分 ─── 模型打分或启发式评分
  ↓
[7] 毒性过滤 ─── 去除有害/违规内容
  ↓
[8] PII 脱敏 ─── 去除个人敏感信息
  ↓
[9] Benchmark 去污染 ─── 避免评测数据泄露
  ↓
清洁数据

2.2 各阶段详解

让我们逐一深入每个阶段。

三、URL 过滤

3.1 黑名单过滤

维护一个低质量 URL 黑名单:

URL 级别:

  • 已知的垃圾站点、SEO 农场
  • 成人内容站点
  • 恶意软件分发站点

域名级别:

  • 低质量内容农场的顶级域名
  • 自动生成内容的站点
  • 大量重复内容的镜像站

3.2 URL 特征过滤

基于 URL 模式的启发式规则:

def url_quality_check(url):
    # 路径深度过深通常是动态生成页面
    if url.count('/') > 8:
        return False
    # 过多查询参数
    if url.count('&') > 5:
        return False
    # 常见低质量后缀
    low_quality_patterns = [
        '/tag/', '/category/', '/page/',
        '?replytocom=', '?share=',
    ]
    return not any(p in url for p in low_quality_patterns)

四、文本提取

4.1 HTML → 纯文本

从 HTML 中提取有意义的文本内容:

主流工具:

  • Trafilatura:质量最高,FineWeb 使用
  • Readability:Mozilla 的算法
  • jusText:基于段落分类
  • boilerpy3:去除样板文本

Trafilatura vs 其他工具的对比:

工具正文提取精度速度样板去除
Trafilatura高中好
Readability中高快中
jusText中快好
Beautiful Soup低(需自定义)快无

FineWeb 的关键发现:Trafilatura 的文本提取质量比 C4 使用的方法高出很多,这一步对最终模型性能有显著影响。

4.2 特殊内容处理

  • 代码块:保留格式和缩进
  • 表格:转换为文本表示或 Markdown
  • 列表:保持层级结构
  • 数学公式:保留 LaTeX 或 Unicode 表示
  • 导航/页脚/广告:移除

五、语言检测

5.1 工具和方法

工具准确率速度支持语言数
fastText langid98%+极快176
CLD396%+快107
langdetect95%+中55

推荐:fastText 的语言检测模型,速度快且准确率高。

5.2 中文特殊处理

中文语言检测的挑战:

  • 简体 vs 繁体区分
  • 中日韩(CJK)字符共用导致误判
  • 夹杂英文的中文文本可能被判为英文

建议:使用 CLD3 + 字符集统计双重验证。

六、基础过滤规则

6.1 文本级别规则

这些是基于简单统计特征的过滤规则:

def basic_text_filter(text):
    # 最小长度
    if len(text) < 200:
        return False
    
    # 最大长度(防止异常数据)
    if len(text) > 500_000:
        return False
    
    # 平均单词长度异常
    words = text.split()
    avg_word_len = sum(len(w) for w in words) / max(len(words), 1)
    if avg_word_len > 20 or avg_word_len < 2:
        return False
    
    # 字母占比过低(可能是乱码)
    alpha_ratio = sum(c.isalpha() for c in text) / max(len(text), 1)
    if alpha_ratio < 0.4:
        return False
    
    # 特殊字符占比过高
    special_ratio = sum(c in '!@#$%^&*' for c in text) / max(len(text), 1)
    if special_ratio > 0.1:
        return False
    
    return True

6.2 行级别规则

def line_level_filter(text):
    lines = text.split('\n')
    
    # 过滤以特定模式开头的行
    bullet_count = sum(1 for l in lines if l.strip().startswith(('•', '·', '-', '*')))
    if bullet_count / max(len(lines), 1) > 0.8:
        return False  # 可能是列表页
    
    # 短行占比过高
    short_lines = sum(1 for l in lines if len(l.strip()) < 20)
    if short_lines / max(len(lines), 1) > 0.7:
        return False  # 可能是导航或目录
    
    # 重复行过多
    unique_lines = len(set(l.strip() for l in lines if l.strip()))
    if unique_lines / max(len(lines), 1) < 0.5:
        return False
    
    return True

6.3 C4 的经典规则

C4(Colossal Clean Crawled Corpus)定义了一组被广泛采用的基础规则:

  1. 句子必须以标点符号结尾
  2. 文本至少包含 3 个句子(后来的研究发现这个规则过于严格)
  3. 删除包含 “bad words” 的页面
  4. 删除包含 “lorem ipsum” 的页面
  5. 删除 JavaScript 代码片段
  6. 每行至少 5 个单词

注意:C4 的规则被后来的研究证明过于激进——它们移除了大量高质量内容(如代码、列表、短文本)。

七、去重

去重是数据清洗中最关键的步骤之一。

7.1 为什么去重如此重要

重复数据的危害:

  1. 浪费训练计算:重复数据不提供新信息
  2. 放大偏见:高频重复的内容会在训练中被过度强调
  3. 记忆而非学习:模型可能逐字记住重复内容,而非学习泛化
  4. 隐私风险:重复出现的个人信息更容易被模型记住

7.2 精确去重

URL 去重:

  • 最简单的方法:相同 URL 的页面只保留一份
  • 注意 URL 标准化(去除 tracking 参数、统一大小写等)

文档哈希去重:

  • 计算每个文档的哈希值(MD5, SHA256)
  • 相同哈希的文档只保留一份
  • 对完全相同的文档有效,但无法处理微小变化

7.3 模糊去重(MinHash + LSH)

模糊去重能识别内容相似但不完全相同的文档。

MinHash 原理:

  1. 将文档表示为 n-gram 集合
  2. 通过多个哈希函数计算最小哈希值
  3. MinHash 签名的一致比例近似 Jaccard 相似度

LSH (Locality-Sensitive Hashing):

  1. 将 MinHash 签名分成多个 band
  2. 相同 band 的文档被映射到同一个 bucket
  3. 只需比较同一 bucket 内的文档对
# 典型配置
n_gram = 5        # 5-gram
n_hash = 128      # 128 个哈希函数
n_bands = 20      # 20 个 band
rows_per_band = 6  # 每个 band 6 行
# 这给出约 0.8 Jaccard 相似度时 ~96% 的召回率

工业级工具:

  • deduplicate-text-datasets(Google):最常用
  • text-dedup(ChenghaoMou):支持多种方法
  • Spark/Dask 版本:大规模分布式去重

7.4 子文档级去重

除了文档级去重,还需要段落/句子级去重:

  • 段落去重:相同段落在多个文档中出现(如版权声明、Cookie 通知)
  • n-gram 去重:长 n-gram 重复表明内容抄袭
  • FineWeb 的做法:使用 15-gram 的精确匹配去重,效果优于文档级去重

八、质量评分

8.1 启发式评分

基于文本特征的规则化评分:

Gopher 规则(DeepMind):

def gopher_quality_score(text):
    score = 1.0
    words = text.split()
    
    # 单词数量
    if len(words) < 50 or len(words) > 100000:
        score *= 0.1
    
    # 平均单词长度
    avg_len = sum(len(w) for w in words) / len(words)
    if avg_len < 3 or avg_len > 10:
        score *= 0.5
    
    # 符号-单词比例
    symbol_count = sum(1 for c in text if c in '#...')
    if symbol_count / len(words) > 0.1:
        score *= 0.5
    
    # 大写比例
    upper_ratio = sum(1 for c in text if c.isupper()) / len(text)
    if upper_ratio > 0.2:
        score *= 0.7
    
    # 省略号密度
    ellipsis_count = text.count('...')
    if ellipsis_count / len(words) > 0.02:
        score *= 0.7
    
    return score

8.2 模型评分

使用训练好的分类器对文本质量进行评分:

方法 1:困惑度过滤(Perplexity Filtering)

用一个在高质量数据上训练的语言模型来评分:

PPL(x)=exp⁡(−1N∑i=1Nlog⁡P(xi∣x<i))\text{PPL}(x) = \exp\left(-\frac{1}{N}\sum_{i=1}^{N} \log P(x_i | x_{<i})\right)

困惑度低 = 更像高质量文本

方法 2:教育质量分类器

FineWeb-Edu 的核心创新:训练一个分类器来判断文本的 “教育价值”。

训练流程:

  1. 用 LLaMA-3 70B 对 50 万条样本标注教育质量分数(0-5)
  2. 用这些标注训练一个小型分类器(如 FastText 或小型 BERT)
  3. 用分类器对全量数据评分
  4. 过滤掉低分数据

效果惊人:仅保留教育分数 ≥ 3 的数据,模型在知识密集型任务上提升 5-10 个百分点。

方法 3:DCLM 的数据筛选

DCLM 使用了一种更系统化的方法:

  1. 训练多个不同的质量分类器
  2. 在小规模实验中评估每个分类器的效果
  3. 选择最佳分类器用于大规模筛选

8.3 质量评分的哲学

质量是一个主观概念——不同的应用场景对 “质量” 有不同的定义:

  • 通用 LLM:百科式的、知识密集的文本
  • 代码模型:高质量的代码和技术文档
  • 对话模型:自然、流畅的对话文本
  • 科学模型:学术论文、教科书

最佳实践:根据目标场景定义质量标准,然后训练对应的分类器。

九、毒性过滤

9.1 毒性类型

类型描述检测难度
显性毒性明确的脏话、侮辱低(关键词即可)
仇恨言论针对特定群体的歧视中
隐性毒性微妙的偏见和刻板印象高
有害信息自残、暴力等有害内容中
虚假信息阴谋论、伪科学高

9.2 检测方法

关键词过滤:最基础但召回率有限

分类器过滤:

  • Perspective API(Google):毒性分数 0-1
  • HateBERT:专门训练的仇恨检测模型
  • Detoxify:多维度毒性检测

实践建议:组合使用关键词 + 分类器,设置合理的阈值避免过度过滤。

十、PII 脱敏

10.1 需要脱敏的信息

  • 邮箱地址
  • 电话号码
  • 身份证号/社会安全号
  • 银行卡号
  • 物理地址
  • 姓名(特定上下文中)
  • IP 地址

10.2 脱敏策略

策略 1:删除 直接删除包含 PII 的句子或文档。简单但损失信息。

策略 2:替换 用占位符替换 PII:

原文: 请联系张三,邮箱 zhangsan@example.com
脱敏: 请联系[PERSON],邮箱 [EMAIL]

策略 3:混淆 用假的但格式正确的信息替换:

原文: 手机号 13812345678
脱敏: 手机号 13899998888

10.3 工具

  • presidio(Microsoft):基于 NER 的 PII 检测
  • 正则表达式:针对格式化的 PII(邮箱、电话)
  • 自训练 NER 模型:针对特定领域的 PII

十一、Benchmark 去污染

11.1 为什么必须去污染

如果训练数据包含评测集的题目和答案,模型会 “记住” 答案而非学会解题——评测结果会严重虚高。

11.2 去污染方法

n-gram 匹配: 检测训练数据中是否包含评测集的长 n-gram(通常 n=10-13)。

def check_contamination(train_text, benchmark_text, n=13):
    train_ngrams = set(get_ngrams(train_text, n))
    bench_ngrams = set(get_ngrams(benchmark_text, n))
    overlap = train_ngrams & bench_ngrams
    contamination_rate = len(overlap) / len(bench_ngrams)
    return contamination_rate

哈希指纹:

  • 对评测集计算 n-gram 哈希指纹库
  • 扫描训练数据,匹配指纹
  • 移除匹配的训练样本

11.3 常见评测集

需要去污染的主要评测集:

  • MMLU(多学科知识)
  • GSM8K(小学数学)
  • HumanEval / MBPP(代码)
  • HellaSwag(常识推理)
  • ARC(科学推理)
  • TruthfulQA(真实性)
  • C-Eval / CMMLU(中文)
  • WinoGrande(常识)

十二、主流数据集对比

数据集年份源数据清洗策略规模模型性能
C42020CC规则过滤750GB基线
The Pile2021混合中等清洗825GB+5%
RefinedWeb2023CC去重 + 规则5T tokens+10%
RedPajama-v22023CC多维质量分30T tokens+12%
FineWeb2024CC深度清洗15T tokens+15%
FineWeb-Edu2024FineWeb教育质量筛1.3T tokens+20%
DCLM2024CC模型筛选3T tokens+18%

十三、实践清单

完整的数据清洗 Checklist

数据采集:

  • 确认数据来源和许可
  • 下载和存储原始数据
  • 建立数据版本管理

清洗流水线:

  • URL/域名过滤
  • HTML → 文本提取(推荐 Trafilatura)
  • 语言检测和过滤
  • 基础规则过滤(长度、字符比例等)
  • 精确去重(URL + 哈希)
  • 模糊去重(MinHash + LSH)
  • 质量评分和过滤
  • 毒性检测和过滤
  • PII 脱敏
  • Benchmark 去污染

验证:

  • 随机抽样人工检查质量
  • 统计各阶段数据保留率
  • 在小模型上验证清洗效果
  • 对比清洗前后的 benchmark 差异

持续改进:

  • 建立质量监控 dashboard
  • 定期更新过滤规则
  • 收集 badcase 反馈
  • A/B 测试不同清洗策略

十四、总结

数据清洗是 LLM 训练中投入产出比最高的环节。核心要点:

  1. 去重是基础:MinHash + LSH 模糊去重是必备技术
  2. 质量筛选是关键:模型打分 >> 规则过滤
  3. 文本提取很重要:Trafilatura 比简单的 HTML 解析好很多
  4. 不要过度清洗:C4 的教训——过于严格的规则会丢掉好数据
  5. 小实验验证:在小模型上验证清洗策略的效果,再应用到大规模数据

数据清洗不是一次性工作,而是一个持续迭代的过程。每一轮清洗策略的改进,都可能带来模型性能的显著提升。