资讯动态

预训练数据集构建实战:清洗、去重、配比与质量校验

发布时间:2026/10/17 5:13:50 来源:尧图企业网站定制
做预训练数据集这活儿很多朋友的第一感觉是把网上能下载的文本堆到一起稍微洗洗就开始训练。真跑过一轮之后你才会知道预训练数据集的构建质量直接决定大模型训练效果的天花板。同样是1000亿token的语料有人喂出来的模型知识面广、语言自然、下游任务微调都能顺利涨点有人喂出来的模型总觉得哪里不对劲——中文偶尔冒英文语序代码里带着HTML标签明明验证集loss不高一上线就露馅。差别不在模型结构就在你手里的预训练语料有没有经历过一套系统的构建流程。这篇文章基于我自己维护过的预训练数据流水线把数据采集、清洗、配比、序列化、质量校验这几个环节拆开讲尽量给出一套可以直接照着落地的实战方案。适合正在准备自己训练基座模型、或者对OpenAI式“大力出奇迹”感到好奇的工程师参考也适合那些已经有训练脚本但数据处理比较粗糙、想回炉重造数据体系的朋友。1. 预训练数据集到底在解决什么问题1.1 数据集决定了模型能力的“天花板”很多人会把大模型训练的重心放在模型架构、并行策略、学习率调度上一谈到数据就简单粗暴地认为“多多益善”。但实际上预训练阶段的语料决定了模型能力的底座语言感、常识、逻辑链条、世界知识甚至代码能力全是在这个阶段长出来的。后面对齐阶段能做的只是把底座上已有的能力向偏好方向引导并不能凭空造出语料里不存在的东西。我的理解是预训练数据集是一个模型的“营养结构”。蛋白质够不够、膳食纤维多不多、有没有重金属残留直接决定了这个模型长大后是精力充沛还是满身毛病。训练脚本解决的是“怎么把营养吸收好”数据流水线解决的是“食材本身好不好”食材出了问题后厨的火再旺也救不回来。所以预训练数据集构建的第一目标不是“大”而是“对”。对当前模型规模、对目标语言、对应用领域。第二个目标才是“大”在质量有保证的前提下尽可能扩大规模和多样性。顺序反了后面会付出极高的返工成本。1.2 三个最容易踩的方向性错误构建预训练数据集时我见过太多团队一开始就把路走偏主要集中在三个误区。第一个误区是“质量越高越好”。有些人把语料过滤到只剩论文、百科、教科书觉得这样训练出来的模型知识密度高。实际跑出来的效果往往是模型变得很“书面”日常表达僵硬一问开放性问题就自动切换成学术腔泛化能力越来越差。高质量小规模数据的代价是多样性不足模型没见过足够多冗余、口语化、甚至带错的真实文本就没法学到稳健的语言规律。第二个误区是“量大了自然就行”。盲目堆量而不做去重会导致模型把高频重复句子背下来。训练到中后期loss降不下去验证集上看不出过拟合但测试时会发现模型生成内容里有大量原句复读这就是典型的重复语料污染。第三个误区是忽略数据分布对模型行为的影响。比如全用新闻语料模型说每句话都像新闻稿代码和自然语言配比失调要么代码能力弱要么自然语言能力下降。预训练数据集的本质是一个多领域混合分布比例配置本身就是一种“超参数”必须像学习率一样去调节。2. 语料从哪来采集源头与配比思路2.1 常见语料来源与各自特点构建预训练语料第一步是决定原材料。没有一种单一来源能同时满足规模、多样性、质量所以现实中的做法是多元混合。网页文本是最主要的规模来源典型代表是Common Crawl这类公开网页语料。优点是体量巨大、语言覆盖广、时效性强缺点是噪声极大。一不留神就会混入HTML标签、导航菜单、用户评论、乱码广告。这类语料必须经过主力清洗流程按我的经验原始网页语料洗完通常能剩下六到七成就算不错。书籍与长文档是高价值补充。小说、教材、技术手册、公开的电子书集合这类文本有完整的段落结构、连贯的上下文和丰富的知识密度对模型学习长距离依赖特别有帮助。缺点是收集过程中版权问题麻烦我一般只使用有明确授权、公开合规的语料集不在灰色地带折腾。代码语料要单独立项。代码的token分布和自然语言差别很大它包含大量符号、嵌套结构、路径字符串。现在主流开源模型都会混入一定比例的代码数据不是为了“让模型写代码”而是为了让模型学会逻辑推导和精确表达。代码数据来源上我常用带许可证过滤的开源代码快照例如GitHub公共仓库的定期导出但会严格过滤掉私人密钥文件、测试数据里的脏代码和大段重复样板。另外还有百科、论文、多语言新闻这些辅助来源。它们质量高但总量有限只能作为“营养补充剂”我的经验是控制在总token的5%到15%比较稳妥。2.2 比例配比别让中英文失衡比例配比这件事很多教程都是一带而过但它对最终模型行为的影响非常直接。我自己常用的一个起步配置大致是网页通用语料占45%左右书籍长文档占20%代码15%多语言语料10%百科和论文等高质量密集文本10%。如果是中文模型我会把中文网页的比例调高一些同时把中文书籍、中文社区文本的比重提到中文域内的一半以上。原因是模型的语感和文化常识都来自这个底层分布中文语料太少光靠后续微调救不回来。这个比例不是拍脑袋定的背后参考了主流开源模型发布时披露的数据构成并且结合了“验证集按同一分布采样”的原则。验证集也要从同样的配比中产出否则你看到验证loss平稳实际分布早就偏了。配比还应考虑模型规模。7B级别模型的训练token量通常在1T上下这个体量足够支撑多样混合但如果你只准备训练500B token强行覆盖20个领域每个领域都只分到25B token每个领域都学不透。这时宁愿砍掉部分领域集中资源把核心领域的数据做厚。2.3 一份值得踩的执行建议数据采集阶段我还有几条亲测有效的建议。第一每个来源单独保存一个目录不要混在一起再后悔。后面做清洗、调权重的成本全在组织方式上。第二采集时保留元数据例如来源域名、抓取时间、原始URL。这些信息不仅方便版权追踪后面做去重、统计来源分布、排查数据污染都要靠它。第三对网页语料优先做一次大规模URL去重很多重复内容都是同一篇文章在多个URL上的镜像。给数据打上“来源标签”这件事尤其重要。我在早期就是吃了没打标签的亏训练中期想查“模型为什么总在生成网络广告口吻的句子”结果所有语料混在一起根本无法定位最后只能重跑整个清洗流程。3. 清洗流水线每一批文本都要过这几道关卡3.1 第一关编码、语言识别与基础过滤清洗流水线的第一站是把原始文本变成“干净、可辨认、边界完整”的纯文本。先统一编码。所有语料统一转成UTF-8处理掉乱码和非法字节。这一步看着基础但在处理爬虫数据时非常关键我遇到过大量用GBK误码、BOM错乱、以及Emoji被截断成半个字符的情况。常见做法是先做字节级合法性检测再转码转码失败的直接丢掉。然后是语言识别。如果你的模型是多语种的这里用fastText的langid模型做语言分类是比较稳的选择准确率高而且快。如果是中文场景还要在简体、繁体之间做统一具体看你的目标面向简体中文生态就转成简体面向古籍或繁体领域则保留繁体并单独配比。基础过滤可以做成一组规则剔除HTML标签残留、去掉连续大写的噪音串、过滤掉控制字符、清理过短的碎片句子。我自己会把这组规则做成一个“规则引擎”按优先级依次执行每次修改配置后跑一次小样本看效果再全量跑。纯粹的规则看起来简单但它是整条流水线里性价比最高的环节能把大部分垃圾挡在源头。3.2 第二关去重防止模型背课文去重是大模型训练数据里永远绕不开的一道工序。重复数据不仅浪费算力还会把模型推向“记忆”而不是“理解”。文档级去重最简单的办法是算SHA-1哈希后按哈希去重速度极快但只能去掉完全相同的副本。真正的难点是近似去重同一篇文章改了标点、插了几个词、调整了段落顺序甚至只是换了个HTML模板。业界常用的方案是MinHash LSH核心思想是对文本做分片和签名再把签名分桶落在同一个桶里的文本就很有可能近似重复。这里有个参数需要根据自己的语料调整MinHash的签名数量和相似度阈值。签名数量决定召回率数量越多对细微改动越敏感计算量也越大阈值决定“多像才去重”。我常用的办法是先拿一个小规模样本人工看几百组被识别为重复的文档对再调整阈值让误杀率控制在可接受范围。宁可漏掉少量重复也不要误杀内容不同的真实数据。清理完文档级重复我还会做一行“句子级去重”统计全量语料中频率异常高的连续句子例如同一句话在10万篇文档里都出现过基本可以判定是页脚、弹窗文案或机器生成的模板。这类内容会让模型学会“念模板”尤其影响生成质量。3.3 第三关质量打分、敏感内容与隐私过滤基础过滤之后剩下的文本长度可能还有百万级文档这时候用规则一条条写已经不现实。我的经验是训练一个轻量质量分类器。做法不复杂。从数据里人工挑出几万条高质量正样本书籍、百科、新闻正文和几万条负样本广告、乱码、纯列表、机器翻译痕迹训练一个二分类模型给每条文本打质量分。打分之后设一个阈值低于阈值的直接丢掉高于阈值的保留。这个分类器不需要很复杂一个百亿参数以下的中小模型就能胜任关键在人工标注样本要覆盖足够多的噪声形态。敏感内容和隐私信息过滤必须单独跑。这里要说的不只是“不良内容”而是一整套合规流程文本里出现手机号、身份证号、银行卡号、家庭住址等个人信息都应当被识别并做脱敏或删除。隐私识别我会用“规则优先模型兜底”的组合正则先抓高置信度的号码、日期、邮箱再让模型识别无法用规则覆盖的长尾形态。涉及版权风险的语料实践上可以做“长引用片段检测”如果一段文本和某本书或某篇文章存在超过若干连续字符的重合就把整条文档踢出训练集。3.4 清洗顺序的排布与工程化清洗流程的先后顺序直接影响效果我的推荐顺序是原始数据→字节校验与转码→语言识别→规则过滤→文档级去重→近似去重→句子级高频过滤→质量打分→隐私过滤→权威内容回填。为什么把语言识别放在规则过滤前因为语言标签可以为后续规则提供上下文比如检测“中文”“英文”“代码”分别用不同的标点统计口径。把去重放在质量打分前是为了节省打分时间——重复数据就没必要再过一遍分类器了。隐私过滤放最后是因为经过前面几轮清洗后文本量已经大幅缩减跑模型识别的时间成本更低。工程上我强烈建议把清洗任务设计成“分片并行”的形态。把原始数据切成2GB左右一个的shard每个shard独立跑完整个清洗流程输出干净的shard。这样既能利用多机多进程也方便对结果做抽样检查。我踩过最大的坑就是刚开始把整个流程写成一个超长脚本单机跑跑了三天发现中间一步规则配错了只能全部返工。改成shard之后哪个切片有问题就重跑那个切片成本完全可控。4. Token化、配比与训练衔接数据不只是“文本”4.1 Tokenizer要和数据一起考虑预训练数据集不能只停留在“干净文本”的层面你还得想清楚分词器怎么处理你的语料。Tokenization决定了模型看到的基本单元。比如中文场景现在主流选择是训练一个BPE或Unigram模型词表大小通常设在32k到64k之间。词表不是越大越好词表大每个token携带的信息更多但embedding矩阵也更大训练和推理的显存开销都会增加词表小文本会被切得更碎长序列的信息密度下降训练难度上升。我的做法是先用文本语料训练候选词表跑一个小规模实验观察中文分词的碎片化程度再决定词表大小。一个小技巧是统计“中文单字作为独立token的比例”。如果这个比例偏高说明分词器把中文切得太碎中文语义会被拆散训练效率会有隐性的损失。适当加入中文词汇到词表或者修改BPE合并规则能把中文token效率从“一个字甚至半字一个token”提升到“一个常见词一个token”。4.2 多领域混合采样比例是一方面采样策略是另一方面配比确定之后真正的难点在采样阶段。假设你的语料里中文只有200GB英文有1000GB你想让中文在训练中占30%总不能直接把中文语料重复采样5次。过度重复会让模型在中文数据上过拟合最后生成的内容虽然“中文感”强但翻来覆去都是那几种句式。正确做法是为每个领域设置“采样概率”同时在训练时对每个样本设置epoch上限。我常用的策略是先把各领域数据量统计清楚设定目标比例然后计算每个领域在每轮中被抽到的权重。如果一个领域的数据量本身不够撑起目标比例那就接受比例折扣而不是盲目复制数据。同时给每条数据加一个访问上限比如最多被采样3次超过就移出采样池。配比的调整也不是一步到位的。我的流程是先用粗配比跑一个几百步的小实验观察每个数据域对loss的贡献再针对性调整。比如代码数据占比15%结果代码域loss很快就降到底而中文域loss还有明显空间那就应该把代码数据的权重下调把中文数据的权重上调。loss曲线在这里是最好的向导。4.3 文本规模估算从token到GB的一笔账规划数据集时经常要先回答“我需要准备多少原始文本”。这里我给出一个实用估算方法。大部分大模型的训练token预算在200B到2T之间。以7B模型为例1T token是比较常规的配置。把token换算成原始文本体积需要考虑语言构成。英文文本平均每token对应3.5到4个字符每个英文字符UTF-8编码占1字节所以1万亿英文token粗算约4TB文本。中文文本平均一个汉字大约对应0.6到1个token每个汉字UTF-8占3字节如果中文占30%整体体积会比纯英文更大1T token的混合中文语料实际体积大概在1.5TB到2.5TB之间。所以当你发现手头可用语料只有几百GB时就别硬撑1T token的训练计划。要么缩小模型和数据预算要么继续扩充语料。用公式化一点的语言说可用文本体积 ÷ 每token平均字节数就能得到大致的token预算。这个估算在项目立项阶段非常有用能避免训练跑到一半发现“数据喂不饱”的窘境。5. 实操流程从原始语料到可训练格式5.1 推荐的数据流水线架构一条我推荐你去复现的流水线大概是这样的原始语料多来源多目录→ 分片shard → 清洗流水线编码、语言、规则、去重、质量、隐私→ 干净shard目录 → 按领域配比采样 → Token化 → 序列打包 → 训练框架输入。在这个架构里每个阶段都输出到独立的目录和元数据文件方便追踪。我强烈建议每个shard都配一个manifest文件记录这个shard包含哪些来源、清洗配置版本号、清洗前后的文档数、总字符数、总token数。有了manifest后面排查问题会轻松一个数量级。比如训练中发现某个数据段的loss异常高我可以顺着manifest快速定位到具体来源而不是大海捞针。5.2 数据格式怎么选JSONL、TFRecord、Parquet训练框架能直接消费的数据格式有很多我在不同阶段用过三套主流方案。最简单通用的是JSONL每行一个JSON对象带text、source、metadata等字段。它的好处是透明、可调试任何脚本都能读非常适合清洗和配比阶段。缺点是体积大、解析慢不适合直接喂给训练框架。训练框架消费阶段我常用TFRecord或者MMap格式。Megatron-LM风格的MMap格式会被预处理脚本直接生成训练时按token索引读取随机访问效率很高。HuggingFace生态下Parquet也是一种不错的选择列式存储压缩比好读取方便很多现代数据加载库原生支持。我自己的实践是清洗阶段全用JSONL因为随时想抽样看一眼都方便配比和Token化之后再转成训练框架原生格式。不要贪图一步到位直接转成二进制前期调试会非常痛苦。5.3 序列打包与断点续训预训练一般使用固定长度序列比如2048个token。但原始文档长短不一短的只有几十个token长的有几十万token。直接截断会浪费大量上下文长文本被截断还会破坏语义连贯性。业界通用的解决办法是sequence packing。把多个短文档拼接成一个固定长度序列文档之间用特殊分隔符隔开并在注意力机制里做掩码让模型不会跨越文档边界做注意力。主流框架像Megatron、LLaMA的预训练脚本大多内置了packing支持。我在实际使用中发现packing能提升约30%的训练吞吐率因为短文本产生的padding token被消除了。特别提醒一点packing时一定要保留文档级元数据。等你需要分析每条样本来自哪个领域、哪个来源时这个元数据就是你的“证据链”。没有它真出了问题就只能靠猜。下面给一个清洗环节的极简示意代码展示如何对单个shard做基础清洗并输出JSONLimport json import re from pathlib import Path def basic_clean(text: str) - str: # 移除控制字符 text re.sub(r[\x00-\x08\x0b-\x1f\x7f], , text) # 移除HTML标签残留 text re.sub(r[^], , text) # 折叠连续空白 text re.sub(r\s, , text).strip() return text def process_shard(input_path: Path, output_path: Path, min_len: int 100): with open(input_path, r, encodingutf-8) as fin, \ open(output_path, w, encodingutf-8) as fout: for line in fin: record json.loads(line) text basic_clean(record.get(text, )) if len(text) min_len: continue record[text] text record[status] cleaned fout.write(json.dumps(record, ensure_asciiFalse) \n) # 实际使用时可按shard分批利用multiprocessing并行这段代码演示的是单shard的基础清洗骨架生产环境里还要把语言识别、去重、质量打分等环节串联上去。我会把每个清洗环节封装成独立函数按顺序组合便于单独调试。多shard并行用multiprocessing.Pool或者提交到任务队列都行关键是不让CPU空等。6. 常见问题与排查经验实录6.1 训练时loss不降或激增先查数据我在预训练过程中遇到最多的问题是“前几步loss降得很快然后突然横盘”或者“某个训练阶段loss出现周期性尖峰”。这两个问题十有八九出在数据上而不是模型上。loss横盘常见原因是数据中隐藏了大量重复或高度相似的样本模型学到一定阶段后开始“背数据”而不是“学规律”。把训练集里的高频重复片段抽样打印出来一眼就能看出问题。另一个原因是语料分布里有极端噪声比如一段几千字的加密乱码被当成正文保留会持续扰乱梯度。loss尖峰的常见原因是数据中出现了一块完全不同的分布。我排查的路径是用manifest定位到尖峰时间点对应的shard和来源再把该shard按序列加载出来人工看几百条样本几乎每次都能找到问题源头。只要发生过一次你就会跟我一样养成“每换一批数据就跑3到5步小训练验证”的习惯这一步便宜且极其有效。6.2 中文语感不对劲多半是数据配比和分词问题有位朋友训练一个中英双语模型结果中文回答一直带有英文句式比如“我有一个问题关于……”这种。我让他做的第一件事不是调参而是统计训练数据里的中英文token比例结果显示中文只有8%。中文语感还没建立起来当然会跟英文的语法结构纠缠在一起。这种情况不用慌我给出的调整路径是先把中文语料占比抬到20%以上再把中文域内的高质量来源书籍、百科、论坛长文比例提高最后检查tokenizer是否把中文切得太碎。如果“中文每个单字独立成token”的比例很高建议直接重新训练一份包含更多中文常见词的词表。顺序不能反数据配比是根源分词优化是手段缺一不可。6.3 清理干净的数据集也要防“脏数据回灌”最后一个提醒是关于数据版本管理的。“清洗过的干净数据”并不是永久的你可能因为某个来源需要补充数据、或者改了清洗规则要重新清洗这时候如果没有版本管理很容易把未清洗的原始数据误当成干净数据直接送进训练脚本。我给团队的规矩很简单每个数据集目录带版本号比如v2_cleaned_20250115原始数据、中间数据、干净数据分三个根目录存放。训练脚本只允许读取特定版本的干净数据路径。任何对清洗配置的修改都必须生成一个新的版本号不能原地覆盖。这样做牺牲了一点存储空间但省掉的排查时间远不止这个数。另外验证集和测试集一定要从干净数据里提前锁一个分片出来绝对不能跟训练集共用样本。验证集的去重也要单独跑一遍防止训练集和验证集出现近似重复样本。否则你会看到一个漂亮但虚假的验证loss等上线才发现模型底下全是水。结尾预训练数据集构建这门手艺说起来就是“清洗、去重、配比、验证”八个字真正做起来会发现每一步都有无数细节在等着你。我在实际维护数据流水线的过程中最深的一个体会是不要想着一步到位也别对第一版数据抱太高期望。先拿几十B token的mini数据集跑通流程训练一个小模型亲自读一读它生成的中文和代码再回头改清洗规则和配比这个迭代周期远远短于一次性把所有数据全部洗到完美再开工。如果让我只给一个建议那就是从项目第一天就给每一条有效语料打上可追溯的来源标签并坚持写shard级别的manifest。这个动作看起来平平无奇却能在后续所有调试、返工、分析场景里反复救你于水火。数据工程没有惊天动地的魔法有的只是一次次细到极致的检查、验证、修正把脏活做扎实模型自然给你正反馈。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑