数据截至 (上游 commit a649de79c14a)
03 · 质量过滤器
这一章讲什么: 「数据清洗」在代码里到底是什么——
BaseFilter的协议,以及 Gopher / C4 / FineWeb 三篇论文的启发式规则如何逐条落成 Python。读完你能回答:一份原始网页要被「扔掉」需要踩中哪些具体的线。
1. 它要解决的小问题
网页抓取数据里充斥着机器不想学的东西:导航栏样板、列表页、复读机内容、乱码、非目标语言、黄赌毒站。问题是双层的:
- 判定层面:用什么规则识别「垃圾」?——这来自论文(Gopher、C4)的消融实验,不是拍脑袋;
- 工程层面:规则要跑在几十亿文档上,且扔掉的东西不能默默消失——事后要审计「这步扔了 30%,扔的是什么」。
2. 思路:统一协议,规则可插拔
所有过滤器共享一个极简协议(BaseFilter.filter,src/datatrove/pipeline/filters/base_filter.py:36-48):
输入一个
Document,返回True(留)或False/(False, "原因")(弃)。
run(:61-82)把协议包成流水线步骤,并做了三件事:
- 计数:
forwarded/dropped/dropped_<原因>各记一笔(:69-78)——这就是 FineWeb 论文里那张「每个过滤阶段扔掉多少」表的数据来源; - 审计:挂
exclusion_writer时,被弃文档带上filter_reason写进独立目录(:79-82); - 批处理钩子:
batch_size > 1时改走filter_batch(:50-59),给模 型类过滤器(fastText 等)批量推理的机会;若子类没实现filter_batch却设了batch_size > 1,构造时直接警告(:33-34)。
文档流 ──► filter(doc) ── True ──► 下游
│
└─ (False, "gopher_short_doc")
│
▼
exclusion_writer 存档
(removed/4_gopher_qual/...)
3. Gopher:两兄弟过滤器
Gopher 论文(Rae et al., 2021, arXiv:2112.11446)附录的质量规则在 DataTrove 里拆成两个 block。
3.1 GopherQualityFilter:文档级「体 检指标」
src/datatrove/pipeline/filters/gopher_quality_filter.py:13。每条规则对应论文里的一句,默认值照抄论文(:16-27):
| 规则 | 默认阈值 | 踩线原因标签 |
|---|---|---|
| 文档词数(不含纯符号词) | <50 或 >100000 | gopher_short_doc / gopher_long_doc |
| 平均词长 | <3 或 >10 字符 | gopher_below/above_avg_threshold |
# 或 ... 与词数之比 | >0.1 | gopher_too_many_hashes / _ellipsis |
以 •/- 开头的行占比 | >0.9 | gopher_too_many_bullets |
以 ... 结尾的行占比 | >0.3 | gopher_too_many_end_ellipsis |
| 含字母的词占比 | <0.8 | gopher_below_alpha_threshold |
| 常见停用词命中数 | <2 个 | gopher_enough_stop_words |
实现上有两个值得看的点:
- 词怎么算:统一走
split_into_words(text, language)(src/datatrove/utils/text.py:308),底层按语言选 NLTK/spaCy/Stanza 等 tokenizer(src/datatrove/utils/word_tokenizers.py:59-73的WordTokenizerABC + 各语言实现)——同一条规则在多语言下仍然成立; - 任一指标不过即弃,返回的 reason 就是上表标签,配合
exclusion_writer可以精确统 计每条规则的杀伤力。
3.2 GopherRepetitionFilter:抓「复读机」
src/datatrove/pipeline/filters/gopher_repetition_filter.py:73。文件开头把论文 Table A1 整表抄成注释(:11-28),实现即查表:
- 行/段重复率:
find_duplicates(:35-46)数「出现过的行再次出现」的比例,按条数和按字符数各算一份,阈值 0.3 / 0.2; - top n-gram 占比(n=2,3,4):最高频 n-gram 独占的字符比例,阈值 0.20/0.18/0.16(
find_top_duplicate,:49-54); - 重复 n-gram 占比(n=5..10):所有重复 n-gram 合计的字符比例,阈值从 0.15 递减到 0.10。
最后一个的实现有个小巧思:find_all_duplicate(:57-70)在命中重复后 idx += n 跳过整个 n-gram 而不是 idx += 1 滑窗——避免一长段复读文本里每个位置都被重复计数,既省时间又让「重复比例」更接近人类直觉。
4. C4:行级删行,文档级弃文
C4QualityFilter(src/datatrove/pipeline/filters/c4_filters.py:27)复刻 C4 论文(jmlr.org/papers/volume21/20-074)及其实现代码里的规则。它引入了一个重要的第二层语义:有的规则只删行,有的规则弃掉整篇(:88-136):
- 删行(该行从文档中抠掉,文档继续走):行不以
.?!等终止符结尾(导航栏特征)、行内词数 <3、含 "javascript"、含 cookie/隐私政策短语(POLICY_SUBSTRINGS,:17-24)、有超 1000 字符的词; - 弃文(整篇扔掉):含 "lorem ipsum"、含花括号
{(代码/CSS 特征)、清理后句子数 <5。
实现里规则逐行生效,保留的行重新拼回 doc.text(:135)——所以过滤器不只是门卫,也兼做「文本整形」。配套的 C4BadWordsFilter(:209)按语言加载脏词表(从 GitHub 仓库下载,cached_asset_path_or_download 进程安全地下载一次),keep_fraction 参数支持「命中脏词的页面随机留一部分」——论文发现全删会损失某些领域的正常文本。日语/中文等无空格语言不要求词边界匹配,且有 _BADWORDS_ALLOWLIST(:206)豁免那些在无空格语言里是常见子串的词。
5. FineWeb 的自研补丁
FineWebQualityFilter(src/datatrove/pipeline/filters/fineweb_quality_filter.py:8)是 HF 在 Gopher+C4 之上加的第四条质量线,源自 FineWeb 消融中新发现的坏模式(:33-56):
- 以标点结尾的行占比 < 0.12 →
line_punct_ratio(抓「全是碎片」); - 短行(≤30 字符)占比 > 0.67 →
short_line_ratio(抓列表页); - 重复行字符占比 > 0.01 →
char_dup_ratio; - 换行数/词数 > 0.3 →
list_ratio。
默认值就是 FineWeb 生产配置。在 examples/fineweb.py:33-73 里,完整顺序是:URLFilter → Trafilatura 抽正文 → LanguageFilter → GopherRepetition → GopherQuality → C4 → FineWebQuality——便宜的在前(URL 匹配是 O(1) 查表),贵的在后(Gopher 要分词统计),这是过滤链排序的通用原则 (inferred)。
6. URL 与语言:两个「查表型」过滤器
6.1 URLFilter:五层黑名单
src/datatrove/pipeline/filters/url_filter.py:33。对 metadata["url"] 依次查(filter,:104-132):
| 层 | 命中即弃原因 |
|---|---|
| 注册域名在黑名单 | domain |
| 完整子域名在黑名单 | subdomain |
| 完整 URL 在黑名单 | url |
| URL 分词后含硬违禁词 | hard_blacklisted |
| 软违禁词数 ≥ 2 | soft_blacklisted |
| 归一化后 含违禁子串 | blacklisted_subword |
工程亮点在最后一层:违禁子串有几百个、URL 有几亿条,逐个 in 判断太慢,于是用 Aho-Corasick 自动机(多模式串一次扫描,ahocorasick.Automaton,:73-78 构建、:129-130 查询)。黑名单本体打包在仓库 assets 里,首次用时解包(download_data,:80-102,同样走 safely_create_file 锁)。
6.2 LanguageFilter:fastText 识别 + 打分入 metadata
src/datatrove/pipeline/filters/language_filter.py:9。后端二选一:FT176(fastText 176 语言模型)或 GlotLID(覆盖更多语种,还产出文字 script,filter 里 lang.split("_"),:52-54)。注意它的行为不只是过滤:无论留弃,language 和 language_score 都会写进 metadata(:55-56)——下游的「按语言归档」「按语言调阈值」全靠这两个键;label_only=True 时甚至只打标不删(:61-65)。
7. 模型与统计类过滤器
FastTextClassifierFilter(src/datatrove/pipeline/filters/fasttext_filter.py:12):用自训练的 fastText 分类器按keep_labels/remove_labels过滤(二选一,同时给会ValueError,:55-56)。filter_mode可切到句/段级——不达标的部分从文中抠掉而不是弃文(:95-111),标签分数写入 metadata。FineWeb 后续版本用它做教育性内容筛选,就是这个 block (inferred)。UnigramLogProbFilter(src/datatrove/pipeline/filters/unigram_log_probs.py:19):思路借自 peS2o——下载 Google 1T 语料的词频表,文档的平均 unigram log 概率低于阈值即弃(get_logprob,:62-68)。便宜地抓「词汇分布怪异」的文档。SamplerFilter(src/datatrove/pipeline/filters/sampler_filter.py:8):按rate随机留样,做子集/消融用。- 其余小件:
RegexFilter、LambdaFilter(自定义函数)、C4ParagraphFilter(mC4 段落规则,c4_filters.py:139)。
8. 关键细节 / 坑
- 阈值全是经验值。 它们来自各论文在英文网页上的消融,换语言/换领域(论坛、代码、学术 PDF)要重新校准;没有任何机制帮你自动调 (inferred)。
- 停用词清单是英文的(
STOP_WORDS,gopher_quality_filter.py:10),多语言要通过stop_words=+language=参数自己喂。 - 行级规则会修改文本(C4 抠行、fastText 句级抠段),下游看到的
doc.text已非原文——把这类过滤器放在「需要原文」的步骤(如某些去重签名)之前要想清楚顺序。 filter必须快。 它在每篇文档上调用,实现里连停用词集合都预先set(...)(gopher_quality_filter.py:58);写自定义过滤器时把重对象放__init__惰性加载,别放filter里。- 代码里看不出的:URL 黑名单(assets 里的
url_filterblacklistsv0_3_0.tar.gz)的具体内容、覆盖面和更新历史,从代码无法评估——它是个二进制数据资产。
9. 本章代码地图
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 过滤器协议 | src/datatrove/pipeline/filters/base_filter.py | BaseFilter.filter、BaseFilter.run、get_filter_result |
| Gopher 质量 | src/datatrove/pipeline/filters/gopher_quality_filter.py | GopherQualityFilter.filter |
| Gopher 重复 | src/datatrove/pipeline/filters/gopher_repetition_filter.py | GopherRepetitionFilter、find_duplicates、find_all_duplicate |
| C4 行级规则 | src/datatrove/pipeline/filters/c4_filters.py | C4QualityFilter.filter、C4BadWordsFilter |
| FineWeb 补丁 | src/datatrove/pipeline/filters/fineweb_quality_filter.py | FineWebQualityFilter.filter |
| URL 黑名单 | src/datatrove/pipeline/filters/url_filter.py | URLFilter.filter、download_data |
| 语言识别 | src/datatrove/pipeline/filters/language_filter.py | LanguageFilter.filter |
| fastText 分类 | src/datatrove/pipeline/filters/fasttext_filter.py | FastTextClassifierFilter.filter |
| unigram 统计 | src/datatrove/pipeline/filters/unigram_log_probs.py | UnigramLogProbFilter.get_logprob |
| 分词底座 | src/datatrove/utils/word_tokenizers.py | load_word_tokenizer、WordTokenizer |