2.1 预训练数据:来源、爬取与清洗(10T Tokens 级别的工程方法论)
预训练阶段的核心在于数据能不能规模化地获得、稳定地处理、持续地提纯 。当总体训练量级来到 10T tokens 甚至更高时,数据质量和数据分布的细微偏差,都会被训练过程不断放大,最终体现在模型的通用能力、对齐倾向和长尾鲁棒性上。
在实践中:
- **通用预训练(Base)**常见规模约为 10T tokens;
- **继续预训练(Continued Pretraining)**通常也至少在 100B tokens (101110^{11}1011 tokens)量级,且往往需要更精细的数据配比与更强的质量控制。
2.1.1 数据来源:通用语料 + 专用语料
预训练语料通常可分为两大类:
1)通用文本数据(General Text)
覆盖范围广、规模大,目标是提升语言建模与世界知识覆盖:
- 网页(Common Crawl 等)
- 书籍 / 长文本(Books、Z-Library 类)
- 论坛 / 社区对话(Reddit 等)
- Wikipedia / 百科类
通用数据的特点是"量大、噪声也大",因此清洗与去重往往比爬取更关键。
2)专用文本数据(Specialized Text)
为了补足高级能力或特定场景能力,常见包含:
- 多语言数据(尤其中英文与目标小语种)
- 科学数据(论文、arXiv、教材)
- 数学数据(题目、解题过程、数学百科)
- 代码数据(GitHub、文档、Markdown)
- 逻辑推理/竞赛题/高质量问答
专用数据通常"质量更高、密度更强",但也更难获取与结构化。
常见数据来源清单与特点
- Common Crawl:开放网页数据平台,覆盖广、更新快,但噪声极高,必须强清洗。
- GitHub:代码数据主来源,既有高质量工程代码,也有大量镜像、自动生成与重复仓库。
- 垂类网页文本:例如数学网站、代码论坛、库文档站等,适合补强特定能力。
- 电子书 / 教育材料 / 论文资料:知识密度高,特别适合提升"可解释推理"和"学术写作风格"。
- 内部数据:公司业务语料或垂域语料,往往与目标任务分布更接近,但风险在于分布偏与合规成本。
开源语料的现实定位:能启动,但不保证长期上限
FineWeb、The Pile、SkyPile、RedPajama 等开源语料可以作为"启动资金",但通常会遇到:
- 质量参差(尤其中文网页语料)
- 重复严重(跨数据集、跨版本、跨预处理 pipeline)
- 格式和结构差(段落混乱、广告污染、模板化文本多)
- 领域偏差(过度依赖某类站点或平台)
因此,开源语料的合理用法往往是:先用来打底,再通过清洗、重采样、增补高质量专用数据逐步拉高上限。
一个继续预训练配比的示例(100B tokens)
一个典型的继续预训练配置可能是:通用 70B tokens,总计 100B tokens。并且在总语料里常按比例控制,例如:
- 英文 : 中文 : 代码 = 70 : 20 : 10
这种配比的直觉:
- 英文语料仍是全球公开知识与高密度技术内容的主要载体;
- 中文语料决定中文能力上限,但质量与分布更难控制;
- 代码语料对结构化推理、工具化表达、格式稳定性有明显增益。
2.1.2 数据爬取:从"能抓到"到"抓得对、抓得全、抓得稳"
预训练数据的获取往往不是单一爬虫能解决,而是一个组合系统:定向抓取 + 搜索驱动抓取 + 关键词驱动抓取,并且要配合解析与结构化。
爬取方式的三种常见路线
1)定向网站爬取(Site-centric)
针对固定站点列表(白名单或重点站点),做全站或特定栏目抓取。
适用场景:
- 领域站点明确(如 arXiv、维基百科镜像、代码文档站)
- 质量相对可控
- 站点结构稳定,可持续更新
典型风险:
- 站点结构变更导致解析失败
- 反爬策略(频率限制、验证码、JS 渲染)
2)关键词驱动爬取(Keyword-centric)
在指定站点或站群中,以关键词检索入口抓取页面,再做扩展抓取。
适用场景:
- 目标领域(数学/代码/医学)关键词可描述
- 希望"按主题"扩大覆盖
关键点在于:关键词集合必须持续迭代,否则会出现覆盖不足 或分布偏置。
实例:为了补强数学推理能力,关键词可能覆盖:
- 数学概念:eigenvalue, determinant, convex, lemma, proof
- 题型:geometry, combinatorics, inequality
- 表达:Let x∈Rx \in \mathbb{R}x∈R, we have, therefore, hence
如果关键词只覆盖竞赛题类,很容易导致模型输出风格"题解化",而对教材/论文的表达风格适配不足。
3)基于搜索引擎爬取(Search-centric)
先通过搜索引擎(或自建索引)获取候选 URL,再进行抓取与解析。
适用场景:
- 站点分散、长尾强
- 需要覆盖更多非结构化高质量内容
风险:
- 搜索引擎结果自身带偏(SEO、热点驱动)
- 重复链接与镜像页极多
PDF 数据:高质量但高成本的"硬骨头"
很多高密度数据(论文、书籍、教材)以 PDF 形式存在。PDF 的难点不是"下载",而是"高质量解析成结构化文本"。
为什么 PDF 难解析?
- 公式与符号不是线性文本(∑,∫,ab\sum, \int, \frac{a}{b}∑,∫,ba 等在 PDF 内是排版对象)
- 表格是视觉结构,不是文本流
- 两栏排版导致阅读顺序错乱
- 图片/图注/脚注混排
一个典型失败案例(解析错序)
两栏论文中,正确顺序是"左栏上 → 左栏下 → 右栏上 → 右栏下",但很多解析会输出:
- 左栏上半段 → 右栏上半段 → 左栏下半段 → 右栏下半段
导致语义断裂,甚至把公式解释拆碎。
实践策略
- 使用专门的 PDF 解析服务通常比通用 Python 库稳定;
- 用大模型解析 PDF 当然可行,但在海量数据场景下成本会非常高;
- 自训 OCR/结构化解析模型是可选项,但前提是有足够的高质量对齐数据(PDF → text)。
2.1.3 数据清洗:决定上限的关键环节
如果说爬取解决"有没有",清洗解决的就是"好不好、像不像、能不能学"。
数据清洗通常包含:
- URL 过滤与站点策略
- 内容抽取(去噪、去模板)
- 语言识别与语种控制
- 低质过滤(篇章级 + 句子级)
- 模型打分(Quality Scoring)
- 数据去重(Deduplication)
- 测试集过滤(Test-set Filtering)
下面逐块展开。
A. URL 过滤:先在入口处"砍掉一大半垃圾"
网页语料噪声常常来自站点层面:成人、博彩、下载站、采集站、SEO 农场、内容聚合页等。URL 过滤通常包括:
- 黑名单:直接过滤明显低质/违规域名或路径模式
- 白名单/保留策略:对高质量来源(如教材站、官方文档站)保留更高采样率
- URL 评分:用启发式规则或训练模型对 URL 质量做预判
一个关键实践点是:为了区分"人工精心构建的数据"与"网页噪声语料",有时会对某些高质量站点做独立处理链路,而不是简单混入网页流里。例如 arXiv、Wikipedia 这类来源,通常会单独清洗与去重,以避免被网页流的规则误伤或被当作"模板页"过滤掉。
B. 内容抽取:从 HTML 里拿到"真正的正文"
抓到 URL 只是开始,HTML 页面里充满:
- 导航栏、目录、相关推荐
- 广告、版权声明、弹窗文本
- 评论区、脚注、脚本内容
内容抽取目标是得到"正文文本流",同时尽量保留段落结构。
实例:同一篇博客页面的正文可能只有 30%,剩下 70% 是侧边栏、推荐、评论与广告。
如果抽取失败,会造成模型学习到大量"关注/点赞/转发/下载/相关阅读"的垃圾模式。
C. 语言识别:保证语种分布与目标一致
当语料规模到 10T tokens 级别,语言分布如果不受控,模型会出现:
- 目标语种能力被稀释
- 混语输出增加
- 特定语种的 tokenization/拼写习惯变差
语言识别通常分两步:
- 语言检测:识别文本语种,并给出置信度分数
- 阈值过滤:置信度低于阈值(如 0.65)直接丢弃或进入"低优先级池"
如果要精确控制中英文比例,可以进一步在采样阶段使用配比公式进行重采样。例如对第 iii 类语料给权重:
pi=niα∑jnjα p_i = \frac{n_i^{\alpha}}{\sum_j n_j^{\alpha}} pi=∑jnjαniα
其中:
- nin_ini 是该类别的可用 token 数(或文档数)
- α\alphaα 控制"均衡程度"
- α=1\alpha = 1α=1 接近按规模采样(大类更大)
- α<1\alpha < 1α<1 会抬升小类比例(更均衡)
- α→0\alpha \to 0α→0 接近均匀采样
这个公式的意义是:即便某类语料很大,也不会无限吞噬训练步数;同时小类(如数学、代码)能被"抬起来",保证技能学习密度。
D. 低质过滤:篇章级 + 句子级的组合
1)篇章级别过滤(Document-level)
目标是直接丢弃"整体不正规"的文档,例如:
- 全文重复同一段(爬取失败、模板页)
- 极端标点比例(乱码、清洗失败)
- 文本长度异常(过短或过长但有效信息密度极低)
- 超多 URL、超多联系方式、超多广告关键词
可以用一些简单的比率特征辅助判断,例如"标点占比":
rpunc=NpuncNchar r_{\text{punc}} = \frac{N_{\text{punc}}}{N_{\text{char}}} rpunc=NcharNpunc
当 rpuncr_{\text{punc}}rpunc 过高时,往往意味着乱码/格式化残留;过低时可能意味着没有自然语言结构(如列表堆砌)。
另一个常用特征是"重复度",例如对文档内重复 nnn-gram 的比例:
rdup=∣DupNGrams(D)∣∣AllNGrams(D)∣ r_{\text{dup}} = \frac{\left|\text{DupNGrams}(D)\right|}{\left|\text{AllNGrams}(D)\right|} rdup=∣AllNGrams(D)∣∣DupNGrams(D)∣
- DDD 为文档
- DupNGrams(D)\text{DupNGrams}(D)DupNGrams(D) 为重复出现的 nnn-gram 集合
- 比例高通常意味着模板、刷屏内容或低质聚合
2)句子级别过滤(Sentence-level)
目标是去掉文档里"无意义但高频的垃圾句式"。常见策略:
- 全大写句子(英语场景常见)
- 纯数字/乱码句子
- 命中广告/引流关键词(关注、转发、点赞、扫码等)
- 过短且命中特定模板(如"登录/注册""展开更多")
实例:
大量网页会在每段中插入"点击展开全文""登录查看更多",如果不清理,模型会学到在任何长回答中插入这些句式,形成明显的输出污染。
E. 规则清洗的边界:不要把分布"清洗歪了"
规则越多,不代表越好。最大的工程风险之一是:
- 清洗规则过强 → 语料分布偏移 → 模型输出偏置
例如,过滤"疫情/新冠"相关词可能把大量医学科普、公共卫生教材一起误伤;过滤某些政治敏感词会把新闻报道、历史资料一起误伤,最终导致模型在相关话题上"知识空洞"。
合理策略是:
- 把"必删项"(色情、博彩、恶意内容)与"可疑项"(热点词、社会事件词)分开;
- 对可疑项更多采用降权、抽样率降低,而不是直接删除;
- 通过抽检与指标监控(困惑度分布、文体分布、主题分布)判断是否清洗过度。
F. 困惑度(Perplexity)辅助:检测"不像人话"的文本
困惑度常用于筛除"表达不自然、乱码、机器拼接"的文本。定义上,对序列 w1:Nw_{1:N}w1:N,语言模型给出的平均负对数似然为:
L=−1N∑t=1Nlogp(wt∣w<t) \mathcal{L} = -\frac{1}{N}\sum_{t=1}^{N}\log p(w_t \mid w_{<t}) L=−N1t=1∑Nlogp(wt∣w<t)
对应的困惑度为:
PPL=exp(L) \text{PPL} = \exp(\mathcal{L}) PPL=exp(L)
直觉解释:
- 如果模型觉得文本很"自然",L\mathcal{L}L 小,PPL 低;
- 如果文本像乱码、拼接、极端模板,L\mathcal{L}L 大,PPL 高。
实践中通常不是用单一阈值硬切,而是:
- 按数据源/语种分桶统计 PPL 分布;
- 去掉极端高 PPL 的尾部样本;
- 避免误删"高难度但高价值"的内容(如数学证明、代码、符号密集文本)。
G. 模型打分:让模型帮你"挑数据"
当规则清洗做到一定程度后,仍会残留大量"看起来像正文但信息密度低"的样本。这时常见做法是训练/使用一个质量打分器,对样本进行质量估计,并用于:
- 过滤(删除低分)
- 降权(低分样本采样率降低)
- 分层采样(高分池更多训练步数)
为什么打分器常用 BERT 类?
- 在相同参数规模下,BERT(双向编码)对文本表征与分类通常更强;
- Decoder-only 更擅长生成,但作为判别器未必是性价比最优。
一个常见的现实策略是:
不要追求打分器 100% 准确,打分器的价值是"让整体更好",而不是"单条样本绝对正确"。训练成本过高反而会拖累迭代效率。
2.1.4 数据去重:规模越大,去重越决定性价比
数据重复会造成:
- 训练计算被浪费(同样内容反复学)
- 模型记忆增强但泛化变差
- 基准评测污染(train-test overlap)
重复大体分三类:
1)训练数据内部与跨数据集重复
- 单文档内部重复(lines/paragraphs/n-grams)
- 文档之间重复(镜像站、转载、聚合)
- 数据集之间重复(同源数据不同预处理版本)
典型例子:同一来源的 Common Crawl 既被处理成某个网页数据集,又被另一条 pipeline 处理成 C4 类数据,两者混用必然重复。
2)训练迭代设置带来的"人为重复"
如果对高质量数据设置更多 epoch,本质上也是重复利用,但这是"可控的重复",目的在于:
- 用更高训练步数强化高质量数据影响力
3)训练集与测试集重复(Test-set Filtering)
必须避免预训练语料与评测 benchmark(或内部验证集)高度相似,否则评测失真。
去重的四步法(从单位定义到测试集过滤)
第一步:确定去重单位(Unit)
不同来源应选择不同粒度:
- 网页:可能是 line-level 或 document-level
- 书籍:book-level(覆盖度超过阈值才算重复)
- 代码:file-level(完全匹配或结构化特征)
选择 Unit 的核心原则:
Unit 太小会误删(把合理的引用、通用表述当重复),Unit 太大会漏删(重复段落藏在大文档里)。
第二步:Unit 自身去重(内部重复)
如果一个 Unit 内部重复比例过高,往往意味着它整体是低质模板或爬取失败页,此时应直接丢弃该 Unit。
可以用前面提到的 rdupr_{\text{dup}}rdup(重复 nnn-gram 比例)或重复段落比例作为判据。
第三步:Unit 之间去重(跨文档重复)
常见方法包括:
- 完全匹配(hash)
- 模糊匹配(SimHash、MinHash)
模糊匹配本质上是在比较集合相似度,例如用 Jaccard 相似度描述两份文本的 shingle 集合 A,BA,BA,B:
J(A,B)=∣A∩B∣∣A∪B∣ J(A,B) = \frac{|A \cap B|}{|A \cup B|} J(A,B)=∣A∪B∣∣A∩B∣
当 J(A,B)J(A,B)J(A,B) 超过阈值(例如 0.8),可以判为"高度重复",保留其中一个。
第四步:测试集过滤(Test-set Filtering)
在确定需要的训练规模后(例如目标 10T tokens),对训练语料做去重时还要额外:
- 与测试集/评测集做相似度匹配
- 删除与测试集相似的训练文档
这一步的目标是确保评测公平,避免模型在预训练阶段"见过题"。