章节导航 第 6 章 / 共 30 章
语种识别与质量过滤
从规则过滤到分类器过滤:FastText/CLD3 语种识别 + 质量分类器(FineWeb-Edu/CommonCrawl Quality)。
本章导读
3T 原始数据 → 1T 清洁数据。质量过滤这道关卡,决定了你模型上限的 60%。
本章由《AI数据工程》(aide) 第 4 章与《大模型数据工程》(de) 第 5 章合并而成。aide 提供新工具链的工程实操(CommonLingua、FineWeb-Edu、DCLM 两阶段策略),de 提供体系化的理论框架(质量定义的多维视角、分类器范式的根本性质疑、KenLM 困惑度、CQF 幻觉、质量感知 Scaling Law)。两条线索在本章交织:先回答”什么是高质量”,再按流水线的真实顺序——语种识别 → 规则过滤 → 分类器过滤 → 困惑度过滤——逐层推进,并在每一层融入两本书的工具与论点。
6.1 质量的定义:到底什么是”高质量”?
在数据工程中最根本也最困难的问题,不是”如何过滤”,而是”你到底要过滤掉什么、保留什么”。在花大量篇幅讨论具体过滤手段之前,必须先打开这个元问题——因为后面所有规则、分类器、困惑度阈值,本质上都是对”质量”某个侧面的代理。
6.1.1 多视角下的”质量”
语言流畅性视角:文本是否语法正确、表达自然、没有乱码?
- 这是最容易衡量的维度
- 但”语言流畅”不等于”有价值”——一篇流畅的 SEO 垃圾文章,语言流畅性满分,信息价值为零
信息密度视角:每个 token 包含多少有用的信息?
- 维基百科的信息密度很高——几乎每句话都传递知识
- 论坛闲聊的信息密度很低——大量的寒暄、重复、离题
- 但论坛闲聊对模型的”对话能力”有独特的训练价值
知识准确性视角:文本中的事实是否正确?
- 学术论文经过同行评审,准确性较高
- 社交媒体上的信息可能包含大量错误和误导
- 但准确性检测极其困难——需要知识图谱或事实验证系统
多样性视角:数据是否覆盖了足够多的主题、风格、观点?
- 全是维基百科风格的数据会让模型的输出变得”百科全书腔”
- 需要不同领域、不同写作风格、不同复杂度的混合
教育价值视角:这段文本能教给模型什么?
- 这是近年来兴起的重要视角
- 一段”教育价值高”的文本可能不是最流畅的(如一个详细解释编程概念的 StackOverflow 回答),但对模型的学习最有帮助
核心提示:质量过滤不是”找到金子”,而是”减少噪声”。认识到这一点,是从”调阈值”走向”理解系统”的开始。
6.1.2 认知演变:从”像维基百科就是好的”到”好数据是目标函数的结果”
“高质量”的定义本身,在过去六年里变了至少三次。理解这条演变线,才能理解为什么今天会有 fastText 分类器、FineWeb-Edu、DCLM 这么多并存的方案。
认知演变:
2019-2020: "像维基百科就是好的"
→ GPT-3 的质量分类器用维基百科作为正例
→ 结果:倾向于保留百科全书风格的文本,过滤掉口语化、非正式、但可能有价值的内容
2022-2023: "教育价值才是关键"
→ FineWeb-Edu 引入"教育价值分类器"——用 LLM 判断文本的教育价值
→ 效果显著优于传统的质量分类器
→ 但仍然有偏差:过度偏好"教材式"的内容
2024: "好数据 = 让下游任务表现好的数据"
→ DCLM 框架:好的数据过滤策略的衡量标准是最终模型在 benchmark 上的表现
→ 数据质量不是数据本身的属性,而是 (数据, 目标) 的联合函数
2025-2026: "多维度质量 + 动态评估"
→ 不同训练阶段需要不同"质量"标准的数据
→ 质量评估需要和训练目标紧密绑定
→ 没有"万能"的质量过滤器
这条线引出一个贯穿全章的张力:任何以特定参考集为”高质量标准”的过滤器,都面临”风格污染”风险(详见 §6.6 CQF 范式质疑)。因此本章不假设存在唯一正确答案,而是把不同年代、不同视角的方法都摆出来,供你按训练目标组合。
6.1.3 为什么”定义问题”决定整个管线设计
很多团队在质量过滤上反复踩坑,根因不在某个分类器训得不好,而在于一开始没想清楚”我要的高质量是哪一类”。下面三个真实场景说明定义如何直接改变过滤策略:
- 通用基座模型(如 7B-70B 通用 LLM):目标是广覆盖、强泛化。这里”高质量”应偏向”信息密度高 + 多样性好”,不能只保留教科书式文本,否则模型丧失口语、方言、长尾知识能力。FineWeb-Edu 的教育价值分类器可用,但要把保留阈值放宽到 ≥2 分(而非 ≥3),并配合领域混合(§6.4.8)。
- 专项模型(如法律/医疗/代码助手):目标是特定领域精度。这里”高质量”几乎等价于”领域对齐”——WebOrganizer 的实验表明,高达 84% 的教育价值增益可被 Topic×Format 领域混合复现。与其花力气做二值质量分类,不如先定领域配方,再在领域内做轻量过滤。
- 继续预训练 / 领域适应:目标是把新知识灌进已有模型。这里”高质量”应重新定义为”模型当前不知道但应该知道”——困惑度景观(§6.5.4)比通用质量分类器更对口,因为它直接衡量”知识鸿沟”。
实践检查清单:启动质量过滤前,先回答四个问题——
- 我的训练目标是通用还是专项?(决定质量是”泛化”还是”对齐”)
- 我有多少目标领域的本地数据?(决定能否训本地分类器,还是得跨语言迁移)
- 我的数据预算(token 数)是否充裕?(决定能否承担严格过滤带来的量减,还是要用数据墙策略 §6.8.3)
- 我用什么模型来验证过滤效果?(决定能否用高效注入验证而非完整训练,见 §6.4.3)
这四个答案共同决定:你该用哪一层过滤器、阈值多严、是否要做误杀恢复。
6.2 语种识别:质量过滤的第一道关卡
在数据流水线中,语种识别(Language Identification, LangID)是质量过滤的第一道关卡,原因有三:
- 下游所有过滤都语种相关:英文停用词表对中文无效,中文 Gopher 规则对英文无效
- 配比计算需要语种标签:训练时按 zh/en/code 分别配比
- 多语种模型需要平衡:知道每条数据的语种才能控制配比
6.2.1 踩坑:不做 LangID 直接训练的灾难
某团队用未做语种识别的混合数据训练,结果模型在中文 benchmark 上表现极差——因为训练数据里 80% 是英文,中文只有 5%。模型把中文当成”奇怪符号”处理,而不是真正的语言。
6.2.2 2026 年 LangID 模型全景对比
语种识别工具在 2026 年有了重大进展。此前 fastText lid.176 是事实上的工业标准,但随着 CommonLingua 的发布(Pleias + GSMA “African Languages in Africa” 项目,2026-04-28,Apache 2.0),格局已被打破。本节从全景对比开始,依次介绍新一代默认方案、传统基线和轻量替代方。
| 模型 | 参数量 | 覆盖语种 | CommonLID Strict Acc | CommonLID Equiv Acc | CommonLID Macro F1 | 模型大小 | 架构亮点 |
|---|---|---|---|---|---|---|---|
| CommonLingua | 2.35M | 334 | 77.63% | 82.92% | 0.788 | 8MB | 无分词器,UTF-8字节级因果Conv1D×3+双向Attention×1+RoPE |
| fastText lid.218 (NLLB) | ~600M | 218 | 59.53% | 71.64% | 0.659 | ~300MB | n-gram哈希+线性分类 |
| fastText lid.176 | ~130M | 176 | ~55% | — | ~0.60 | 126MB | 同上(旧版) |
| OpenLID v2 | ~600M | 200 | 55.77% | 70.19% | 0.639 | — | Transformer 系,官方 CommonLID 基线之一 |
| GlotLID v3 | ~600M | 2102 | 57.69% | 71.26% | 0.673 | ~2GB | Transformer+CLS分类,2 102 标签覆盖长尾 |
| Gherbal v4 | ~85M | 214 | — | 0.836(Avg) | 200MB | 阿拉伯方言专用优化 | |
| UniLID (Qwen2.5) | 0 (共享分词器) | — | — | 0.929(GlotLID-C) | 0 | UnigramLM分词器复用 | |
| XLM-R LID v2 | ~278M | 200+ | — | 0.966(语码转换) | ~1.1GB | transformers 原生,跨语言编码器 | |
| CLD3 | ~1M | 107 | — | — | ~10MB | Chromium 内置 n-gram+朴素贝叶斯,纯 CPU |
CommonLID 评测集:376K 样本,覆盖 200+ 语种,是迄今最全面的开源 LangID 基准测试。UniLID 采用 GlotLID-C 评测集(不同基准,数据不可横向对比)。
⚠️ 上表的口径分界线(重要):CommonLingua / fastText lid.218 (NLLB) / OpenLID v2 / GlotLID v3 这四行是官方 CommonLID 的同一 pipeline 重评结果——376k held-out paragraphs、200+ 语种,所有基线经
iso639-lang归一化与等价类折叠的同一流程重跑,因此可以直接横向对比。其中 GlotLID 必须取 v3(~600M / 2 102 标签 / 57.69% / 0.6729);早期版本的 1665 标签 / 66.23% / 0.715 属于另一版本的数字,混进来会破坏 apples-to-apples 前提。fastText lid.176 / Gherbal v4 / UniLID / XLM-R LID v2 / CLD3 五行来自其他来源与口径(UniLID 用 GlotLID-C、XLM-R 报语码转换场景),不能与上四行混比,只作工程侧参考。
📌 83% 与 77.63% 是同一个模型的两个指标(2026-10-08 补):发布方(Pleias 与 GSMA)的联合新闻稿宣传”准确率 83%“,本表 CommonLingua 一格写 77.63%。两者都对:83% 对应 Equiv acc 82.92%,77.63% 是 Strict acc。差别来自是否做等价类折叠(iso639 归一化后把互为等价的语言变体合并计分)。引用任一方时必须写明取哪一列,否则同一模型会出现两个”官方准确率”。
评估指标外推需谨慎(社区实践):CommonLingua/CommonLID 的官方指标来自受控评测集,在真实 Web 噪声场景下表现通常更严格——噪声文本更易被判为低置信而被丢弃。一次 Common Crawl 清洗实战中,fastText + langid.py 双模型在受控样本上一致性达 98.91%,可作为工程上的交叉验证参考。CLD3(Chromium 的 Compact Language Detector v3)在 107 种语言上开箱即用、纯 CPU、零依赖,是 fastText 之外的轻量基线选项,适合在延迟敏感、不想加载百 MB 模型的边缘场景使用。
6.2.3 CommonLingua:新一代默认选择
CommonLingua 在仅 8MB 的模型文件上实现了对 334 种语言的顶级识别精度,全面超越参数量约 255 倍的 fastText lid.218(~600M vs 2.35M)与同为 ~600M 量级的 OpenLID v2,macro F1 领先次优基线 +11.5 个点(0.788 vs 0.673)。吞吐同样压倒性:H100 bf16、batch 4096 下 26,236 texts/s(fp32 10,962,2.4×),Sapphire Rapids CPU 8 线程 bf16 下 553 texts/s。其架构设计有几个关键创新:
训练数据与可迁移性(2026-10-08 补):CommonLingua 在 2,482,568 个段落上训练,语料来自 Structured Wikipedia 与 Common Corpus,刻意包含带 OCR 错误的真实文档,并侧重长尾——支持 61 种非洲语言,包括几乎没有覆盖的语言。由于训练语料全部是开放许可数据,Pleias 同步发布了原始数据集(含逐条许可贡献明细),这在 LangID 模型里是少见的透明度。⚠️ 官方模型卡另有一条重要限定:CommonLingua 未在很短的文本片段上做过评估,在这类输入上很可能不如其他方案(“was not assessed on very short text segments and will likely perform less well than alternatives”)——这条与本节下文 §6.2.8b 风险列的”极低文本置信度波动”是同一件事,接短文本任务时需实测。
- 无分词器:直接在 raw UTF-8 字节上操作,彻底避免了 tokenizer 偏置
- Trigram 哈希嵌入:固定词汇量,跨语言同质化训练
- Causal Conv1D × 3 + Bidirectional Attention × 1 + RoPE:兼顾序列建模与全局上下文
- Trigram 哈希嵌入带来的增益可量化:官方消融显示,加 trigram 信号相对无 n-gram 基线提升 +1.2 macro F1
- Apache 2.0:无许可障碍
⚠️ 适用边界(官方明示,工程上最容易踩):CommonLingua 面向段落级语料治理设计,官方未在很短文本上做过评测,并明确提示短文本场景”will likely perform less well than alternatives”。输入按 ≤512 字节截断(以 paragraph 为单位,不足则 padding)。中文 Web 清洗里大量出现的标题、短评、导航文案、弹幕都属于这一类短文本——这类场景应当保留 fastText 或 CLD3 作为补充判别器,而不是拿 CommonLingua 的低置信输出直接当结论。
# CommonLingua 语种识别
# 安装:pip install commonlingua
from commonlingua import CommonLingua
model = CommonLingua.from_pretrained("PleIAs/CommonLingua")
def detect_language_cl(text, threshold=0.5):
"""
CommonLingua 语种识别
返回 (language_code, confidence)
"""
if len(text.strip()) < 10:
return 'unknown', 0.0
predictions = model.predict(text, k=3)
top_lang = predictions[0].code # ISO 639-3
top_prob = predictions[0].confidence
if top_prob < threshold:
return 'unknown', top_prob
return top_lang, top_prob
# 批量识别示例(比fastText更快,支持内置批处理)
texts = ["The quick brown fox jumps over the lazy dog.",
"知识就是力量。",
"Le savoir est le pouvoir."]
results = model.predict_batch(texts)
for text, pred in zip(texts, results):
print(f"{text[:30]}... → {pred[0].code} ({pred[0].confidence:.2f})")
6.2.4 fastText lid.176 与 CLD3:作为对比基线
fastText lid.176 曾是 2019-2025 年间的事实标准,本章保留其完整工程代码作为”对比基线”,方便你(1)理解 LangID 基本原理(2)在需要传统兼容方案时使用。CLD3 则作为更轻量的纯 CPU 替代,便于嵌入浏览器端或轻量服务。
fastText 的 lid.176 特点:
- 支持 176 种语言:覆盖几乎所有有文字系统的语言
- 模型大小:压缩后约 126MB,可单机加载
- 速度:单核 10,000+ docs/s
- 准确率:在 FLORES-200 上 95%+(常见语言 99%+),但在 CommonLID 基准上明显低于 CommonLingua
# fasttext 语种识别的最简用法
import fasttext
# 下载:https://dl.fbaipublicfiles.com/fasttext/supervised-models/lid.176.bin
model = fasttext.load_model('lid.176.bin')
def detect_language(text, threshold=0.5):
"""
返回 (language_code, confidence)
threshold 低于此置信度的标记为 'unknown'
"""
# fasttext 要求输入是单行(不能有换行)
text = text.replace('\n', ' ').strip()
if len(text) < 10: # 太短的文本不可靠
return 'unknown', 0.0
predictions = model.predict(text, k=3) # 取top-3
langs, probs = predictions
top_lang = langs[0].replace('__label__', '')
top_prob = probs[0]
if top_prob < threshold:
return 'unknown', top_prob
return top_lang, top_prob
CLD3 的极简用法(纯 CPU、107 语种、开箱即用):
# CLD3 语种识别(Chromium 内置检测器的 Python 封装)
# 安装:pip install pycld3
import cld3
def detect_language_cld3(text):
"""
返回 (language_code, confidence, is_reliable)
CLD3 在短文本和噪声场景下比 fastText 更稳定,但语种覆盖较少
"""
prediction = cld3.get_language(text)
return prediction.language, prediction.probability, prediction.is_reliable
# 示例
code, prob, reliable = detect_language_cld3("知识就是力量。")
print(code, prob, reliable) # ('zh', 0.98, True)
工程参数调优(fastText):
# 生产环境推荐配置
LANGID_CONFIG = {
'model': 'lid.176.bin', # 176语种版本
'min_text_length': 10, # 小于此长度标记 unknown
'confidence_threshold': 0.5, # 低于此置信度标记 unknown
'top_k': 3, # 返回top-3用于多语种文本
'mixed_lang_threshold': 0.3, # top-2与top-1差距<0.3视为多语种
# 对于代码/数学公式密集的文本,需要预处理
'strip_code_blocks': True, # 去除```...```代码块
'strip_latex': True, # 去除$...$公式
'strip_urls': True # 去除URL
}
6.2.5 CommonLingua 工程参数
# CommonLingua 推荐配置
COMMONLINGUA_CONFIG = {
'model': 'PleIAs/CommonLingua', # HuggingFace 模型ID
'min_text_length': 15, # 略高于fastText(以充分利用更深层特征)
'confidence_threshold': 0.6, # CommonLingua置信度分布与fastText不同,建议0.6
'top_k': 5, # 334语种覆盖面广,建议取top-5检查多语种
'batch_size': 64, # 支持内置批处理,大幅提升吞吐
# CommonLingua 原生处理代码/公式,无需strip预处理
# 这是相比fastText的一大优势
}
6.2.6 常见误判
| 文本类型 | 误判为 | 原因 | 解决 |
|---|---|---|---|
| 代码 + 注释 | en | 注释主导 | 先剥离代码再识别 |
| 短文本(<10字符) | unknown | 信息不足 | 拒绝短文本 |
| 拉丁化中文(pinyin) | en/id | 拼音被识别为印尼语 | 用CommonLingua/GlotLID补充 |
| 历史文本(古文) | ja | 繁体字相似 | 用专门古文分类器 |
| 含大量数字 | unknown | 数字无语种信息 | 先剥离数字 |
| 阿拉伯语方言 → MSA | ar (现代标准阿语) | 方言与MSA混淆 | 用 Gherbal v4 (Omneity Labs, 2026),16/16 阿拉伯方言分类,Avg Acc 0.836,200MB |
6.2.7 轻量方案:UniLID — 无模型的语种识别
如果你不想为语种识别单独部署一个模型,UniLID(Clara Meister et al., ICML 2026)提供了一个极简方案:直接复用 LLM 自带的 UnigramLM 分词器做语种识别,零额外模型成本。
核心原理:UnigramLM 分词器中不同语种的子词单元分布差异显著,通过计算测试文本在各语种子词单元上的负对数似然即可推断语种。
关键数据(GlotLID-C 基准):
- UniLID (via Qwen2.5 tokenizer):Macro F1 = 0.929
- fastText 基线:Macro F1 = 0.944
- 差距仅 0.015,但 UniLID 零额外模型、零额外内存
适用场景:
- 你的流水线已经加载了 LLM 分词器(如训练场景)
- 追求最小依赖的简化流水线
- 对准确率要求接近 fastText 即可(差距 <2%)
6.2.8 LLM 驱动的语种识别
另一个有趣的产业级实践是 Ada CX Labs(2026)的方案:用 LLM 的 tool-call 能力替换 standalone LangID 模型。
实验数据(367K 样本,55 语种):
- 独立 fastText 分类器:91.8% 准确率
- LLM tool-call 方案:94.9% 准确率
思路是让 LLM 在预处理阶段同时完成语种识别(作为”摘要” tool_call 的副产品),避免了单独的 LangID 模块。但这需要你的流水线已经集成 LLM 推理,否则代价超过收益。
趋势观察:2024 年”语种识别 = fastText”是一个不假思索的结论;2026 年,你需要根据场景选择 CommonLingua(最强精度)、CLD3(最轻量、纯 CPU、零依赖)、UniLID(零模型成本)、或 LLM tool-call(流水线整合)。
6.2.8b 部署成本与选型对照
语种识别看似简单,但在万亿 token 规模下,模型大小、吞吐、依赖会直接决定它能不能放在管线里。下表给出工程选型速查:
| 方案 | 模型大小 | 是否需要 GPU | 单核吞吐 | 适用位置 | 主要风险 |
|---|---|---|---|---|---|
| CommonLingua | 8MB | 否(⚠️ 仅批吞吐成立,见下) | 高(批吞吐,内置批处理) | 主 LangID(默认) | 极低文本置信度波动;CPU 吞吐两口径冲突待核 |
| fastText lid.176 | 126MB | 否 | 10k+ docs/s | 对比基线 / 兼容 | 噪声场景低于 CommonLingua;Python 绑定 fasttext 最新 0.9.3(2024-06-12) |
| CLD3 | ~10MB | 否 | 极高(C++ 原生) | 边缘 / 浏览器 / 轻服务 | 仅 107 语种;Python 绑定 pycld3 最新 0.22(2021-03-09) |
⚠️ 「CPU 友好」不是一个无条件结论(2026-10-08 订正):本章此前给 CommonLingua 一个不加区分的”CPU 友好”,但发布方的两份官方材料给出的 CPU 吞吐相差约 27 倍:
来源 CPU 吞吐 GPU 吞吐 一手模型卡( PleIAs/CommonLingua)Sapphire Rapids 8 线程 bs=32:553 texts/s(bf16)/ 183(fp32);单线程 114 / 44 H100 80GB bs=4096:26,236 texts/s(bf16) / 10,962(fp32) GSMA 联合发布新闻稿(2026-04-28) CPU 上约 20 texts/秒 单 GPU 最高 3,000 texts/秒 两侧在 CPU(553 vs 20)与 GPU(26,236 vs 3,000)上都差一个数量级上下,差异原因本轮未查明(疑为批吞吐 vs 单条延迟两种场景)。
在查清之前的处理:①”CPU 友好”限定为离线批吞吐场景(553 texts/s,8 线程 bf16);②”在线 / 边缘 / 浏览器内”这一行的 CommonLingua 建议降级为需实测,该场景默认改为 CLD3(见下方选型经验同步调整);③若你的场景是单条、低延迟、无批处理,请按 20 texts/s 做容量规划,不要按 553。 | UniLID | 0(共用分词器) | 是(已有分词器) | 取决于 LLM 推理 | 训练态已有分词器时 | 仅能判已覆盖语种 | | LLM tool-call | 0(复用推理) | 是 | 低(LLM 推理贵) | 已集成 LLM 的预处理 | 成本 / 延迟 |
选型经验:
- 离线批处理万亿 token:CommonLingua 为主,CLD3 做低成本交叉校验
- 在线 / 边缘 / 浏览器内:CLD3(默认);CommonLingua 在该场景需实测——若按发布方新闻稿的 ~20 texts/s,则不适合低延迟路径
⚠️ 两个 Python 绑定的维护状态(2026-10-08 补):本节与 §6.2.4 的示例依赖
import fasttext与import cld3,但两个绑定都已多年未更新——fasttext最新 0.9.3(2024-06-12)、fasttext-wheel最新 0.9.2(2020-09-03)、pycld3最新 0.22(2021-03-09)。这不等于”不能用”(底层 C++ 实现稳定),但在新 Python 版本上的可安装性是真实风险。本章多处建议”锁定版本”——请读者先确认绑定能在目标 Python 上装起来,再谈锁定哪个版本。
- 训练集群已加载 LLM 分词器:UniLID 零额外成本
- 已跑 LLM 预处理(如摘要、结构化):顺手用 LLM tool-call,省一个独立模块
置信度阈值经验值:CommonLingua 建议 confidence_threshold=0.6、min_text_length=15;fastText 建议 0.5 / 10;CLD3 看 is_reliable 标志。低于阈值的文本标记为 unknown——但对 unknown 的处理策略要慎定:直接丢弃会损失低资源语种(其置信度天然偏低),更稳妥的做法是单独归桶、后续人工或专用分类器复核,而非一刀切丢弃。
6.2.9 混合语言与语码转换的标注边界
前面讨论的 LangID 工具默认给整段文本一个单一语种标签。但在真实 Web 抓取语料中,大量文本是语码转换(code-switching)——同一句话内嵌多种语言片段(如”这篇 tutorial 的 performance 很好”)。这类文本是 LangID 漏判/误判的主要来源。
工程上存在两种取舍:
- 单一标签:整段文本只输出一个主导语种(简单,但会丢失混合语言信息,配比统计失真)
- 分段边界标注:先做语种分段,再对每段独立标注(准确,但成本高、边界判定本身是难题)
一篇 2026 年关于哈萨克语-俄语语码转换标注的工作(arXiv:2608.00581)将 XLM-R LID v2 作为语种识别基线,报告宏 F1 达 0.966,其核心关切正是”混合语言句子的标注边界”。对多语种/低资源语种场景,建议在 LangID 流水线中额外加入语码转换检测,而非仅依赖单一标签模型。
6.3 规则过滤:Gopher 启发式与工程演进
语种识别之后,第一层”质量过滤”通常是启发式规则——它们不依赖任何模型,只看文本的表面统计特征。这是工业界至今仍在用的基础防线。
6.3.1 Gopher 八条规则
DeepMind 在 Gopher 论文(Rae et al., 2022)中提出了 8 条经典的启发式质量过滤规则,至今仍是工业界基础:
def gopher_filter(text):
"""
Gopher 8条启发式规则
任一规则不通过即拒绝
"""
if not text or len(text.strip()) == 0:
return False, 'empty'
words = text.split()
word_count = len(words)
# 规则1:单词数 50-100,000
if word_count < 50:
return False, 'too_few_words'
if word_count > 100000:
return False, 'too_many_words'
# 规则2:平均词长 3-10
avg_word_len = sum(len(w) for w in words) / word_count
if avg_word_len < 3 or avg_word_len > 10:
return False, 'abnormal_word_length'
# 规则3:符号占比 <10%
symbol_count = sum(1 for c in text if c in '!@#$%^&*()[]{}|\\:";\'<>?,./`~')
symbol_ratio = symbol_count / len(text)
if symbol_ratio > 0.10:
return False, 'too_many_symbols'
# 规则4:停用词占比 ≥3%
# 英文停用词表(简化版)
stop_words = {'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be',
'been', 'being', 'have', 'has', 'had', 'do', 'does',
'did', 'will', 'would', 'could', 'should', 'may',
'might', 'must', 'shall', 'can', 'need', 'dare',
'ought', 'used', 'to', 'of', 'in', 'for', 'on',
'with', 'at', 'by', 'from', 'as', 'into', 'through'}
stop_count = sum(1 for w in words if w.lower() in stop_words)
stop_ratio = stop_count / word_count
if stop_ratio < 0.03:
return False, 'too_few_stopwords'
# 规则5:字母占比 ≥60%
alpha_count = sum(1 for c in text if c.isalpha())
alpha_ratio = alpha_count / len(text)
if alpha_ratio < 0.60:
return False, 'too_few_alpha'
# 规则6:重复行占比 <30%
lines = text.split('\n')
if len(lines) > 1:
line_set = set(lines)
dedup_ratio = 1 - len(line_set) / len(lines)
if dedup_ratio > 0.30:
return False, 'too_many_dup_lines'
# 规则7:n-gram重复检测
# 前100个3-gram中重复 >0.4 拒绝
ngrams = [tuple(words[i:i+3]) for i in range(min(100, word_count-2))]
if ngrams:
ngram_set = set(ngrams)
ngram_dup_ratio = 1 - len(ngram_set) / len(ngrams)
if ngram_dup_ratio > 0.40:
return False, 'too_many_dup_ngrams'
# 规则8:文档末尾不能是单一字符重复(爬虫残留)
if len(text) > 100:
tail = text[-100:]
if len(set(tail)) < 10: # 末尾100字符只有<10种
return False, 'tail_repetition'
return True, 'pass'
6.3.2 Gopher 规则调优经验
| 参数 | 默认值 | 推荐范围 | 说明 |
|---|---|---|---|
| min_words | 50 | 30-100 | 太严会丢列表页 |
| max_words | 100,000 | 50,000-200,000 | 严一点防爬虫残留 |
| symbol_ratio | 0.10 | 0.08-0.15 | 代码数据要放宽到0.20 |
| stop_ratio | 0.03 | 0.02-0.05 | 中文/多语种需独立参数 |
| alpha_ratio | 0.60 | 0.50-0.70 | 代码/数学可放宽到0.50 |
| dup_line_ratio | 0.30 | 0.20-0.40 | 严一点防模板页 |
踩坑经验:直接套英文 Gopher 规则到中文会过滤掉 80%+ 优质中文数据(中文没有空格分词,词长/停用词规则全部失效)。中文必须用单独的规则集:
def gopher_chinese(text):
"""中文专用Gopher规则"""
char_count = len(text)
# 规则1:字符数 50-100,000
if char_count < 50 or char_count > 100000:
return False
# 规则2:中文字符占比 ≥60%
cn_chars = sum(1 for c in text if '\u4e00' <= c <= '\u9fff')
if cn_chars / char_count < 0.60:
return False
# 规则3:标点符号占比 5%-30%
punctuations = ',。!?;:、""''()【】《》…—'
punct_count = sum(1 for c in text if c in punctuations)
punct_ratio = punct_count / char_count
if punct_ratio < 0.05 or punct_ratio > 0.30:
return False
# 规则4:常用字覆盖率 ≥90%
common_chars = load_common_3500_chars() # 国家3500常用字
char_set = set(c for c in text if '\u4e00' <= c <= '\u9fff')
common_coverage = len(char_set & common_chars) / max(len(char_set), 1)
if common_coverage < 0.90:
return False
# 规则5:重复行占比 <30%
lines = text.split('\n')
if len(lines) > 1:
line_set = set(lines)
if 1 - len(line_set) / len(lines) > 0.30:
return False
return True
6.3.3 质量过滤的技术演进路线
在进入下一节的模型打分前,有必要理解质量过滤在 2020-2026 年间的演进路线:
2020-2022: 启发式规则 (Gopher 8条)
→ 2022-2024: 分类器打分 (FineWeb-Edu, DCLM)
→ 2024-2025: 多维度评分 (QuRating, DSIR)
→ 2025-2026: LLM辅助审查 (NeMo Curator Ensemble Classifier)
2026 年的新趋势:NeMo Curator 的质量过滤器引入了 ensemble quality classifier(集成质量分类器),将数据分为 20 个质量桶(quality buckets),允许用户按桶自由选择数据比例(如只保留质量桶 8-19)。这代表了从”二值过滤(好/坏)“到”连续质量分级(1-20 级)“的范式转换。
此外,LLM-as-judge 开始被用于对边缘样本进行二次审查——当分类器信心不足时(如评分 2-3 之间),用 LLM 做最终裁定。但需权衡成本:100B tokens 数据中,如果 LLM 审查 10%(10B tokens),按 GPT-4 定价约为 $10,000-50,000,需评估收益是否匹配。
6.3.4 规则过滤的跨语言陷阱与组合实践
规则过滤最大的陷阱是把英文规则直接套到非英文语料。除了 §6.3.2 已点出的中文”无空格分词导致词长/停用词规则全失效”,还有几类常见跨语言坑:
- 日语 / 中文:没有空格,基于
text.split()的词数统计全部失真,必须改用字符数或形态分析分词 - 阿拉伯语 / 波斯语:从右到左书写,Unicode 双向控制字符会被符号占比规则误判为”符号过多”
- 泰语 / 缅甸语:词间无显式空格,且字符重复模式与拉丁语系差异大,n-gram 重复阈值需单独标定
- 代码 / 数学密集文本:符号占比天然高(公式、运算符),
symbol_ratio要放宽到 0.20,alpha_ratio可放宽到 0.50
组合实践:生产环境很少只跑一条规则,而是把多条规则按”成本-收益”叠成漏斗。下面给出一个可落地的组合顺序(先便宜后贵、先粗后细):
原始文档
│
▼ [规则A] 长度门禁:字符数 < 50 或 > 100k → 丢弃(最便宜,挡掉 99% 爬虫残留)
▼ [规则B] 符号/字母占比:symbol>10% 或 alpha<60% → 丢弃(挡乱码页)
▼ [规则C] 停用词/常用字覆盖:中英分别参数 → 丢弃(挡非自然语言)
▼ [规则D] 重复行 + n-gram 重复:dup_line>30% 或 dup_ngram>40% → 丢弃(挡模板页)
▼ [规则E] 末尾单一字符重复(tail_repetition)→ 丢弃(挡爬虫 footer)
│
▼ 通过规则初筛的文档进入 §6.4 分类器精筛
经验:规则层的目标不是”挑出好数据”,而是”以极低代价干掉明显垃圾”。把规则阈值设得稍宽松(宁可放过、不可错杀)通常比卡死更好——真正的质量区分交给后面的分类器。这与 FineWeb 第三代范式(§6.8.4)“宽松启发式初筛 + 分类器精筛 + 误杀恢复”完全一致。
6.4 分类器过滤:从维基百科分类器到教育价值
启发式规则”只看表面”,更高级的做法是用模型本身来评估文本质量。这一节是 aide 与 de 重叠最深的区域:aide 侧重 FineWeb-Edu / DCLM 的实操,de 侧重分类器范式的历史脉络与根本性质疑。我们先讲方法,再在 §6.6 集中讨论质疑。
6.4.1 GPT-3 的维基百科分类器
GPT-3 的做法是训练一个二分类器来判断网页文本的”质量”:
- 正例:维基百科文章、高质量参考资料
- 负例:随机采样的 Common Crawl 网页
使用 logistic regression 或 fastText 分类器,基于 n-gram 特征。
问题:
- 分类器学到的实际上是”像维基百科”而非”高质量”
- 结果会过滤掉大量非百科体裁但有价值的内容(如口语化的教程、个人经验分享、技术论坛的讨论)
- 关键认知:分类器的偏差来自正例的选择——正例的特征会被放大
6.4.2 fastText 质量分类器
这是目前最常用的质量过滤方案之一。
训练流程:
# 1. 准备训练数据
# 正例:从维基百科、精选网站抽取的文本
# 负例:Common Crawl 的随机样本
# 2. 特征工程
# fastText 使用字符 n-gram 作为特征
# 通常使用 bigram (n=2) 到 trigram (n=3)
# 3. 训练
import fasttext
model = fasttext.train_supervised(
input='quality_training_data.txt',
lr=0.1,
epoch=5,
wordNgrams=2,
dim=100,
)
# 4. 推理(极快,CPU 上每秒可处理数万条)
label, score = model.predict("一段待评估的文本")
优点:
- 速度极快,适合万亿 token 级别的数据
- 实现简单,易于迭代
缺点:
- 依赖于正例的选择——如果正例有偏差,分类器就有偏差
- 基于 n-gram 特征,对语义理解有限
- 无法区分”语言流畅但信息为空”和”语言流畅且信息丰富”
改进方向:验证驱动的种子数据优化。Ultra-FineWeb 针对 fastText 分类器的核心痛点——“种子数据选择依赖人工经验”——提出了系统性的解决方案。其核心思路是:引入高效验证策略,以最小计算成本评估不同种子数据选择对下游 LLM 训练效果的影响,从而将种子数据优化从”拍脑袋”变成”有数据支撑的决策”。具体来说,Ultra-FineWeb 通过优化正负样本的选择来改善分类器的鲁棒性,最终构建了约 1T 英文 token + 120B 中文 token 的高质量数据集。
6.4.3 高效验证策略:从”训练验证”到”注入验证”
传统上,验证一个数据过滤策略是否有效的黄金标准是:用过滤后的数据训练一个完整的 LLM,然后在 benchmark 上评估。但这个验证流程极其昂贵——一次完整的预训练可能需要数千 GPU 小时。这使得过滤策略的搜索空间被极大压缩,很多潜在的好策略因为”验证不起”而从未被尝试。
Ultra-FineWeb(OpenBMB/清华/ModelBest, arXiv: 2505.05427)提出的高效验证策略改变了这个局面:
核心方法:用一个近训练完成的 LLM(而非从头训练),在训练的最后若干步中注入候选语料,观察性能提升幅度来评估数据质量。
传统验证流程:
从头训练完整LLM → 评估benchmark → 数千GPU小时/次
高效验证流程:
取近完成的LLM → 注入候选语料训练少量步 → 评估增量 → 数十GPU小时/次
加速比:约 50-100x
三个关键设计决策:
- 为什么用近完成的 LLM 而非从头训练? 近完成的 LLM 已经学会了大部分通用知识,对数据质量的敏感性更高——高质量数据能带来可观测的增量提升,而低质量数据则不会。
- 公正种子选择(Fair Seed Selection):基于”高质量种子数据有益于 LLM 训练”这一基本假设,自动选择正负样本,减少人工主观性。
- fastText 分类器的选择:Ultra-FineWeb 最终选择了 fastText 而非 LLM 分类器,理由是:推理效率远超 LLM 分类器、质量过滤效果相当、万亿 token 级别上推理效率差异意味着数天计算节省。
数据集成果:Ultra-FineWeb-en (~1T tokens) + Ultra-FineWeb-zh (~120B tokens),已成为 MiniCPM4 核心预训练 Web 数据。
⚠️ 注入验证的适用边界(2026-10-08 补):先去污染,再注入验证
注入验证测的是”benchmark 增量”,而 benchmark 增量本身可以被污染伪造。Gaperon 的 Garlic 变体证明:在持续预训练阶段把测试集混入数据,即可”恢复”有竞争力的基准分数,而对生成质量的损害只是中等。更麻烦的是,这种污染还会连带抬高 held-out 基准上的表现——也就是说,“换一个没见过的基准来交叉验证”这道常见的防线在这里失效。
由此得到本节方法论的一条硬前置条件:候选语料在注入验证之前必须先过一遍去污染(第 7 章 §7.10 的多层检测)。否则注入验证加速的不只是验证,它同样加速了把污染数据选进来的过程——50–100× 的加速比,对污染数据也是 50–100×。
另外补一条验证指标上的修正:不要只用 benchmark 判断数据质量。Gaperon 同时发现,按语言质量过滤会提升文本的流畅度与连贯性,却产生偏低的基准成绩——“生成质量”与”benchmark 分数”这两条轴可以反向。这与 §4.3 已有的”公正种子选择”是两件事:后者管的是种子有没有偏差,前者管的是评分维度够不够。建议验证时同时看生成质量(人工或自动评分)与 benchmark,两条轴一起报告。
6.4.4 FineWeb-Edu 教育价值分类器
FineWeb-Edu 是当前最强开源质量过滤方案之一。核心是用一个分类器打”教育价值 1-5 分”。aide 与 de 都重点介绍,这里合并两书的说明。
训练方法:
- 从 FineWeb 中抽取大量文本样本
- 用 LLM(如 Llama 3 70B)给每个样本打”教育价值”分数(0-5 分)
- 用这些标注训练一个轻量级的分类器(如 BERT-small)
打分标准:
- 0 分:垃圾内容、广告、SEO 文本
- 1 分:有基本信息但组织混乱
- 2 分:有一定教育价值但不系统
- 3 分:有明确的教育价值,解释清晰
- 4 分:高质量的教育内容,有深度和结构
- 5 分:教科书级别的内容
# FineWeb-Edu 分类器 = Snowflake-arctic-embed-m(109M) + 单输出回归头
# 训练:冻结 embedding 与 encoder,只训回归头,20 epoch,lr=3e-4
# 训练数据:450,000 条 Llama3-70B-Instruct 打标样本(非人工标注)
# 输入:网页文本
# 输出:连续 score;int_score = int(round(clamp(score, 0, 5))),即 0-5 分
# 二分类化(阈值 ≥3)时 F1 = 82%
<!-- edit: 2026-09-19 按官方模型卡 HuggingFaceFW/fineweb-edu-classifier 订正三处硬错误:基座原写「Llama3-8B 微调」(实为 Snowflake-arctic-embed-m 加回归头且编码层冻结)、训练数据原写「约 5000 篇人工标注」(实为 450,000 条 Llama3-70B-Instruct 打标,差 90 倍)、输出原写「1-5 分整数」(实为 0-5,0 分档在本章上文打分标准里已列出) -->
# 用 transformers 调用
from transformers import pipeline
classifier = pipeline(
'text-classification',
model='HuggingFaceFW/fineweb-edu-classifier',
device='cuda:0'
)
# 单条打分
result = classifier(text[:4096], truncation=True)
# 输出: [{'label': '4', 'score': 0.85}]
# 批量打分
texts = [doc.text[:4096] for doc in documents]
results = classifier(texts, batch_size=32, truncation=True)
# 推荐保留 ≥3 分的,作为高质量数据
# ≥4 分的作为退火数据
官方模型卡给出的等价写法(直接取回归头输出,便于自定义阈值与批量打标):
# 官方模型卡示例(HuggingFaceFW/fineweb-edu-classifier,Apache-2.0)
from transformers import AutoTokenizer, AutoModelForSequenceClassification
tokenizer = AutoTokenizer.from_pretrained("HuggingFaceFW/fineweb-edu-classifier")
model = AutoModelForSequenceClassification.from_pretrained(
"HuggingFaceFW/fineweb-edu-classifier"
)
inputs = tokenizer(text, return_tensors="pt", padding="longest", truncation=True)
score = model(**inputs).logits.squeeze(-1).float().item()
int_score = int(round(max(0, min(score, 5)))) # 0-5
⚠️ 注意:官方模型卡明确给出
int_score >= 3作为数据策展的推荐阈值,并提示该分类器可能过度偏好”看起来像学术论文”的文本(“It might overfit to academic looking content for the higher scores”)。这与下文”效果与局限”里提到的”过度偏好教材式内容”是同一现象的两个观察面,不是两处独立发现。
FineWeb-Edu 评分分布与用途(合并两书数据):
| 评分 | 标准 | 占比 | 用途 |
|---|---|---|---|
| 5 | 大学级以上深度内容,结构化、信息密集 | <1% | 退火精选 |
| 4 | 高中-大学级,有论证结构,事实准确 | ~4% | 高质量训练集 |
| 3 | 初中-高中级,常规知识性内容 | ~10% | 主训练数据 |
| 2 | 简单叙述,新闻列表式 | ~25% | 边缘保留 |
| 1 | SEO、广告、模板 | ~60% | 拒绝 |
效果与局限:
- 使用教育价值 ≥ 3 的数据训练的模型,在多个 benchmark 上显著优于使用全量数据的模型
- 特别在推理和知识密集型任务上提升明显,即使训练 token 数减少了 50%+,模型性能反而提升
- 但过度偏好”教材式”内容——模型输出可能变得过于”说教”
- 对话风格内容几乎全被过滤掉——但这些内容对模型对话能力很重要
- LLM 标注一致性有限——同一段文本可能被评为 2 分也可能被评为 4 分
6.4.5 DCLM 与 fastText 分类器
DCLM(DataComp for Language Models)框架提出更系统的方法:
- 使用预训练的 LLM 直接对每段文本打分
- 分数反映”这段文本对训练有多大帮助”
- 通过可控实验验证不同过滤策略的效果
与 FineWeb-Edu 的区别:
- DCLM 使用更大的模型来打分,精度更高但成本也更高
- DCLM 的评估标准更直接——“对下游任务的帮助”而非”教育价值”
DCLM 也提供了一个轻量的 fastText 质量分类器,适合做大规模粗筛:
# DCLM fastText 分类器
# 模型下载:https://huggingface.co/matorgii/dclm-fasttext-classifier
import fasttext
model = fasttext.load_model('dclm_quality.bin')
def dclm_quality_score(text):
"""返回 0-1 的质量分数"""
text = text.replace('\n', ' ').strip()
if len(text) < 50:
return 0.0
predictions = model.predict(text)
label, score = predictions
if '__label__high' in label[0]:
return score[0]
return 1 - score[0]
# 推荐阈值:保留 score > 0.5 的
fastText 优势:速度是 BERT 类的 100 倍,适合做大规模粗过滤;劣势是准确率略低,建议先用 fastText 粗筛再用 FineWeb-Edu 精筛。
6.4.5b CCNet:流水线化的分类器过滤范式
CCNet(Wenzek et al., 2020)是 CommonCrawl 处理流水线中最早把”分类器过滤”工程化、可复现的方案之一,至今仍是许多团队的参考实现。它与 GPT-3 维基百科分类器的思路一脉相承,但更强调可复现的端到端管线:
- 语言识别:先用 fastText 语言模型给每段分句打语种标签,并丢弃低置信度片段
- 去重:基于文档级 / 行级模糊去重(为质量过滤减负,见 §6.9.1)
- 质量分类:用”维基百科 vs CommonCrawl”的 fastText 分类器给每段打分,按百分位阈值保留高质量段
- 分词 / 打包:按模型训练需要的 token 序列长度切分打包
CCNet 的关键价值不在某个单独的创新,而在于它证明了”分类器过滤”可以做成可复现、可审计、可迭代的离线流水线——每一个阈值、每一个分类器都固化下来,便于后续消融(§6.9.3)。它的局限也正是整个 CQF 范式的局限(§6.6):正例是维基百科,因此学到的”质量”本质是”像维基百科”。
6.4.5c 分类器选型决策表
面对众多分类器,下面一张表帮你按约束取舍(合并 aide 与 de 两书的工程经验):
| 你的约束 | 首选方案 | 理由 | 注意 |
|---|---|---|---|
| 万亿 token、CPU 预算 | fastText / DCLM-fastText | 快、可迭代、Ultra-FineWeb 证明可匹敌 LLM | 需高效验证优化种子 |
| 追求最强质量、有 GPU | FineWeb-Edu (Llama3) | 教育价值打分,推理/知识任务增益大 | 偏教材腔、对话数据流失 |
| 需要严格下游验证 | DCLM 框架 | 以 benchmark 增益为准则 | 成本高 |
| 目标领域明确 | WebOrganizer 领域混合 / CoLoR-Filter | 直接优化目标域损失 | 偏离”通用质量” |
| 多语言低资源 | FineWeb-2 (XLM-R) + Q3 采样 | 跨语言迁移 + 逐语言 retention | 防 rank instability |
| 想轻量且有效 | Ultra-FineWeb fastText | 高效注入验证 50-100x | 需自建验证闭环 |
核心结论:没有”最好的分类器”,只有”最匹配你验证策略与目标函数的分类器”。两书共同指向一个趋势——分类器本身的强弱不再是瓶颈,验证策略的高效与否才是。
6.4.5d 过滤器的污染放大效应:质量过滤与去污染必须联合设计
上一节的结论是”没有最好的分类器,只有最匹配你验证策略的分类器”。这里要补一条它没覆盖的失效模式——分类器不只会判断不准,它还会主动恶化语料的另一个属性:污染程度。
证据:Gaperon 的第三项贡献给出实证——被广泛使用的神经质量过滤器,尤其是那些被训练成偏好教学性/指令性内容的过滤器,会放大预训练语料中的基准污染;具体到被测对象,DCLM 分类器系统性地把 benchmark 样本排进样本的前 5 百分位(ACL Anthology 摘要原文:“with the DCLM classifier systematically ranking benchmark samples in the top-5 percentiles of samples”)。
机制不难理解,而且令人不安:benchmark 样本本身就是”教学性、结构良好、指令化”的文本,正是这类过滤器被训练出来要给高分的对象。于是形成一个正反馈——过滤器越偏好这种风格,就越系统性地把评测集的近邻往上提。质量分打得越高的数据,越可能是评测集的邻居。
这一条与 §6.6 已有的两条质疑(Apple 的”质量幻觉”、Nait Saada 的”Removing Noise, not Finding Gold”)方向不同:那两条说的是”过滤器学到的不是真质量,而是分布差异”——过滤器判断不准;Gaperon 这条说的是过滤器判断得挺准,但它在优化一个会伤害别处的目标。
三条工程含义:
- 质量过滤与去污染不是两个独立环节,必须联合设计。本书目前把质量过滤放在第 6 章、去污染放在第 7 章 §7.10,两处此前没有任何交叉引用——这个拆分本身就是问题。过滤器会把被保护对象(评测集)的近邻往上提,等于在去污染之后又把污染重新富集一遍。(第 7 章 §7.10 已补对应的反向交叉引用。)
- 评估过滤收益时,若不同时报告去污染后的分数,看到的提升里可能混有污染贡献。一个”过滤后 benchmark 涨了 3 个点”的结论,在没有去污染对照的前提下无法区分这 3 个点里有多少是真实质量收益。
- “用 benchmark 增益选数据”这条方法论需要加一个前置条件:先去污染,再验证。这直接约束 §6.4.3 的高效注入验证与 §6.8.4 的结论。
⚠️ 待核实:本轮取的是 ACL Anthology 摘要与预印本摘要(两处一致,且为正式录用论文),论文正文 §7.2.2「Impact of Quality Filters on Contamination」的实验设置与百分位分布未逐条读。写入具体实验细节前请回正文。
6.4.6 损失驱动的目标导向选择:CoLoR-Filter
除了基于分类器和困惑度的方法,CoLoR-Filter(Brandfonbrener et al., NeurIPS 2024, arXiv:2406.10670)提出了一种从损失角度直接建模数据价值的方法。其核心思想是:对于目标领域(如 Books),用 auxiliary 模型评估每条数据在该领域的损失,相对损失最低的数据(即 auxiliary 模型最容易预测的)往往是最有价值的目标域数据。实验表明,150M 模型选择的 1B token 数据可以让 1.2B 目标模型达到随机选择 25B token 的效果(Books),下游多选问答任务上数据量减少 11 倍。
这提供了一类新的筛选范式:“损失驱动”而非”特征驱动”——不是判断数据”看起来好不好”,而是判断数据”对目标模型的贡献有多大”。
6.4.7 多语言质量分类器:跨语言迁移与 FineWeb-2
对于非英文语言(如中文),高质量本地数据量可能不足以训练可靠的本地质量分类器。两条路径:
跨语言质量分类器(Turki et al., 2026, arXiv:2604.20549):
- 使用多语言 fastText 模型:用多语言预训练模型统一建模多语言质量信号
- 用 LLM 评估文本的教育/信息价值(与 FineWeb-Edu 方法类似),适用于任意语言
- 基于翻译增强的种子数据构建:将英文高质量数据的质量标签通过翻译迁移到目标语言
FineWeb-2:统一多语言过滤框架(EPFL, NeurIPS 2025, arXiv:2502.10361)是目前最系统性的多语言数据过滤工作:
FineWeb-2 多语言过滤管线:
1. FastText 初始过滤
└─ 语言特定规则 → 去除明显低质量内容
2. XLM-RoBERTa 嵌入分类器(核心创新)
└─ 用 XLM-RoBERTa 编码文本 → 嵌入空间中定义质量边界
└─ 质量标准:"结构化 + 知识丰富"
└─ 跨语言共享分类器参数 → 利用高资源语言的知识辅助低资源语言
3. 统一评估
└─ 各语言独立评估 + 跨语言一致性检查
关键结果:仅用 15% 的 token 即可匹配 FineWeb-2 基线的 MMLU 分数。这意味着 85% 的多语言 Web 数据对模型训练的贡献为零或为负——在多语言场景下,质量过滤的收益比英文场景更加显著。
需要注意的局限:EPFL 团队在后续研究(arXiv:2604.20549, ICLR 2026 DATA-FM Workshop)中发现,仅靠扩大语言池规模并不能自动保证低资源语言的过滤稳定性。大规模的跨语言迁移可能带来 rank instability——不同规模的池化产生的质量排序显著不同。推荐的补救手段包括:(1) 第三四分位数采样(Q3)——取质量分数前 25% 而非简单阈值切分;(2) retention rate 逐语言调优。这对中文团队尤为重要——中文的过滤稳定性不能仅依赖英文大规模池化的迁移信号。
6.4.8 另一种视角:领域构建 vs 质量筛选(WebOrganizer)
前面讨论的方法都假设”质量”是一个客观可测量的属性。但 WebOrganizer(Allen AI / Princeton, “Organize the Web”, 2024)提出了一个重要反例:在某些场景下,“质量”本质上是”领域对齐”。
核心实验:FineWeb-Edu 通过教育价值筛选带来的 MMLU 增益,其中 高达 84% 可以通过简单的 Topic × Format 领域混合(domain mixing)复现——即不删除任何”低质量”数据,而是调整训练数据中不同主题(Topic,如科学/人文/编程)和格式(Format,如文章/问答/代码)的比例。
这意味着:
- 如果你训练的是通用模型,质量过滤确实能提升平均性能
- 如果你训练的是专项模型(如教育场景),与其花精力做质量二值分类,不如用领域混合做数据配比优化
- 过度依赖”教育价值”打分可能导致领域偏置——所有数据都像教科书,但现实中用户需要处理口语、方言、网络用语
实践建议:在数据工程早期,先做领域分析(你的目标场景需要哪些 Topic×Format),再做质量过滤。避免”先过滤,后才发现关键领域被误删”。
6.5 困惑度过滤:KenLM 与双边争议
困惑度(Perplexity, PPL)是比分类器更”古典”的质量信号——它用一个已训练好的语言模型计算每段文本的困惑度。PPL 反映的是文本在该模型看来的”意外程度”——PPL 越低,模型认为文本越”正常”。
6.5.1 KenLM 方案
KenLM 是一个高效的 n-gram 语言模型工具,可以快速计算 PPL,且不需要 GPU。
# 训练一个 KenLM 模型(用维基百科作为参考语料)
lmplz -o 5 -S 80% -T /tmp < wiki_zh.txt > wiki_zh.arpa
build_binary wiki_zh.arpa wiki_zh.binary
import kenlm
model = kenlm.Model('wiki_zh.binary')
def compute_ppl(text: str) -> float:
"""计算文本的困惑度"""
# KenLM 需要分词后的文本
words = list(text) # 中文按字分
log_prob = model.score(' '.join(words))
ppl = 10 ** (-log_prob / max(len(words), 1))
return ppl
# 使用
ppl = compute_ppl("大语言模型的训练数据质量非常重要。")
print(f"PPL: {ppl:.2f}") # 正常中文文本的 PPL 通常在 50-500 之间
ppl = compute_ppl("asdfgh 乱码内容 #$%^")
print(f"PPL: {ppl:.2f}") # 乱码的 PPL 通常在 5000+ 以上
优点:
- 速度极快——比分类器还快
- 不需要标注数据——只需要一个参考语料
缺点:
- 基于 n-gram,对语义理解有限
- 严重依赖参考语料的选择——用维基百科训练的 KenLM 会给非百科体裁打高 PPL
6.5.2 用参考模型做 PPL 过滤(PyTorch 路线)
aide 给出的另一路线是用一个小巧的因果语言模型(如 gpt2-medium)直接计算 loss:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
class PPLFilter:
def __init__(self, model_name='gpt2-medium'):
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map='auto'
)
self.model.eval()
def compute_ppl(self, text, max_length=1024):
"""计算文本的Perplexity"""
encodings = self.tokenizer(
text, return_tensors='pt',
max_length=max_length, truncation=True
)
input_ids = encodings.input_ids.to(self.model.device)
with torch.no_grad():
outputs = self.model(input_ids, labels=input_ids)
loss = outputs.loss.item()
return loss # PPL = exp(loss)
# PPL > 阈值的过滤掉
if text_ppl > 5.5: # 阈值需根据数据集调整
reject()
PPL 阈值的踩坑(两书共识):
- PPL 阈值不能太低:会把专业领域文本(数学、医学)当成低质
- PPL 阈值不能太高:会让乱码文本通过
- 不同语种需要不同阈值:中文 PPL 普遍比英文高 1-2
推荐做法:用 PPL 分布的百分位数而非绝对值:
# 推荐做法:用百分位
def ppl_threshold_filter(ppl, all_ppls):
"""
根据PPL分布的百分位数过滤
推荐去掉top 20%最高PPL的文本
"""
p80 = np.percentile(all_ppls, 80)
p95 = np.percentile(all_ppls, 95)
if ppl > p95: # 极端值一定拒绝
return False
if ppl > p80 and random() < 0.5: # 高PPL按50%概率丢弃
return False
return True
6.5.3 PPL 过滤的反直觉发现
发现 1:太低的 PPL 不意味着”高质量”
PPL 极低(< 30)的文本往往是:
- 重复内容(如”是的是的是的是的…”)
- 模板化内容(如法律声明、标准条款)
- 模型训练数据的”记忆”(如果参考语料中有这段内容)
这些文本”符合模型预期”,但对训练几乎没有价值。
发现 2:中等 PPL 的文本可能最有价值
PPL 在 100-500 之间的文本,通常是正常但有一定信息量的内容。太低说明模型已经”知道”了(没有新信息),太高说明内容可能有问题。
建议的 PPL 过滤策略(双边过滤):
过滤 保留 过滤
←──────────→ ←─────────────────→ ←──────────→
PPL: 0 30 50 500 1000 ∞
|_____|_____|___________________|_____|_______|
重复/模板 最佳区间 可疑 乱码/错误
也就是说:PPL 过滤应该是双边的——既过滤极高 PPL 也过滤极低 PPL。
6.5.4 PPL 的范式升级:从过滤到选择
上面讨论的 PPL 过滤本质上是”去坏”——设定阈值,过滤掉 PPL 异常的数据。但 PPL 的潜力不止于此。一项关于困惑度感知数据 Scaling Law 的研究提出了一种更积极的用法:将 PPL 从过滤工具升级为选择工具。
核心思想:PPL 不仅反映数据质量,还反映数据对当前模型的信息价值。
- 低 PPL 数据:模型已经”知道”的内容,训练价值有限(冗余信号)
- 中高 PPL 数据:模型尚未充分掌握的知识,蕴含”知识鸿沟”——这可能恰恰是最有训练价值的内容
- 极高 PPL 数据:超出模型理解范围的噪声(乱码、外语等)
通过构建困惑度景观(PPL Landscape)——对候选数据集中每条数据计算基础模型的 PPL,形成一张”信息价值地图”——可以实现自适应数据子集选择:优先选择能最大化知识吸收、最小化冗余的内容。
适用场景:这一方法在持续预训练(Continual Pre-training)和领域适应中尤其有效,因为在这些场景下,“模型已知”和”模型未知”之间的界线更清晰。对于从零开始的预训练,PPL 选择的效果取决于用什么模型来计算 PPL——如果用一个已有的通用模型作为参照,仍然可以从中获得”信息增量”的信号。
6.5.5 参考语料选择:PPL 过滤最隐蔽的坑
PPL 过滤严重依赖”用什么参考语料训出的语言模型”。同一段文本,用维基百科 KenLM 算和用代码语料 KenLM 算,PPL 可能差几个数量级。这正是 PPL 最容易被误用的地方:
- 用维基百科参考模型过滤代码数据:代码文本的 PPL 会异常高,被当成”乱码”误杀——但代码是高质量训练数据
- 用新闻参考模型过滤口语 / 论坛数据:口语的句法分布与新闻差异大,PPL 偏高被误杀——但论坛数据对对话能力有价值
- 跨语种混用:用中文参考模型算英文文本,PPL 同样失真
工程准则:
- 参考语料应与被过滤数据的预期分布一致:代码数据用代码语料训 KenLM,中文用中文维基/百科,英文用英文
- 双语/多语场景要按语种分别算 PPL:即先 §6.2 做语种识别,再分流到对应语种的 PPL 模型
- 阈值必须用百分位而非绝对值:不同参考模型给的绝对 PPL 尺度不同,但”在自身分布内的百分位位置”可比(见 §6.5.2 的
p80/p95做法) - PPL 仅作辅助信号:单独用 PPL 过滤误差大,建议与分类器分数联合(如 §6.7.1 的
fluency维度),不作为唯一裁决
6.5.6 代码与数学数据的质量过滤特例
代码与数学文本是质量过滤里的”特殊公民”——它们既不是自然语言,又有极高的训练价值。两书都提示需单独处理:
- 符号/字母占比放宽:数学公式、代码标识符天然拉高
symbol_ratio,§6.3.4 已给0.20/0.50的放宽值 - PPL 参考模型换成代码 LM:如用 StarCoder / Code Llama 的 PPL 评估代码数据,而非通用 KenLM
- 结构而非流畅性:代码质量的代理是”能否编译 / 是否含可运行片段 / 是否来自高星仓库”,而非语言流畅性
- 去重更关键:代码数据高度重复(相同 snippet 遍布 GitHub),模糊去重(§6.9.1)对代码尤其重要,否则模型会过记忆热门片段
PPL 与 CPQS 的对照:6.4.6 的 CPQS-Tuning 用目标模型的 per-sample loss 作质量信号,本节 PPL 用参考模型评估。前者更精确捕捉”数据-模型匹配度”,后者更轻量。二者形成”损失驱动”与”困惑度景观”的互补。
6.6 CQF 范式的根本困境:质量幻觉
上面介绍的所有分类器方法——从最早的维基百科分类器到最新的 DCLM、FineWeb-Edu、Ultra-FineWeb——都属于基于分类器的质量过滤(Classifier-based Quality Filtering, CQF)范式。2026 年初,Apple 联合牛津大学发表的 “The Data-Quality Illusion” 对这整个范式提出了根本性挑战。这是 de 第 5 章最具理论重量的部分,本章将其单列一节。
核心发现:CQF 的有效性可能不是我们以为的那个原因。
传统理解:分类器学会了区分”高质量”和”低质量”数据 → 过滤后保留的是”真正高质量”的数据 → 模型训练效果更好。
Apple 的发现揭示了一个悖论:
-
分类器学到的是”分布差异”而非”语义质量”。当我们用维基百科作为正例、Common Crawl 作为负例训练分类器时,分类器学到的是”像维基百科”vs”像 Common Crawl”的分布差异,而非通用的”质量”概念。
-
CQF 在过滤预训练数据的同时,也隐式地过滤了高质量数据集本身。如果你把分类器反过来应用于正例数据(如维基百科),会发现相当一部分正例也会被判为”低质量”——这说明分类器并没有真正理解”质量”。
-
下游性能提升不代表模型在高质量数据上的语言建模能力提升。经过 CQF 过滤后训练的模型,在特定 benchmark 上表现更好,但在高质量数据集上的困惑度并没有相应降低。
风格 vs 质量的混淆——Wikipedia 格式化逆转实验:Klimaszewski & Andruszkiewicz(arXiv:2605.23721, 2026-05)提供了一个更直观的定量证据。他们在 FineWeb-Edu 的 CQF 分类器上做了一个简单实验:仅将文档重新格式化为 Wikipedia 风格——添加 infobox 标记、章节标题、引用格式等 Wikipedia 标记——就能逆转分类器对约 7% 文档的过滤决策。原本被判定为”低质量”的内容,穿上 Wikipedia 的”外衣”后即可通过质量阈值。这说明 CQF 分类器学到的是”看起来像 Wikipedia”而非”具有教育价值”。
这个实验的价值在于它可复现、可理解——任何读者都能立即想象一个 SEO 垃圾页面用 Wikipedia 格式包装后”洗白”进入训练集的场景。需强调:这并非 FineWeb-Edu 的固有缺陷,而是整个 CQF 范式的系统性局限——任何以特定参考集为”高质量标准”的分类器都面临同样的”风格污染”风险。
6.6.1 Nait Saada 等人的根本性质疑:Removing Noise, not Finding Gold
2026 年 6 月,Nait Saada、Bethune、Klein、Grangier、Cuturi、Ablin(Apple + 巴黎萨克雷大学,arXiv:2510.00866v3)发表 “Removing Noise, not Finding Gold” 工作,从更深的层次挑战了 CQF 范式:
- 核心实验:他们训练”通过随机 token 排列获得质量递增的合成数据”的模型,与 CQF 过滤后的模型对比,观察语言建模能力随质量分数的变化
- 反直觉结论:CQF 虽然能提升下游任务表现,但不一定能提升高质量数据上的语言建模能力。当 perplexity 在真正的高质量 holdout 集上测量时,CQF 模型的”质量优势”部分消失
- 机制解释:CQF 会隐式地把”高质量参考集”中的部分样本也过滤掉——质量分数是相对于训练集分布的,而非绝对的”高质量”度量
这一发现与 Apple 牛津工作形成证据链:
- Apple 工作:CQF 捕获的是”分布差异”而非”语义质量”
- Nait Saada 工作:CQF 隐式过滤了高质量参考集本身,CQF 模型的”质量提升”在高质量 holdout 上部分消失
对实践的影响:
- FineWeb-Edu、CPQS-Tuning、Ultra-FineWeb 等基于 CQF 的工作仍然是有效的——它们训练出的模型在下游任务上确实更好
- 但 CQF 提升的可能是”下游任务适配度”而非”对高质量内容的建模能力”
- 在评估”质量过滤收益”时,建议同时报告下游 benchmark 和高质量 holdout 的 PPL 变化
关于隐式过滤风险(2026 新发现):Nait Saada 等人的研究在机制层面解释——CQF 训练的分类器会隐式过滤掉参考集中约 5-15% 的样本(具体数字因数据集而异)。这意味着过滤后的数据并不等同于”真正的高质量数据”加上”被过滤掉的部分”——分类器在参考集上同样会产生假阴性。ScalingFilter(Li et al., 2024)通过两个 LLM 在同数据上的困惑度差异作为质量信号,是一次不依赖参考集的尝试——但目前看仍属”度量相似度”的范畴,未能完全跳出”相对性”陷阱。
这对实践意味着什么? CQF 仍然是有效的——经过过滤的数据训练出的模型确实表现更好。但我们需要认识到,CQF 的有效性可能源自”选择了特定分布的数据子集”,而不是”选择了真正高质量的数据”。这意味着:
- 不要过度依赖单一分类器。不同分类器捕捉不同的分布特征,多分类器组合可以缓解单一偏差
- 误杀恢复更加重要。既然分类器不是在判断”质量”,那么被过滤掉的数据中存在大量有价值内容就不奇怪了
- 关注数据的多样性。CQF 本质上是在压缩数据分布,过度过滤会损害多样性
6.7 多维度质量评分与联合优化
实际生产中,最好不要依赖单一的质量指标,而是构建一个多维度的质量评分体系。这一节融合 de 的综合评分框架与 aide 的 QuRating 多维打分思想。
6.7.1 综合评分框架
class QualityScorer:
def __init__(self):
self.ppl_model = load_kenlm_model()
self.edu_classifier = load_edu_classifier()
self.safety_classifier = load_safety_classifier()
def score(self, text: str) -> dict:
"""多维度质量评分"""
# 维度1: 语言流畅性 (基于PPL)
ppl = self.compute_ppl(text)
fluency_score = self.ppl_to_score(ppl) # PPL越低分越高,但极低也扣分
# 维度2: 教育价值
edu_score = self.edu_classifier.predict(text)
# 维度3: 信息密度 (简单启发式)
density_score = self.compute_info_density(text)
# 维度4: 安全性
safety_score = self.safety_classifier.predict(text)
# 综合分数 (加权平均)
weights = {
'fluency': 0.2,
'education': 0.35,
'density': 0.25,
'safety': 0.2,
}
overall = (
weights['fluency'] * fluency_score +
weights['education'] * edu_score +
weights['density'] * density_score +
weights['safety'] * safety_score
)
return {
'fluency': fluency_score,
'education': edu_score,
'density': density_score,
'safety': safety_score,
'overall': overall,
}
def compute_info_density(self, text: str) -> float:
"""启发式信息密度评估"""
chars = list(text)
unique_ratio = len(set(chars)) / max(len(chars), 1)
knowledge_markers = len(re.findall(r'[\d]|[a-zA-Z]{3,}', text))
knowledge_density = knowledge_markers / max(len(text), 1)
paragraphs = [p for p in text.split('\n') if p.strip()]
structure_score = min(len(paragraphs) / 10, 1.0)
return (unique_ratio * 0.4 + knowledge_density * 0.3 +
structure_score * 0.3)
安全维度的工业实践:上面代码中的 safety_classifier 看起来只是一行调用,但实际生产中安全过滤的设计远比这复杂。Anthropic 2025 年发表的预训练安全过滤研究提供了一个完整的工业案例:
- 分类方法对比:他们实验了 6 种分类方法——包括微调语言模型、宪法分类器(Constitutional Classifier)、FastText、字符串匹配等。最终选择了微调宪法分类器作为精度与成本的最佳平衡方案
- 效果验证:在中等规模模型上,安全过滤将有害能力评估得分降低了 33%,同时标准基准测试性能无显著下降
- 反直觉发现:单一高质量分类器优于多分类器组合策略。多个分类器组合虽然覆盖面更广,但引入了更多的假阳性(把正常内容误判为有害),反而降低了整体效果
这个发现与 CQF 质量幻觉的研究(§6.6)形成了有趣的对比——在安全过滤领域,“少即是多”可能是更好的策略。
6.7.2 QuRating 多维打分
QuRating(Pratap et al., 2024)是另一种思路——不是过滤,而是用多维评分做加权采样:
# QuRating 评估的维度
QUALITY_DIMENSIONS = {
'educational_value': '该文本的教育/学习价值',
'factuality': '事实准确性',
'reasoning_required': '是否需要推理才能理解',
'language_style': '语言风格(正式/口语化)',
'audience': '目标读者(专业/大众)',
'information_density': '信息密度',
'originality': '原创性(避免模板化)'
}
# 每个维度用一个分类器打分(1-5)
# 然后按任务需要做加权采样
# 例如训练数学模型:
# educational_value: 高权重
# factuality: 高权重
# language_style: 偏正式
多维打分的优势:
- 可解释:知道每个数据样本为什么被选中
- 可调度:不同下游任务用不同加权
- 可对齐:与后训练需求对齐(如教育价值高的样本用于教育场景)
6.7.3 质量评估的人工抽样方法论
模型打分归模型打分,人工抽样评估是不可替代的验证手段。
抽样策略:
1. 从质量分数的不同区间各抽取样本:
- 高分区 (>0.8): 100 条 → 验证"好数据确实好"
- 中分区 (0.5-0.8): 200 条 → 这是最关键的区间
- 低分区 (<0.5): 100 条 → 验证"差数据确实差"
- 边界区 (0.45-0.55): 100 条 → 检查过滤阈值的合理性
2. 每条样本由 2-3 人独立评估
- 使用标准化评估表(1-5分,多维度)
- 计算标注一致性(Cohen's Kappa ≥ 0.6 为可接受)
3. 分析结果
- 分类器在每个区间的准确率
- 假阳性分析(被过滤掉的好数据 → 损失)
- 假阴性分析(通过过滤的差数据 → 风险)
Cohen’s Kappa 的解读:
| Kappa 值 | 一致性水平 | 建议 |
|---|---|---|
| < 0.2 | 几乎没有一致性 | 标注指南需要重写 |
| 0.2-0.4 | 一般 | 需要改善标注指南 |
| 0.4-0.6 | 中等 | 可以接受但需要改善 |
| 0.6-0.8 | 较好 | 标注质量合格 |
| > 0.8 | 很好 | 标注质量优秀 |
6.7.4 从独立评分到联合优化:QuaDMix 范式
上面的评分框架将质量评估分解为多个独立维度,然后用加权平均得到综合分数。但这种”先打分、再过滤”的两阶段方法有一个隐含假设:质量和多样性可以分开处理。
字节跳动提出的 QuaDMix 框架挑战了这一假设:单纯最大化质量分数会牺牲多样性,而质量和多样性需要在同一框架下联合优化。
QuaDMix 的核心设计:
- 参数化采样函数:不是用固定阈值过滤,而是为每条数据计算一个采样概率,该概率同时考虑质量标签和多样性标签
- 多维度质量评估 + 领域分类:用多个质量评估器打分,同时对数据进行领域分类以度量多样性
- 高效参数搜索:用 LightGBM 在小模型上快速搜索最优的质量-多样性平衡参数,然后将策略迁移到大模型
实验结果:联合优化策略在下游任务上平均提升 7.2%,显著优于先过滤后混合的两阶段策略。
实践启示:从”评分 → 阈值过滤”到”评分 → 概率采样”的转变意味着,质量过滤的输出不一定是”保留/丢弃”的二元决策,也可以是一个采样权重,让高质量数据有更高的采样概率,但不完全排除中等质量数据——这天然地在质量和多样性之间取得平衡。QuaDMix 的概率采样策略相比硬阈值,在缓解”隐式过滤参考集”问题(§6.6)上更具鲁棒性。
6.8 质量感知 Scaling Law 与数据墙
6.8.1 数据量 vs 质量的 Trade-off
质量过滤本质上是一个数据量和数据质量的权衡:过滤越严格,保留的数据质量越高,但量越少。这个权衡不是线性的,它遵循一个类似 S 曲线的规律:
模型性能
↑
│ ╭─────── 质量上限
│ ╱
│ ╱
│ ╱
│ ╱
│ ╱
│ ╱
│╱
├────────────────→ 过滤严格程度
│
│ ← 此区间过滤收益大 → ← 过度过滤,收益递减甚至为负 →
关键拐点:存在一个”甜蜜点”(sweet spot),在这个点上过滤带来的质量提升恰好平衡了数据量减少带来的损失。超过这个点,继续加严过滤会导致模型性能反而下降。
6.8.2 质量感知 Scaling Law:从定性直觉到数学框架
2026 年,ICLR 2026 上的一项研究首次将数据质量作为显式变量纳入了 Scaling Law 框架。
经典的 Chinchilla Scaling Law 描述的是 Loss = f(N, D),即模型损失是模型规模 N 和数据量 D 的函数。质量感知 Scaling Law 将其扩展为:
Loss = f(N, D, Q)
其中 Q 是一个无量纲的数据质量参数。论文提出了两种实用的 Q 估算方法:
| 方法 | 思路 | 适用场景 |
|---|---|---|
| 损坏率代理 | 向数据中注入已知比例的噪声,观察损失变化 | 受控实验、数据管线评估 |
| 缺陷度量 | 用一组质量指标的加权组合估算 Q | 大规模生产环境 |
关键发现:
- 有效数据随质量下降呈次线性衰减。这意味着少量低质量数据的影响并不严重——模型不会因为 5% 的噪声而崩溃
- 模型对中等程度的数据损坏具有鲁棒性。这为”适度放宽过滤标准以保留更多数据”提供了理论支撑
- 质量投资的边际效益可量化。通过
f(N, D, Q),可以计算将数据质量从 Q₁ 提升到 Q₂ 带来的损失降低,并与获取更多数据或增加模型规模进行成本比较
发现(补充):合成数据的过度重复会引发性能回退。InfoLaw(arXiv: 2605.02364, 2026)提出质量加权数据混合 + 重复次数的联合 Scaling Law:在数据受限的过度训练场景下,增加高质量数据的上采样权重会加剧重复,反而降低性能。最优配比不仅取决于质量分数,还取决于是否处于过度训练区间。
6.8.3 “数据墙”(Data Wall)的概念
随着模型规模和训练 token 数的持续增长,一个越来越严峻的问题是:高质量数据可能不够用了。
- 互联网上的高质量英文文本总量约在 10-15T token
- 中文的高质量文本可能只有 2-3T token
- 经过严格质量过滤后,可用的高质量数据可能更少
应对数据墙的策略:
- 放宽质量过滤标准——接受”中等质量”的数据,扩大数据量
- 合成数据混入——Meta 团队训练了超 1000 个 LLM(超 10 万 GPU 小时),发现了明确的实践指导:
- 最佳配比:约 1/3 改写型合成数据 + 2/3 自然数据,可将训练效率提高 5-10 倍
- 类型很重要:改写型合成数据(对自然文本进行改写润色)是安全的,但纯生成型(如纯教科书风格数据)在小数据预算下会导致多领域验证损失升高,甚至引发模型崩溃
- 生成器规模:8B 参数的生成器是性价比最优选择
- 多次 epoch——在高质量数据上多次训练(但有记忆化风险)
- 数据回收——重新审视被过滤掉的数据,恢复被误杀的内容
- 高效验证策略——通过降低数据质量探索的成本(如 Ultra-FineWeb 的高效注入验证,加速比 50-100x),使得在有限数据预算下更高效地搜索最优数据配方成为可能
6.8.4 FineWeb 团队的三代过滤范式
FineWeb 团队在大量消融实验后,总结了质量过滤范式的演变:
第一代:纯启发式规则
- 代表:C4、OSCAR
- 方法:长度阈值、字符比例、坏词列表等规则
- 优点:简单、快速、可解释;缺点:粗糙,大量误杀和遗漏
第二代:模型打分
- 代表:DCLM、FineWeb-Edu
- 方法:用分类器或 LLM 给文本打质量分,按分数过滤
- 优点:比规则更精准;缺点:依赖打分模型的偏差,可能导致数据多样性下降
第三代:混合式 + 被误过滤数据的恢复
- 代表:FineWeb(最终版)
- 方法:宽松的启发式规则做初筛 → 质量分类器做精筛 → 关键创新:从被过滤掉的数据中恢复误杀的好数据
- 优点:在质量和多样性之间取得更好的平衡
Ultra-FineWeb:新一代数据集标杆(OpenBMB/清华/ModelBest, 2025):
| 维度 | FineWeb-Edu | Ultra-FineWeb |
|---|---|---|
| 分类器 | LLM 教育价值分类器(需 GPU) | fastText 轻量分类器(CPU 可运行) |
| 验证方法 | 完整训练验证 | 高效注入验证(50-100x 加速) |
| 种子选择 | 人工标注 | 验证驱动的自动优化 |
| 多语言 | 仅英文 | 英文 + 中文 |
| 实际应用 | 开源基准 | MiniCPM4 核心预训练数据 |
实践意义:Ultra-FineWeb 的 fastText 分类器选择特别值得注意——它证明了在高效验证策略的指导下,轻量分类器可以达到与 LLM 分类器相当的效果。这对于计算资源有限的团队是一个重要信号:关键不在于分类器有多强,而在于验证策略有多高效。
6.9 过滤管线的组合与评估
6.9.1 将质量过滤融入完整管线
质量过滤不是孤立的步骤。它需要与清洗、去重协调工作。
推荐的管线顺序:
原始数据
│
▼
[清洗] 文本提取 + 基础清洗 + 语言过滤(语种识别)
│
▼
[去重] 精确去重 + 模糊去重
│
▼
[质量过滤]
│ ├─ 启发式初筛(去掉明显垃圾)
│ ├─ 模型精筛(质量分类器 / PPL 过滤)
│ └─ 误杀恢复(从被过滤的数据中恢复好数据)
│
▼
高质量训练数据
为什么质量过滤在去重之后?
- 去重后数据量显著减少(通常减 20-40%),质量过滤的计算量也相应减少
- 去重后每条文档的”代表性”更强——一条文档不再代表多个重复副本
- 避免”去重保留了低质量副本而丢弃了高质量副本”的问题
6.9.2 两阶段质量过滤策略(工业级)
工业级生产环境的推荐策略——先粗筛后精筛,兼顾速度与准确:
全量数据 → fastText 粗筛(保留60%)
→ FineWeb-Edu 精筛(保留20%)
→ 进入下游流水线
def two_stage_quality_filter(text):
"""两阶段质量过滤"""
# Stage 1: fastText 粗筛(速度优先)
dclm_score = dclm_quality_score(text)
if dclm_score < 0.3:
return False, 'rejected_by_dclm'
# Stage 2: FineWeb-Edu 精筛(准确优先)
if dclm_score > 0.6:
return True, 'high_quality_pass'
# 中间分数用 FineWeb-Edu 二次确认
edu_score = fineweb_edu_classify(text)
if edu_score >= 3:
return True, 'pass_with_edu_check'
return False, 'rejected_by_edu'
6.9.3 消融测试:确认每个步骤的贡献
方法:逐步关闭管线中的各个步骤,观察最终模型性能的变化。
实验设计示例:
基准管线(完整): 模型分数 = 65.2
去掉启发式过滤: 模型分数 = 63.7 → 贡献 +1.5
去掉质量分类器: 模型分数 = 62.1 → 贡献 +3.1
去掉PPL过滤: 模型分数 = 64.8 → 贡献 +0.4
去掉误杀恢复: 模型分数 = 64.5 → 贡献 +0.7
全部去掉(只做基础清洗): 模型分数 = 60.3 → 质量过滤总贡献 +4.9
分析:
- 质量分类器贡献最大 (+3.1)
- 启发式过滤仍有价值 (+1.5)
- PPL 过滤在分类器之上的增量较小 (+0.4) → 可以考虑简化
- 误杀恢复是值得做的 (+0.7)
⚠️ 这张表默认了一个前提,而该前提在 2026 年被证伪(2026-10-08 补):它默认”分数差 = 该步骤的真实贡献”。在 §6.4.5d 的证据下这个默认不成立——如果基准管线没有做过去污染,那么”去掉质量分类器”那一行掉下来的 3.1 分里,有一部分其实不是”少了质量过滤”造成的,而是”少保留了一批 benchmark 近邻样本”造成的。这两者在消融表里长得一模一样。
正确的消融设计应增加一组对照:把同一套消融在去污染后的语料上再跑一遍,对比两组各步骤的贡献值。差异大的步骤,说明其收益与污染纠缠在一起——这类步骤的”贡献”在换了评测集之后可能大幅缩水。这条与 §6.4.5d 是一体两面,请一起落地。(本条属由 §6.4.5d 的证据推出的方法论推论,未见论文原文直接表述,但可直接检验。)
6.10 反直觉发现合集
本章结束前,总结几个在质量过滤领域被反复验证的反直觉发现(合并两书):
发现 1:训练 token 减半,模型更好
用质量过滤器过滤掉 40-60% 的数据后,模型在 benchmark 上的表现反而优于用全量数据训练的版本。不是所有 token 都平等——低质量的 token 不仅没有贡献,还会拖累模型。
发现 2:过滤器自身的偏差会被放大
如果你的质量分类器是用维基百科作为正例训练的,它会倾向于保留”像维基百科”的文本。然后用这些数据训练出的模型会产出”百科全书腔”的回答。分类器的偏差 → 训练数据的偏差 → 模型的偏差,形成放大效应。
发现 3:被过滤掉的数据中有宝藏
FineWeb 团队在检查被过滤掉的数据时发现,其中约 10-20% 其实是有价值的。这些数据被过滤是因为它们的”形式”不符合分类器的预期(如论坛帖子的格式、口语化的表达),但”内容”是有信息量的。
发现 4:PPL 太低比太高更危险
高 PPL 的文本你一眼就能看出问题(乱码、外语)。但低 PPL 的文本看起来”正常”,可能逃过人工审查。然而这些文本可能是高度模板化、重复度高的内容,对模型的训练贡献为负。
发现 5:最优过滤策略随模型规模变化
小模型(如 400M)上的最优过滤策略,扩展到大模型(如 70B)上可能不是最优的。大模型对噪声的容忍度更高,但也更容易记忆重复内容。因此,proxy 实验的结果不能盲目外推。
发现 6:CQF 范式的根本性质疑(Removing Noise, not Finding Gold)
CQF 虽然能提升下游任务表现,但不一定能提升高质量数据上的语言建模能力;CQF 会隐式地把”高质量参考集”中的部分样本也过滤掉。详见 §6.6。
发现 7:MIRA 框架——把 CQF 思想推广到中训练
2026 年 5 月,北航/上海交大/UBC/IQuest/澜舟科技(Wang et al.)发表 MIRA(Mid-training Rubric Anchoring for Source-Aware Data Selection) 框架,把”质量分类器 + 多维评分”思想推广到中训练数据筛选:
- 核心创新是”自锚定 rubric 发现”——自动发现每个数据源组应该评估的特定维度,再蒸馏到可扩展的学生评分器
- 在 21 个数据源 / 5 个源组的代码训练上,使用半量 token 即可匹配全量训练效果
- 对 CQF 范式的”全球统一标准”局限是直接回应——评分标准本身随数据源变化而调整
MIRA 的”自锚定”思路与本章 CQF 讨论的”相对性陷阱”形成方法学对照:MIRA 承认”高质量是数据源相关的”,并在机制层面解决了这个问题。
6.11 工业级质量过滤流水线实例
下面把本章各层方法串成一个完整的流水线实现(基于 aide 的工业级范例,整合语种识别、规则、两阶段分类器):
class QualityFilteringPipeline:
"""工业级质量过滤流水线"""
def __init__(self):
self.langid = fasttext.load_model('lid.176.bin')
self.dclm = fasttext.load_model('dclm_quality.bin')
self.edu_classifier = pipeline(
'text-classification',
model='HuggingFaceFW/fineweb-edu-classifier'
)
def filter(self, doc):
"""主过滤函数"""
text = doc['text']
# Step 1: 语种识别
lang, conf = self._detect_lang(text)
if lang == 'unknown' or conf < 0.5:
return None
doc['lang'] = lang
# Step 2: 启发式规则(按语种选择规则集)
if lang == 'en':
passed, reason = gopher_filter(text)
elif lang == 'zh':
passed, reason = gopher_chinese(text)
else:
passed, reason = self._generic_filter(text, lang)
if not passed:
return None
# Step 3: fastText 粗筛
dclm_score = self._dclm_score(text)
if dclm_score < 0.3:
return None
# Step 4: FineWeb-Edu 精筛(仅对中间分数)
if dclm_score < 0.7:
edu_score = self._edu_score(text)
if edu_score < 2:
return None
doc['edu_score'] = edu_score
else:
doc['edu_score'] = self._edu_score(text) # 高分也打标
return doc
6.12 局限与开放问题
当前质量过滤领域尚未解决的关键开放问题清单:
| 开放问题 | 当前最优应对 | 局限 |
|---|---|---|
| ”质量”的 intrinsic 度量 | ScalingFilter、困惑度差异 | 仍是”相对性”度量 |
| CQF 的”伪影”问题 | 多分类器组合、误杀恢复 | 仍未根除 |
| 中训练场景的源特定质量 | MIRA 自锚定 rubric | 仅在代码训练验证 |
| 多语言质量度量 | 跨语言迁移(XLM-R 等) | 低资源语言仍薄弱 |
| 质量过滤与多样性权衡 | QuaDMix 联合优化 | 多样性度量本身不统一 |
6.13 动手环节:构建一个数据质量评分器并验证过滤效果
目标:实现一个多维度的文本质量评分器,并通过消融实验验证不同过滤策略的效果。
练习 1:多维度质量评分器
"""
实现本章介绍的多维度质量评分框架
不需要外部模型,用纯启发式方法实现
"""
import re
import math
from collections import Counter
from typing import List
class SimpleQualityScorer:
"""基于启发式的多维度质量评分器"""
def score(self, text: str) -> dict:
scores = {
"fluency": self._score_fluency(text),
"info_density": self._score_info_density(text),
"structure": self._score_structure(text),
"repetition": self._score_repetition(text),
}
weights = {"fluency": 0.25, "info_density": 0.35,
"structure": 0.2, "repetition": 0.2}
scores["overall"] = sum(scores[k] * weights[k] for k in weights)
return scores
def _score_fluency(self, text: str) -> float:
sentences = re.split(r'[。!?.!?]', text)
sentences = [s.strip() for s in sentences if len(s.strip()) > 5]
if not sentences:
return 0.1
lengths = [len(s) for s in sentences]
avg_len = sum(lengths) / len(lengths)
if 10 <= avg_len <= 100:
len_score = 1.0
elif avg_len < 10:
len_score = avg_len / 10
else:
len_score = max(0.3, 1 - (avg_len - 100) / 500)
punct_count = len(re.findall(r'[,。!?、;:""''()【】]', text))
punct_ratio = punct_count / max(len(text), 1)
punct_score = 1.0 if 0.03 <= punct_ratio <= 0.15 else 0.5
return (len_score * 0.6 + punct_score * 0.4)
def _score_info_density(self, text: str) -> float:
chars = list(text)
if len(chars) < 10:
return 0.1
ttr = len(set(chars)) / len(chars)
knowledge_markers = len(re.findall(r'\d+|[a-zA-Z]{3,}|[A-Z]{2,}', text))
marker_density = min(knowledge_markers / (len(text) / 100), 1.0)
return ttr * 0.6 + marker_density * 0.4
def _score_structure(self, text: str) -> float:
lines = text.split('\n')
non_empty = [l for l in lines if l.strip()]
if len(non_empty) < 2:
return 0.3
score = 0.4
if len(non_empty) >= 3: score += 0.2
if any(l.strip().startswith('#') for l in non_empty): score += 0.2
if any(re.match(r'\s*[-•·\d]+[.、)]', l) for l in non_empty): score += 0.2
return min(score, 1.0)
def _score_repetition(self, text: str) -> float:
chars = list(text)
if len(chars) < 10:
return 0.5
trigrams = [tuple(chars[i:i+3]) for i in range(len(chars)-2)]
unique_ratio = len(set(trigrams)) / max(len(trigrams), 1)
return min(unique_ratio * 1.2, 1.0)
# === 测试 ===
scorer = SimpleQualityScorer()
test_texts = {
"高质量文章": (
"大语言模型的训练数据质量是决定模型性能的关键因素。\n\n"
"## 三个关键维度\n\n"
"1. 数据的多样性:覆盖不同领域和主题\n"
"2. 数据的准确性:事实正确、逻辑自洽\n"
"3. 数据的清洁度:无噪声、无重复、无 PII\n\n"
"FineWeb 团队的研究表明,精心设计的数据管线可以让模型在 MMLU 上提升 3-5 个百分点。"
),
"SEO垃圾": (
"最好的减肥方法 快速减肥 如何减肥 减肥食谱 减肥运动 "
"最有效减肥方法 减肥秘诀 减肥方法大全 快速减肥秘诀 "
"减肥食谱推荐 减肥运动推荐 减肥方法 减肥 减肥 减肥"
),
"正常论坛": (
"我最近在用 PyTorch 训练一个 NLP 模型,遇到了一个问题。"
"训练 loss 一直在震荡,不收敛。学习率用的是 1e-3,"
"batch size 是 32。有人知道可能是什么原因吗?"
),
}
print(f"{'类型':<12} {'流畅':>6} {'信息':>6} {'结构':>6} {'重复':>6} {'综合':>6}")
print("-" * 50)
for name, text in test_texts.items():
s = scorer.score(text)
print(f"{name:<12} {s['fluency']:>6.2f} {s['info_density']:>6.2f} "
f"{s['structure']:>6.2f} {s['repetition']:>6.2f} {s['overall']:>6.2f}")
练习 2:PPL 双边过滤实验
"""
验证本章 6.5 节的核心发现:PPL 过滤应该是双边的
用字符级 n-gram 模型模拟困惑度计算
"""
def char_bigram_ppl(text: str, reference_text: str) -> float:
chars = list(reference_text)
bigram_counts = Counter()
unigram_counts = Counter()
for c in chars:
unigram_counts[c] += 1
for i in range(len(chars)-1):
bigram_counts[(chars[i], chars[i+1])] += 1
vocab_size = len(unigram_counts)
target_chars = list(text)
log_prob = 0
n_tokens = 0
for i in range(len(target_chars)-1):
bg = (target_chars[i], target_chars[i+1])
prob = (bigram_counts.get(bg, 0) + 1) / (unigram_counts.get(target_chars[i], 0) + vocab_size)
log_prob += math.log2(prob)
n_tokens += 1
return 2 ** (-log_prob / max(n_tokens, 1))
reference = ("自然语言处理是人工智能的重要分支。"
"深度学习技术推动了该领域的快速发展。"
"预训练语言模型如 BERT 和 GPT 取得了突破性进展。") * 50
texts = {
"正常文本": "机器学习是从数据中自动学习规律的方法。深度学习是机器学习的一个分支。",
"模板化文本": "是的是的是的是的是的是的是的是的是的是的是的是的是的是的。",
"乱码文本": "asdfgh jklqwe rtyuio zxcvbn 123456 !@#$%^ 乱码内容随机字符",
"高质量技术": "Transformer 的自注意力机制允许模型在 O(n²) 复杂度下捕获长距离依赖关系。",
}
print(f"{'类型':<12} {'PPL':>10} {'建议':<15}")
print("-" * 40)
for name, text in texts.items():
ppl = char_bigram_ppl(text, reference)
if ppl < 30:
advice = "⚠️ PPL 过低(可能模板化)"
elif ppl > 1000:
advice = "⚠️ PPL 过高(可能乱码)"
else:
advice = "✅ PPL 适中(保留)"
print(f"{name:<12} {ppl:>10.1f} {advice}")
练习 3:过滤阈值消融实验
设计一个实验:准备 100 条文本样本(50 条好、50 条差),用你的质量评分器在不同阈值下过滤,统计准确率、召回率和 F1:
# 伪代码框架
thresholds = [0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
for threshold in thresholds:
# 用 scorer.score(text)["overall"] > threshold 来判断
# 统计 TP, FP, FN, TN
# 计算 Precision, Recall, F1
pass
# 画出 Precision-Recall 曲线,找到你的"甜蜜点"
思考题:如果你的过滤器在”好数据”上的 recall 从 95% 提高到 99%,但 precision 从 80% 降到 60%,这个交换在万亿 token 场景下值得吗?(提示:考虑 FineWeb 的”被过滤数据中有 10-20% 的宝藏”这一发现,以及 §6.6 关于 CQF 隐式过滤参考集的讨论。)
本章要点回顾
- “高质量”没有统一定义——它取决于你的训练目标。数据质量是 (数据, 目标) 的联合函数
- 语种识别是第一道关卡——CommonLingua(最强精度,8MB)、fastText lid.176 / CLD3(轻量基线)、UniLID(零模型成本)按场景选择
- 三代过滤范式:纯规则(Gopher)→ 模型打分(FineWeb-Edu/DCLM)→ 混合式+误杀恢复
- 质量分类器的选择:fastText 快但粗,教育价值分类器精但有偏差,DCLM 方法论最严谨但成本高;Ultra-FineWeb 证明轻量分类器 + 高效验证可匹敌 LLM 分类器
- CQF 的”质量幻觉”——分类器学到的可能是分布差异而非语义质量,CQF 仍然有效但原因可能不是我们以为的那样(§6.6)
- PPL 过滤要双边——既过滤高 PPL(乱码)也过滤极低 PPL(模板化/重复内容);更进一步,PPL 可从”过滤工具”升级为”选择工具”
- 质量和多样性需要联合优化——先打分再过滤的两阶段方法可能不如联合优化(如 QuaDMix)
- 质量感知 Scaling Law——Loss = f(N, D, Q) 首次将数据质量 Q 作为显式变量纳入 Scaling Law
- 合成数据混入有明确配比——约 1/3 改写型 + 2/3 自然数据是当前最佳实践,纯生成型有模型崩溃风险
- 人工抽样不可替代——模型打分只是自动化手段,最终的质量判断需要人来做
- 高效验证策略将数据质量探索从昂贵实验变为快速迭代——关键不在分类器多强,而在于验证策略多高效
- 多语言过滤进入统一框架时代——FineWeb-2 用 15% 的 token 即可匹配基线,跨语言迁移使低资源语言也能获得可靠过滤
🛠️ 本章衍生工具
数据工程·语种识别过滤台
《AI 数据全书》第 6 章「语种识别与质量过滤」配套——CommonLingua(334语种/8MB/77.63%) vs fastText lid.218 vs GlotLID vs UniLID 七模型同台,10 个陷阱样本(拼音→印尼语/古文→日语/阿拉伯方言→MSA)演示六类误判,字符 3-gram 归因高亮可视化「无分词器 UTF-8 字节级」判据 · Gopher 八关实时体检 + 中文规则集对照(英文规则误杀 80%+ 优质中文) · FineWeb-Edu 五档分布 + NeMo 20 桶 + 两阶段漏斗(100%→60%→20%) · DSIR 重要性采样 vs 硬过滤 vs QuRating 加权
训练数据猜猜乐 3.0
合成数据时代的数据质量判断——52 样本 · 8 类别(+合成数据/多语言混合)· 限时挑战 30s · 过滤器流水线构建器 · 高分排行榜 · 成绩分享卡片——2026 全面升级
数据质量评分 3.0
Bitter Lesson 范式切换——双数据哲学对比 · Heuristic/fastText/Edu 三方评分 · 第 9 步 PII 正则脱敏(4 条正则 + 占位符替换,不含 NER/三层防御/Tier 分诊,完整版见「数据工程·PII 脱敏实验室」) · 6 步管线可视化