高质量预训练数据工程:清洗、去重与配比实战指南

发布时间:2026/9/8 11:40:37
高质量预训练数据工程:清洗、去重与配比实战指南 很多人以为预训练大模型就是把海量文本直接扔进 GPU 开始跑真正决定模型上限的往往不是网络结构而是数据工程。斯坦福大模型开发课 EP14 把“高质量预训练数据”作为独立一章来拆核心就落在清洗、去重与配比这三个关键词上。这篇博客不打算逐条翻译课程 PPT而是从工程视角把它整理成一套可以拿走就用的数据流水线方法论并配上可执行的代码示例和踩坑清单。如果你正在收集开源语料、准备自己训练一个小规模模型或者只是好奇为什么每个团队都说“数据比模型更重要”这篇文章能给你一个完整的框架先搞清楚数据清洗在洗什么再用去重保住训练效率和评估可信度最后通过配比策略把数据从“堆料”变成“训练信号”。1. 这篇文章真正要解决的问题很多开发者在接触大模型时第一步不是跑通模型代码而是想办法“搞一批数据”。常见做法是到处下载开源数据集把 C4、The Pile、RefinedWeb、中文开源语料等全部拼接在一起然后直接开始预训练。这个流程表面看没问题实际隐患非常多。数据里混着大量 HTML 标签、乱码、重复段落模型会学到无意义的格式噪声。不同来源之间存在大量重复内容同一个句子可能在训练集中出现几十次不仅浪费算力还会让模型过度拟合高频文本。测试集没有和训练集去重导致评估指标虚高看起来效果好实际一上线就露馅。不同来源的数据按什么比例混合直接决定了模型偏重语言能力、推理能力还是代码能力。很多人凭感觉设定比例实验完全不具备可复现性。这门斯坦福大模型开发课 EP14 的价值在于它把数据工程从“想起来很重要”变成了“有明确方法和步骤”的环节。文章下面会按照 清洗 → 去重 → 配比 三个环节配合代码思路逐步拆解。读完之后你至少能回答三个问题自己的数据到底脏在哪重复数据该怎么去除不同来源的数据按什么经验规则混合最稳妥。2. 数据工程质量为什么决定模型上限在开始讲操作之前先建立一个核心判断模型结构决定能力下限数据质量决定能力上限。这句话听起来有点绝对但放在预训练场景里基本成立。以 Transformer 为基础架构的模型在过去几年里结构变化并不大真正拉开差距的往往是训练数据。同样的参数量、同样的算力数据清洗和配比做得好模型在常识推理、代码生成、多语言能力上的表现会明显更好。从训练动态的角度看也能解释清楚。模型本质上是在学习训练数据的分布如果数据里都是重复文本模型就是在死记硬背而不是泛化。低质量文本包含大量错误信息和不规范表达模型会把这些噪声吸收进参数后期即使有对齐阶段也很难完全消除。各语料来源的信号分布不同比如代码数据更强调逻辑一致性百科数据更强调事实密度如果配比不合理模型会出现偏科。所以数据清洗、去重和配比不是训练的辅助环节它们就是训练本身的一部分。很多团队在“预训练后对齐”阶段花大量精力去修复模型坏习惯但根子可能早在数据准备阶段就埋下了。这里可以做一个类比把模型训练想象成做饭。模型结构是锅和灶算力是火力数据是食材。锅再高级、火再大食材里有沙子、有腐烂的叶子、有洗不干净的农药残留做出来的菜一定不行。数据清洗就是择菜洗菜去重是把重复的菜叶挑出来配比则是决定荤素搭配的比例。一道菜最后好不好吃很大程度取决于后厨怎么处理食材而不是灶台有多贵。有了这个背景下面三个环节就非常清晰了。3. 数据清洗从“能用”到“好用”的三层手段3.1 数据清洗到底在处理什么很多人以为数据清洗就是把明显的乱码删掉。实际上预训练场景下的数据清洗要复杂得多它至少包括三个层次第一层是格式清洗。爬虫抓下来的网页数据带有一堆 HTML 标签、超链接、导航信息、脚本代码需要提取正文并统一格式。常见手段包括去除标签、过滤过短文本、修正编码问题、删除控制字符。第二层是规则清洗。根据统计规则过滤明显低质量的内容。比如全英文大写占比过高的文本、重复字符过多的文本、句子长度分布异常的文本、包含脏话或违规内容的文本。这一层可以靠正则表达式和简单的统计函数完成成本低、见效快。第三层是语义清洗。规则处理不掉的内容依赖分类模型来判断质量。比如一篇看起来完整但逻辑混乱的文章或者机器翻译痕迹明显的文本规则很难捕捉需要训练一个质量分类器。常用做法是人工标注一小批高质量和低质量样本训练一个简单的文本分类模型再对全量数据打分。3.2 三个层次分别怎么做实际操作中这三个层次是依次执行的每一层解决不同的问题。第一层格式清洗典型代码逻辑如下# 文件路径data_pipeline/step1_format_clean.py import re def clean_text(raw: str) - str: # 去除 HTML 标签 text re.sub(r[^], , raw) # 去除多余空白字符 text re.sub(r\s, , text) # 去除不可见控制字符 text .join(ch for ch in text if ch.isprintable() or ch in \n\t) return text.strip()这一步看起来简单却是整个流水线里收益最高的操作之一。如果你从 Common Crawl 这类来源取数正文提取质量直接决定了后面所有环节的输入质量。真实项目中一般会用 trafilatura 或读 litt 抽取正文而不是自己写正则硬刚。第二层规则清洗可以定义一组过滤条件。# 文件路径data_pipeline/step2_rule_filter.py def is_low_quality(text: str) - bool: # 过滤过短文本 if len(text) 100: return True # 过滤重复字符过多的文本例如 aaaaaa 或 哈哈哈 if max_repeat_ratio(text) 0.1: return True # 过滤标点符号比例过低的文本 if punctuation_ratio(text) 0.01: return True return False def max_repeat_ratio(text: str) - float: # 一个简化示例统计单字符重复比例 if not text: return 0.0 return max(text.count(ch) for ch in set(text)) / len(text) def punctuation_ratio(text: str) - float: import string punc set(string.punctuation) return sum(1 for ch in text if ch in punc) / len(text)第三层语义清洗现实中通常用分类模型。这里不推荐直接自己从头训一个复杂模型更稳妥的路径是先用规则过滤把明显低质量的数据清掉再用一个 lightweight 的二分类模型给文本质量打分设定阈值完成筛选。3.3 不要只盯着英文数据如果用中文语料清洗逻辑还要额外注意几句话中文没有天然空格需要用分词或字符统计方式判断句子完整性繁体简体混用文本尽量统一从 PDF 抽取的中文文本经常出现全角半角混用、段落断裂的问题。这些看起来是小问题但大规模拼接后模型会学到不稳定的字符分布。另外要留意数据版权和合规问题。清洗环节不负责解决版权但如果你把数据流水线做到生产级别清洗阶段就应该顺带记录每一条数据的来源和授权状态而不是等模型发布前再去清理。3.4 小结论数据清洗不是一次性动作而是一套分层策略。格式层解决“能不能读”规则层解决“像不像正常文本”语义层解决“值不值得学”。越往后越贵所以工程上要从便宜的规则开始先用规则过滤大部分垃圾再用模型处理边界情况。4. 去重消除冗余守住评估可信度清洗是去掉“明显坏”的数据去重则是去掉“重复好”的数据。很多人对去重不够重视认为多重复几遍没有坏处。实际上重复数据对预训练的影响非常大。训练重复数据会放大某些句子的权重导致模型对高频表达过拟合。重复数据浪费算力同一段文本被重复计算多次训练效率下降。最关键的问题是训练集与测试集不进行去重评估结果完全不可信。4.1 文档级去重还是句子级去重去重要先定粒度。文档级去重是判断两个文档是否近似重复句子级去重是判断同一句话是否在不同文档中反复出现。实际操作中两者都要做顺序一般是先文档级后句子级。文档级去重处理的是“同一篇文章被多个来源转载”的情况。这种文档往往整体结构相似局部略有差异。句子级去重处理的是“同一句名言、同一段代码片段在大量文档里反复出现”的情况。比如某句法务声明可能出现在几十万个网页底部如果不去除模型会认为这句话出现概率极高。4.2 精确去重与模糊去重最简单的去重是精确匹配比如对整篇文档做哈希完全相同的直接保留一份。这个方案只能对付完全相同的文本一旦文档里有细微改动就会漏掉。真正需要关注的模糊去重。常用方法包括 SimHash、MinHash 等核心思想是对文本做特征抽取然后用 Jaccard 相似度判断两篇文档是否重复。MinHash 是工程上最常用的方法之一配合 LSH局部敏感哈希可以处理千万级别文档的去重。这里给一个 MinHash 的工程示意。示例代码使用 datasketch 库如果在实际项目中使用请以该库的当前文档为准。# 文件路径data_pipeline/step3_dedup.py from datasketch import MinHash, MinHashLSH import hashlib def shingles(text: str, k: int 5): # 将文本切分为连续 k 个字符的片段作为特征 text text.replace( , ) for i in range(len(text) - k 1): yield text[i:i k] def create_minhash(text: str, num_perm: int 128): m MinHash(num_permnum_perm) for shingle in shingles(text): m.update(shingle.encode(utf-8)) return m # 示例构建 LSH 索引 lsh MinHashLSH(threshold0.8, num_perm128) documents { doc_1: 大模型训练的重复数据会降低训练效率, doc_2: 大模型训练中重复数据会导致训练效率下降, doc_3: 今天天气很好适合去公园散步 } minhashes {doc_id: create_minhash(doc) for doc_id, doc in documents.items()} for doc_id, mh in minhashes.items(): lsh.insert(doc_id, mh) # 查询与 doc_1 相似的文档 result lsh.query(minhashes[doc_1]) print(result)这段代码至少说明了一个关键点模糊去重本质上是把文本相似度问题转化成了集合相似度问题。先用 shingle 把文本切碎成特征集合再用 MinHash 估算集合的 Jaccard 相似度最后用 LSH 加速查找。从工程角度看超过千万级别的文档最好不要单机跑可以用 Spark 的 MinHash LSH 实现分布式处理或者用支持分片的 LSH 索引。数据量不大时直接单机跑 Python 脚本也没问题。4.3 训练集与评估集去重这一条在公开讨论中经常被忽略但实际影响极大。如果你用某个公开测试集来评估模型能力而这个测试集里的文本恰好也出现在训练语料中那评估出的指标就不是真正的泛化能力而是记忆能力。正确做法是在训练前就把所有评估集和训练集做一次去重。具体策略包括将评估集作为查询集合训练集作为被查询集合。对评估集中的文档或句子做 MinHash 特征与训练集进行比较。相似度高于阈值的训练样本一律删除或者在记录中打上标记不参与训练。这一条不仅适用于公开测试集也适用于你自己构建的私有评估集。评估集越干净后续实验结论越可信。4.4 小结论去重是数据流水线里最容易被偷懒的环节。有人觉得“我的数据来源不同不会重复”但现实中不同来源互相转载、引用、拼接的情况非常常见。去重不仅省算力更重要的是保住训练数据分布的纯净度和评估数据的可信度。5. 配比策略数据科学的最后一个关键环节5.1 配比为什么重要清洗和去重确定了每一份数据的质量配比则决定了不同质量、不同领域数据的混合比例。这个部分在 EP14 里被重点提出也是很多团队在实践中容易拍脑袋做决定的地方。一个简单的直觉是训练数据里有百科、新闻、代码、书籍、论坛对话如果代码数据占 80%模型可能特别擅长写代码但语言表达很生硬如果新闻数据占 80%模型可能对常识和代码完全无感。合理的配比能让模型均衡发展不合理的配比会让某方面能力严重偏科。更微妙的问题是不同来源的数据并非独立贡献。代码数据不仅提高代码能力也会增强逻辑推理能力书籍数据增强长文本理解和叙事能力多语种数据会影响模型的语种分布。配比策略实际上是在为最终模型的能力画像做规划。5.2 常见配比策略与数据配比图下面用一个表格对比几种常见配比思路。配比思路核心理念适合场景风险点按来源自然比例各语料库有多大就按多大比例混合快速起步、结构简单容易偏向数据量大的来源忽略语料价值人工经验配比根据下游任务方向手工调整各类比例有明确目标场景的中小团队依赖经验泛化能力不确定按 token 比例不是按文档数而是按 token 数计算比例需要精确控制训练计算量需要额外统计工程复杂度增加动态调整训练过程中根据 loss 或下游指标调整采样比例已具备完整评估体系的团队实验复杂度高需要监控能力实际工程中很少只用单一策略。主流做法是先定义一个基础配比再基于评估结果微调。课程里经常出现“数据配比图”这类可视化工具它的本质是把训练数据按来源、语言、领域切分后用图表展示每个分片的占比并在训练不同阶段观察模型的偏好变化。这类图表非常直观可以帮助团队快速定位“模型为什么突然偏科”的问题。5.3 价格比和采样策略配比策略里有一个关键概念unsupervised data contributes to different capabilities at different marginal returns. 通俗说不同领域的数据对模型能力的边际收益不同。代码数据在提升推理能力上的边际收益通常高于继续增加百科类数据。但这不意味着代码数据可以无限增加因为不同领域数据之间还存在干扰。常见的一个现象是代码数据比例过高时模型的自然语言流畅度会下降。这就需要用配比策略做平衡。在实操中我们可以把配比问题转化为带权重采样问题。不需要真的把所有数据混合成一个大文件而是每次采样 batch 时按配比概率从不同数据源抽取数据。下面给出一个简单的 batch 采样示例# 文件路径data_pipeline/step4_mixture_sampling.py import random # 假设三个数据源权重分别表示每个来源的采样占比 sources { wikipedia: {path: /data/wiki.jsonl, weight: 0.3}, code: {path: /data/code.jsonl, weight: 0.5}, news: {path: /data/news.jsonl, weight: 0.2}, } def sample_one_source(): total_weight sum(item[weight] for item in sources.values()) r random.uniform(0, total_weight) upto 0 for name, item in sources.items(): upto item[weight] if upto r: return name return list(sources.keys())[-1] # 只是示意真实流水线里这里会打开文件读取对应样本 for i in range(10): src sample_one_source() print(fbatch {i}: sample from {src})这段代码的核心思想是配比不是把数据文件硬切而是通过带权重的采样器在训练循环里动态决定每一 batch 主要来自哪个来源。当你想调整配比时只改权重即可不需要重新生成数据集。5.4 训练数学上的“降采样”操作一个容易被忽略的问题是有些数据源已经很大如果按原始 token 数参与训练可能一个 epoch 都跑不完模型已经见过太多重复模式。常见的解决办法是设置降采样比例。比如某代码数据集有 100B token但你认为它只需要在训练中占 10% 的比例就可以只采样其中的一部分而不是全部送进训练。降采样比例和 epoch 次数要一起考虑。如果多个数据源都被降采样实际训练数据量会变小模型对某些领域的覆盖可能不足。反之如果数据源很小可能需要提高采样次数让它多被重复几轮。这个平衡没有通用答案需要靠下游评估来验证。5.5 配比实验怎么做配比策略的制定一定要有实验闭环否则就是拍脑袋。比较推荐的流程是先确定目标能力维度。例如希望模型在代码生成、中文问答、英文理解上的表现分别达到什么水平。设定两到三个候选配比方案。不要一上来就十个方案先跑小规模实验。在相同训练步数下做对比训练。控制变量只改配比其他超参数保持一致。用统一评估集评测观察不同维度指标的变化。根据评测结果调整配比重复迭代。这里最关键的是“同一训练步数”这个条件。如果两个实验训练步数不一样模型能力的差异就很难归因到配比策略上。所以配比实验最好配套一个固定的训练预算例如固定训练 100B token然后调整配比看结果。5.6 小结论配比策略没有完美的固定公式但它有一套可迭代的方法论明确目标能力设定候选配比控制变量实验评估调整。比起凭感觉混合数据这种工程方式虽然慢但每一步都能积累可复现的经验。6. 数据流水线的工程化落地6.1 从脚本到流水线很多初学者把数据清洗、去重、配比理解成三个独立的 Python 脚本每个脚本跑一遍出结果就行。真实项目中这样做会带来两个问题一是数据量大了以后单机跑不动二是脚本输入输出没有规范后续无法追溯。工程化落地时至少要保证以下几点每个处理阶段都有明确的输入输出格式比如统一为 JSONL每一行是一条带 id 的样本。每个阶段可断点续跑。清洗跑到一半断了不需要从头开始。每个阶段输出样本数量、过滤比例等统计信息方便检查。这里可以给一个简单的命令思路数据流水线不一定要一步到位设计得非常复杂。一个可用的起点是先把清洗脚本、去重脚本、采样脚本分开每个脚本固定读取某个目录、输出某个目录再用 shell 串起来跑。# 文件路径run_pipeline.sh python step1_format_clean.py --input /data/raw/ --output /data/clean/ python step2_rule_filter.py --input /data/clean/ --output /data/filtered/ python step3_dedup.py --input /data/filtered/ --output /data/dedup/ python step4_mixture_sampling.py --config configs/mixture.yaml --output /data/final/这个流水线还比较粗糙但已经具备三个好特征每一阶段可独立运行、输入输出路径清晰、可以用配置文件控制配比参数。在数据量扩大到需要分布式处理之前这个程度完全够用。6.2 数据版本与可追溯性数据工程里最容易被低估的模块是数据版本管理。训练数据每天都在变清洗规则也在迭代如果不记录每一版数据对应的清洗规则和配比配置后续根本说不清楚某个模型到底用了哪些数据训练的。推荐做法是给每个数据版本都记录一份 manifest 文件里面至少包含数据来源列表和各自的 token 数。清洗规则版本号。去重阈值参数。配比权重配置。数据准备时间。处理脚本的 git commit hash。有了这份 manifest模型评估时如果发现异常就能快速定位是数据问题还是模型问题而不需要翻聊天记录和脚本历史。6.3 小结论数据流水线工程化不等于要用多牛的平台先把目录规范、断点续跑、统计信息和版本记录做到位就已经超过大部分临时脚本方案了。这部分的收益不是立刻体现在模型指标上的而是体现在团队排查问题的效率上。7. 常见问题与排查思路下面整理几个数据预处理过程中常见的问题和排查路径。问题现象可能原因排查方式解决方案清洗后文本仍有大量乱码原始编码判断错误抽样查看原始数据与清洗后数据统一编码为 UTF-8对无法解码的样本直接丢弃过滤规则误杀太多正常文本阈值设置过严统计各规则过滤比例放宽阈值增加人工抽检使用小样本标注验证去重后数据量骤减模糊去重阈值太低检查相似度分布调高阈值优先去掉近似重复而不是所有相近文本训练集评估集没去重流水线遗漏该步骤检查数据准备脚本是否包含评估集去重将评估集作为查询集合重新执行去重模型在代码任务上表现差配比中代码数据占比过低查看数据配比图和 token 统计提高代码数据权重重新训练或继续训练中文文本质量差仅用通用规则清洗缺少中文专项规则抽样中文样本检查断句和乱码增加繁简转换、全角半角统一、中文标点规范处理训练到一半发现数据有重复段落缺少句子级去重抽取批量样本做 n-gram 重复率统计增加句子级去重对重复率高的数据源降采样这些问题的共性是都需要先有统计信息才能定位。所以在写清洗脚本的时候每个阶段最好都输出过滤比例和抽样结果不要等模型训练完了再回头看数据。8. 最佳实践与工程建议结合前面的内容这里汇总几条做数据流水线时的工程建议。8.1 数据优先模型其次训练一个新模型之前先花时间把数据质量做到位比调模型结构更有效。数据决定了模型能力的上限模型结构再先进也很难弥补数据里的系统性缺陷。8.2 每一步都要可量化和可追踪清洗过滤比例、去重保留比例、配比权重、训练 token 数这些数据都要记录。没有量化就没有判断没有追踪就无法复现。8.3 先小规模跑通再上大规模很多团队一上来就把几百 TB 数据塞进流水线结果洗了一个星期发现规则写错了所有输出都要重跑。正确做法是先拿小样本验证脚本逻辑确认各环节输出符合预期再全量执行。8.4 数据安全与合规处理数据时要关注来源的版权和授权条件尤其在商用模型场景下更要注意。数据清洗、去重、配比环节应该记录每条数据的来源信息模型发布时才能提供清晰的数据合规报告。对于涉及隐私或个人身份信息的文本应按照相关法规进行脱敏或删除不要为了追求数据量而把风险带进训练集。8.5 权限与生产环境变更如果你在团队里负责数据流水线涉及生产环境的数据删除、大规模去重任务或数据库变更时务必遵循最小权限原则。先在测试环境用相同代码跑一小批数据确认删除或覆盖逻辑正确后再操作生产数据并提前做好备份和回滚方案。数据删除类操作尤其要谨慎宁可先标记后删除也不要直接覆盖原始文件。8.6 评估集不要污染一旦开始做配比实验和模型迭代评估集就要冻结并且要保证评估集和训练集严格去重。评估集一旦被污染后续所有实验对比都会失真浪费大量算力和时间。8.7 避免规则越来越复杂清洗规则很容易越加越多最终变得难以维护。建议定期统计每条规则过滤的数据量删除过滤量几乎为零的规则保持流水线简洁。能用权重解决的问题不要堆规则。9. 总结与后续学习方向这篇文章围绕斯坦福大模型开发课 EP14 的核心议题把高质量预训练数据拆成了清洗、去重、配比三个环节。清洗解决数据“脏”的问题去重解决数据“重复”和“评估失真”的问题配比解决模型“偏科”的问题。三者合在一起才是一条完整的数据工程流水线。如果你打算从零搭建一套预训练数据流水线可以先做三件事把自己的数据来源列成清单统计数量、格式和明显质量差异。先写一个格式清洗和规则过滤的小脚本处理 1 万条样本人工检查过滤效果。选定一个评估集立即做训练集与评估集的去重。数据工程没有终极方案但每一步都可以做得更扎实。后续如果想继续深入可以关注这些方向分布式去重框架的设计、训练过程中动态配比调整、数据质量分类模型的训练、基于 loss 的数据贡献分析。这些内容单拎出来都够单独写几篇文章先把这一篇里的基础流水线跑通再往这些方向延伸会更顺。建议收藏本文等到真正动手准备预训练数据时对照着每一步检查能少踩很多坑。