ARTICLE DETAIL

资讯详情

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

MindSpore大模型预训练数据清洗实战:分层过滤让Loss快速收敛

MindSpore大模型预训练数据清洗实战:分层过滤让Loss快速收敛 几个月前我用 MindSpore 跑一个中等规模的大模型预训练实验语料是从多个公开站点抓下来的。前三天 Loss 一直挂在 5.8 上下不动我换模型结构、调学习率、改优化器参数折腾了一圈没什么实质变化。最后实在没辙抽了一千条语料人工逐条看才知道问题出在数据本身——将近三成是 HTML 残留、重复段落、广告文本和标点符号成片堆砌的“样子货”。把数据清洗掉一轮之后同样的训练配置在两天内就把 Loss 压到了 4.7。那次经历彻底改变了我的工作方式大模型预训练的工程链路里数据质量过滤不是预处理环节的一个可选项它是决定模型能否快速收敛、下游能否出效果的基础条件。这篇文章就围绕我在 MindSpore 生态下做大模型预训练数据质量过滤的整套方案展开包括常见的脏数据形态、分层过滤架构、规则粗筛和模型精筛的具体做法以及如何用 MindRecord 和 Dataset API 把逻辑落地。适合正在用 MindSpore 跑预训练或者准备搭建大规模数据管线的团队参考哪怕你暂时只跑小模型这套过滤思路同样值得提前沉淀。1. 数据决定大模型上限算力之外先过数据这一关1.1 先搞明白预训练语料里到底藏着哪些“脏东西”很多人觉得预训练数据无非是“网上抓的文本”差不多就行。实际把原始语料摊开看你会发现里面远不止“干净句子”。我平时会把脏数据大致分成五类抓取噪声HTML 标签残留、Cookie 弹窗文案、版权页、站点导航栏、分页器文字。这类内容占原始抓取量的比例通常高得惊人尤其是从论坛和新闻类站点抓的数据经常整段都是“上一篇 下一篇 返回列表”这种没有语义的东西。近似重复内容同一篇新闻被几十个网站转载或者摘要、正文、评论混在一份文档里。注意这里的重复不一定是完全一样的字符串更多是“同一意思换了几种表达”直接哈希很难识别。低语义密度文本SEO 凑字页面、评论区刷屏、闲聊灌水、随机字符序列。这类文本句子结构看着正常但单位长度里几乎不含有效信息模型学到的全是如何“正确地说废话”。机器生成文本早期机器翻译的硬译稿、某些自动摘要工具吐出来的病句甚至包括上一代模型生成的重复倒车文本。它们语法未必错但逻辑链是断裂的模型会吸收这种“似通非通”的语感。隐私与有害内容手机号、邮箱、证件号、详细地址这类个人信息以及不该进入训练语料的恶意内容。模型对这类样本的记忆力很强一旦进入预训练语料很可能在生成阶段被原样复述出来这是非常现实的产品与合规风险。这些脏数据有一个共同特点人眼扫一眼就能判断“这不是正常人话”但程序不会自动帮你识别。所以数据过滤的第一步不是“选择什么高级算法”而是“用一套机制把不像人话的文本挡在门外”。1.2 行业里的公开经验都指向同一件事这不是我一个人拍脑袋得出的结论。你去翻这几年公开的模型训练报告会发现一个高度一致的规律规模越大的预训练对数据质量的处理越细致。GPT-3 当年对 Common Crawl 做过两轮处理先按文档与高质量参考集的相似度打分只保留高分文档又做了一次模糊去重把训练集从几十 TB 压到几百 GBLLaMA 团队在训练 65B 模型时数据管线里显式放了启发式规则过滤和 PPL困惑度打分两步最终过滤掉了约 9% 的数据Falcon 模型公开的技术报告里数据去重的工程细节甚至比模型架构写得还多。国内 MindSpore 社区里一些大模型训练案例也都在数据清洗部分花了大量篇幅。这些信息放在一起能说明一个问题前几年“堆数据就能涨点”的粗放阶段已经过去了。现在的共识是高质量、多样性、低噪声的数据集比一味追求 token 总量更划算——单位算力预算下的训练效率完全不同。1.3 过滤掉 30% 数据反而训练更快这里有个反直觉的点值得展开说。很多人担心过滤会“损失多样性”觉得数据少了对模型不好。但实践结果恰恰相反当我把一份语料过滤掉约 35% 的内容后同等训练配置下 Loss 下降得更快、更稳。原因不难理解。重复度高的数据会让模型反复拟合相同的局部模式等于把算力浪费在“不断确认已知内容”上噪声数据会产生很强的梯度扰动优化器只能用更低的学习率来硬扛否则 Loss 会反复震荡。过滤之后每个 batch 里的样本质量分布更均匀模型在更少的 step 内接触到更多有效的“信息组合”收敛自然更快。而且过滤还有一个隐性好处它能保证训练过程的稳定性。我以前跑预训练时最烦的就是某几个 batch 整体是垃圾文本Loss 突然从 3.2 冲到 5.0一查样本全是抓取噪声。这类异常步进对学习率调度器的影响很大一旦触发热启动或者重启机制浪费的可就不只是几个小时了。2. 分层过滤架构粗筛、精筛、去重缺一不可2.1 一条完整的大模型数据过滤管线是怎样的我现在使用的标准数据管线大致长这样原始语料抓取 → 文本抽取 → 语言识别 → 规则粗筛 → 全局去重与模糊去重 → 质量打分 → 内容安全过滤 → 抽样人工复核 → 转成 MindRecord → 进入训练。每一道环节都会把结果落成一份中间文件。这样做的目的很简单任何一个环节出了问题只需要从对应步骤重新跑不用把整条链路从头再来一遍。比如后来我发现某批爬取数据的编码识别有误中文全变成了乱码因为中间文件还在直接从语言识别那步开始重跑就行整个清洗流程两小时就结束了。2.2 为什么要把过滤拆成粗筛、精筛和去重三层很多团队最开始做数据清洗喜欢用一个“大而全”的模型去给所有文本打分试图一步到位。但我试过之后就放弃了原因很朴素数据量太大模型推理成本扛不住垃圾种类太多单一模型很难在“误杀”和“漏网”之间找到平衡。分层处理的价值在于把不同成本、不同精度的工具放到合适的位置上粗筛层只做规则判断比如语言、长度、字符占比几乎不消耗 GPU用多进程跑吞吐量可以做得很高。它负责干掉那些“明显一眼假”的文本通常一次能删掉三四成的数据。精筛层用模型给文本打分成本比规则高一个量级但更聪明能识别出“说话通顺但信息量很低”的内容。这一层面对的是粗筛之后剩下的“高度疑似人类写的”文本。去重层单独拎出来处理是因为它跟“质量好坏”是两个维度。一段高质量文本被转载了十万次你说它是垃圾还是精品单看质量它是好的但放进语料里它就是有害无益的重复项。去重必须在质量过滤的框架里有独立地位。这三层如果混在一起做参数耦合会很严重调一个阈值会殃及另一个目标。分开之后每层都可以独立迭代、独立回滚工程上清爽很多。2.3 分层策略对资源调度的意义拆分还有一个直接好处资源调度更灵活。粗筛层全跑 CPU用 Python 多进程加分片任务就能打满几十个核不占用宝贵的 GPU精筛层才上 GPU但这一层的输入已经小了很多GPU 利用率能打上去去重层则放在粗筛之后避免在大量垃圾文本上白白消耗 MinHash 的签名计算。在 MindSpore 的分布式训练环境里我通常把清洗流程放在一个独立的算力集群上跑跟训练集群错开时间。比如白天训练集群在跑晚上训练停了就把清洗任务调度上去。数据管线跟训练管线解耦之后两边都不会互相拖慢。3. 规则粗筛层怎么搭语种识别、统计特征、安全过滤3.1 语种识别先定边界如果你训练的目标语种是明确的比如中文或者英文单语模型那语种识别就是第一道闸门。这里不需要上太重的大模型一个 fastText 的 lid.176 模型就够用了它对常见语种的识别准确率非常高而且推理速度极快几百万条文本分分钟跑完。中文语料有个特殊坑点日文汉字和韩文中的汉字字符容易被误判成中文。我的做法是在 fastText 识别之后再叠一个规则校验——统计文本中平假名、片假名、谚文音节的比例超过千分之一就直接丢掉不跟自己过不去。如果训练的是多语种模型语种识别的逻辑会更复杂一点因为你不能简单按“是否目标语言”来筛。我的建议是至少做到语种打标后面采样时按比例控制各语种数据量防止某一种语种的噪音数据趁机冲进来。3.2 几个有效又廉价的统计特征阈值粗筛层核心靠统计特征打天下。我常用的几个特征和阈值如下注意这些阈值不是拍脑袋定的而是先抽 5000 条样本做分布统计再按 5% 和 95% 分位点截出来的。特征我的常用阈值说明平均句长中文 8~40 字英文 30~200 字符过短可能是碎片过长可能是截断错误非字母数字字符占比超过 50% 丢弃常见于 HTML 残留和符号堆砌短行比例少于 10 个字符的行占比超 10% 丢弃抓取噪声大量短行三-gram 重复率句子三-gram 重复度高于 30% 丢弃识别洗稿和机械重复字符信息熵低于 3.5中文丢弃内容极度单一重复率高这些阈值看起来土但确实是性价比最高的第一道防线。我见过一个公开数据集的技术报告里面说单靠“长度过滤 符号占比 语言识别”这三板斧就干掉了原始语料里的 16% 内容而且误杀率极低。脏数据往往在基本统计特征上就藏不住。3.3 安全过滤和隐私脱敏不能靠堆关键词安全这块容易走两个极端一种是完全不处理另一种是堆一个巨大的关键词表。我个人的经验是这两者都不够更合理的是“正则识别隐私 关键词表 轻量分类模型”三件套。隐私信息过滤以正则为主手机号、邮箱、身份证号、银行卡号这类强模式的东西正则已经能覆盖九成场景。命中隐私特征的样本是整条删除还是脱敏后保留取决于你的模型用途——如果做通用生成模型建议直接删除避免模型学到“可以原样输出别人手机号”这个行为。有害内容的识别则不能只靠关键词。关键词表有天然的滞后性而且“上下文是否越界”得靠模型判断。我通常训练一个很轻量的文本二分类模型输入文本输出一个风险分数分数超过阈值就剔除。这类模型不需要很大一两层的文本分类网络就够关键是数据标注要做扎实。注意安全过滤这一层我建议放在质量打分之后。原因是质量打分会把很多低质噪声文本先干掉安全模型的输入相对干净误判率会低很多。如果你的顺序倒过来让安全模型先去处理一堆乱码和 HTML 标签它很容易产生奇怪的误杀。4. 模型精筛层怎么搭PPL打分、质量分类器、模糊去重4.1 PPL困惑度打分原理和筛选陷阱精筛层我用的第一个工具是困惑度打分。简单解释一下原理让一个语言模型读一段文本模型对这段文本能预测得多准决定了它的困惑度高低。如果文本非常符合语言的常规模式模型预测得很顺利PPL 就低如果文本语无伦次、结构混乱模型每预测一个词都“很意外”PPL 就会飙高。PPL 能自动识别出很多规则层看不出来的问题。比如一段英文机器翻译的中文文本规则特征完全正常但读起来就是别扭PPL 会把这种别扭量化出来。我做这个环节时用的是 MindSpore 上载入的一个小型 Transformer 模型直接基于要训练的语料本身跑了一个轻量自监督任务做适配这样可以更贴合目标语料的风格。但 PPL 有个陷阱阈值卡太严会让语料“过于通顺”。我曾经在一版实验里把 PPL 上限设得特别低留下来的全是极其简单直白的句子结果模型整体风格变得很呆生成质量反而下降。后来我改成按语料分桶做百分位截断——先统计所有候选文本的 PPL 分布然后只砍掉分布尾部的 15%而不是用一个全局硬阈值。这样既能剔除“奇形怪状”的文本又不会牺牲表达多样性。4.2 训练一个质量分类器才是真正贴合数据集的方案PPL 衡量的是“文本是否流畅”但流畅不等于有用。一段排版精美的垃圾广告文PPL 可能很低可模型真学了它只会增加废话能力。所以精筛层的另一个关键组件是质量分类器。我训练质量分类器的思路是这样的构造一个二分类数据集正样本选高质量书籍、学术论文、头部新闻媒体的正文负样本选抓取噪声、机器翻译硬译稿、SEO 灌水页面、低质评论。用一个小型预训练模型在这些数据上做微调。这个分类器输出的分数跟 PPL 结合起来给每个样本算出一个综合质量分。这个方案有个额外的好处它是可以不断迭代的。每跑完一轮清洗我抽一批被过滤掉的样本回去看发现有些其实质量不错就把它们捞回来放进正样本集发现有些漏网之鱼就把它们加进负样本集。分类器每迭代一次对“你这个数据集的好文本长什么样”的理解就更深一层。4.3 去重的工程细节精确去重、MinHash、SimHash去重是老生常谈但工程细节决定了效果。我从粗到细做三层精确去重对整篇文档做 SHA1、MD5 哈希完全相同的文档直接删掉。这层成本最低先把“复制粘贴型转载”干掉。MinHash 模糊去重把每个文档切分成 token 窗口比如中文按 5 个词一组做 shingle然后对每个 shingle 算多个哈希值取每个哈希桶的最小值作为文档签名。有了签名之后再用 LSH 分桶找出候选重复对最后做精确比对。这套方案对“改了几个字、调整了段落顺序”的转载很有效。SimHash 指纹去重把文本映射成 64 位指纹汉明距离小于等于 3 的视为近似重复。SimHash 比 MinHash 更省内存适合在超大规模语料上做一轮粗过滤。这里有几个实操注意点。一是不要对短文档做 MinHash文档太短时签名信息量不够误判率会飙升我一般限定长度低于 500 字的文档只做精确去重。二是中文 shingle 要按分词后的 token 切不能按单字切否则会大量误判。三是去重一定要放在质量精筛之前因为精筛要跑模型每处理一条文本都是成本先把重复项删掉能省不少 GPU 时间。5. 在 MindSpore 里落地MindRecord格式、Dataset API与异步管线5.1 先把清洗结果转成 MindRecord 再进训练大模型预训练的数据量不是开玩笑的动辄几十 TB 文本。我强烈不建议把清洗逻辑放在训练脚本里在线执行——每次数据加载都跑一遍规则训练效率会崩掉。正确做法是离线清洗后直接转成 MindRecord 格式训练时只做高效读取。MindRecord 是 MindSpore 的原生数据格式它的核心优势是随机读取友好。大模型训练时每个 epoch 要 shuffle 数据如果直接读一堆散装文本文件IO 随机访问效率很低转成 MindRecord 后数据按块组织可以配合多进程并行读取吞吐会明显提升。转格式的时候要注意分片数量。我第一版图省事把 200GB 数据写成了一个 MindRecord 文件训练时发现 IO 成了瓶颈。后来改成每个分片 1~2GB一共拆几十上百个分片再让多个 Worker 并行读训练吞吐才跑起来。5.2 用 Dataset API 的 map/filter 做兜底过滤清洗可以在离线阶段做彻底但上线训练时我仍然会在数据管线里留一道轻量过滤作为兜底。用 MindSpore 的 Dataset API 做这事很方便下面是我常用的一个模式import json import mindspore.dataset as ds def preprocess_and_filter(line): obj json.loads(line) text obj.get(text, ) # 这里只保留代价极低的规则模型打分不放在线 if len(text) 200: return None if len(text) 10000: return None if not is_target_language(text): return None return text dataset ds.GeneratorDataset( sourceiter(open(cleaned_samples.jsonl, encodingutf-8)), column_names[line], num_parallel_workers8, python_multiprocessingTrue, ) dataset dataset.map(operationspreprocess_and_filter, input_columns[line]) dataset dataset.batch(32)这个模式里map 的预处理函数会返回文本也可以返回 None 表示丢弃但实际使用中我更喜欢把过滤逻辑放到 GeneratorDataset 的迭代器内部这样不需要依赖 map 对 None 的特殊处理逻辑更可控class DatasetIter: def __init__(self, file_path): self.file_path file_path def __iter__(self): with open(self.file_path, r, encodingutf-8) as f: for line in f: obj json.loads(line) text obj.get(text, ) if not pass_basic_rule(text): continue yield text, obj.get(score, 0.0) dataset ds.GeneratorDataset( sourceDatasetIter(cleaned_samples.jsonl), column_names[text, score], num_parallel_workers8, python_multiprocessingTrue, )在线兜底过滤的定位是“防漏网”不是“做清洗”。所以这层只保留廉价规则不让任何模型参与打分避免把训练吞吐拖垮。5.3 异步数据管线调优多进程、预取、shuffle用 MindSpore 跑大模型预训练数据加载速度经常比 GPU 计算慢这块一定要调。几个关键参数我踩过不少次坑num_parallel_workersGeneratorDataset 和 map 里的并行线程数。我之前默认设 4后来发现 16 左右才有明显提升。注意这个值要跟机器 CPU 核数匹配不要一上来就写 64不然线程切换开销反而致命。python_multiprocessing如果处理函数是纯 Python尽量开成 True走多进程而不是多线程否则 GIL 锁会卡死你。但也要注意进程间传输 Python 对象有序列化开销处理函数越重多进程收益越明显。prefetch_size预取缓冲区大小。训练时如果发现 GPU 利用率频繁掉零优先调大它我一般设在 16 以上。shuffle 的位置不要在读取原始 JSONL 时就 shuffle那会打乱文件的随机读取优势。先在 map 之后做 buffer shuffle让数据进入训练前被打乱即可。MindDataset 读取 MindRecord 时同样有 num_parallel_workers 的配置。我跑分布式训练时每个 rank 单独读自己的分片避免多个 rank 抢同一个文件的 IO。5.4 过滤参数怎么跟训练超参联动这是一个容易被忽视的点数据过滤的结果会直接影响训练超参的选择。过滤后的数据更“干净”每个 batch 的样本质量更均匀这时候可以大胆一点把 batch size 调大、把学习率上限调高一档收敛速度会比脏数据状态下更快。过滤后总 token 数缩水了对应的训练步数和 epoch 数也要重新算。我习惯在配置里记录过滤率比如原始语料 120B token过滤后只剩 78B那我训练计划就按 78B 来定而不是拍脑袋写一个固定的 step 数。如果你的过滤率超过 30%并且数据集本身是静态的那就要回头考虑补抓数据了否则多样性不够后面几轮训练就是反复看图。6. 实测效果与踩坑记录过滤前后的横向对比与经验复盘6.1 一组我自己跑出来的对比数据我拿一份从公共网页抓取的约 60GB 原始语料做了一组对比实验训练目标是同一个参数规模的小模型训练配置完全一致只改数据侧的处理流程数据版本样本量折算 token训练到 Loss3.5 所需 step下游 5 个任务平均准确率未过滤原始数据约 12B485K基准仅规则粗筛约 9B392K3.1%规则粗筛 精筛打分 去重约 6.8B331K4.6%这组数据只代表我的某一次实验不是通用结论但趋势非常明显数据量小了 40% 多收敛速度反而快了三成下游指标还涨了接近五个点。自那以后我就认准了一件事——过滤不是把数据变少是让剩下的数据“每一分算力都花在刀刃上”。6.2 踩坑记录最典型的几个问题把这些年踩过的坑整理一下最典型的有这么几个第一规则阈值照搬英文语料中文样本被误杀。早年用过一套英文清洗规则平均句长阈值对中文完全不适用跑完之后中文语料少了四分之一抽出来一看大量正常的长句全被当成异常截断处理了。后来每套阈值都用目标语料的小样本来定不跨语种套用。第二PPL 阈值设太严语料全是“标准答案体”。有一版生成的文本风格非常死板回头查数据PPL 上限压得太低留下的全是简单句和模式化表达。改成按分位截断之后风格才缓过来。第三先精筛后去重浪费大量 GPU 时间。我第一次搭管线时精筛在前去重在后等于把重复文本也跑了一遍质量打分白烧了很多机器。把去重提前到精筛之前同样的流程省出差不多三分之一的精筛时间。第四MindRecord 分片太大训练 IO 直接拖垮。前面说过单文件 200GB 的教训还热乎着。现在我的标准是单分片 1GB 左右宁可多几个文件也不要一个巨型文件。第五在线兜底过滤塞了个大模型训练吞吐崩了。曾经试图在 Dataset 的 map 里放一个质量分类器兜底结果每一条文本都要过一遍推理Step 耗时直接翻倍。现在的原则很明确训练脚本里只放最廉价的规则过滤重的活全部离线干完。6.3 几条稳定有效的实操经验最后分享几条在实战里被反复验证的经验都是踩坑之后沉淀下来的。一是新语料先抽 1000 条人眼扫一遍再定过滤阈值。这个习惯看着原始但比任何自动化分析都管用。你不用看完一千条看到两三百条基本上心里就有数了——这批数据是偏论坛风格还是偏新闻风格、噪声主要来自哪种类型、要不要单独调语种规则。二是过滤参数按数据来源独立配置。不同站点抓下来的数据脏的性质完全不同。论坛数据主要需要删短句和灌水新闻数据主要需要去重和去转载百科数据反而要小心别把冷门条目当噪声误杀。我在配置里给每个内容源单独留一份过滤参数效果比用一套全局参数好很多。三是保留“被过滤样本”清单至少一周。清洗完先别急着删被过滤的中间结果存一份带有过滤原因标记的清单。这样带来一个好处训练效果不好时可以反查是不是过滤误杀太多去重模块被质疑时可以立马上调一批“被判定为重复”的样本做人工复核。如果你正打算用 MindSpore 跑大模型预训练我最大的一个建议是别急着把模型结构改来改去先花一周时间把数据管线做扎实。我后来养成的习惯是每次拿到新语料先抽样本、定规则、跑清水线再谈训练这个习惯带来的收敛速度提升比任何花哨的模型技巧都实在。
返回列表