
预训练数据的“脏”与“净”为什么数据清洗、去重与配比决定了模型上限如果你是做 LLM 应用开发或正在研究大模型预训练大概率已经听过一句话数据决定了模型的上限模型架构和训练技巧只是在逼近这个上限。这句话在学术界和工业界几乎已经变成共识。这次我们来看斯坦福大模型开发课 EP14 的内容核心就三个词数据清洗、去重、配比策略。它们不是可有可无的预处理步骤而是真正决定模型知识容量、泛化能力和训练稳定性的关键环节。很多初学者下载到开源语料后第一反应是直接跑分词和训练脚本结果训练到一半 Loss 抖动、模型输出全是重复文本或者跑完一个阶段后困惑度死活下不来。问题往往不在模型结构而在喂进去的数据太脏。这篇内容会把预训练数据工程的完整链路拆开讲清楚包括数据从采集到筛选、清洗、去重、配比评估的每一步以及每一步有哪些工程上可行的做法。从材料看这门课聚焦的是高质量预训练数据所以文章会围绕三个核心环节展开数据清洗解决语料质量问题去重解决信息冗余问题配比策略解决知识结构失衡问题。三件事互相独立但又彼此影响。比如过度去重会丢失低频但关键的领域知识而如果配比没调好再怎么清洗也无法提升特定能力。文章整体偏工程实践向适合正在准备预训练数据集、复现开源模型训练流程或者打算用开源语料做领域微调的同学阅读。接下来从数据工程的整体流程开始拆解。1. 预训练数据工程核心能力速览能力模块主要内容关键方法/工具思路对模型的影响数据采集获取原始语料爬虫、开源数据集、OCR、语音转写决定数据的上限和覆盖度语言过滤去除低质量语言FastText 语言识别、启发式规则减少无效 token提升有效数据密度质量清洗去 HTML、去广告、去噪声规则引擎、分类器评分、困惑度过滤降低训练噪声提升 Loss 收敛效率数据去重消除重复和近似重复文本精确去重、MinHash、SimHash 模糊去重防止训练重复、降低记忆过拟合隐私/安全过滤移除个人信息和有害内容PII 检测、敏感词表、人工抽检合规红线影响模型安全数据配比调整各来源/领域数据比例自然分布、过采样、课程学习控制模型的知识结构和能力偏向质量评估验证数据是否可训练小规模训练、Loss 观测、下游任务验证提前发现数据问题避免浪费算力从这张表可以看到数据工程不是某一个步骤而是一条完整的流水线。很多开源项目比如 RedPajama、RefinedWeb核心工作就是把这套流程流水线化。斯坦福课程 EP14 的重点就是讲清楚这条流水线里每一步为什么存在、怎么做、踩过哪些坑。2. 为什么数据清洗是预训练的第一步2.1 原始语料到底有多脏互联网爬取语料看起来体量巨大但实际质量参差不齐。以 Common Crawl 为例里面包含大量 HTML 标签残留、重复导航栏、广告文本、乱码字符串、机器生成的垃圾内容甚至恶意注入文本。如果直接把这些数据送进 tokenizer结果就是模型学到大量无意义的字符模式训练效率极低。从课程里的思路来看数据清洗要解决的是去掉模型不需要学习的内容而不是去掉所有看起来不像自然语言的内容。比如代码语料中本来就有特殊符号、Markdown 标记、JSON 结构这些对代码模型反而是有效特征。所以清洗规则必须和数据来源强相关不能一套规则打天下。2.2 清洗的典型层级数据清洗通常分三个层级第一层格式清洗这一步处理最基础的格式问题包括去 HTML 标签、统一编码为 UTF-8、转换全角半角、清理控制字符、截断过长文本、合并断行等。PySpark 或 pandas 都能做这类批处理但工程上通常用 PySpark 做分布式处理因为预训练语料动辄几十 TB单机处理不现实。# 简单示例用 Python 清洗单条文本的 HTML 标签 import re def clean_html(text): # 去掉 script 和 style 标签块 text re.sub(rscript.*?/script, , text, flagsre.S) text re.sub(rstyle.*?/style, , text, flagsre.S) # 去掉其余 HTML 标签 text re.sub(r[^], , text) # 还原常见 HTML 实体 text text.replace(amp;, ).replace(lt;, ).replace(gt;, ) return text.strip() sample htmlbodypHello bWorld/b/p/body/html print(clean_html(sample)) # 输出: Hello World第二层语言与内容过滤用 FastText 语言分类模型识别语言过滤掉非目标语言的文本。内容过滤则针对色情、暴力、赌博、政治敏感等不合规内容通常用关键词黑名单和分类模型双重过滤。这一层同时也是合规的关键节点。第三层质量评分过滤这是清洗的核心升级。很多高质量数据集方案采用一个分类器或质量评分模型对文本质量进行打分。比如 RefinedWeb 用 Perplexity困惑度作为质量指标困惑度太低的文本往往是重复的垃圾内容困惑度太高的文本往往是随机噪声取中间区间的文本作为高质量语料。课程里强调的一个重要思想是清洗目标不是“追求最干净”而是“保留可学习的信息”。过度清洗会删除代码、数学公式、非规范表达等内容而这些恰恰是让模型具备多样性和泛化能力的东西。正确的做法是分层清洗对不同来源数据设置不同的规则强度。2.3 操作层面的清洗流程实际搭建清洗 Pipeline 时可以按照下面的顺序操作解析原始文件格式WARC、JSONL、Parquet。提取正文内容去掉导航、页脚、广告等噪声区。统一字符编码清理控制字符。语言识别筛选目标语言文本。规则过滤去除过短、过长、重复字符比例过高的样本。质量分类器评分按阈值筛选。输出清洗后数据按来源和主题分区存储。# 示例使用 pyarrow 读取 parquet 格式的语料文件 python -c import pyarrow.parquet as pq table pq.read_table(path/to/corpus.parquet) df table.to_pandas() print(df.shape) print(df.columns) 如果数据条数非常多建议对清洗前的数据进行抽样检查先人工查看 100 到 200 条样本确认噪声类型再制定清洗规则。这一步能省下大量重复调试时间。3. 预训练数据去重精确去重与模糊去重3.1 为什么要去重重复数据对预训练的影响常常被低估。一个直观的问题如果语料中同一个网页出现 10 次模型会花同样的算力反复学习这段内容但学习到的信息增量是零。更严重的是重复数据会导致模型过拟合到某些特定表达方式降低泛化能力同时推高训练困惑度。从另一个角度看大语言模型的记忆能力很强训练集中重复出现的样本会被“背下来”。这在生成场景中表现为回答内容大量复述训练语料的原文而不是理解后组织语言。因此去重不仅是节省算力也是在控制模型的记忆与泛化平衡。3.2 精确去重 vs 模糊去重精确去重精确重复是指完全相同或只差少量字符的文本。工程上最简单的做法是对文本内容做哈希比如 MD5、SHA256然后比较哈希值。但精确哈希对任何一点字符变化都非常敏感比如正文里多了一个空格或者换行符就判断为不同样本。更实用的做法是先对文本做归一化比如去掉所有空白字符、统一大小写、去除标点符号再做哈希。这样可以把“内容相同但格式不同”的文本识别为重复。import hashlib def get_dedup_hash(text: str) - str: normalized .join(text.split()).lower() return hashlib.sha256(normalized.encode(utf-8)).hexdigest()模糊去重互联网语料中更多重复是近似重复同一篇新闻被不同网站转载标题格式不同正文中加了网站前缀或版权声明。这种情况哈希方法无能为力需要用到模糊去重算法。课程里提到的方法是 MinHash最小哈希配合 LSH局部敏感哈希。核心思路是把文本切分成 n-gram 集合对每个 n-gram 做哈希取集合中最小的几个哈希值作为文本的“指纹”。两篇文本的指纹重叠比例越高越可能近似重复。# MinHash 思路演示生成文本的 n-gram 哈希集合 def shingles(text: str, k: int 5): tokens text.split() for i in range(len(tokens) - k 1): yield .join(tokens[i:ik]) def minhash_fingerprint(text: str, num_perm: int 128): import hashlib hashes [] for shingle in shingles(text): h int(hashlib.md5(shingle.encode(utf-8)).hexdigest(), 16) hashes.append(h) if not hashes: return [] return sorted(hashes)[:num_perm]这里要注意上面的代码只是演示算法思路。真实的大规模场景下需要用到 datasketch 库的 MinHashLSH或者 Spark 的近似去重实现而不是自行实现全量哈希。课程强调的重点是理解 MinHash 的相似度语义而不是重复造轮子。3.3 去重层级文档级、段落级、句子级去重不只是文档层面的操作。课程里区分了三个层级的去重文档级去重处理完全重复或近似重复的网页/文档。段落级去重有些文档内容不重复但其中某一段落来自重复来源这时候需要在段落粒度做去重防止同一段文字在大量文档中以不同上下文出现。句子级去重用于处理固定表达的泛滥比如大量网页都有“欢迎关注我们的公众号”这类句子。这类句子对训练无益优先剔除。实际工程中可以先用精确去重做一遍粗筛再用 MinHash 做模糊去重最后在段落级和句子级做细致清理。3.4 去重的度不是越干净越好课程里特别提醒一个问题去重力度太强会损害模型的领域知识覆盖。比如某些长尾知识只在极少数页面中出现如果和类似文本一起被误判为重复就会被删除。这会让模型在特定领域的能力进一步下降。更稳妥的做法是对于通用文本采用严格的去重策略对于领域专业文本比如医学、法律、数学采用宽松的去重阈值优先保留多样性。去重阈值通常是基于采样实验确定的而不是拍脑袋定一个相似度数字。4. 数据清洗与去重的工程实现从 HuggingFace 到分布式流水线4.1 用 HuggingFace Datasets 做中等规模清洗对于中等规模的数据集百 GB 级别直接用 HuggingFace 的 datasets 库就可以完成大多数清洗和去重操作。它的 map 操作支持多进程处理简单易用。from datasets import load_dataset dataset load_dataset(json, data_filesraw_data.jsonl, splittrain) # 定义清洗函数 def clean_example(example): text clean_html(example[text]) # 过滤过短文本 if len(text.split()) 20: return {text: None} return {text: text} # 过滤无效数据 dataset dataset.map(clean_example) dataset dataset.filter(lambda x: x[text] is not None) # 精确去重 def dedup_hash(example): example[hash] get_dedup_hash(example[text]) return example dataset dataset.map(dedup_hash) dataset dataset.unique(hash) # 这一步会触发全量哈希比较 dataset.save_to_disk(cleaned_data)这只是单机可运行的最小流程。真正需要优化的点是 map 函数的 num_proc 参数和内存管理避免一次加载过多数据。4.2 大规模场景下的分布式处理思路当数据规模到 TB 级别单机方案就失效了。课程推荐用 Spark 或者 Ray 做分布式数据管道。Spark 的 DataFrame 天然支持大规模数据操作配合 minhash 库可以搭建分布式去重 Pipeline。# 用 Spark 做语言过滤的伪代码示例 from pyspark.sql import SparkSession from pyspark.sql.functions import udf from pyspark.sql.types import StringType spark SparkSession.builder.appName(data_cleaning).getOrCreate() df spark.read.json(s3://raw_data/) udf(StringType()) def language_filter(text): # 这里调用 fasttext 模型 lang detect_language(text) return text if lang zh else None cleaned df.withColumn(text, language_filter(df[text])) cleaned.write.parquet(s3://cleaned_data/)Spark 方案的重点不是代码本身而是资源规划和任务调度。几十 TB 数据的 Full Scan 即使分布式也要数小时甚至数天。务必要先做数据采样验证确认清洗规则不会误删关键内容再全量运行。5. 预训练数据配比策略自然分布还是人为干预5.1 为什么不能“有多少喂多少”很多人会问语料各来源按原始体量直接混合不就行了吗课程给的答案是绝对不行。原因有三个。第一不同来源的数据质量差异极大。维基百科质量高但体量小Common Crawl 质量参差但体量大。如果按自然分布混合低质量网页会在训练语料中占据绝对主导模型学到的语言风格会更偏向噪音较多的低质量内容。第二数据分布和任务需求不完全一致。如果目标是做一个通用助手需要更多对话数据和指令数据如果目标是代码模型那么通用网页语料再大也不如代码语料重要。自然分布不会考虑你的模型定位。第三过度强调某类数据会导致灾难性遗忘或能力偏移。一个典型例子是如果训练语料中英文占比超过 95%中文能力会严重受损。所以需要人为调整配比保证多语言、多领域的相对平衡。5.2 课程中的配比思路奖赏函数与重复采样配比策略的本质是一个优化问题在训练 token 预算固定的情况下如何分配各领域数据让模型在目标能力上表现最优。常见的做法是用“奖赏函数”评估某个领域的数据在训练后对模型能力的提升幅度然后按奖赏高低动态调整权重。具体到实操层面比常见的策略是过采样与降采样对于高质量但量少的数据比如维基百科、书籍、代码可以设置较高的采样权重让它们在训练轮次中出现多次。对于低质量的网页数据可以降低采样权重或者在每轮训练中只抽样部分数据。课程学习Curriculum Learning训练前期多喂通用高质量语料让模型建立基础语言能力后期逐渐增加领域语料和复杂文本强化专业知识。这种配比策略和训练阶段绑定效果好于静态配比。动态配比根据训练中某个领域数据的 Loss 变化动态调整采样权重。如果某个领域 Loss 下降很快说明模型已经掌握得差不多可以降低该领域权重如果 Loss 一直居高不下说明数据难度过高需要增加该领域数据或做数据增强。5.3 实际配比参考维度在工程实践中配比调整通常从以下几个维度切入维度考虑因素常见做法语言目标用户使用的主要语言中文模型提高中文数据权重英文保持基础比例来源网页、书籍、论文、代码、对话书籍和论文权重高于普通网页领域通用知识、医学、法律、数学、代码根据产品定位增加领域数据质量高质量精选 vs 低质量网页高质量数据可重复采样多次长度短文本 vs 长文本保证模型能够处理长上下文需要提高长文本比例时间新数据 vs 旧数据近期数据比例提高降低知识过时影响配比不是一次定死而是需要多轮训练实验验证。如果资源有限可以从记录 Baseline 开始固定模型结构不变只调整数据配比对比不同配比下的下游任务表现。这是成本最低的验证方式。6. 数据质量评估训练前怎么知道数据能不能用数据清洗、去重、配比都做完后绝对不能直接启动大规模训练。课程里反复强调Data-Centric Evaluation即在正式训练前对数据质量做量化评估。主要手段包括6.1 困惑度过滤器困惑度是衡量一段文本在语言模型下“意外程度”的指标。用已经训练好的小型语言模型计算每段文本的困惑度可以发现异常文本。困惑度极低的文本往往是重复套话困惑度极高的文本往往是乱码或非自然语言。# 示例用 GPT-2 计算文本困惑度 from transformers import GPT2LMHeadModel, GPT2Tokenizer import torch import math model_name gpt2 tokenizer GPT2Tokenizer.from_pretrained(model_name) model GPT2LMHeadModel.from_pretrained(model_name) model.eval() def compute_perplexity(text: str) - float: inputs tokenizer(text, return_tensorspt) with torch.no_grad(): outputs model(**inputs, labelsinputs[input_ids]) loss outputs.loss return math.exp(loss.item()) text 这是一个用于测试困惑度的文本。 print(fPerplexity: {compute_perplexity(text):.2f})实际项目中可以拿一小部分模型输出和人工判断做对比确定一个合理的困惑度阈值范围再应用到全量数据。6.2 重复率 Statistic在去重之后统计 n-gram 重复率。如果数据集内部重复率依然偏高说明去重步骤没有生效。一个简单指标是“重复 token 比例”即训练语料中多少个 token 在前面已经出现过。重复率过高时模型容易学会复读机行为。6.3 小型代理训练实验最可靠的评估方式还是小规模训练。取清洗后数据的 1% 到 5%跑一个小模型比如 100M 到 500M 参数观察训练 Loss 曲线形态。正常情况下训练 Loss 应平滑下降验证集困惑度同步下降。如果 Loss 出现周期性尖峰或者验证集上快速过拟合说明数据存在明显问题。# 小规模训练验证命令示例需要按实际框架调整 python train.py \ --model_config small \ --data_path ./cleaned_data_sample \ --max_steps 5000 \ --save_steps 1000 \ --logging_steps 100这个代理模型不需要训练完全收敛跑几千步就够了。重点观察前 1000 步的 Loss 走势如果连小模型都无法稳定收敛后面的全量训练大概率也会出问题。7. 数据 Pipeline 的模块化设计课程里涉及的清洗、去重、配比流程在实际项目中不是一次性脚本而是长期运行的流水线。最常见的工程架构是原始语料 - Raw Storage - 解析与格式清洗 - 语言过滤 - 质量评分过滤 - 精确去重 - 模糊去重 - 隐私过滤与人工抽检 - 配比采样器 - 训练数据包按 shard 存储每一层都是独立的模块可以单独配置、单独重跑。这样做的原因是数据质量问题是动态的新增一批语料后可能只需要重新跑质量过滤和去重不需要把全流程从头再执行一遍。实际项目里建议用 DVCData Version Control或类似工具管理数据管线的版本。每调整一个清洗规则或去重阈值就记录一次数据版本。训练复现时确保模型训练代码和数据处理代码版本锁定一致否则结果很难追溯。8. 常见问题与排查方法问题现象可能原因排查方式解决方案训练 Loss 不下降数据噪声过大或数据量不足抽样检查清洗后数据查看 Loss 曲线加强清洗规则补充高质量语料Loss 周期性尖峰某些 batch 包含异常文本定位尖峰对应数据批次加强异常检测过滤调整 batch 大小模型输出重复文本语料去重不彻底统计数据 n-gram 重复率增加模糊去重步骤调整阈值模型常见语言偏向严重配比不合理统计语料语言分布调整多语言采样权重困惑度评估异常高清洗后仍含乱码或非目标语言文本抽样检查评估异常文本加强语言过滤和质量评分特定领域能力差该领域数据占比过低统计领域关键词覆盖度增加领域语料或过采样去重后数据量骤减去重阈值过于严格抽样查看被删除文本调整相似度阈值区分领域保留策略全量训练前代理训练已过拟合数据重复过高或多样性不足查看训练集和验证集 Loss 差异加强去重增加数据来源多样性9. 最佳实践与合规建议9.1 数据工程的最佳实践先小后大任何清洗和去重规则先在一万条样本上验证再上全量。随时留档每一版清洗后的数据都要保留样本文件方便事后追溯数据版本。多维度观测不只盯着 Loss还要定期生成模型输出样例人工判断数据质量是否真实影响生成效果。设置数据预算预训练 token 总量通常固定配比调整要在预算内完成避免数据无限膨胀导致训练成本失控。重复使用已有 Pipeline优先复用 RedPajama、RefinedWeb 等开源数据管线的经验而不是从头造轮子。9.2 版权与隐私边界在做预训练数据收集和处理时必须注意数据来源的合法性和合规性。对于有明确版权声明的数据需要确认是否允许用于模型训练。对于涉及个人信息的内容要执行 PII个人身份信息检测与删除避免模型在训练后记忆并泄漏用户的隐私信息。课程里也强调了数据合规在预训练流程中的前置性。数据清洗到后期隐私过滤不只是技术问题更是产品上线前必须完成的安全底线。建议在数据管线中加入隐私过滤步骤并对过滤结果进行人工抽检确保敏感信息没有被遗漏。9.3 开源数据的补充使用实践中可以基于 RedPajama、RefinedWeb、Mc4 等开源数据集做二次清洗和配比调整而不是从零开始爬取全网数据。这样能避开大量原始数据质量问题的坑把更多精力放在配比和质量评估上。10. 总结与下一步数据清洗、去重与配比策略是预训练大模型中最容易被轻视却最影响结果的部分。课程 EP14 的核心价值在于把数据工程从“玄学”变成可执行的流程清洗阶段去除噪声和质量垃圾去重阶段消除冗余和记忆偏差配比阶段控制知识覆盖和能力偏向。对于正准备开始预训练或继续研究大模型的读者建议按这样的顺序验证先下载一小批开源语料跑通清洗、去重和配比的基础 Pipeline再用一个小模型做代理训练观察 Loss 和输出效果。走通这一遍后再考虑扩展数据规模和正式训练。这套流程本身就是最值得收藏的产出物。后续可以继续关注的数据方向包括多模态数据的清洗与配比、动态配比策略的训练时调整方法、以及数据质量与模型可解释性之间的关联分析。