跳到主要内容

数据截至 (上游 commit a649de79c14a)

03 · 质量过滤器

这一章讲什么: 「数据清洗」在代码里到底是什么——BaseFilter 的协议,以及 Gopher / C4 / FineWeb 三篇论文的启发式规则如何逐条落成 Python。读完你能回答:一份原始网页要被「扔掉」需要踩中哪些具体的线。


1. 它要解决的小问题

网页抓取数据里充斥着机器不想学的东西:导航栏样板、列表页、复读机内容、乱码、非目标语言、黄赌毒站。问题是双层的:

  • 判定层面:用什么规则识别「垃圾」?——这来自论文(Gopher、C4)的消融实验,不是拍脑袋;
  • 工程层面:规则要跑在几十亿文档上,且扔掉的东西不能默默消失——事后要审计「这步扔了 30%,扔的是什么」。

2. 思路:统一协议,规则可插拔

所有过滤器共享一个极简协议(BaseFilter.filtersrc/datatrove/pipeline/filters/base_filter.py:36-48):

输入一个 Document,返回 True(留)或 False / (False, "原因")(弃)。

run:61-82)把协议包成流水线步骤,并做了三件事:

  1. 计数forwarded / dropped / dropped_<原因> 各记一笔(:69-78)——这就是 FineWeb 论文里那张「每个过滤阶段扔掉多少」表的数据来源;
  2. 审计:挂 exclusion_writer 时,被弃文档带上 filter_reason 写进独立目录(:79-82);
  3. 批处理钩子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 或 >100000gopher_short_doc / gopher_long_doc
平均词长<3 或 >10 字符gopher_below/above_avg_threshold
#... 与词数之比>0.1gopher_too_many_hashes / _ellipsis
/- 开头的行占比>0.9gopher_too_many_bullets
... 结尾的行占比>0.3gopher_too_many_end_ellipsis
含字母的词占比<0.8gopher_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-73WordTokenizer ABC + 各语言实现)——同一条规则在多语言下仍然成立;
  • 任一指标不过即弃,返回的 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:行级删行,文档级弃文

C4QualityFiltersrc/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 的自研补丁

FineWebQualityFiltersrc/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
软违禁词数 ≥ 2soft_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,filterlang.split("_"):52-54)。注意它的行为不只是过滤:无论留弃,languagelanguage_score 都会写进 metadata(:55-56)——下游的「按语言归档」「按语言调阈值」全靠这两个键;label_only=True 时甚至只打标不删(:61-65)。


7. 模型与统计类过滤器

  • FastTextClassifierFiltersrc/datatrove/pipeline/filters/fasttext_filter.py:12):用自训练的 fastText 分类器按 keep_labels/remove_labels 过滤(二选一,同时给会 ValueError:55-56)。filter_mode 可切到句/段级——不达标的部分从文中抠掉而不是弃文(:95-111),标签分数写入 metadata。FineWeb 后续版本用它做教育性内容筛选,就是这个 block (inferred)。
  • UnigramLogProbFiltersrc/datatrove/pipeline/filters/unigram_log_probs.py:19):思路借自 peS2o——下载 Google 1T 语料的词频表,文档的平均 unigram log 概率低于阈值即弃(get_logprob:62-68)。便宜地抓「词汇分布怪异」的文档。
  • SamplerFiltersrc/datatrove/pipeline/filters/sampler_filter.py:8):按 rate 随机留样,做子集/消融用。
  • 其余小件:RegexFilterLambdaFilter(自定义函数)、C4ParagraphFilter(mC4 段落规则,c4_filters.py:139)。

8. 关键细节 / 坑

  • 阈值全是经验值。 它们来自各论文在英文网页上的消融,换语言/换领域(论坛、代码、学术 PDF)要重新校准;没有任何机制帮你自动调 (inferred)。
  • 停用词清单是英文的STOP_WORDSgopher_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.pyBaseFilter.filterBaseFilter.runget_filter_result
Gopher 质量src/datatrove/pipeline/filters/gopher_quality_filter.pyGopherQualityFilter.filter
Gopher 重复src/datatrove/pipeline/filters/gopher_repetition_filter.pyGopherRepetitionFilterfind_duplicatesfind_all_duplicate
C4 行级规则src/datatrove/pipeline/filters/c4_filters.pyC4QualityFilter.filterC4BadWordsFilter
FineWeb 补丁src/datatrove/pipeline/filters/fineweb_quality_filter.pyFineWebQualityFilter.filter
URL 黑名单src/datatrove/pipeline/filters/url_filter.pyURLFilter.filterdownload_data
语言识别src/datatrove/pipeline/filters/language_filter.pyLanguageFilter.filter
fastText 分类src/datatrove/pipeline/filters/fasttext_filter.pyFastTextClassifierFilter.filter
unigram 统计src/datatrove/pipeline/filters/unigram_log_probs.pyUnigramLogProbFilter.get_logprob
分词底座src/datatrove/utils/word_tokenizers.pyload_word_tokenizerWordTokenizer