ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

大规模预训练语言模型数据集分析:清洗与去重实战指南

大规模预训练语言模型数据集分析:清洗与去重实战指南 简介这份PDF分析报告系统梳理了从2018年到2022年初用于训练GPT-1、GPT-2、GPT-3、GPT-NeoX-20B、Megatron-11B、MT-NLG及Gopher等大规模预训练语言模型的数据集组成逐一对比各模型的数据来源、数据规模与令牌数量并指出主流模型在数据集记录与披露上的明显不足。针对自然语言处理研究者、机器学习工程师与数据科学家既能快速了解维基百科、Common Crawl、语料书籍等数据在不同模型中的占比与取舍也能看到Books1与Books2差异、The Pile构成、Gopher MassiveWeb等典型问题的分析为未来构建更透明、更规范的数据集提供方法论参考。资源为单个PDF文件包体仅2.36MB内含概览、各模型数据集明细、附录及参考文献结构清晰便于定向检索。目前已有112人学习下载适合需要系统梳理预训练数据脉络、关注模型可复现性或从事数据治理的相关读者。1. 大规模预训练语言模型数据集规模不是护城河清洗才是如果你刚做完一个文本分类项目认为“搞预训练就是找几 TB 网页文本喂给模型”那接下来的内容值得看完。我接手过的几套大规模预训练语言模型数据集最终决定模型好坏的从来不是数据总量而是数据经过去重、去毒、质量过滤和混合配比后的“有效信息密度”。很多团队把大量精力花在堆机器结果预训练跑到一半发现重复文本让 loss 曲线始终压不下去或者中文语料里夹杂着大量乱码与机器翻译痕迹。对预训练语言模型来说大规模只是入场券真正要操心的是数据集的综合分析怎么做、指标怎么定、坑在哪。2. 数据集分析的三个对象源结构、重复度与质量分布2.1 先区分“语料”和“数据集”预训练数据集的组成单元是什么做视觉检测的人会先想到 COCO2017 的数据集结构、ImageNet-1k 的下载与目录组织这些都以“单张图像 一个 json/xml 标注文件”为最小单元。但大规模预训练语言模型数据集的最小单元是一段文本通常以“文档”为独立样本。所谓文档在 Common Crawl 里可能是一整个抓取页面在 GitHub 代码语料里是一个仓库的单个代码文件在 ArXiv 语料里是一篇论文的 LaTeX 源文件。在动手分析之前我一般先把数据切分成三个视角看待源视角数据来自哪个站点或哪个类别——网页、书籍、论文、代码、对话记录。文档视角以每一个文本文件为样本记录其长度、语言、来源形成一张元信息表。内容视角文档内部的结构比如段落、代码块、表格、列表这些结构会被 tokenizer 拆成不同形态的序列。这三个视角对应三类分析源分布决定混合比例文档级重复度决定了去重策略内容质量决定了过滤规则怎么设计。你没法用一个 50GB 的原始文件直接分析第一步永远是先把原始语料转成可统计的样本清单。2.2 为什么必须做分布分析长尾效应与未知污染预训练数据集的规模普遍在 100GB 到 TB 级任何人工抽查都只能覆盖极小比例所以要用分布分析来代替“感觉”。分布分析要回答的问题很具体多少比例的文档来自少数头部站点 token 长度集中在哪个区间英文和中文的比例是多少代码语料是否集中在少数编程语言这些分布直接决定模型的能力偏向。举个例子如果某个数据集的“代码”来源里 JavaScript 占了 60% 以上那么模型在其他语言的代码补全上很可能会表现平平。再比如你从 Common Crawl 抓数据时没有按站点白名单过滤那么 wiki 类高质量页面可能只占总量的 3%剩下的全是导航页、广告页和自动生成的垃圾页。这就是长尾整体数据量很大但真正撑起模型能力的头部内容实际上很稀疏。另一个必须做分布分析的场景是“未知污染”——评测集与训练集重叠。如果不做文档级去重测试集里的样本可能已经被原始的爬虫抓进去了。后续跑 benchmark 得到的高分很可能是记忆出来的不是理解和推理出来的。这个风险在公开的预训练数据集中普遍存在唯一的解法就是在数据预处理阶段进行严格去重并在分析阶段记录所有与评测集疑似重叠的样本。2.3 拿什么和它对比把公开数据集当作基准参照物分析自己的数据集时最好的参照物不是随机选择而是那些开箱可用的主流公开语料。The Pile、C4、RefinedWeb、RedPajama 这四类语料都是大规模预训练语言模型数据集的典型代表它们各自给出了一个可复现的处理管道。我在做新的数据分析时会把它们当成“基准测试集”自己的语料在不同类别上的分布是否过于倾斜过滤后的保留率是否在合理区间去重后的 token 损失是否算得上正常。如何获得这些公开语料做法各个不同。The Pile 直接提供整理好的 jsonl 文件C4 可以从 TensorFlow Datasets 读取RedPajama 则提供 CC 子集的索引。不过需要注意不同版本之间清洗程度差异很大RedPajama 的 raw 版本保留了大量低质页面而它的 “dedup” 版本去重后体积会缩减很多。这个差异本身就是很好的分析素材——用来对比同一份原始数据在不同阶段的分析指标会怎么变化。3. 从原始语料到分析清单一个可复现的元信息抽取流程3.1 确定字段规范分析清单需要的最小字段集做综合分析的第一步是统一字段。不管原始文件是 jsonl、parquet 还是纯文本我一般会先把数据归一成一个独立的元信息表每行对应一个文档至少包含以下字段字段类型说明doc_idstring文档唯一标识推荐用源文件的 hashsourcestring数据来源例如 common_crawl、arxiv、wikipediafile_pathstring原始文件所在路径raw_lenint原始字符长度token_lenint经 tokenizer 切分后的 token 数量langstring语言识别结果例如 zh、en、codeurlstring原始 URL如果没有则留空categorystring类别标签如果不做分类则写 unknown字段不要贪多。数据量一大每多一个字段就意味着清洗管道多一层负担。token 长度最好用一个固定的 tokenizer 计算不推荐在不同实验中换来换去否则所有基于 token 的对比都会失真。语言识别用 fasttext 的 lid.176.bin 模型即可它对短文本也有效而且 CPU 上每秒能处理几千条短文本。这里不推荐在第一步就上大型模型进行语义分类统计阶段的核心是“快和稳定”语义理解留给后续阶段。3.2 批量读取 jsonl 并产出一份 minifest 清单最常见的原始格式是 jsonl每一行是一个 JSON 对象。下面的脚本处理的是这种格式输入一个目录逐个文件读取把每条记录转换成元信息行。它刻意保持简单目的不是最终清洗而是先让数据“能统计”。import json import os import hashlib import argparse from pathlib import Path def compute_id(text: str) - str: # 用内容 hash 作为文档 id天然支持后续去重判断 return hashlib.md5(text.encode(utf-8, errorsignore)).hexdigest() def process_jsonl(input_dir: str, output_tsv: str): out_lines [] total_files 0 total_docs 0 for fpath in sorted(Path(input_dir).glob(*.jsonl)): total_files 1 with open(fpath, r, encodingutf-8, errorsignore) as f: for line in f: line line.strip() if not line: continue try: obj json.loads(line) except json.JSONDecodeError: continue # 兼容不同的字段命名 text obj.get(text) or obj.get(content) or if not text: continue raw_len len(text) # source 通常可以从文件名推断也可以用配置传入 source os.environ.get(DATA_SOURCE, unknown) doc_id compute_id(text) out_lines.append( f{doc_id}\t{source}\t{fpath.name}\t{raw_len}\t{text[:100]}\n ) total_docs 1 with open(output_tsv, w, encodingutf-8) as fout: fout.write(doc_id\tsource\tfile_name\traw_len\tpreview\n) fout.writelines(out_lines) print(fprocessed files: {total_files}, docs: {total_docs}) if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--input_dir, requiredTrue) parser.add_argument(--output_tsv, defaultmeta.tsv) args parser.parse_args() process_jsonl(args.input_dir, args.output_tsv)这段代码只做三件事按行解析 JSON、抽取文本字段、输出一条精简的预览。这样后续的统计完全不依赖原始目录结构可以随时再审。需要说明一点这里 preview 只截前 100 字符避免元信息表本身变得过分臃肿如果要检查乱码或质量再用脚本单独抽样完整文本。单机跑不了大文件时最简单的做法是按 100 万行一个小文件分批处理得到多个 TSV最后再用 Pandas 合并。若数据已经达到 TB 级再考虑用 Spark 或其他分布式方案否则你花在集群调试上的时间往往比节省出来的处理时间更多。数据量没有大到一定量级之前单机的效率是最高的。3.3 用 Spark 做更大规模的分布统计当文档数量超过千万单机脚本的处理速度会明显变慢。这时常见做法是用 Spark 重写同样的逻辑。核心思路不变读入 DataFrame选择文本字段添加 doc_id、length、source 等列最后写出 parquet。区别仅仅在于 Spark 可以并行读取多个输入分片。一旦数据量超过单机内存我不建议继续用 pandas 硬扛。一个几百 GB 的 jsonl 文件用 pandas 读取时内存会翻倍好多次反而直接把机器打挂。Spark 的 write 格式优先用 parquet因为列式存储在后续统计分析里能省掉大量 I/O同时保留 schema 信息。如果你的环境不方便使用 Spark也可以选择换用 DuckDB 对原始文件做 SQL 查询但要接受 JSON 解析效率的下降。4. 质量过滤、去重与混合配比分析结果怎么变成预处理参数4.1 质量打分的两个流派规则过滤器与困惑度分类器有了元信息表就可以做第一轮质量判断。质量过滤不是靠人看样本定标准而是用可计算的指标来评分。主流方案有两类实际使用通常两层都做。第一层是规则过滤检查文本是否包含黑名单词、是否全大写、是否乱码、是否长度过短、是否 HTML 噪声。第二层是分类器过滤用一个训练好的文本分类器判断文档属于“高质量”还是“低质量”。在工程实现上困惑度perplexity也常被用来做代理指标。一个在 Wikipedia 上训练的小语言模型会对正常文本给出相对较低的困惑度而对格式错乱或机器生成的低质文本给出较高的困惑度。这个方法的缺点是存在领域偏差代码文本的困惑度天然比自然语言高专业论文中的长词也会提高分数。所以困惑度阈值不能全语料统一至少要按源类别分别设定。规则过滤的典型阈值如下——注意阈值是初始估计需要在自己数据集上抽样验证后再固化。规则常见阈值说明文本长度少于 50 字符丢弃太短无法提供有效训练信号重复字符比例超过 0.5 丢弃通常说明是噪声文本或日志非字母字符比例超过 0.4 丢弃中文语料需要单独处理广告黑名单词命中即丢弃需要在中文环境下扩充词表HTML 标签残留简单正则匹配抓取数据常见4.2 MinHash 去重从“看起来重复”变成“可计算的重复”要去重最常被拿来做演示的做法是 URL 一级去重也就是只删掉 URL 相同的文档。但预训练数据里大量重复来自不同 URL 的相似内容比如新闻聚合网站转载同一篇稿件、多个 GitHub 仓库复制同一段 README。这类重复必须做内容级去重。MinHash 是工业级的默认选项。它的思路是把一个文档转换成多个“shingle”通常是 5-gram 或 8-gram再压缩成固定长度的指纹集合两个文档相似度就转化为 Jaccard 相似度的无偏估计。下面给出一个可运行的简化实现import hashlib import itertools from datasketch import MinHashLSH def shingles(text: str, k: int 5): # 去掉空格后再切分 shingle能减少轻量级格式差异造成的误判 text .join(text.split()) for i in range(len(text) - k 1): yield text[i : i k] def doc_minhash(text: str, num_perm: int 128, k: int 5): m MinHash(num_permnum_perm) for shingle in shingles(text, k): digest hashlib.md5(shingle.encode(utf-8, errorsignore)).digest() m.update(digest) return m # 使用示例 # lsh MinHashLSH(threshold0.8, num_perm128) # for doc_id, text in stream_docs(): # m doc_minhash(text, num_perm128) # lsh.insert(doc_id, m)这里三个参数直接影响效果。k是 shingle 长度取值越大对轻微改写越敏感推荐自然语言用 5代码语料用 8。num_perm越大相似度估计越稳定但同时内存开销增长常见选择是 128。threshold设为 0.8 表示 80% 以上相似即判为重复这个数值对自然语言来说比较合适如果做代码语料去重建议调低到 0.7因为开源代码的复制往往伴随变量改名和格式调整。4.3 混合配比怎么定用一个小规模实验验证各源贡献分布分析之后你会拿到一个各来源占比的表格。直接用这个比例做训练是常见做法但不是最优的。因为来源占比并不等于信息价值占比。一个稳妥的流程是先按照源比例抽取一个 5GB 的小样本保持分布与原数据集一致然后按照你设想的比例均匀混合各源数据最后训练一个小模型参数量 100M 以下对比困惑度和下游任务分数。这样做的原因是大规模预训练语言模型数据集的配比调整每轮全量实验成本太高必须要有小规模前置验证。具体的混合比例通常遵循“高质量源数据适当过采样”的原则。举例来说包含 Wikipedia、书籍、ArXiv、代码等高质量来源即便原始占比很低最终混合比例里也会抬高而 Common Crawl 这类噪声占比较高的源需要配合严格过滤后保留的干净子集使用。混合实验看得最多的两个指标一个是验证集困惑度能否顺利下降另一个是小型分类任务上的准确率有没有明显偏向某类语言或领域。若代码比例提上去之后困惑度在自然语言任务上回升说明配比失衡需要回调。通常一个 5GB 小样本跑 3 到 5 轮就能看出趋势不必等全量实验结束。4.4 产出一份“可解释”的数据集分析报告综合分析不能只停留在内部表或临时脚本里最终要输出一份结构化的分析报告。我用固定的模板包含四张图和三张表源分布占比图、token 长度直方图、语言占比图、重复文档占比图以及按源统计的平均 token 数表、质量过滤命中率表、与公开语料的对比表。这份报告的价值至少有两层。对下它直接指导训练超参数的调整比如序列长度设置、采样权重、是否启用动态批处理对上它能帮助团队判断数据构建的后续投入方向。如果你发现重复率偏高优先做去重如果语言分布严重失衡优先补充对应语种的数据而不是盲目增加总量。5. 预训练数据集综合分析的五个常见踩坑与排查路线5.1 中文语料全是乱码但 lang 识别却列为“zh”现象语言识别结果是 zh打开文本却到处是“锟斤拷”“”一类不可读字符。原因原始页面编码是 GBK 或其他编码抓取管道按 UTF-8 解码后产生大量替换字符fasttext 对这类包含 Unicode 替换符的文本仍然能猜出语种所以语言识别没有报错。解决在质量过滤规则里增加“Unicode 替换字符比例”指标计算\ufffd占文本字符的比例阈值高于 0.01 直接丢弃。同时抽查时不要只看预览而是把完整文本拉出来人工确认。5.2 去重把所有短文档都删掉了现象跑完 MinHash 去重后统计显示文档保留率只有 30%而且剩下的全是长文档短新闻几乎全部消失。原因短文档本身信息量少shingle 数量少很多短文本之间的 Jaccard 相似度天然偏高被 LSH 误判为重复。这与文档的实际来源是否相关无关。解决去重前先按长度分桶。字符数低于 500 的文档不做 MinHash 去重只做 URL 精确去重字符数大于 500 的文档再进入内容去重管道。这看起来是降低标准实际上保护了短文本的多样性。5.3 训练时 loss 正常下降但下游任务分数越来越差现象模型预训练 loss 曲线很正常但下游分类准确率不升反降。原因绝大概率是数据源配比失衡。比如代码语料占比过大模型在 NLP 语义理解任务上的能力被稀释。另一个原因是语料的领域过于集中在技术类内容导致通用知识覆盖不足。解决回到配比实验把各源混合比例调到 1:1 重新测试。同时检查质量过滤是否把教材类、百科类数据中的长文本误删了。如果过滤操作让 wikipedia 子集的保留率比其他子集低那大概率是规则阈值设得过严。5.4 报告说“重复率仅 2%”实际训练时数据重复仍然很高现象分析报告显示文档级重复率只有 2%但训练时模型不断重复生成同样的句子。原因文档级去重只去掉了“整篇重复”的文档但预训练文本真正的重复往往是大规模模板化句子相同的页面导航、cookie 弹窗文案、页脚链接文本在各种网页里反复出现。这些片段在文档级粒度下并不构成整篇重复却在 token 级重复率上高出很多。解决在报告里增加一处“句子级重复”的专项统计抽样 10000 条句子统计 Top 50 的重复句子占比。如果这类模板句子占比超过 1%需要先用句子级去重或者添加黑名单模板库。实际经验是CC 语料跑文档级去重之后token 级重复率仍然可能达到 10% 以上。5.5 过滤掉的低质数据被同等质量的新采样替代白做一遍现象做完全量过滤后数据总量变少重新规划训练集时又按原比例从原始语料里补了一部分最终质量没有明显变化。原因过滤只是“去掉坏的部分”但没有做“补好的部分”。训练数据的总量不能随意压缩过滤后的缺口必须由其他高质量来源补上且补入之前要重新过一遍同样的质量过滤管道。解决先把所有候选语料全部跑完质量过滤得到一份“预清洗候选池”再做配比和采样。这样不会出现“先清洗、后补齐、补进来的又是脏数据”的循环。6. 用数据营养表做质量验收一个能复制到新项目的工作模板我最后给出的建议是把上面所有分析结果收敛成一张“数据营养表”。这张表不是给算法工程师看的而是给整个训练项目组做决策用的。它的内容固定为六个字段总 token 数去重后、源占比、语言占比、质量过滤命中率、重复率文档级和句子级、配比实验结论。任何新的预训练项目启动时把新的 candidate 语料往这张表里套一遍五分钟之内就能判断要不要继续投入。我在实际项目里吃过的最大教训是过分相信“多源混合能自动解决问题”结果模型效果差的时候根本不知道是哪一源拖了后腿。后来强制规定每个数据源单独记录质量指标禁止合并成一个混合文件后再来分析。这样一旦训练出现问题可以直接回溯到具体来源的过滤参数而不是从最终混合包里重新反推。一个更具体的验证技巧是每轮数据版本都留一个固定种子的小型评测集只评测这类任务——语言建模困惑度、常识问答、代码补全、中文理解。数据集版本换一版就把同样的测试集跑一遍前后对比。这个做法的成本很低但能最快暴露出数据版本之间的质量波动。如果你维护过多个模型的数据集会发现这种“数据版本基准”比训练指标本身更灵敏。无论你的数据是从开源渠道下载还是从公网抓取后自行构建上述这条分析路径都适用先拆文档再统计分布再过滤去重最后用小实验验证配比。不要一上来就训练大模型先在一份小数据上跑通整个分析管道再把它放大到全量数据。这条路径会替你的训练阶段省下大量返工时间希望帮到你。本文还有配套的精品资源点击获取
返回列表