
1. 大模型预训练里数据质量过滤到底在解决什么问题做过大模型预训练的人都有一个共识模型效果的上限很大程度上在数据准备阶段就已经被决定了。算力可以堆并行策略可以调学习率可以反复试但如果喂进去的语料本身充满乱码、重复段落、机器翻译腔、广告垃圾和低信息密度的口水文那训练出来的模型大概率会表现得什么都懂一点但什么都不精。这就是数据质量过滤方案存在的意义。我接触 MindSpore 这套框架有一段时间了从最早的 ModelArts 上跑小模型到后来用 MindSpore 做百亿参数级别的预训练实验踩过的坑不算少。数据质量过滤这个环节说实话是最容易被低估、又最容易翻车的一环。很多团队把精力全放在并行策略、混合精度、算子优化上结果数据管道里混进了大量低质样本loss 曲线看着挺漂亮下游评测一塌糊涂。这篇内容我想聊的是在 MindSpore 生态下一套可落地的大模型预训练数据质量过滤方案应该怎么设计、怎么实现、怎么排查问题。核心关键词就是MindSpore、大模型预训练、数据质量过滤。适合谁看如果你正在用 MindSpore 做预训练或者准备从其他框架迁移过来又或者你负责的是数据工程这一块那这篇内容应该能帮你少走一些弯路。即便你只是刚接触 MindSpore想了解它的数据处理能力也能从里面找到可参考的思路。先说清楚一个前提数据质量过滤不是一个跑个脚本就完事的动作它是一整套流水线包含规则过滤、模型打分、去重、配比采样等多个环节每个环节都有它的取舍逻辑。下面我会按设计思路、核心细节、实操实现、问题排查四个大块来展开尽量把每个决策背后的为什么讲透。2. 整体方案设计与思路拆解2.1 为什么不能只靠单一规则过滤刚入门的时候很多人会写一堆正则表达式把 HTML 标签、特殊符号、超短文本过滤掉觉得这就叫数据清洗了。我早期也这么干过结果发现两个问题一是规则永远写不全新的脏数据类型层出不穷二是规则过滤太硬容易误杀。比如一篇技术博客里包含大量代码符号正则一刀切下去好数据也被干掉了。所以一套靠谱的方案应该是分层过滤的思路。我通常把它分成四层第一层格式与编码规范化。统一编码、去除控制字符、修复乱码这是最基础的不做后面全是白搭。第二层规则过滤。基于统计特征和启发式规则快速筛掉明显不合格的样本比如长度过短、重复率过高、特殊符号占比异常。第三层模型打分。用一个轻量级质量分类模型给每条数据打分区分高质量自然语言和低质内容这一层是精度提升的关键。第四层语义去重。用 MinHash、SimHash 或者向量相似度做近似去重避免模型在重复内容上反复过拟合。这四层的顺序不能乱。规则过滤放在模型打分前面是因为模型推理是有成本的先用便宜的规则砍掉一大半垃圾能显著降低整体开销。去重放在最后是因为去重本身计算量也不小而且它依赖前面已经过滤干净的语料否则会在垃圾数据上浪费算力。2.2 MindSpore 在这套方案里的角色定位MindSpore 本身提供了mindspore.dataset这个数据处理模块支持map、filter、batch、shuffle等算子可以构建流式数据管道。但要注意MindSpore 的 dataset 管道更适合做在线处理和轻量过滤重度的离线过滤和模型打分建议放在独立的数据工程流程里比如用 Spark 或者 Ray 做分布式预处理产出的干净语料再交给 MindSpore 做训练时的加载。为什么这么设计因为预训练语料动辄几百 GB 到几 TB如果全部塞进 MindSpore 的 dataset 管道里做复杂过滤会拖慢训练时的数据供给速度GPU/NPU 经常处于等数据的饥饿状态。我实测过把模型打分环节放进训练管道吞吐直接掉了一半以上。所以合理的分工是离线阶段做重过滤在线阶段只做必要的轻量校验和格式转换。2.3 过滤强度与数据保留率的平衡这是最考验经验的地方。过滤太松垃圾数据进模型过滤太狠数据量不够模型欠拟合。我一般会先做一轮小规模实验用不同的过滤阈值跑几个 baseline看下游任务的评测指标变化。一个实用的经验值是经过完整过滤后保留率在 30% 到 60% 之间比较健康。如果保留率低于 20%说明阈值可能过严或者原始数据质量太差如果高于 80%那基本等于没怎么过滤。当然这个数字跟数据来源强相关网页爬取的数据保留率通常偏低而已经经过初步清洗的书籍、论文语料保留率会高一些。3. 核心细节解析与实操要点3.1 格式规范化最容易被跳过却最重要的一步格式规范化听起来简单但细节特别多。我列几个必须处理的点编码统一全部转成 UTF-8处理掉 GBK、Latin-1 等混杂编码导致的乱码。Python 里可以用ftfy库自动修复常见的 mojibake 问题。控制字符清理去掉\x00到\x1f之间的不可见控制字符但要注意保留\n、\t这些有意义的空白符。Unicode 规范化用 NFC 或 NFKC 形式统一避免同一个字符有多种表示方式导致去重失效。HTML 残留清理即使已经做过 HTML 解析仍可能有nbsp;、amp;这类实体残留需要统一替换。提示格式规范化一定要在过滤流水线的最前面做否则后面的长度统计、字符占比计算都会因为编码问题失真。3.2 规则过滤的关键指标设计规则过滤的核心是设计一套能区分好坏数据的统计指标。我常用的有这几个指标含义典型阈值说明字符长度样本总字符数200 ~ 100000太短信息不足太长可能是拼接垃圾平均行长度总字符数/行数10 ~ 2000过短说明碎片化严重特殊符号占比非字母数字字符比例 0.3过高可能是代码或乱码重复行占比重复行数/总行数 0.3过高说明模板化或爬虫重复停用词占比常见停用词出现频率 0.02过低可能是非自然语言数字占比数字字符比例 0.2过高可能是日志或表格这些阈值不是拍脑袋定的而是通过对一批人工标注的好/坏样本做统计分析得出的。我建议你也先标注几百条样本跑一遍分布统计再确定适合自己数据的阈值。直接抄别人的阈值大概率水土不服。3.3 模型打分轻量分类器的选型与训练模型打分这一层核心是训练一个二分类器判断样本是高质量还是低质量。选型上有几个考量模型不能太大因为要对全量语料打分推理成本必须可控。我一般用 1 亿到 3 亿参数的小模型比如轻量级的 BERT 变体。训练数据要平衡正样本从高质量来源书籍、百科、优质问答采样负样本从被规则过滤掉的垃圾里采样比例控制在 1:1 到 1:2。输出用概率而非硬标签保留打分概率后续可以按分位数灵活调整阈值而不是一刀切。在 MindSpore 里训练这个分类器可以直接用mindspore.nn.BertModel加一个分类头损失函数用BCEWithLogitsLoss。训练完成后导出成 MindIR 格式推理时用mindspore.nn.GraphCell加载性能比动态图模式好不少。3.4 语义去重的算法选择去重这块MinHash LSH 是性价比最高的方案。它的原理是把每条文本转成一组哈希签名通过 Jaccard 相似度判断是否重复。相比精确去重它能识别改了几个词但实质相同的近似重复。具体参数上我一般用128 个哈希函数5 个 band每个 band 4 行。这个配置在召回率和误判率之间比较平衡。如果数据量特别大可以先用 SimHash 做粗筛再用 MinHash 精筛。注意去重一定要在文档级别和段落级别各做一次。文档级去重解决整篇重复段落级去重解决同一段内容在不同文档里反复出现的问题。后者往往更隐蔽但对模型伤害更大。4. 实操过程与核心环节实现4.1 环境准备与依赖安装先说一下环境。我用的组合是 MindSpore 2.x Python 3.9 Ascend 910 或者 GPU 环境。安装 MindSpore 的时候要注意版本和硬件匹配官方提供了不同硬件对应的安装命令。pip install mindspore2.2.0 pip install ftfy langdetect datasketch pip install numpy pandas tqdmftfy用于编码修复langdetect用于语种识别datasketch提供 MinHash 和 LSH 的实现。这几个库是数据过滤流水线的常客。4.2 构建分层过滤流水线我把整个流水线写成一个可配置的 Python 脚本每个环节独立成函数方便单独调试和替换。核心结构大概是这样import ftfy import re from datasketch import MinHash, MinHashLSH def normalize_text(text): text ftfy.fix_text(text) text re.sub(r[\x00-\x08\x0b-\x1f], , text) text re.sub(rnbsp;|amp;|lt;|gt;, , text) return text.strip() def rule_filter(text, min_len200, max_len100000): if len(text) min_len or len(text) max_len: return False lines text.split(\n) if not lines: return False avg_line_len len(text) / len(lines) if avg_line_len 10 or avg_line_len 2000: return False special_ratio sum(1 for c in text if not c.isalnum() and not c.isspace()) / len(text) if special_ratio 0.3: return False return True def compute_minhash(text, num_perm128): m MinHash(num_permnum_perm) for token in text.split(): m.update(token.encode(utf-8)) return m这段代码里normalize_text做格式规范化rule_filter做规则过滤compute_minhash生成去重签名。实际使用时我会把规则过滤的阈值做成配置文件方便针对不同数据源调整。4.3 模型打分的 MindSpore 实现模型打分环节我用 MindSpore 训练了一个轻量分类器。训练脚本的核心部分import mindspore as ms import mindspore.nn as nn import mindspore.ops as ops from mindspore import Tensor class QualityClassifier(nn.Cell): def __init__(self, backbone, num_classes2): super().__init__() self.backbone backbone self.classifier nn.Dense(backbone.config.hidden_size, num_classes) def construct(self, input_ids, attention_mask): outputs self.backbone(input_ids, attention_mask) pooled outputs[1] logits self.classifier(pooled) return logits loss_fn nn.CrossEntropyLoss() optimizer nn.Adam(classifier.trainable_params(), learning_rate2e-5)训练数据我准备了大概 50 万条正负样本各半。正样本来自维基百科、优质书籍和人工筛选的问答负样本来自规则过滤掉的垃圾。训练 3 个 epoch 后验证集准确率能到 92% 左右这个精度对于数据过滤来说已经够用了。推理阶段把模型导出成 MindIRms.export(classifier, input_ids, attention_mask, file_namequality_cls, file_formatMINDIR)然后批量推理时用ms.load加载配合batch_size256跑单卡吞吐能到每秒几千条处理 TB 级语料也就几个小时的事。4.4 去重环节的完整实现去重我用 MinHash LSH先建索引再查询。关键代码如下from datasketch import MinHashLSH lsh MinHashLSH(threshold0.8, num_perm128) def dedup_documents(docs): unique_docs [] for idx, doc in enumerate(docs): m compute_minhash(doc) result lsh.query(m) if not result: lsh.insert(str(idx), m) unique_docs.append(doc) return unique_docsthreshold0.8表示相似度超过 0.8 就判定为重复。这个值我调过几次0.7 太松会误杀0.9 太严会漏掉近似重复0.8 是比较稳的中间值。段落级去重稍微复杂一点需要先把文档切成段落对每个段落做 MinHash然后维护一个全局的段落索引。切段落的时候我一般按换行符切同时过滤掉长度小于 50 字符的短段落避免碎片化。4.5 数据配比与采样策略过滤完之后还有一步容易被忽略数据配比。不同来源的语料质量不同不能简单混合。我一般按来源分层给每层设定采样权重。比如书籍和论文权重 0.3百科和知识库权重 0.25优质网页权重 0.25问答和对话权重 0.2这个配比不是固定的要根据下游任务调整。如果模型主要做知识问答百科和书籍的权重可以调高如果做对话问答数据的权重就要上去。我通常会在小规模实验里试几组配比看评测指标再定。5. 常见问题与排查技巧实录5.1 过滤后数据量骤降怎么办这是最常见的问题。跑完流水线发现保留率只有 10%第一反应是阈值太严。排查思路是逐层统计保留率看是哪一层砍得最狠。我遇到过一次规则过滤后还剩 70%模型打分后直接掉到 15%。后来发现是分类器的训练数据分布和实际语料不匹配正样本太偏向书面语导致口语化但质量不差的语料被误判。解决办法是补充多样化的正样本重新训练或者把打分阈值从 0.5 降到 0.3。提示每一层过滤都要记录输入输出数量形成漏斗图。没有这个统计排查问题就是盲人摸象。5.2 去重后仍有大量重复内容如果发现去重后还有重复通常是两个原因一是 MinHash 的 num_perm 太小签名区分度不够二是去重只做了文档级没做段落级。我建议 num_perm 至少 128数据量大就上 256。段落级去重一定要做尤其是网页爬取的数据导航栏、页脚、版权声明这些内容会在每个页面重复出现文档级去重根本抓不到。5.3 模型打分推理速度慢推理慢一般是 batch_size 太小或者没开图模式。MindSpore 默认是动态图推理时切换到静态图模式能快不少ms.set_context(modems.GRAPH_MODE, device_targetAscend)另外把模型导出成 MindIR 再推理比直接加载 checkpoint 快 20% 到 30%。如果还是慢考虑用多卡并行推理或者把打分环节放到离线集群上跑。5.4 常见问题速查表问题现象可能原因排查方向解决建议保留率过低阈值过严/分类器偏差逐层统计保留率放宽阈值/重训分类器去重不彻底num_perm 太小/缺段落级去重检查签名维度提高 num_perm/加段落去重推理速度慢动态图模式/批次小检查 context 配置切图模式/导出 MindIR训练 loss 异常数据配比失衡统计各来源占比调整采样权重下游效果差过滤误杀高质量数据人工抽检被过滤样本修正规则/补充正样本5.5 几个踩坑经验第一个坑是语种识别不能省。我早期做多语种预训练没做语种过滤结果英文语料里混进了大量其他语种模型在英文任务上表现明显下降。后来加了langdetect做语种分流效果立竿见影。第二个坑是去重的顺序。我一开始先做模型打分再做去重结果发现打分模型在重复数据上浪费了大量算力。正确顺序应该是先去重再打分这样打分环节处理的都是唯一内容。第三个坑是阈值不能一劳永逸。数据源变了、采集时间变了合适的阈值也会变。我现在的做法是每次换数据源都重新跑一遍分布统计动态调整阈值而不是沿用旧配置。第四个坑是别忘了保留原始数据。过滤是有损操作万一发现过滤错了想回滚没有原始数据就麻烦了。我一般会把原始语料和过滤后的语料分开存储过滤脚本也做版本管理。6. 关于数据质量过滤的一些个人体会做预训练数据过滤这几年我最大的感受是这件事没有银弹只有不断迭代。规则会过时模型会漂移数据分布会变化唯一不变的是持续监控、持续调整这个动作本身。我现在习惯在流水线里加一个抽样人工审核环节每次过滤完随机抽几百条人工看看被保留的和被过滤的样本质量。这个动作花不了多少时间但能发现很多自动化指标看不出来的问题。比如有一次我发现被过滤的样本里有一批质量其实不错的技术文档原因是特殊符号占比超标后来专门为技术类语料放宽了这个阈值。另外MindSpore 生态在数据处理这块还在快速演进mindspore.dataset的能力每个版本都在增强。我建议你保持对官方文档的关注尤其是 dataset 相关的算子更新有时候一个新算子就能省掉你自己写的一大段代码。最后分享一个小技巧如果你的过滤流水线要跑很多次建议把每个环节的中间结果都落盘缓存。这样调整某一层参数时不需要从头重跑直接从缓存加载上一层的输出就行。我靠这个习惯把单次实验的迭代时间从几小时压缩到了几十分钟效率提升非常明显。