高质量的预训练数据清洗、去重与配比策略,是大模型预训练里最容易被低估、又最能拉开差距的一环。斯坦福大模型开发课 EP14 把这部分单独拿出来讲,正好说明数据问题在课程设计里的分量:模型架构可以抄、训练框架可以复现,但一份干净、均衡、适合目标场景的训练语料,必须自己一层一层处理出来。这篇文章面向正在做大模型预训练、数据工程,或者准备系统学习数据处理的开发者,核心是把清洗、去重、配比这三件事拆清楚,同时补充我在实际项目里验证过的顺序、参数和判断标准。
很多人一开始接触大模型,注意力都在模型结构、训练框架和算力调度上,等到真的从零开始处理训练数据,才发现数据问题远比想象的复杂。原始语料里什么都有:HTML 标签、乱码、转载文章、机器生成的聚合页、重复评论、隐私信息。这些问题不处理干净,模型训练得再充分,能力上限也会被数据质量卡住。
1. 预训练数据为什么值得花一半精力处理
1.1 数据质量决定模型能力上限
在预训练阶段,模型学到的是语言结构、世界知识、推理习惯和回答风格。这些东西不是来自某一个模型模块,而是来自海量语料里的统计规律。数据里没有的知识,模型无法凭空产生;数据里的错误和噪声,模型也会一并学会。
很多刚开始接触预训练的同学会把注意力放在模型参数量、并行策略和学习率上,这些当然重要,但数据才是真正的起点。以我在项目里的经验,同样规模的模型,喂一份经过清洗、去重、合理配比的数据,和喂一份直接从网上抓下来没处理的数据,训练结果差距非常明显。脏数据训练出来的模型,最典型的症状是生成内容经常出现重复句、模板句,或者在某些领域的知识明显错乱。
反过来说,当训练结果不符合预期时,第一件要检查的也不是训练框架,而是训练语料。数据层的问题如果不在预训练之前解决,后面做微调、做对齐,都会跟着被拖累。
1.2 课程给的是决策框架,不是固定命令
EP14 的讲解逻辑,本质上是在回答三个问题:哪些数据该进训练集,哪些数据该被过滤;语料里的重复内容怎么去掉才不误伤;不同来源的数据按什么比例混合,模型能力才均衡。
这三个问题不是一次性处理完的,而是一条循环链路:采集、过滤、清洗、去重、配比、训练、评估,再回到数据层调整。常见开源工具链包括网页解析、正文抽取、语言识别、质量过滤、哈希去重等环节,具体工具可以按团队情况选择,但流程顺序基本一致。
我建议把课程内容当成一套决策框架来理解,而不是照着某一个仓库照搬。不同团队的数据来源、算力规模、目标场景差异很大,硬搬别人写死的规则,很容易在自建语料上翻车。比如有些人专门做代码模型,有些人做中文通用模型,还有些人做医疗或法律领域模型,数据处理的侧重点完全不一样。
1.3 数据工程要提前启动,不能等训练前才补
在真正落地的团队里,数据工程往往不是一个独立职位能完成的。做数据清洗的人要懂爬虫结构,要懂规则过滤,还要能写一点模型推理代码来做语义过滤;训练同学需要能读懂数据分布,知道哪类语料在训练 loss 上引起了异常。
这门课把数据单独列成一讲,也是提醒开发者:不要等训练要开始了才想起数据。数据流程至少要提前一到两周跑起来,因为过滤规则和去重参数的验证本身就需要时间。我的经验是,第一版数据管线往往要在小样本上迭代很多轮,才能达到可以上全量的稳定状态。
2. 数据清洗:先把“脏数据”的边界定义清楚
2.1 第一层清洗:格式、编码和模板噪声
网络爬虫拿到的原始页面,远没有我们平时看到的网页那么干净。一个典型的 HTML 页面里,除了正文,还有导航栏、侧边广告、页脚版权、评论区、时间戳和一堆 JavaScript 占位文本。如果直接把这段文本塞进训练语料,模型会学到大量与语言无关的噪声模式。
第一层清洗通常处理三件事:去掉 HTML 标签和脚本片段;统一编码,把乱码、全角半角、不可见字符处理掉;去掉通用模板文本,比如网址、日期、“本文来自某某站点”这类重复内容。这层清洗成本最低,用正则表达式和解析库就能完成,但千万不能只写一条正则就跑全量。
我的做法是先抽 100 条样本,人工扫一遍,把最常见、占比最高的模板模式写进规则表,然后跑小批量,再看过滤日志里的命中情况,反复迭代两三轮再上全量。
# 文档级格式清洗示意:实际规则需要根据语料来源持续补充 import re def normalize_document(text: str) -> str: # 移除 HTML 标签和脚本样式块 text = re.sub(r"<script.*?</script>", " ", text, flags=re.S | re.I) text = re.sub(r"<style.*?</style>", " ", text, flags=re.S | re.I) text = re.sub(r"<[^>]+>", " ", text) # 压缩换行和空白 text = re.sub(r"[ \t]+", " ", text) text = re.sub(r"\n{2,}", "\n", text) # 常见模板噪声按需补充 text = re.sub(r"https?://\S+", " ", text) return text.strip()这段代码只是示意,真正的生产规则表会复杂得多。收拾完格式之后,再进入内容质量判断。
2.2 第二层清洗:内容质量、隐私和有害文本
格式层之后,语料里可能还残留三类问题。
第一类是低质量文本:正文只有一行字、大量标点符号堆砌、中英文混杂乱码、机器翻译痕迹明显的段落。这类可以用统计规则过滤,比如文档长度、平均句长、符号占比、大写比例等。举个例子,如果一段文本里逗号和句号占了一半字符,那它很可能是乱码或符号堆积,直接过滤掉比指望模型自己学会更稳妥。
第二类是隐私信息:身份证号、手机号、邮箱、地址。预训练语料一旦对外发布,这部分必须脱敏。很多开源数据集的隐私泄漏问题,就是在早期清洗时没有做这一步。
第三类是有害内容:色情、暴力、仇恨言论、违法违规信息。这类内容不仅在合规上有风险,也会让模型学到错误的价值倾向,后续做对齐要花更多成本去纠正。
语义层面的过滤,通常会借助困惑度或者一个轻量分类器完成。困惑度的思路是:如果一段文本被一个通用语言模型判为“很难预测”,它很可能包含乱码、非自然语言或极端风格。分类器则更直接,训练一个两个类别的模型,输出“保留”和“删除”。这类方案效果不错,但也最贵,要放在流程后面用。
2.3 清洗顺序:先粗后细,先规则后模型
这里很容易踩的坑是顺序反了。有人一上来就上一个很大的分类器模型,把每条文本都推理一遍,成本高,而且还没有先解决格式噪声,分类器很容易被模板文本干扰。
更稳妥的顺序是先便宜后昂贵:先用正则、长度、编码规则把大量明显噪声过滤掉,再用统计阈值处理质量分数,最后才用模型做语义判断。每一层过滤都要留日志,至少要知道这一层删了多少条,删掉的样本长什么样。
建议在数据管线的每个环节旁边都保留一个抽样存储目录,方便随时回头检查。清洗的目标不是把所有数据都留下来,而是在保证质量和规模之间找到平衡点。数据规模太小时,模型学不到足够的知识;数据规模太大但质量差时,模型会学到太多噪声。
| 清洗层级 | 处理对象 | 常用手段 | 单条成本 |
|---|---|---|---|
| 格式层 | HTML、脚本、编码、空白 | 正则、解析库 | 极低 |
| 内容层 | 长度、符号占比、语言混杂 | 统计阈值 | 低 |
| 语义层 | 低质、机器翻译腔、重复表达 | 困惑度、分类器 | 中 |
| 安全层 | 隐私、有害文本 | 词典、模型审核 | 高 |
在实际项目里,四个层级不一定要全部做完,取决于数据用途。如果只是做领域模型训练,隐私和有害文本过滤仍然建议保留,因为合规风险不会因为模型用途改变而消失。
3. 去重:文档级和句子级要分开做
3.1 互联网语料为什么重复率那么高
预训练语料的一大来源是公开网页。网页内容天然存在大量复制和转载:同一篇新闻被多个站点转载,同一段产品说明出现在不同页面,论坛帖子的签名、评论区复读、机器生成的文章聚合站,都会制造重复。
还有一部分重复来自数据管道本身:同一个 URL 可能被采集多次,同一个来源的不同字段可能被重复拼接。互联网公开语料里重复和近重复内容的占比相当高,这一点在公开数据集分析中经常被提到。
如果不做去重,这些重复内容会在训练时被反复学到,白白浪费算力,同时让模型产生“背诵式输出”。遇到相似问题时,模型会倾向复述训练集中的固定片段,而不是真正泛化。这也是为什么去重不是锦上添花,而是预训练数据处理的必选项。
3.2 文档级去重:精确哈希和近重复检测
先把“完全重复”和“近乎重复”区分开。完全重复文档,直接对全文做哈希,比如 MD5 或 SHA1,把相同哈希值的文档只保留一份。这个操作简单、快,适合多源采集时合并同文。
但实际问题里,更多是“近乎重复”:两篇文章内容高度相似,只是多了几个标点、改了个标题、加了一段广告。这时需要做模糊去重,最常见的方案是 MinHash 加 LSH。核心思路是为每篇文档生成一组“签名”,让相似文档的签名也相似,再用 LSH 把这些文档分桶,最后在桶内计算 Jaccard 相似度,超过阈值就认为是近重复。
相比全量两两比较,这个方案能把复杂度压到可以接受的范围,是目前网页语料去重的主流做法。
MinHash + LSH 去重的基本流程: 1. 将文档切分为 shingle(如 5-gram 或 8-gram) 2. 对每个 shingle 做哈希,生成文档签名 3. 用 LSH 将签名分桶,在候选人集合内计算相似度 4. 相似度超过阈值的文档标记为近重复,只保留一条SimHash 是另一种常见思路,它的做法是把文档压缩成固定长度的指纹,再通过汉明距离判断相似度。MinHash 更擅长处理集合交叠型相似,SimHash 更偏向把文档映射到低维空间后算距离。工程上选哪个,取决于语料规模和可用的计算资源。
对我而言,纯文本网页语料优先考虑 MinHash 加 LSH,因为它对“删除几句话、改几个词”这类噪声更鲁棒。
3.3 句子级去重:处理模板化文本和重复表达
文档级去重处理完,语料里仍然会有不少句子级重复。比如同一个新闻标题出现在不同文章里,某段产品介绍被反复拼接,评论区大量复读文本。这类内容的共同点是:从文档整体看并不重复,但其中某些句子在全量语料里出现次数极高。
句子级去重通常的做法是切出句子,再对句子做 n-gram 统计,把出现频率超过阈值的句子或 n-gram 删掉或替换。还有一种常见操作是“文档内去重”,即删除单篇文档内部的重复段落,这在抓取网页时尤其常见,有些页面会把正文块重复渲染几遍。
去重顺序建议先文档级,再句子级。先删掉整篇重复文档,能明显降低后续句子级去重的数据量,也减少不必要的计算开销。
3.4 阈值怎么定、影响怎么评估
去重阈值是一个典型的“看数据说话”的问题。很多项目会把文档间 Jaccard 相似度阈值设在 0.7 到 0.8 之间,低于 0.7 通常视为不同文档,高于 0.8 视为近重复。但具体数值必须结合语料验证。
我建议先选几个候选阈值,比如 0.6、0.7、0.8,分别跑一次小规模去重,人工查看被判定为重复和未被判定的样本,确认边界是否合理。不要只看去重百分比,还要看边界处被删掉的内容是不是真的有价值。
去重之后要看三个指标:保留数据规模、重复率下降程度、下游任务评测变化。如果去重之后模型在知识类任务上明显变差,很可能是阈值过严,把有价值的长尾信息也删掉了。注意,去重不是一次性的,数据源更新后要定期重跑。
| 去重方法 | 粒度 | 处理目标 | 适用场景 | 计算成本 |
|---|---|---|---|---|
| 全文哈希 | 文档 | 完全相同内容 | 多源合并、URL 去重 | 低 |
| MinHash + LSH | 文档 | 近重复、部分改写 | 网页语料为主 | 中 |
| SimHash | 文档 | 相似度排序、聚类 | 大规模通用语料 | 中 |
| n-gram / 句子去重 | 句子、段落 | 高频模板句、复读 | 新闻、评论、问答语料 | 中高 |
有一点容易被忽略:去重还会影响数据配比。如果一个领域的数据本身来源单一,去重之后数量骤减,那么接下来配比时就要考虑是否需要补充这个领域的数据。所以去重和配比不是两个孤立步骤,它们共享同一份数据统计报表。
4. 数据配比:让模型“不偏科”的关键
4.1 配比不是拼数据量,而是拼信息增益
配比要解决的核心问题,是在有限算力下,把哪些数据以多大比例放进训练集,让模型能力更均衡。很多人误以为数据越多越好,但如果一个领域的数据量已经大到模型训练不完一个 epoch,继续堆数据带来的收益就会递减。
真正决定配比价值的,是数据能给模型带来多少新增信息。这个思路和经典的数据规模结论一致:预训练更关注数据质量和多样性,而不是单纯的数据总量。
课程在讲这部分时,核心会落到一对矛盾上:数据多样性要够,数据重复要克制。同一个高质量数据集重复读几遍,模型可能记住它,但泛化能力没有提升;而如果把一份小但高质量的核心数据连续重复很多遍,整体 loss 会好看,下游能力却可能只在局部任务上突出。
4.2 常见配比维度:领域、语言和质量分层
配比可以分为几个维度。
按领域划分:通用网页语料、书籍、学术论文、代码、数学题、多轮对话等。每个领域对模型能力的贡献不同:代码语料提升逻辑推理和指令遵循,书籍和论文提升长文本理解,对话语料提升交互形式。如果只堆通用网页语料,模型可能很会接句子,但写代码、做数学推理的能力会很弱。
按语言划分:中文场景要重点考虑简体中文、繁体中文以及中英混合的比例。英文语料在公开数据集里数量庞大,质量也相对稳定,但一个面向中文用户的模型如果中英比例失衡,生成中文时会明显生硬。
按质量分层:可以把语料分为高、中、低三档,低档语料负责覆盖多样性,高档语料负责核心能力。一个常见的起点是,通用网页语料占大头,书籍和论文占一小部分,代码和数学单独配比,再根据目标场景加入领域数据。不同团队的起点差异很大,这个比例必须用自己的评测集去验证。
4.3 控制重复 epoch,避免模型“背数据”
配比和 epoch 控制是连在一起的。对通用网页语料这种大体量数据,通常只需要让模型看到不到一个 epoch,也就是语料量大于训练步数可以消费的量,这样模型不会把某一条文本背下来。
对高质量的领域数据,比如精修后的数学题、专业知识库,数量少但价值高,可以适当重复几遍。关键是要观察训练曲线:如果训练 loss 持续下降,但评测集上的损失在某一固定步数之后开始反弹,说明模型开始过拟合训练数据,这时就要减少该领域数据的重复次数或降低配比。
课程里经常会用曲线图说明这种现象。实际项目中,我一般会在训练过程中每个固定步数记录一次各评测集的指标,和数据配比表放在一起,方便定位是哪一类数据导致的过拟合。
4.4 用评测集反向调整配比
配比没有一个固定答案,最好的办法是让评测结果来决定。
第一步,建立一组覆盖面广的评测集:通用常识、代码、数学、阅读理解、翻译、指令跟随。第二步,用初始配比训练一个较小的模型,看各评测集的表现。第三步,单独调整某一个领域的数据比例,重新训练,对比结果。
每次只改一个变量,是配比实验里最容易犯的错误的反面。有人一次改了三四个领域比例,最后效果变化了,却不知道是哪个因素起的正面作用。记得把所有配比版本、数据统计和评测结果保存下来,形成一张可追溯的实验记录表。
配比优化的本质是搜索,不是一次就能找到最优解。我见过不少团队在第一版就花大量时间调配比,结果模型规模一变,之前的配比又要重调。更务实的做法是先用一个合理的初始配比跑通训练,再根据评测结果做一轮一轮的小幅度修正。
| 检查项 | 判断方式 | 调整方向 |
|---|---|---|
| 领域覆盖 | 对应评测集是否明显偏弱 | 补充该领域数据或提高配比 |
| 语言覆盖 | 目标语言能力与使用场景不符 | 调整语言配比 |
| 重复程度 | 高质量小数据集重复过多导致过拟合 | 降低重复次数 |
| 训练曲线 | 评测 loss 中期反弹 | 减少对应数据源占比 |
5. 落地验证:清洗前后怎么对比效果
5.1 先用小模型做数据验收
数据清洗、去重、配比做完之后,不能直接就拿最终模型去验证,成本和周期都太高。更实用的做法是先训练一个小模型,比如 1B 以内,用固定的随机种子、固定的步数和固定的优化器配置,只改变数据管线。
因为小模型对数据质量差异同样敏感,而且一次训练成本低,可以快速比较“清洗前、清洗后、去重后、不同配比”几组数据的效果。对比时最怕变量不统一,训练超参数尽量完全一致,只允许数据这一层变化,否则结果很难归因。
如果连小模型训练的资源都不够,退一步可以用一个更轻量的方式:直接用困惑度模型对清洗前后的语料打分,看平均困惑度是否下降,再配合人工抽样判断。这种方式虽然不能完全代替训练验证,但可以作为第一道快速筛选。
5.2 看哪些指标
验证阶段要同时看三类信号。
第一类是训练曲线:训练 loss、验证 loss 是否平滑下降,有没有异常的尖峰或反弹。第二类是评测集结果:按领域分开看的 benchmark 分数,比一个总分更有诊断价值。第三类是人工抽样:我自己每次清洗完一份语料,都会在过滤后的数据里随机抽 20 到 50 条样本,人工读一遍。
这个习惯看着原始,却能发现很多指标看不出来的问题,比如段落被截断、语言混杂、版权声明混入正文。另一个值得关注的指标是数据的去重统计:重复文档数、最大重复簇大小、句子级重复占比。把这些指标放在数据质量报告里,训练前后各产出一份,团队里的人都能看懂。
5.3 成功和失败的信号
清洗和去重做得好的表现是:训练 loss 下降稳定,评测集分数均衡提升,抽样样本质量稳定,模型生成内容不再频繁出现训练语料的固定片段。
做得过猛的表现是:数据总量大幅收缩,多样性明显下降,知识类任务分数反而比清洗前更低;或者模型在风格上变得单一,遇到长尾问题容易答非所问。
做得不足的表现是:训练 loss 很快下降但评测集分数上不去,模型输出经常出现重复句、模板句,或者某个领域的知识明显错误。遇到这些情况,不要急着继续加清洗规则,先回到抽样日志里看数据,判断问题是出在过滤阈值、去重阈值还是配比结构上。
6. 实际项目里的常见误区和排查顺序
6.1 大多数问题不是清洗代码,而是源头数据
做过几轮数据工程之后,我发现真正让人头疼的问题,往往不是清洗代码写错了,而是源头数据本身就不可控。同一个采集源,可能今天返回正常正文,明天就返回一个验证码页面;同一个 URL 模板,可能在不同地区返回不同的广告内容;还有不少页面是机器聚合生成的,正文和导航几乎没有区分度。
所以在进入清洗流程之前,我建议先把每个数据源的字段结构、抓取时间、页面模板版本记录下来。一旦发现某一段时间的训练结果异常,可以快速回看是哪个数据源发生了变化。
数据管道需要和训练流程一样对待版本管理,每一份训练数据都打上来源、清洗规则版本、时间戳和过滤统计。这套记录看起来繁琐,但在排查问题时能省下大量时间。
6.2 排查顺序
当训练效果或数据质量出问题时,按下面顺序排查,比乱试规则高效得多。
第一步,看现象:是训练 loss 异常,还是评测集分数低,还是生成内容重复;不同现象指向不同环节。第二步,看输入:检查原始语料的格式、编码、来源分布,确认是否混入了异常数据源。第三步,看清洗日志:每一层过滤分别删了多少比例的数据,如果某一层删除比例突然大幅变化,优先检查这一层。
第四步,看去重参数:阈值是否合理、是否设置了文档级和句子级两级去重、去重后各领域数据量变化。第五步,看配比和 epoch:是否存在某个领域数据被过度重复。
最后才去怀疑训练框架。实际上,绝大多数数据问题都能在第三步和第四步确认。我见过有人花两三天排查训练代码,最后发现是一份语料里混入了大量乱码页面。
6.3 什么时候该停下来
数据清洗是一个很容易让人陷入无限优化的环节。规则越加越多,每一层都能发现新的噪声,数据量不断缩小,时间成本不断上升。
我的判断标准是:当人工抽样已经很难找到明显噪声,或者再增加规则对评测集带来的提升已经小于增加的算力和维护成本,就应该停下来,把当前版本的数据固化、打标签、进入训练。
数据是迭代出来的,不是一次性做完美的。先跑通一版端到端流程,第一个模型无论效果如何,都能告诉你下一步最该优化的是哪一类数据。与其花三周追求完美数据,不如先花一周产出一份质量合格的数据,再根据训练反馈精准调整。
从斯坦福大模型开发课 EP14 的内容来看,预训练数据清洗、去重和配比并不是三个孤立的技术点,而是一套需要一起设计的数据决策流程。课程的价值在于把这些问题系统化,而真正让流程跑起来的,是实践中积累的阈值、日志和验证习惯。如果只看一个结论,我的建议是:先建立可重复、可审计的数据管线,再追求数据质量的绝对最优;先用小模型验证每一处改动,再投入全量训练。按这个节奏走,数据层给你带来的回报会比想象中更稳定。