第 6 章 篇一:预训练数据工程

语种识别与质量过滤

从规则过滤到分类器过滤: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)比通用质量分类器更对口,因为它直接衡量”知识鸿沟”。

实践检查清单:启动质量过滤前,先回答四个问题——

  1. 我的训练目标是通用还是专项?(决定质量是”泛化”还是”对齐”)
  2. 我有多少目标领域的本地数据?(决定能否训本地分类器,还是得跨语言迁移)
  3. 我的数据预算(token 数)是否充裕?(决定能否承担严格过滤带来的量减,还是要用数据墙策略 §6.8.3)
  4. 我用什么模型来验证过滤效果?(决定能否用高效注入验证而非完整训练,见 §6.4.3)

这四个答案共同决定:你该用哪一层过滤器、阈值多严、是否要做误杀恢复。


6.2 语种识别:质量过滤的第一道关卡

在数据流水线中,语种识别(Language Identification, LangID)是质量过滤的第一道关卡,原因有三:

  1. 下游所有过滤都语种相关:英文停用词表对中文无效,中文 Gopher 规则对英文无效
  2. 配比计算需要语种标签:训练时按 zh/en/code 分别配比
  3. 多语种模型需要平衡:知道每条数据的语种才能控制配比

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 AccCommonLID Equiv AccCommonLID Macro F1模型大小架构亮点
CommonLingua2.35M33477.63%82.92%0.7888MB无分词器,UTF-8字节级因果Conv1D×3+双向Attention×1+RoPE
fastText lid.218 (NLLB)~600M21859.53%71.64%0.659~300MBn-gram哈希+线性分类
fastText lid.176~130M176~55%—~0.60126MB同上(旧版)
OpenLID v2~600M20055.77%70.19%0.639—Transformer 系,官方 CommonLID 基线之一
GlotLID v3~600M210257.69%71.26%0.673~2GBTransformer+CLS分类,2 102 标签覆盖长尾
Gherbal v4~85M214—0.836(Avg)200MB阿拉伯方言专用优化
UniLID (Qwen2.5)0 (共享分词器)——0.929(GlotLID-C)0UnigramLM分词器复用
XLM-R LID v2~278M200+—0.966(语码转换)~1.1GBtransformers 原生,跨语言编码器
CLD3~1M107——~10MBChromium 内置 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数字无语种信息先剥离数字
阿拉伯语方言 → MSAar (现代标准阿语)方言与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单核吞吐适用位置主要风险
CommonLingua8MB否(⚠️ 仅批吞吐成立,见下)高(批吞吐,内置批处理)主 LangID(默认)极低文本置信度波动;CPU 吞吐两口径冲突待核
fastText lid.176126MB否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 / 44H100 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_words5030-100太严会丢列表页
max_words100,00050,000-200,000严一点防爬虫残留
symbol_ratio0.100.08-0.15代码数据要放宽到0.20
stop_ratio0.030.02-0.05中文/多语种需独立参数
alpha_ratio0.600.50-0.70代码/数学可放宽到0.50
dup_line_ratio0.300.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

三个关键设计决策:

  1. 为什么用近完成的 LLM 而非从头训练? 近完成的 LLM 已经学会了大部分通用知识,对数据质量的敏感性更高——高质量数据能带来可观测的增量提升,而低质量数据则不会。
  2. 公正种子选择(Fair Seed Selection):基于”高质量种子数据有益于 LLM 训练”这一基本假设,自动选择正负样本,减少人工主观性。
  3. 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 都重点介绍,这里合并两书的说明。

训练方法:

  1. 从 FineWeb 中抽取大量文本样本
  2. 用 LLM(如 Llama 3 70B)给每个样本打”教育价值”分数(0-5 分)
  3. 用这些标注训练一个轻量级的分类器(如 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%边缘保留
1SEO、广告、模板~60%拒绝

效果与局限:

  • 使用教育价值 ≥ 3 的数据训练的模型,在多个 benchmark 上显著优于使用全量数据的模型
  • 特别在推理和知识密集型任务上提升明显,即使训练 token 数减少了 50%+,模型性能反而提升
  • 但过度偏好”教材式”内容——模型输出可能变得过于”说教”
  • 对话风格内容几乎全被过滤掉——但这些内容对模型对话能力很重要
  • LLM 标注一致性有限——同一段文本可能被评为 2 分也可能被评为 4 分

6.4.5 DCLM 与 fastText 分类器

DCLM(DataComp for Language Models)框架提出更系统的方法:

  1. 使用预训练的 LLM 直接对每段文本打分
  2. 分数反映”这段文本对训练有多大帮助”
  3. 通过可控实验验证不同过滤策略的效果

与 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 维基百科分类器的思路一脉相承,但更强调可复现的端到端管线:

  1. 语言识别:先用 fastText 语言模型给每段分句打语种标签,并丢弃低置信度片段
  2. 去重:基于文档级 / 行级模糊去重(为质量过滤减负,见 §6.9.1)
  3. 质量分类:用”维基百科 vs CommonCrawl”的 fastText 分类器给每段打分,按百分位阈值保留高质量段
  4. 分词 / 打包:按模型训练需要的 token 序列长度切分打包

CCNet 的关键价值不在某个单独的创新,而在于它证明了”分类器过滤”可以做成可复现、可审计、可迭代的离线流水线——每一个阈值、每一个分类器都固化下来,便于后续消融(§6.9.3)。它的局限也正是整个 CQF 范式的局限(§6.6):正例是维基百科,因此学到的”质量”本质是”像维基百科”。

6.4.5c 分类器选型决策表

面对众多分类器,下面一张表帮你按约束取舍(合并 aide 与 de 两书的工程经验):

你的约束首选方案理由注意
万亿 token、CPU 预算fastText / DCLM-fastText快、可迭代、Ultra-FineWeb 证明可匹敌 LLM需高效验证优化种子
追求最强质量、有 GPUFineWeb-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 这条说的是过滤器判断得挺准,但它在优化一个会伤害别处的目标。

三条工程含义:

  1. 质量过滤与去污染不是两个独立环节,必须联合设计。本书目前把质量过滤放在第 6 章、去污染放在第 7 章 §7.10,两处此前没有任何交叉引用——这个拆分本身就是问题。过滤器会把被保护对象(评测集)的近邻往上提,等于在去污染之后又把污染重新富集一遍。(第 7 章 §7.10 已补对应的反向交叉引用。)
  2. 评估过滤收益时,若不同时报告去污染后的分数,看到的提升里可能混有污染贡献。一个”过滤后 benchmark 涨了 3 个点”的结论,在没有去污染对照的前提下无法区分这 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):

  1. 使用多语言 fastText 模型:用多语言预训练模型统一建模多语言质量信号
  2. 用 LLM 评估文本的教育/信息价值(与 FineWeb-Edu 方法类似),适用于任意语言
  3. 基于翻译增强的种子数据构建:将英文高质量数据的质量标签通过翻译迁移到目标语言

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 同样失真

工程准则:

  1. 参考语料应与被过滤数据的预期分布一致:代码数据用代码语料训 KenLM,中文用中文维基/百科,英文用英文
  2. 双语/多语场景要按语种分别算 PPL:即先 §6.2 做语种识别,再分流到对应语种的 PPL 模型
  3. 阈值必须用百分位而非绝对值:不同参考模型给的绝对 PPL 尺度不同,但”在自身分布内的百分位位置”可比(见 §6.5.2 的 p80/p95 做法)
  4. 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 的发现揭示了一个悖论:

  1. 分类器学到的是”分布差异”而非”语义质量”。当我们用维基百科作为正例、Common Crawl 作为负例训练分类器时,分类器学到的是”像维基百科”vs”像 Common Crawl”的分布差异,而非通用的”质量”概念。

  2. CQF 在过滤预训练数据的同时,也隐式地过滤了高质量数据集本身。如果你把分类器反过来应用于正例数据(如维基百科),会发现相当一部分正例也会被判为”低质量”——这说明分类器并没有真正理解”质量”。

  3. 下游性能提升不代表模型在高质量数据上的语言建模能力提升。经过 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: 偏正式

多维打分的优势:

  1. 可解释:知道每个数据样本为什么被选中
  2. 可调度:不同下游任务用不同加权
  3. 可对齐:与后训练需求对齐(如教育价值高的样本用于教育场景)

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 的核心设计:

  1. 参数化采样函数:不是用固定阈值过滤,而是为每条数据计算一个采样概率,该概率同时考虑质量标签和多样性标签
  2. 多维度质量评估 + 领域分类:用多个质量评估器打分,同时对数据进行领域分类以度量多样性
  3. 高效参数搜索:用 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大规模生产环境

关键发现:

  1. 有效数据随质量下降呈次线性衰减。这意味着少量低质量数据的影响并不严重——模型不会因为 5% 的噪声而崩溃
  2. 模型对中等程度的数据损坏具有鲁棒性。这为”适度放宽过滤标准以保留更多数据”提供了理论支撑
  3. 质量投资的边际效益可量化。通过 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
  • 经过严格质量过滤后,可用的高质量数据可能更少

应对数据墙的策略:

  1. 放宽质量过滤标准——接受”中等质量”的数据,扩大数据量
  2. 合成数据混入——Meta 团队训练了超 1000 个 LLM(超 10 万 GPU 小时),发现了明确的实践指导:
    • 最佳配比:约 1/3 改写型合成数据 + 2/3 自然数据,可将训练效率提高 5-10 倍
    • 类型很重要:改写型合成数据(对自然文本进行改写润色)是安全的,但纯生成型(如纯教科书风格数据)在小数据预算下会导致多领域验证损失升高,甚至引发模型崩溃
    • 生成器规模:8B 参数的生成器是性价比最优选择
  3. 多次 epoch——在高质量数据上多次训练(但有记忆化风险)
  4. 数据回收——重新审视被过滤掉的数据,恢复被误杀的内容
  5. 高效验证策略——通过降低数据质量探索的成本(如 Ultra-FineWeb 的高效注入验证,加速比 50-100x),使得在有限数据预算下更高效地搜索最优数据配方成为可能

6.8.4 FineWeb 团队的三代过滤范式

FineWeb 团队在大量消融实验后,总结了质量过滤范式的演变:

第一代:纯启发式规则

  • 代表:C4、OSCAR
  • 方法:长度阈值、字符比例、坏词列表等规则
  • 优点:简单、快速、可解释;缺点:粗糙,大量误杀和遗漏

第二代:模型打分

  • 代表:DCLM、FineWeb-Edu
  • 方法:用分类器或 LLM 给文本打质量分,按分数过滤
  • 优点:比规则更精准;缺点:依赖打分模型的偏差,可能导致数据多样性下降

第三代:混合式 + 被误过滤数据的恢复

  • 代表:FineWeb(最终版)
  • 方法:宽松的启发式规则做初筛 → 质量分类器做精筛 → 关键创新:从被过滤掉的数据中恢复误杀的好数据
  • 优点:在质量和多样性之间取得更好的平衡

Ultra-FineWeb:新一代数据集标杆(OpenBMB/清华/ModelBest, 2025):

维度FineWeb-EduUltra-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 隐式过滤参考集的讨论。)


本章要点回顾

  1. “高质量”没有统一定义——它取决于你的训练目标。数据质量是 (数据, 目标) 的联合函数
  2. 语种识别是第一道关卡——CommonLingua(最强精度,8MB)、fastText lid.176 / CLD3(轻量基线)、UniLID(零模型成本)按场景选择
  3. 三代过滤范式:纯规则(Gopher)→ 模型打分(FineWeb-Edu/DCLM)→ 混合式+误杀恢复
  4. 质量分类器的选择:fastText 快但粗,教育价值分类器精但有偏差,DCLM 方法论最严谨但成本高;Ultra-FineWeb 证明轻量分类器 + 高效验证可匹敌 LLM 分类器
  5. CQF 的”质量幻觉”——分类器学到的可能是分布差异而非语义质量,CQF 仍然有效但原因可能不是我们以为的那样(§6.6)
  6. PPL 过滤要双边——既过滤高 PPL(乱码)也过滤极低 PPL(模板化/重复内容);更进一步,PPL 可从”过滤工具”升级为”选择工具”
  7. 质量和多样性需要联合优化——先打分再过滤的两阶段方法可能不如联合优化(如 QuaDMix)
  8. 质量感知 Scaling Law——Loss = f(N, D, Q) 首次将数据质量 Q 作为显式变量纳入 Scaling Law
  9. 合成数据混入有明确配比——约 1/3 改写型 + 2/3 自然数据是当前最佳实践,纯生成型有模型崩溃风险
  10. 人工抽样不可替代——模型打分只是自动化手段,最终的质量判断需要人来做
  11. 高效验证策略将数据质量探索从昂贵实验变为快速迭代——关键不在分类器多强,而在于验证策略多高效
  12. 多语言过滤进入统一框架时代——FineWeb-2 用 15% 的 token 即可匹配基线,跨语言迁移使低资源语言也能获得可靠过滤

🛠️ 本章衍生工具