资讯动态

大模型预训练数据全流程:清洗、去重与配比策略实战

发布时间:2026/9/7 5:52:39 来源:尧图企业网站定制
最近在做大模型相关项目时很多同学在群里问过同一个问题为什么开源社区复现 LLaMA、GPT 等模型的代码并不难难的是把训练数据准备好其实答案就藏在这条技术链路的源头——预训练数据。大模型的能力上限很大程度上由预训练数据的质量和配比决定。今天的文章我会围绕斯坦福大模型开发课 EP14 的内容系统梳理高质量预训练数据清洗、去重与配比策略。这篇文章适合以下读者正在准备从头预训练一个中小规模大模型的研究生或工程师。已经会用 Hugging Face 加载开源模型但不太清楚数据管线如何搭建。想做数据领域项目希望通过大模型场景深入理解数据清洗、去重和配比。读完本文你能掌握预训练数据管线的完整流程、常用工具链、可复现的代码示例以及真实项目中容易踩到的数据坑。内容会有一点长建议先收藏再按章节逐步操作。1. 为什么说高质量数据是预训练模型的基石大模型训练业界通常遵循一个公式模型能力 模型架构 算力 训练数据规模与质量。早期大家拼参数量后来发现单纯堆参数会带来边际效应递减而数据层面的改善往往能带来更直接的收益。斯坦福 EP14 把数据质量放到预训练全流程的首位原因就在这里。1.1 数据质量如何影响模型能力先看一个直观的例子。假设你的语料库里有大量重复的新闻稿片段模型可能会在生成时反复输出同一段话表现得非常“复读机”。如果语料里混入了未清洗的 HTML 标签模型在回答问题时可能会莫名输出/div或classarticle这类字符。更隐蔽的问题是低质量数据会消耗有限的训练预算。大模型预训练阶段每个 token 都要经过多次前向和反向传播计算计算成本非常昂贵。如果把训练预算花在重复、低质量、符合噪声分布的语料上模型的收敛速度和最终效果都会受到影响。高质量数据管线的核心目标可以总结为四个字干净、多样。干净是指去掉噪声、重复和有害内容多样是指数据的来源、语言、领域分布足够广泛让模型具备更强的泛化能力。1.2 一条完整的预训练数据流水线在进入具体策略之前我们需要在大脑中建立整条管线的全貌。预训练数据从原始抓取到最终训练通常经过以下几个阶段数据获取从公开网页、开源语料、书籍、代码仓库、学术论文等渠道收集原始数据。数据清洗过滤掉无效内容、去除格式污染、统一编码。数据去重去除精确重复和近似重复的文本片段。数据过滤基于质量评分、语言识别、有害内容检测进行二次筛选。数据配比按照语言、领域、任务类型设计混合比例。数据格式化转换为模型可以直接读取的 token 序列生成训练样本。EP14 的核心内容就落在第 2 到第 5 步也就是清洗、去重与配比。这三步都是文本数据处理中的经典问题只是在大模型场景下被放大到了 PB 甚至 EB 级规模。1.3 为什么不能直接使用公开爬虫数据很多初学者会想我直接把网上的开源语料下载下来塞给模型训练不就行了吗实践告诉我们这样训练出来的模型很容易出现以下问题语言模型困惑度偏高生成文本混乱。模型存在严重的“记忆过拟合”对训练集中的重复内容过度还原。存在隐私信息或有害内容输出风险。多语言能力不均衡例如中文语料占比过低时中文能力明显偏弱。所以说预训练数据绝不是“越多越好”而是“越干净、越多样、配比越合理越好”。下面我们进入数据清洗的具体方法。2. 环境准备与数据说明实操部分我会以 Python 环境为例因为数据清洗生态里 Python 工具最全也最容易扩展。2.1 推荐运行环境操作系统Linux / macOS / Windows 均可建议 Linux 服务器。Python 版本3.9 或 3.10版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。核心依赖库pandas、numpy、tqdm、datasets、transformers。去重相关库datasketchMinHash 实现、bloom_filter2。高性能文本处理pyarrow、polyglot、langid或者 fastText 语言识别模型。如果你使用的是本地开发机建议先创建一个独立的虚拟环境python -m venv llm-data source llm-data/bin/activate2.2 本文使用的数据源类型大模型预训练常用的数据源包括网页文本Common Crawl、CC-NEWS、中文互联网页面。书籍与论文Books3、arXiv。代码GitHub 公开仓库。百科与问答Wikipedia、Stack Exchange。不同数据源污染程度差异很大。网页爬虫数据通常含大量导航菜单、广告文本、重复模板书籍和论文相对干净但也存在目录页、参考文献页等噪声代码数据则常常夹杂二进制内容、生成的 lock 文件等。在后面的示例中我会用一个模拟的 JSONL 数据集作为演示结构和真实爬虫数据类似。你可以替换成自己的真实数据文件。2.3 样例数据文件设计为了方便复现我们定义一个简单的 JSONL 数据格式{id: 1, text: htmlbody人工智能是计算机科学的一个分支。它企图了解智能的实质。/body/html, source: web, lang: zh} {id: 2, text: 人工智能是计算机科学的一个分支。它企图了解智能的实质。, source: web, lang: zh} {id: 3, text: htmlbody点击这里了解更多https://example.com/article/123/body/html, source: web, lang: zh}以上数据中第 1 条和第 2 条存在重复内容第 1 条包含 HTML 标签第 3 条是典型的低质量文本。后面我们会逐步处理这些问题。3. 预训练数据清洗的核心方法数据清洗是预训练数据管线中的第一道工序。它的目标不是保留最多数据而是保留对模型训练最有价值的数据。清洗做得好不好直接影响后续去重和配比的效果。3.1 文本提取与格式清理原始数据从网页或文档中解析出来时经常会带着大量与正文无关的内容。典型的格式污染包括 HTML 标签、Markdown 标记、控制字符、URL、乱码、重复的导航栏文本等。来看一个用 Python 清理 HTML 标签的例子import re def clean_html(text: str) - str: # 去除 HTML 标签 text re.sub(r[^], , text) # 去除 script 和 style 标签内容 text re.sub(r(?is)(script|style).*?.*?/\1, , text) # 还原常见 HTML 实体 text text.replace(nbsp;, ).replace(amp;, ) text text.replace(lt;, ).replace(gt;, ) return text sample htmlbody人工智能是计算机科学的一个分支。/body/html print(clean_html(sample)) # 输出: 人工智能是计算机科学的一个分支。除了 HTML 标签还需处理全角半角不统一、零宽字符、控制字符等问题。建议统一做一次 Unicode 规范化import unicodedata def normalize_text(text: str) - str: # NFC 规范化统一码位 text unicodedata.normalize(NFC, text) # 去除零宽字符 text text.replace(\u200b, ).replace(\u200c, ).replace(\u200d, ) # 将全角空格转为普通空格 text text.replace(\u3000, ) return text这里需要注意的是格式清理不能过度。有的同学喜欢把所有连续的空白字符都折叠成一个空格这在英文场景下问题不大但中文文本中必要的换行和段落分隔需要保留因为语言模型需要学习自然语言的段落结构。3.2 语言识别与过滤多语言预训练模型中语言识别是区分语料质量的重要手段。一个常见的问题场景是中文语料中混入大量日文汉字片段的网页如果不去除会干扰模型的语种判断。常用方案有两种基于 langid 或 fastText 的语言识别模型。基于 Unicode 字符范围判断的规则方法。示例使用 fastText 的lid.176.bin模型进行语言识别import fasttext # 注意实际使用时需要下载 lid.176.bin 模型文件 model fasttext.load_model(lid.176.bin) def detect_language(text: str) - str: if not text.strip(): return unknown labels, scores model.predict(text.replace(\n, ), k1) return labels[0].replace(__label__, ) print(detect_language(人工智能是计算机科学的一个分支。)) # 输出通常是 zh print(detect_language(Artificial intelligence is a branch of computer science.)) # 输出通常是 en如果你不想引入外部模型也可以用字符范围做中文语料粗筛def is_chinese_ratio_in_range(text: str, min_ratio: float 0.5) - bool: chinese_chars len(re.findall(r[\u4e00-\u9fff], text)) if len(text) 0: return False ratio chinese_chars / len(text) return ratio min_ratio语言过滤的原则是根据训练目标确定语言列表过滤掉非目标语言或明显混杂的语料。如果你训练的是中文大模型英文高质量语料通常仍可保留一部分但占比要控制。3.3 低质量文本过滤低质量文本过滤是清洗阶段最依赖经验的部分也是 EP14 中强调的重点。常见的低质量信号包括文本过短比如少于 100 个字符。标点符号占比过高。重复字符比例过高比如“哈哈哈哈哈哈”或大量无意义符号。句子结构混乱出现大量乱码。以链接、导航菜单、广告为主的页面内容。下面实现一个简单的质量评分函数def quality_score(text: str) - float: if not text: return 0.0 # 长度过短 if len(text) 50: return 0.0 # 统计标点占比 punctuation_count len(re.findall(r[。、,.!?;:], text)) punct_ratio punctuation_count / len(text) # 统计英文字母占比 alpha_count len(re.findall(r[a-zA-Z], text)) alpha_ratio alpha_count / len(text) # 中文语料通常标点占比在 1% ~ 15% 之间 if punct_ratio 0.005 or punct_ratio 0.3: return 0.0 # 混合英文占比过高的中文文本通常质量存疑 if alpha_ratio 0.4: return 0.0 return 1.0这个函数只是一个示例真实生产环境会使用更复杂的启发式规则和分类器组合。斯坦福 EP14 中提到的做法是先设计一组规则人工标注一批数据再训练一个分类器对大规模语料进行打分。实践中初始规则集往往就能过滤掉一半以上的噪声数据。3.4 有害内容与隐私过滤这一部分虽然不需要我们逐个实现复杂模型但必须在管线中预留接口。典型的做法包括使用已有的有害内容分类模型对文本进行评分。对身份证号、手机号、邮箱地址等 PII个人身份信息做正则匹配和过滤。对出现敏感关键词的文本进行标记或剔除。示例检测邮箱地址和手机号def contains_pii(text: str) - bool: # 检测邮箱 email_pattern r[a-zA-Z0-9._%-][a-zA-Z0-9.-]\.[a-zA-Z]{2,} # 检测中国大陆手机号 phone_pattern r1[3-9]\d{9} if re.search(email_pattern, text): return True if re.search(phone_pattern, text): return True return False处理合规问题要遵循一个原则在无法确认数据来源和授权范围时宁可过滤掉可疑样本也不要冒险保留。预训练语料的合规性已经成为大模型发布前的重要审查项。4. 数据去重技术详解清洗完成之后下一步是去重。为什么要单独把去重拎出来讲因为重复数据对预训练模型的影响非常大。实验表明大量重复文本会导致模型产生记忆过拟合训练不稳定并且降低泛化能力。同时删除重复数据可以节省大量算力。4.1 精确去重最简单也最有效精确去重指的是完全一样的文本片段只保留一份。对于一个 JSONL 文件我们可以用集合或者 pandas 的 drop_duplicates 轻松完成。import json import hashlib def deduplicate_exact(input_file: str, output_file: str) - int: seen set() total 0 unique 0 with open(input_file, r, encodingutf-8) as fin, \ open(output_file, w, encodingutf-8) as fout: for line in fin: line line.strip() if not line: continue total 1 obj json.loads(line) text obj.get(text, ) # 对文本内容做 hash text_hash hashlib.md5(text.encode(utf-8)).hexdigest() if text_hash in seen: continue seen.add(text_hash) unique 1 fout.write(json.dumps(obj, ensure_asciiFalse) \n) return total - unique这里使用 MD5 而不是直接存储文本是为了节省内存。当数据量达到亿级别时把每一条原始文本都放在内存里是不现实的但 16 字节的哈希值相对可接受。需要注意的一个细节是有些同学会先对整行做哈希去重但整行 hash 会忽略文本内容相同、而元数据字段不同的情况。更保险的做法是只对 text 字段做哈希保留源信息字段。4.2 模糊去重MinHash 与 LSH精确去重只能解决完全相同的文本。现实中更多出现的是“改了标点、换了顺序、插入广告尾巴”的近似重复。这时需要用到 MinHash LSH局部敏感哈希方案。MinHash 的核心思想将文本切分成 shinglen-gram集合再通过多个哈希函数生成签名用签名之间的相似度近似 Jaccard 相似度。LSH 则用来把可能相似的文档分到同一个桶中避免两两比较带来的平方级计算量。来看一个基于 datasketch 的实现思路from datasketch import MinHashLSH, MinHash def build_shingles(text: str, k: int 5) - set: # 将文本切分为长度为 k 的字符片段 text text.replace(\n, ) if len(text) k: return {text} return {text[i:ik] for i in range(len(text) - k 1)} def minhash_from_text(text: str, num_perm: int 128) - MinHash: m MinHash(num_permnum_perm) for shingle in build_shingles(text): m.update(shingle.encode(utf-8)) return m # 创建一个 LSH 索引 lsh MinHashLSH(threshold0.8, num_perm128) # 示例逐条插入文本 for doc_id, text in documents: m minhash_from_text(text) lsh.insert(doc_id, m) # 查询与某条文本相似的文档 query_m minhash_from_text(query_text) result lsh.query(query_m)这里的 threshold 表示相似度阈值。0.8 意味着 Jaccard 相似度大于 0.8 的文档会被认为是近似重复。生产环境中num_perm 常用 128 或 256阈值根据任务调整比如中文网页去重时常用 0.7 到 0.9 之间。MinHash 的优势在于能在大规模语料上近似找出相似文档而且计算复杂度可控。缺点是需要针对不同语言调整 shingle 长度中文场景 k5 通常够用英文场景可以设成 8 到 10。4.3 去重粒度问题去重并不是把文档之间互相比一下就行还有一个粒度问题值得注意。EP14 中特别提到预训练数据去重可以分为三个层级文档级去重适合剔除完全重复或近重复的文章。段落级去重很多网页会共享同一个版权声明或模板段落这时要把这些重复段落剥离。句子级去重如果同一个高质量句子在大量文档中反复出现模型会过度强化这个句子导致生成时频繁引用。在实际工程中先做文档级精确去重再做段落级 MinHash 去重最后按需做句子级去重。句子级去重成本较高一般在数据规模不是特别大或者对文本多样性要求很高的场景下使用。4.4 代码数据去重的额外注意点如果你训练的数据中包含代码去重时需要注意代码文件中的 import 语句、license 头、注释块会大量重复但这些内容不一定需要全部去除。更合理的做法是保留代码的重要逻辑部分仅对整体指纹做去重。对于 GitHub 代码语料一个常用技巧是使用文件名 文件内容哈希组合先去掉同一仓库中的重复文件再做跨仓库的相似代码检测。5. 数据配比策略决定模型能力分布的关键清洗和去重解决的是“数据的质”配比解决的是“数据的构成”。相同的清洗流程、相同的模型结构如果配比不同训练出来的模型能力会有非常大的差异。5.1 语言配比不要盲目追求所有语言均衡多语言预训练中常见的误区是追求语言数量均衡。实际上模型训练时会优先从数据量大的语言中学习低资源语言往往需要更强的规则约束。实际项目中的语言配比通常原则是英文数据仍是很多开源模型的中坚力量占比常常在 60% 以上。中文和其他语言合计占比根据目标市场调整。如果目标是训练中文大模型中文数据占比不应低于 60%同时保留 10%~20% 的英文数据提高模型综合能力。这里需要特别提醒语言配比不是拍脑袋决定的。你应该先在小规模实验上测试不同配比对下游任务的影响再确定最终比例。斯坦福 EP14 给出的思路是构建一个验证集其中包含数学、代码、通用知识、多语言问答等子集分别评测不同配比的效果。5.2 领域配比根据模型用途设计比例分布领域配比指的是通用网页、百科、书籍、代码、论文等来源的混合比例。例如如果模型聚焦通用对话需要适当提高百科、问答社区和书籍的比例。如果模型需要代码能力代码语料比例应显著提高通常可以达到 10%~30%。如果模型用于学术场景arXiv 和书籍比例需要增加。一个经典做法是把数据划分为多个 bucket每个 bucket 有自己的采样权重。从 Hugging Face 的 datasets 库来看可以通过简单的加权采样实现。from datasets import load_dataset dataset load_dataset(json, data_files{train: cleaned_data.jsonl}) # 假设需要按 source 字段进行领域配比 weights [] for example in dataset[train]: source example[source] if source wikipedia: weights.append(2.0) elif source code: weights.append(1.5) else: weights.append(1.0) # 按权重进行随机采样 sampled_dataset dataset[train].select_columns(text).shuffle(seed42)需要注意的是配比可以体现在两个位置训练数据集的采样分布。训练过程中不同 epoch 的数据使用顺序。很多团队在实验中发现通用知识与领域数据混合训练时保持领域数据比例稳定的同时逐步增加领域数据难度会得到更好的效果。5.3 分阶段训练与数据调度配比的另一个高级话题是“数据调度”也就是不同训练阶段使用不同的数据配比。业界常见做法包括预训练初期使用高质量通用语料帮助模型建立稳定的语言表征。预训练中期逐步引入更多领域数据扩展模型知识面。预训练后期或继续预训练阶段针对目标任务增加垂直领域数据比例让模型适应特定领域表达。这种方式类似于课程学习目的是先让模型掌握通用能力再向特定领域迁移。对于算力有限的团队数据调度比模型结构改动更容易带来收益。# 模拟一个分阶段的数据调度示例 # stage1: 通用语料 100% # stage2: 通用语料 70% 代码 30% # stage3: 通用语料 50% 学术论文 30% 代码 20% stage_weights [ {general: 1.0, code: 0.0, paper: 0.0}, {general: 0.7, code: 0.3, paper: 0.0}, {general: 0.5, code: 0.2, paper: 0.3}, ]这里有一个工程经验每个阶段切换时模型 loss 会有一个短暂的上升期这是正常的。可以通过在阶段交界处降低学习率或者做几轮 warmup 来平滑过渡。6. 完整实战构建一个轻量级预训练数据清洗流水线前面的章节我们分别介绍了清洗、去重和配比。现在把这些内容整合成一个完整的实战案例。这个案例可以运行在你的本地机器上处理万级别以下的小规模语料。6.1 创建项目结构llm-data-pipeline/ ├── data/ │ ├── raw_data.jsonl │ └── cleaned_data.jsonl ├── scripts/ │ ├── clean.py │ ├── dedup.py │ └── sample.py └── config/ └── config.yaml6.2 数据清洗脚本 clean.pyimport json import re import unicodedata import sys def clean_text(text: str) - str: # 去除 HTML 标签 text re.sub(r[^], , text) # 去除 script/style 内容 text re.sub(r(?is)(script|style).*?.*?/\1, , text) # 替换 HTML 实体 text text.replace(nbsp;, ).replace(amp;, ) # Unicode 规范化 text unicodedata.normalize(NFC, text) # 去除零宽字符 text text.replace(\u200b, ).replace(\u200c, ) # 合并多余空白 text re.sub(r[ \t], , text) text re.sub(r\n{3,}, \n\n, text) return text.strip() def is_valid_text(text: str, min_length: int 50) - bool: if len(text) min_length: return False # 标点比例 punct_count len(re.findall(r[。、,.!?;:], text)) if punct_count / len(text) 0.3: return False # URL 比例 url_count len(re.findall(rhttps?://, text)) if url_count 3: return False # 重复字符比例 repeat_chars re.findall(r(.)\1{5,}, text) if len(repeat_chars) 5: return False return True def process_file(input_path: str, output_path: str) - None: removed 0 with open(input_path, r, encodingutf-8) as fin, \ open(output_path, w, encodingutf-8) as fout: for line in fin: line line.strip() if not line: continue try: obj json.loads(line) except json.JSONDecodeError: removed 1 continue text obj.get(text, ) text clean_text(text) if not is_valid_text(text): removed 1 continue obj[text] text fout.write(json.dumps(obj, ensure_asciiFalse) \n) print(f处理完成共移除 {removed} 条无效数据) if __name__ __main__: process_file(data/raw_data.jsonl, data/clean_stage1.jsonl)6.3 数据去重脚本 dedup.pyimport json import hashlib from datasketch import MinHashLSH, MinHash def exact_dedup(input_path: str, output_path: str) - None: seen set() with open(input_path, r, encodingutf-8) as fin, \ open(output_path, w, encodingutf-8) as fout: for line in fin: obj json.loads(line) text obj.get(text, ) h hashlib.md5(text.encode(utf-8)).hexdigest() if h in seen: continue seen.add(h) fout.write(json.dumps(obj, ensure_asciiFalse) \n) def fuzzy_dedup(input_path: str, output_path: str, threshold: float 0.8) - None: records [] with open(input_path, r, encodingutf-8) as fin: for line in fin: records.append(json.loads(line)) lsh MinHashLSH(thresholdthreshold, num_perm128) doc_ids [] # 第一阶段插入 LSH for idx, obj in enumerate(records): text obj.get(text, ) if len(text) 5: continue shingles {text[i:i5] for i in range(len(text) - 4)} m MinHash(num_perm128) for s in shingles: m.update(s.encode(utf-8)) lsh.insert(idx, m) doc_ids.append(idx) # 第二阶段查询相似文档 duplicate_ids set() for idx in doc_ids: text records[idx].get(text, ) shingles {text[i:i5] for i in range(len(text) - 4)} m MinHash(num_perm128) for s in shingles: m.update(s.encode(utf-8)) similar_docs set(lsh.query(m)) - {idx} duplicate_ids.update(similar_docs) with open(output_path, w, encodingutf-8) as fout: for idx, obj in enumerate(records): if idx in duplicate_ids: continue fout.write(json.dumps(obj, ensure_asciiFalse) \n) print(f模糊去重完成移除 {len(duplicate_ids)} 条近似重复数据) if __name__ __main__: exact_dedup(data/clean_stage1.jsonl, data/clean_stage2.jsonl) fuzzy_dedup(data/clean_stage2.jsonl, data/clean_stage3.jsonl)这里再次提醒fuzzy_dedup 中逐步对每一条文档做 LSH query只是一个演示思路。在真正的大规模语料上你通常不会逐条查询而是把所有文档插入同一个 LSH 索引后用lsh.query配合批量处理或 MapReduce 框架完成。6.4 数据配比采样脚本 sample.pyimport json import random # 领域权重配置 DOMAIN_WEIGHTS { wikipedia: 0.4, news: 0.2, code: 0.2, book: 0.2, } def weighted_sample(input_path: str, output_path: str, sample_ratio: float 0.8) - None: with open(input_path, r, encodingutf-8) as fin: records [json.loads(line) for line in fin if line.strip()] # 为每个样本计算领域权重 weighted_records [] for obj in records: source obj.get(source, unknown) weight DOMAIN_WEIGHTS.get(source, 0.1) weighted_records.append((obj, weight)) # 根据权重采样 total_weight sum(w for _, w in weighted_records) target_count int(len(records) * sample_ratio) sampled [] for _ in range(target_count): r random.uniform(0, total_weight) upto 0 for obj, w in weighted_records: if upto w r: sampled.append(obj) break upto w with open(output_path, w, encodingutf-8) as fout: for obj in sampled: fout.write(json.dumps(obj, ensure_asciiFalse) \n) print(f采样完成原始数据 {len(records)} 条采样后 {len(sampled)} 条) if __name__ __main__: weighted_sample(data/clean_stage3.jsonl, data/train_ready.jsonl)这个采样脚本是一个简化版。生产环境的加权采样通常会在数据读取时就计算累计权重避免每次迭代都做线性扫描。如果你的数据量达到 GB 级别建议使用 datasets 库的select或shard功能代替。6.5 运行与预期输出按顺序执行python scripts/clean.py python scripts/dedup.py python scripts/sample.py预期输出类似处理完成共移除 312 条无效数据 模糊去重完成移除 28 条近似重复数据 采样完成原始数据 960 条采样后 768 条最终得到的train_ready.jsonl就是可以直接输入到 tokenizer 阶段的数据文件。7. 预训练数据领域的常见问题与排查思路在实际操作中数据清洗和去重环节经常遇到各种问题。表格中的排查思路可以直接套用。问题现象常见原因解决思路清洗后数据量骤减低于预期清洗规则过于严格误删大量有效样本检查规则中长度阈值和标点比例阈值先对 1000 条样本人工核验再放量去重耗时过长内存溢出精确去重时直接存储原始文本或亿万级 hash 全放内存改用布隆过滤器或分批去重模糊去重使用 LSH 的 batch 查询能力中文语料中英文占比过高语言识别模型阈值设置不合理调高中文判定阈值或在清洗阶段加入中文字符占比规则MinHash 去重误杀率高shingle 长度太短或阈值过低中文场景把 k 从 3 提到 5阈值从 0.7 提到 0.85模型训练后生成内容单一复读严重去重粒度过粗句子级重复未处理增加段落级或句子级去重流程训练 loss 下降缓慢数据配比中代码/领域数据比例过高而通用语料不足降低领域数据权重先保证通用语料基础训练另外一个常被忽略的坑是数据清洗后的文本要重新做 tokenization 长度的统计。有些文本清洗前 512 token清洗后可能只剩 200 token如果直接用固定长度采样会造成大量 padding。建议清洗完成后重新统计 token 长度分布再设计数据分桶策略。8. 最佳实践与工程建议到这里我们基本讲完了预训练数据清洗、去重和配比的核心流程。最后分享一些我在工程落地中总结的经验希望能帮助大家少走弯路。8.1 数据管线的可观测性数据清洗不是一锤子买卖而是需要持续迭代的过程。建议在管线中记录每个步骤的数据量变化、过滤原因分布、字符级统计指标。例如清洗前总字符数清洗后总字符数。按过滤原因统计的被删文档数量。去重前后文档数和 token 数变化。各类数据源占比变化。这些指标不仅能帮助你确认管线是否按预期运行还能在后续调整规则时提供对比依据。如果条件允许把指标输出到可视化面板例如 TensorBoard 或 Grafana方便团队共享。8.2 规则优先模型后置很多团队一上来就想用一个巨型分类器做数据过滤这是不现实的。对于预训练数据清洗我建议遵循“规则优先模型后置”的顺序先用低成本的启发式规则过滤掉明显的噪声。规则过滤后再训练一个轻量级分类器对剩余数据做质量打分。最后对少量高价值数据做人工抽检形成质量反馈闭环。这样做的好处是规则阶段能快速处理大部分低质量数据模型阶段则通过打分更精细地筛选出隐藏的问题样本避免浪费昂贵的模型训练资源。8.3 保留数据版本与回溯能力预训练模型训练时间长数据管线的版本控制非常重要。建议为每个清洗后的数据集附带一份 dataset card记录数据来源。清洗规则和参数。去重阈值。配比权重。清洗时间。操作人。这样当模型出现异常时可以快速定位到是数据问题还是模型结构问题。Hugging Face 的 datasets 库也支持给数据集添加描述和元数据建议直接使用这个能力。8.4 小规模实验先行数据配比调整对模型的长期影响很大不建议直接在一张大卡上跑全量预训练。更高效的做法是先用 1B 以下的小模型做少量步数的训练观察 loss 和数据配比的关系。使用相同的训练步数对比不同配比的验证集表现。选定最优配比后再放大到正式训练。小规模实验虽然不能完全复现大模型的效果但能帮助你发现配比中的明显问题比如某类数据比例过高导致生成风格偏移。8.5 安全与合规意识预训练数据中的隐私和有害内容过滤不是可选项而是必须项。特别是面向公众发布的大模型数据合规性会直接影响产品上线。建议在数据管线的末尾单独设置一道安全检测步骤对最终数据集进行再一次扫描。对于身份证号、手机号、邮箱地址、银行卡号等 PII 信息应该使用正则或专用模型进行脱敏或移除。9. 总结与学习路线本文围绕斯坦福大模型开发课 EP14 的核心内容系统地梳理了高质量预训练数据的清洗、去重与配比策略。我们从一个原始 JSONL 文件出发实现了文本清洗、质量过滤、精确去重、MinHash 模糊去重以及领域加权采样最终得到可以直接用于模型训练的文本数据。现在回看开头的问题为什么很多团队复现模型结构很容易训练出来的模型效果却差距很大答案并不神秘就在于数据管线上的每一个细节。数据清洗时的规则是否合理、去重时阈值是否恰当、配比时领域权重是否科学这些看似琐碎的工作最终会以模型困惑度、生成质量和下游任务分数的形式体现出来。如果你正在准备自己的预训练项目下一步可以按这个路线继续深入阅读斯坦福 EP14 的完整视频和讲义对照本文的代码理解每个策略背后的动机。下载 RedPajama 或中英文混合的开源数据集手动跑一遍清洗和去重流程感受不同参数对结果的影响。学习 datasketch 和 datasets 库的高级功能尝试在分布式环境下处理更大的语料。参考 GPT-3、LLaMA、Qwen 等模型公开的技术报告分析它们公布的数据配比经验。在小规模模型上做配比消融实验形成自己的数据配比方法论。预训练数据是一条需要耐心打磨的工程赛道。它是脏活累活但也是最容易拉开模型差距的部分。如果本文对你有帮助可以收藏备用后续我会继续整理大模型预训练相关的实操内容。欢迎在评论区交流你在数据清洗、去重和配比中遇到的问题。

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

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

免费获取报价