大模型预训练数据质量过滤:MindSpore语料清洗实战指南
做过大模型预训练的朋友应该都有类似体验代码写得再漂亮显卡堆得再多只要喂进去的数据是乱的loss 曲线就会用各种方式教你做人。我去年用 MindSpore 跑一个大模型预训练项目前期花在数据上的时间比真正跑训练的时间还长而其中最关键、也最容易翻车的一环就是数据质量过滤方案。所谓数据质量过滤就是在语料进入模型之前把重复、乱码、广告、非目标语言、毒性内容和隐私信息筛掉只给模型喂干净、多样、有用的文本。这篇内容适合准备自己从零整理预训练语料、或者正在优化现有数据管线的团队参考我会把设计思路、实操代码和踩坑经验一次性讲清楚尽量少让你走弯路。数据质量过滤听起来不像模型结构那么“有技术含量”但它直接决定预训练的天花板。同样的参数量、同样的训练卡数数据干净程度不同最终效果可以拉开一个身位。所以这篇文章不只是给一套脚本而是帮你想清楚为什么要筛、按什么维度筛、在 MindSpore 上怎么高效落地。1. 大模型预训练的第一步是给数据“体检”我见过不少团队上来就急着调模型结构、跑 benchmark结果语料还是爬虫抓完没清洗过的原始文件。第一版模型训出来生成文本里夹着广告链接、乱码和重复片段这时才回头补数据白白烧掉一大笔训练预算。1.1 脏数据会怎么反噬训练效果脏数据对预训练的影响不是“有一点干扰”而是会直接改变模型学到的概率分布。第一种典型危害是重复文本导致的记忆坍塌。自回归模型的核心任务是预测下一个 token某句话如果在语料中出现了一万次模型就会把它当成一个极高频的先验生成时动不动就复读这句话。我之前跑过一个测试原始语料里有一条论坛帖子被爬虫重复抓了 4000 多遍模型训完之后只要上下文稍微沾点边就整段整段往外吐这段内容看起来像是在“背诵”而不是在“生成”。第二种危害是噪声语料占用训练预算。你规划好训 1000 亿 token如果里面 10% 是乱码、广告导航、口水话那等于有 100 亿 token 的算力被浪费在垃圾上。更麻烦的是这些噪声并不是均匀分布的它们往往集中在某些域名或某些采集来源里模型会在局部领域学出一堆错误映射。第三种危害是错误知识被固化。模型预训练阶段没有人工纠错机制喂进去多少错误信息它就老老实实记多少。等到下游指令微调阶段再想去掉这些错误知识非常困难因为它们的权重已经深深写进参数里了。数据质量过滤本质上就是在大规模训练之前把模型将来可能犯的“低级错误”提前掐死在摇篮里。1.2 数据质量问题的典型形态与危害等级很多人以为数据清洗就是去掉 HTML 标签其实真实语料里的问题种类多到能列一长串。我整理了一个常用的问题形态表方便你做初检时对号入座。问题形态典型特征危害等级常见来源乱码文本出现“锟斤拷”、替换符、异常 Unicode高编码转换失败、错误解码重复文本整行重复、段落重复、近似改写高爬虫重复抓取、站群采集广告导流“点击查看”“免费领取”“加群”等中高网页样板、营销内容口水话“哈哈哈”“我也是醉了”等无意义短句中社交平台灌水非目标语言混入大量外语文本中跨语言采集、混合语料个人隐私信息手机号、身份证、邮箱、地址高公开数据集泄露毒性与攻击性文本辱骂、暴力、色情等内容高未审核的 UGC 内容格式残缺半截 HTML 标签、残留 Markdown 标记低中正文抽取失败需要注意的是这些问题经常叠加出现。一条样本可能同时是乱码文本加广告导流也可能前半段是正常新闻、后半段被拼进了论坛灌水回复。所以过滤不能只做一道最好是多条规则并行、层层设卡。1.3 质量过滤在 MindSpore 预训练流水线里的位置一条完整的预训练数据管线大致是这样原始语料采集 → 格式清洗 → 质量过滤 → 去重 → Tokenization → 组装训练样本 → 数据并行加载 → 模型训练。质量过滤放在 Tokenization 之前原因很简单token 化之后再做过滤很多内容已经被切碎重组原始文本信号反而丢了。而且同样的过滤规则在 token 化前只需要处理一次在 token 化后却可能涉及样本拼接逻辑排查问题时非常痛苦。在 MindSpore 生态里模型训练时数据是通过mindspore.dataset系列 API 流入 Trainer 的。你可以把质量过滤逻辑放在 Dataset 流水线里用filter算子做也可以提前离线处理完毕再转成 MindRecord 格式。我的建议是重活、脏活全部离线做训练时的 Dataset 只保留最后一层轻量兜底过滤。因为在训练阶段反复执行昂贵的 Python 过滤函数不但拖慢数据加载速度还会在每个 epoch 重复计算纯粹是浪费。2. 拆开数据质量过滤的六个技术维度“数据质量”是个很宽泛的词真要落地必须拆成可计算的维度。我习惯把过滤方案拆成六块语言识别、质量打分、去重、毒性过滤、隐私过滤、格式清洗。每块解决一类问题互相之间也有重叠但边界尽量清晰这样出问题时能快速定位是哪一环失守了。2.1 语言识别先把混入的杂音找出来预训练语料如果目标语言是中文第一道关就是把非中文内容筛掉。有些人觉得这一步无所谓反正 tokenizer 会自动处理但实际上混入的大量英文、日文、韩文文本会浪费 tokenizer 的词表容量还会让语料分布变得不伦不类。做语言识别不需要一上来就上大模型。对中文语料最简单的“汉字占比法”就能解决 90% 以上的问题。我常用的做法是统计文本里中文字符[\u4e00-\u9fff]的占比低于某个阈值就淘汰。代码只有几行import re HANZI_RE re.compile(r[\u4e00-\u9fff]) def hanzi_ratio(text: str) - float: if not text: return 0.0 return len(HANZI_RE.findall(text)) / len(text)中文新闻类语料的汉字占比通常在 0.7 以上如果低于 0.5基本可以断定是外文或者乱码。但要注意一个特殊情况代码和数学公式占比高的文本汉字占比会显著下降比如一篇技术文档里大量出现英文变量名和代码块汉字占比可能只有 0.3。这种文本不能一棍子打死建议单独走“代码语料白名单”通道或者把语言识别的阈值调低放到质量打分环节再判断。如果你处理的是多语言语料可以用 fastText 的lid.176.bin这类轻量语言识别模型它输出每个语言的置信度。实际操作时记得设置置信度阈值比如 0.5 以上才认定为目标语言避免把语种模棱两可的短文本误判。2.2 质量打分规则裁判和模型评测员怎么配合语言识别只能判断“是什么语言”判断不了“内容质量好不好”。真正决定过滤质量的核心环节是给每条样本打一个质量分。我常用的方案是“规则裁判 模型评测员”双层结构两层配合而不是只用一层。规则裁判是一组加权特征文本长度、平均句长、标点密度、特殊符号占比、重复 n-gram 比例、URL 数量。它的优点是执行快、可解释性强每条规则都能单独看统计。我给自己项目的打分器设计了这样的核心特征太短的文本直接给低分因为单句口水话信息量有限URL 占比太高说明大概率是导航页或采集站标点密度异常低可能是纯符号堆叠连续重复字符比例高则可能是乱码或刷屏。把这些特征标准化到 0 到 1 区间再按经验权重加权求和就得到一个可用的quality_score。模型评测员则负责那些“看起来正常、但读起来很别扭”的文本。主流做法是用困惑度Perplexity衡量训练一个小规模语言模型给每条文本算困惑度模型越“意外”的序列困惑度越高。这些高分困惑度样本里确实藏了大量低质文本但也误伤了代码、古文、数学公式这类“高信息熵”内容。我印象最深的一次就是把困惑度最高的 10% 全删了结果古文语料全没了模型后来在文言文相关的下游任务上直接崩盘。所以我的建议是模型评测员不要做“一刀切删除”而是做“分层降采样”。把困惑度分位数算出来最高的 10% 样本按 30% 概率保留中间的 30% 按 70% 概率保留低困惑度的全部保留。这样既压制了坏样本又保留了多样性。2.3 去重让每一条样本都有新鲜感质量过滤解决的是“每一条样本自身质量”的问题去重解决的是“样本之间互相重复”的问题。预训练语料里重复现象极其普遍尤其是资讯站采集的文章经常出现完全相同的段落被复制到不同页面。精确去重没什么好说的对整篇文本做 MD5 或 SHA1完全相同就去掉这是第一道廉价的拦网。但真正麻烦的是近似重复两篇文章 90% 内容相同只改了几个段落首尾精确哈希完全失效。模糊去重我推荐 MinHash LSH。核心思路是把文本切成一堆 shingle一般是 5 到 8 个字符的滑动窗口把它们转成哈希集合再用 MinHash 把集合压缩成固定长度的签名最后通过局部敏感哈希LSH加速查找相似签名。这样你能以较高召回率找出 Jaccard 相似度超过阈值的文本对。实操时可以直接用datasketch库from datasketch import MinHashLSH, MinHash def build_minhash(text: str, num_perm: int 128, shingle_size: int 6): mh MinHash(num_permnum_perm) if len(text) shingle_size: return mh for i in range(len(text) - shingle_size 1): mh.update(text[i:ishingle_size].encode(utf-8)) return mh在大规模场景下比如几十亿条样本直接用 MinHashLSH 查询内存可能扛不住。可以先用 Bloom Filter 统计高频 n-gram把明显撞车的样本过滤掉再对剩下的候选集做 MinHash。数据量级越大越要遵循“先粗筛、再细查”的思路否则方案再好也跑不动。2.4 毒性、隐私与格式清洗安全底线类的过滤质量打分管“好不好”去重管“重复不重复”还有一类过滤管的是“能不能用”。毒性过滤解决语料里可能存在的攻击性、暴力、色情等不合规内容。技术上通常用两类手段并行一类是维护一个覆盖常见词汇的词表做正则匹配另一类是训练一个轻量文本分类器对整条样本打标签。词表匹配速度快但容易误伤分类器更准但需要标注数据。我的经验是先词表粗筛命中可疑样本再交给分类器复核这样性能与准确率都能兼顾。隐私过滤处理的是个人敏感信息比如身份证号、手机号、邮箱、具体家庭住址。这类信息一旦进入模型训练完之后可能被模型在生成时“复述”出来这是非常严重的安全隐患。先写正则做第一轮匹配手机号和邮箱这类有明确格式的很好处理地址和姓名要靠命名实体识别补漏。对于预训练语料建议匹配到就直接整段删除不要抱着“只有一小段泄露了没关系”的侥幸心理。格式清洗则是把 HTML 标签、控制字符、零宽空格、异常 Unicode 清掉。这里要提醒一句不要过度清洗。如果你把文本里的括号、引号、代码符号全部干掉语料的可读性会大打折扣。特别是代码类语料特殊符号本身就是语义的一部分盲目清理等于把代码阉割了。格式清洗的目标是把“噪音”清掉而不是把“特征”清掉。2.5 过滤维度的取舍不是过滤得越狠越好很多团队第一次做质量过滤容易走上另一个极端规则叠加了一堆恨不得把可疑文本全干掉结果原本 500GB 的语料过滤完只剩 100GB。数据量骤降带来的直接问题就是多样性受损模型学习的知识面变窄在长尾任务上表现明显变差。我自己的原则是“硬过滤”与“软过滤”分开。硬过滤针对的是绝对不可用的内容比如乱码、隐私信息、涉暴涉恐、重复度接近 100% 的文本这些直接删除。软过滤针对的是“质量不太好但还有信息量”的文本比如口水话夹杂少量有效信息、广告周边带一点正文这些按质量分降采样保留一部分。阈值怎么定不是拍脑袋拍出来的。我的做法是每次调参前从数据里随机抽 400 条人工打标分成“可用”“一般”“垃圾”三类再画特征分布图看哪些分数段能把“垃圾”和“可用”明显分开。没有这步人工标注所有阈值都只是猜测。3. MindSpore 上落地数据质量过滤 Pipeline 的完整过程维度想清楚了接下来就是工程实现。这一章我会完整展示在 MindSpore 上搭建过滤管线的过程从开发环境、总体架构、核心代码到分布式并行都会写到你可以直接照着搭。3.1 开发环境准备VSCode 配 MindSpore 内核调试效率翻倍我强烈建议你不要在终端里黑灯瞎火地写数据处理脚本。我自己的习惯是在本地用 VSCode 做 Remote-SSH 远程开发连到训练服务器上把 Python 解释器切到安装了 MindSpore 的 conda 环境。这样在 VSCode 里新建 notebook 时就能选择对应的 MindSpore 内核直接在 Jupyter 环境里逐行调试数据处理代码。别小看这个环境配置的差别。处理数据时你经常想看看某条样本被过滤后到底长什么样、某个特征算出来是多少如果只能靠print打日志效率会低很多。用 notebook 加断点你可以在内存里直接检查text变量的内容、查看 Dataset 的 shape、单步跑过滤函数整个调试体验是质的提升。特别是 MindSpore 的 Dataset API 链式调用很长你可以在每一步之后查看输出确认shuffle、batch、repeat的顺序有没有写错。这里有个小提醒在共享训练服务器上调试时别直接在登录节点跑大规模for循环尽量用交互开发节点或者先取一小块数据来跑。我见过有人在登录节点跑全量过滤把整个服务器的 CPU 占满导致其他同事的作业全部排队差点引发事故。3.2 Pipeline 总体架构从原始语料到可训练数据集我建议把过滤管线拆成离线预处理和训练时加载两个阶段。离线预处理阶段用 Python 多进程脚本处理原始语料产出的中间格式是 JSONL然后再把清洗后的 JSONL 转成 MindRecord供训练时的高效加载。整个流程长这样原始语料JSONL / TXT → Python 多进程预处理语言识别、质量打分、去重、敏感词过滤 → 清洗后 JSONL → 转换为 MindRecord 分片 → MindSpore Dataset 加载 → 按卡号切分数据 → shuffle / batch / repeat → 进入训练为什么中间格式用 JSONL因为一行就是一个样本天然支持大文件流式读写清洗过程中出问题可以用grep抽查任意一行断点续跑时只需要记录处理到哪个文件了。它最大的缺点是读取速度慢但那是训练阶段的事不是预处理阶段的事。为什么训练前要转 MindRecord因为 MindRecord 是 MindSpore 的原生数据格式采用二进制存储并支持按 shard 随机读取。同样的 20GB 数据用 JSONL 在训练时每次读取都要做字符串解析转换成 MindRecord 之后能明显降低数据加载的 CPU 开销。更关键的是MindRecord 天然支持num_shards切分方便你在分布式训练时让每张卡只读属于自己的那部分数据。3.3 核心代码实现质量打分器 Dataset 过滤先给出一份可以直接改的规则打分器代码。我只保留最核心的特征避免把函数写得过于臃肿你可以在实际项目中往里面继续加特征。import re HANZI_RE re.compile(r[\u4e00-\u9fff]) URL_RE re.compile(rhttps?://[^\s]|www\.[^\s]) CONTROL_RE re.compile(r[\x00-\x08\x0b\x0c\x0e-\x1f]) PUNCT_SET set(。、,.!?;:) def quality_score(text: str) - float: if not text: return 0.0 text CONTROL_RE.sub(, text) text_len len(text) if text_len 30: return 0.0 hanzi_ratio_value len(HANZI_RE.findall(text)) / text_len url_count len(URL_RE.findall(text)) url_ratio url_count / text_len punct_count sum(1 for ch in text if ch in PUNCT_SET) punct_ratio punct_count / text_len # 连续重复字符比例用于识别乱码与刷屏 repeat_count 0 for i in range(len(text) - 1): if text[i] text[i 1]: repeat_count 1 repeat_ratio repeat_count / max(text_len - 1, 1) score ( 0.35 * min(hanzi_ratio_value, 1.0) 0.25 * min(punct_ratio * 4, 1.0) 0.25 * (1.0 - min(url_ratio * 20, 1.0)) 0.15 * (1.0 - min(repeat_ratio * 10, 1.0)) ) return score这个打分器的输出在 0 到 1 之间。0.6 这个阈值是我抽了 400 条人工标注之后画分布图找出来的不是随手定的。你可以照这个流程在自己数据上调。接下来是离线并行过滤脚本。注意这里用multiprocessing.Pool按文件粒度并行避免多个进程同时写一个输出文件造成的锁竞争。import json from multiprocessing import Pool from pathlib import Path def filter_one_line(line: str): line line.strip() if not line: return None try: obj json.loads(line) except json.JSONDecodeError: return None text obj.get(text, ) if not text or quality_score(text) 0.6: return None return json.dumps(obj, ensure_asciiFalse) def process_file(src_path: Path, dst_path: Path): with src_path.open(r, encodingutf-8) as fin, \ dst_path.open(w, encodingutf-8) as fout: for line in fin: filtered filter_one_line(line) if filtered: fout.write(filtered \n) if __name__ __main__: src_files list(Path(raw_chunk).glob(*.jsonl)) tasks [] for src in src_files: dst Path(clean_chunk) / src.name tasks.append((src, dst)) with Pool(16) as pool: pool.starmap(process_file, tasks)清洗完之后需要把 JSONL 转成 MindRecord。这里我写了一个生成器避免一次性把所有数据读进内存。import json from pathlib import Path from mindspore.mindrecord import FileWriter def data_generator(clean_dir: str): for file in Path(clean_dir).glob(*.jsonl): with file.open(r, encodingutf-8) as fin: for line in fin: obj json.loads(line) text obj.get(text, ) score obj.get(score, 0.0) if text: yield {text: text, score: float(score)} schema {text: {type: string}, score: {type: float32}} writer FileWriter(file_namedata/pretrain.mindrecord, shard_num8) writer.add_schema(schema, filtered pretrain corpus) writer.write_raw_data(data_generator(clean_chunk)) writer.commit()最后是训练时的 Dataset 加载和兜底过滤。这时 MindRecord 已经按score字段离线筛过一遍了训练时再过滤一次是为了挡住那些离线阶段可能漏过的极端短文本。import mindspore.dataset as ds def is_good_record(record): text record[text] # 兜底规则长度太短的文本直接丢弃 if len(text) 50: return False return True dataset_files [data/pretrain.mindrecord] full_ds ds.MindDataset(dataset_files, num_parallel_workers8) full_ds full_ds.filter(predicateis_good_record) full_ds full_ds.shuffle(buffer_size10000) full_ds full_ds.batch(batch_size256, drop_remainderTrue)这里要特别说明不要把昂贵的大模型评分逻辑写进训练时的filter因为filter会在每个 epoch 被重复执行大模型跑一次就要命。所有重计算都应该离线完成训练时只留最轻量的规则。3.4 分布式并行让每张卡拿到不同但均衡的数据大模型预训练基本都是分布式训练MindSpore 里通过MindDataset的num_shards和shard_id参数来给每张卡分数据。示例代码如下from mindspore.communication import init, get_rank, get_group_size init() rank_id get_rank() world_size get_group_size() train_ds ds.MindDataset( dataset_filesdata/pretrain.mindrecord, num_shardsworld_size, shard_idrank_id, num_parallel_workers8, )有一个细节容易被忽略分片之后的数据均衡问题。如果 MindRecord 各个分片的样本数差太多训练时某些卡的梯度计算会快某些卡会慢整个集群都在等最慢的那张卡。所以你在FileWriter转格式时shard_num不要设得比卡数少最好设成卡数的整数倍并且写入数据时尽量打散后再写避免某个分片恰好集中了某个来源的语料。另外shuffle在分布式训练里要特别注意。每条数据在加载时已经按num_shards切给了固定卡号如果shuffle只在数据加载初期做一次整个训练过程每张卡见到的数据组合就固定了。我建议每个 epoch 都换一下随机种子或者直接开启shuffle_each_epoch保证跨 epoch 的数据顺序不同防止模型在固定顺序上过拟合。3.5 性能优化CPU 才是这场战役的主战场不少人会误以为数据过滤是 GPU 的活真跑起来才发现瓶颈全在 CPU 和磁盘 IO。质量过滤本质上是一个 CPU 密集加 IO 密集的任务优化顺序非常重要。第一优先级的优化是“把重活离线化”不要在训练循环里反复做文本正则和大模型推理。第二优先级是“并行化”用multiprocessing.Pool按文件粒度并行我实测过同样对 1GB 文本做规则过滤单进程跑要接近 20 分钟16 进程并行后压到 3 分钟以内。第三优先级是“减少重复计算”正则表达式统一预编译不要在每行样本里重复re.compile对大文件用缓冲读取不要一行一行做系统调用。如果你的过滤规则里还包含大模型打分那就要引入“先粗后细”的策略。先用廉价的规则打分器把所有样本跑一遍只有落在“灰色地带”的样本才交给大模型做二次判断。比如规则分在 0.4 到 0.7 之间才让大模型重新打分规则分很高或很低的样本直接放行或删除。这样既保住了精度又不会让大模型成为整个管线的吞吐瓶颈。MindSpore 的 Dataset API 虽然提供num_parallel_workers参数但 Python 层回调在并行时会有 GIL 和跨语言调用开销。如果确实需要在 Dataset 里跑自定义过滤函数建议把函数写得足够轻量并且用num_parallel_workers和prefetch_size配合调参找到你机器上性价比最高的参数组合。4. 数据过滤实战中的常见问题与排查技巧数据过滤这套系统做完之后真正的挑战才刚刚开始。你在真实数据上跑几轮一定会遇到过滤过猛、漏网、性能崩盘这类问题。我把实际踩过的坑和排查思路整理出来希望能帮你省掉几个晚上的排查时间。4.1 过滤过猛训练集缩水了怎么办最常见的翻车现场是规则叠加得很爽语料量却肉眼可见地缩水了。我有一次把“符号占比”“URL 占比”“最短长度”三条规则一起收紧结果原本 300GB 的语料只剩 80GB。当时第一反应是“数据太脏了”后来抽查被删样本才发现大量包含数学符号和代码片段的优质技术文章被误杀了。排查方法其实不复杂给每条过滤规则单独加一个计数器统计它分别淘汰了多少样本。我会把过滤结果分成“已通过”“已删除”“灰色待定”三个输出目录并定时对“已删除”目录做抽样人工检查。看到底是规则本身太激进还是阈值设得不对。修复时不要只调一个阈值我建议把“硬删除”改为“分层降采样”。质量分低于 0.2 的直接删0.2 到 0.5 之间的按 20% 概率保留0.5 以上的全部保留。这样即使某类文本被某个规则误伤至少还有一部分能留下来不至于整个语料类型消失。4.2 坏样本漏网如何科学评估过滤器过滤器上线后你还需要一套评估机制否则很难回答一个关键问题到底漏掉了多少坏样本。我自己的做法是构造一个“合成坏样本集”拿干净文本人为插入广告句式、重复段落、乱码字符、敏感词、隐私信息然后跑过滤器看召回率。举个例子你可以生成 1000 条测试样本其中 500 条是干净样本500 条是伪造的坏样本。跑完过滤器之后算两个指标坏样本召回率坏样本被删掉的比例和干净样本误杀率干净样本被误删的比例。对硬过滤规则我要求坏样本召回率不低于 99%对软过滤降采样规则则要求误杀率足够低否则会伤害语料多样性。这套测试可以沉淀成 pytest 用例每次改过滤规则之后先跑一遍再上全量数据。否则你很难知道自己加了一条规则之后到底改善了效果还是制造了新的误杀。4.3 流水线跑得太慢先定位瓶颈再动手数据过滤跑得慢人人都想优化但很多人一上来就盲目加进程数结果 CPU 早就满了瓶颈其实在磁盘 IO。先做一次分阶段计时把处理单个文件的时间拆成“读文件时间”“逐行处理时间”“写文件时间”三部分。如果是逐行处理时间占比高先检查是不是正则没有预编译或者每条样本都调了一次大模型。如果是写文件时间占比高考虑用更大的写入缓冲或者让每个进程只写一个独立输出文件最后再合并。如果是读取时间占比高检查是不是大量的样本集中在一个超大文件里导致无法并行读取提前把大文件切成多个小分片就能解决。我还习惯用py-spy这类工具在工作进程上 dump 一下调用栈看看 Python 层到底卡在哪个函数里。有时候你以为卡在quality_score实际是日志打印太频繁print成了性能杀手。把日志改成按比例采样流水线速度能快不少。4.4 参数速查表与我的默认配置不同语料的最优参数肯定不同但你可以从下面这套“起始配置”出发再根据自己数据的情况调整。它是我在中英文混合语料上验证过的一版作为起点足够安全。过滤项推荐方案参考阈值/参数备注语言识别汉字占比法目标语言汉字占比不低于 0.5代码/数学文本单独处理质量规则长度、标点、URL、重复字符quality_score不低于 0.6阈值靠人工标注分布确定精确去重MD5完全相同即删除第一道去重拦网模糊去重MinHash LSHshingle6Jaccard0.8 判重大规模前先跑 Bloom Filter敏感词过滤词表匹配 分类器复核词表覆盖 召回率测试注意谐音和变体隐私过滤正则匹配 PII身份证/手机号/邮箱命中即删用 NER 做补充召回格式清洗HTML 标签剥离 控制字符清理保留代码与数学符号不要清掉所有特殊符号分片写入MindRecordshard_num 卡数整数倍保证各卡数据均衡这版配置不一定适合所有场景。如果你的语料是垂直领域比如代码、医学、法律建议把领域特有的质量特征加进打分器并且重新抽样本做人工标注。迁移到一个新领域时最忌讳直接照搬上一套阈值而不做校验。最后说一点个人体会。数据质量过滤方案不是一个做完就固定的脚本而是要跟着模型评测结果不断迭代的工程系统。我第一次把 300GB 语料过滤到 80GB觉得数据非常干净了结果模型在代码生成任务上明显变笨后来才发现是代码和数学语料被误杀。调整阈值、把硬删除改成降采样第二轮就把效果拉了回来。现在我的原则很简单过滤 Pipeline 一定要可观测、可回滚、可抽查。每条规则过滤了多少样本要有统计每次调整阈值之前要有备份每个被删的样本都要有地方能翻出来复查。能做到这三点数据质量过滤这套方案就不会成为你预训练项目里的定时炸弹。