ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MindSpore大模型预训练数据质量过滤实战:分层去重去噪与质量打分

MindSpore大模型预训练数据质量过滤实战:分层去重去噪与质量打分 1. 大模型预训练里数据质量过滤到底在解决什么问题做过大模型预训练的人都有一个共识模型效果的上限很大程度上不是被网络结构卡住的而是被数据质量卡住的。我刚开始接触 MindSpore 做预训练任务时也曾经天真地以为只要把海量文本一股脑喂进去模型就能自己学会说话。结果跑完一轮 loss 曲线看着还行实际生成出来的内容却惨不忍睹——重复、乱码、逻辑断裂甚至把网页导航栏和广告文案都背下来了。后来复盘才发现问题根本不在模型而在于我喂进去的数据里有大量低质、重复、格式混乱的垃圾内容。所谓数据质量过滤说白了就是在数据进入训练流程之前用一套规则加模型的手段把那些“吃了没用甚至有害”的样本筛掉。它要解决的核心问题有三个第一去重互联网文本重复率极高同一篇新闻可能被转载几百次模型反复看同一句话会严重过拟合第二去噪网页里夹杂的 HTML 标签、乱码、无意义符号、机器生成的垃圾文本都会污染训练信号第三筛选有效信息密度有些文本虽然通顺但信息量极低比如“点击这里查看更多”“版权所有”这类模板化内容对模型学习语言规律几乎没有帮助。这套方案适合谁参考如果你正在用 MindSpore 做百亿甚至千亿参数级别的预训练或者你手上有几十 GB 到几 TB 的原始语料需要清洗那这篇内容基本可以直接抄作业。哪怕你只是做小规模微调数据过滤的思路同样适用只是规模不同而已。我下面会从整体设计、核心细节、实操流程到踩坑排查完整拆一遍我在 MindSpore 环境下落地这套方案的全过程。2. 整体方案设计与技术选型思路2.1 为什么过滤要分层次而不是一刀切很多人一上来就想用一个模型分类器把所有低质数据干掉我试过效果并不好。原因很简单不同维度的质量问题需要用不同的手段处理混在一起反而会误杀。比如一篇技术博客可能包含少量 HTML 残留但内容本身很有价值如果你用严格的正则一刀切可能连正文都删了反过来一篇纯广告文本虽然格式干净但信息密度为零正则根本识别不出来。所以我的整体设计是四层过滤管线从粗到细逐层收紧第一层格式规范化与基础清洗处理编码、HTML 标签、特殊符号、空白字符把原始数据变成统一格式的纯文本。第二层规则级过滤用统计指标和启发式规则快速剔除明显低质样本比如长度过短、重复率过高、符号占比异常。第三层去重处理包括精确去重和近似去重解决互联网文本高度重复的问题。第四层模型级质量打分用轻量分类模型对剩余数据做质量评分保留高分样本。这个分层逻辑的好处是前两层成本极低能干掉大部分垃圾减轻后面模型打分的压力后两层精度高但计算贵只处理已经比较干净的数据。实测下来在 MindSpore 上跑一套 TB 级语料分层过滤比单模型过滤快了三倍以上而且误杀率明显更低。2.2 MindSpore 在这个流程里的角色定位MindSpore 在这套方案里主要承担两个职责一是数据处理的并行加速MindSpore 的 Dataset 模块支持多进程、多线程的数据管道配合map、filter、batch等操作可以很自然地把过滤逻辑嵌入数据加载流程二是质量分类模型的训练与推理我用 MindSpore 训练了一个小型的文本质量分类器参数量控制在千万级别推理速度快适合在预处理阶段大规模跑。这里有个选型细节值得说为什么不用 PyTorch 或 TensorFlow 做质量分类模型而是坚持用 MindSpore因为整个预训练流程都在 MindSpore 生态里数据管道和模型推理如果跨框架中间的数据格式转换和序列化开销会非常大。我试过用外部框架跑分类器再把结果导回 MindSpore Dataset光磁盘 IO 就多了一倍。统一在 MindSpore 里做数据可以以内存友好的方式流转整体吞吐量提升很明显。2.3 过滤强度与数据保留率的平衡这是最容易被忽视但最致命的问题过滤太松垃圾数据影响模型效果过滤太严数据量不够模型欠拟合。我一开始按网上一些教程设了很严格的阈值结果 1TB 原始数据过滤完只剩 80GB模型训练时明显感觉数据多样性不足生成内容变得非常保守和模板化。后来我调整策略采用分级保留的思路把数据按质量分成高、中、低三档高质量数据全部保留中质量数据按比例采样保留低质量数据直接丢弃。具体比例根据你的总数据量来定如果原始数据超过 500GB可以适当收紧如果只有几十 GB就要放宽标准优先保证数据量。这个平衡点没有绝对公式需要根据你的模型规模和任务目标做几次小规模实验来校准。3. 核心细节解析与实操要点3.1 格式规范化把脏数据变成可处理的纯文本原始语料最常见的几种脏法编码混乱GBK、UTF-8 混在一起、HTML 标签残留、多余空白和换行、特殊控制字符。这一步的目标不是过滤而是把所有数据变成统一、干净、可比较的纯文本为后面的规则和模型处理打基础。在 MindSpore 的 Dataset 管道里我通常把这一步写成自定义的map操作。核心处理逻辑包括编码统一用chardet检测编码统一转成 UTF-8遇到无法解码的字符用忽略策略而不是报错中断。HTML 清洗用正则或BeautifulSoup去掉标签但要注意保留段落结构不能把p直接删掉导致所有文字粘在一起。空白规范化把连续多个空格、换行、制表符统一成单个空格或换行去掉首尾空白。控制字符过滤去掉 ASCII 控制字符和 Unicode 私有区字符这些通常是乱码来源。import re import html def normalize_text(text): # 解码 HTML 实体 text html.unescape(text) # 去掉 HTML 标签但保留换行 text re.sub(rbr\s*/?, \n, text) text re.sub(r[^], , text) # 去掉控制字符 text re.sub(r[\x00-\x08\x0b\x0c\x0e-\x1f\x7f], , text) # 规范化空白 text re.sub(r[ \t], , text) text re.sub(r\n{3,}, \n\n, text) return text.strip()注意HTML 清洗不要用过于激进的正则比如.*?这种非贪婪匹配在嵌套标签场景下会出错。如果数据里 HTML 占比很高建议用专门的解析库虽然慢一点但准确率高很多。3.2 规则级过滤用统计指标快速筛掉明显垃圾这一步是整个管线里性价比最高的环节。我常用的规则指标有五个指标含义典型阈值处理方式文本长度字符数少于 50 或超过 100000丢弃平均行长度总字符数/行数少于 10丢弃符号占比非字母数字字符比例超过 0.5丢弃重复行占比重复行数/总行数超过 0.3丢弃停用词占比常见停用词比例低于 0.01丢弃这些阈值不是拍脑袋定的我是先抽样统计了 10 万条数据看各项指标的分布再取分位数作为阈值。比如文本长度我看了下 5% 分位数是 48 个字符95% 分位数是 87000所以把下限设 50、上限设 100000既能去掉太短的垃圾又不会误杀长文档。在 MindSpore 里实现规则过滤可以直接用 Dataset 的filter操作import mindspore.dataset as ds def quality_filter(sample): text sample[text] if len(text) 50 or len(text) 100000: return False lines text.split(\n) if len(lines) 1: avg_line_len sum(len(l) for l in lines) / len(lines) if avg_line_len 10: return False repeat_ratio 1 - len(set(lines)) / len(lines) if repeat_ratio 0.3: return False symbol_ratio sum(1 for c in text if not c.isalnum() and not c.isspace()) / len(text) if symbol_ratio 0.5: return False return True dataset dataset.filter(quality_filter)实操心得规则过滤的阈值一定要根据你的数据来源调整。中文数据和英文数据的符号占比分布完全不同中文里标点占比天然更高阈值要适当放宽。我一开始用英文数据的阈值套中文结果误杀了一大批正常文本。3.3 去重处理精确去重与近似去重的组合拳互联网文本的重复率有多高我实测过一批爬取的新闻数据精确重复率大约 15%近似重复率超过 30%。如果不做去重模型会反复看到同样的内容导致生成时倾向于复制训练数据缺乏创造性。精确去重比较简单对文本做哈希比如 SHA256维护一个哈希集合遇到已存在的就丢弃。但精确去重只能处理完全一样的文本对于改了几个词的转载文章无能为力。近似去重我用的是MinHash LSH的方案。核心思路是把每篇文档拆成 n-gram 集合用多个哈希函数生成 MinHash 签名再通过 LSH 把可能相似的文档分到同一个桶里最后在桶内做精确的 Jaccard 相似度计算。这个方案在 MindSpore 里可以用mindspore.dataset配合自定义的 Python 操作实现也可以先用 Spark 做一轮粗筛再用 MindSpore 处理剩余数据。from datasketch import MinHash, MinHashLSH def get_minhash(text, num_perm128): m MinHash(num_permnum_perm) for i in range(len(text) - 5): m.update(text[i:i5].encode(utf8)) return m lsh MinHashLSH(threshold0.8, num_perm128) for idx, text in enumerate(texts): m get_minhash(text) if lsh.query(m): continue # 近似重复丢弃 lsh.insert(idx, m)注意MinHash 的num_perm和threshold需要根据数据规模调。数据量越大num_perm可以适当降低以节省内存threshold设 0.8 意味着相似度超过 80% 就算重复这个值在中文场景下比较合适英文可以设到 0.85。3.4 模型级质量打分用轻量分类器做最终把关经过前三层过滤剩下的数据已经比较干净了但仍有部分“格式干净但内容无意义”的样本比如机器生成的伪原创、关键词堆砌的 SEO 垃圾。这时候就需要一个质量分类模型来做最终判断。我的做法是人工标注 5000 条样本分为高质量、中等、低质量三档训练一个基于 BERT 的小型分类器。模型结构不用太复杂用 MindSpore 的nn.BertModel加载预训练权重接一个三分类头训练几个 epoch 就能达到 90% 以上的准确率。推理阶段把分类器嵌入 MindSpore Dataset 管道对每条数据打分只保留高质量和部分中等质量样本。这里有个性能优化点分类器推理要 batch 化不要一条一条跑。我一开始用单条推理吞吐量只有几百条每秒改成 batch size 64 之后直接提升到上万条每秒。import mindspore.nn as nn import mindspore.ops as ops class QualityClassifier(nn.Cell): def __init__(self, bert_model, num_classes3): super().__init__() self.bert bert_model self.classifier nn.Dense(768, num_classes) self.softmax nn.Softmax(axis-1) def construct(self, input_ids, attention_mask, token_type_ids): _, pooled self.bert(input_ids, attention_mask, token_type_ids) logits self.classifier(pooled) return self.softmax(logits)实操心得标注数据时一定要让多个标注员交叉验证保证标注一致性。我一开始只让一个人标结果模型学到的标准很偏后来改成三人标注加投票模型效果明显更稳。4. 完整实操流程与关键环节实现4.1 环境准备与依赖安装在开始之前先把环境搭好。我用的组合是 MindSpore 2.x Python 3.9 CUDA 11.6如果你用 Ascend 环境把 CUDA 相关依赖换成对应的 CANN 包即可。pip install mindspore-gpu2.2.0 pip install datasketch pip install beautifulsoup4 pip install chardet pip install tqdm注意MindSpore 版本和 Python 版本有对应关系装之前先查一下官方兼容性列表。我有一次用 Python 3.11 装 MindSpore 2.0结果各种报错换成 3.9 就顺利了。4.2 数据加载与管道构建MindSpore 的 Dataset 支持从多种格式加载数据我通常把原始语料存成 JSONL 格式每行一个样本包含text和source字段。加载时用ds.TextFileDataset或ds.GeneratorDataset然后依次挂上规范化、规则过滤、去重、质量打分的操作。import mindspore.dataset as ds import json def gen_samples(file_path): with open(file_path, r, encodingutf-8) as f: for line in f: item json.loads(line) yield (item[text],) dataset ds.GeneratorDataset(gen_samples(raw_data.jsonl), column_names[text]) dataset dataset.map(normalize_text, input_columns[text]) dataset dataset.filter(quality_filter) dataset dataset.batch(64, drop_remainderTrue)这里有个细节map和filter的顺序很重要。一定要先做规范化再做过滤否则规则过滤会误判。比如原始文本里有 HTML 标签符号占比会很高直接过滤会误杀正常内容。4.3 去重模块的工程实现去重是整套流程里最耗资源的环节尤其是近似去重。我的工程实现分两步走第一步精确去重用哈希集合在内存里做速度快内存占用可控。如果数据量超过内存容量可以用布隆过滤器替代代价是有极小的误判率。第二步近似去重用 MinHash LSH。这里的关键是分片处理把数据按来源或时间分片每片单独做 LSH最后合并结果。这样既能控制内存又能并行加速。def dedup_pipeline(dataset, lsh_threshold0.8): seen_hashes set() lsh MinHashLSH(thresholdlsh_threshold, num_perm128) for batch in dataset.create_dict_iterator(): texts batch[text].asnumpy().tolist() for text in texts: h hashlib.sha256(text.encode(utf-8)).hexdigest() if h in seen_hashes: continue m get_minhash(text) if lsh.query(m): continue seen_hashes.add(h) lsh.insert(h, m) yield (text,)实操心得MinHash 的 n-gram 大小建议用 5中文场景下 5-gram 能比较好地捕捉语义相似性。太小容易误判太大又抓不住改写后的重复。4.4 质量分类模型的训练与推理质量分类模型的训练数据来自人工标注我标了 5000 条按 8:1:1 划分训练、验证、测试。训练时用 MindSpore 的TrainOneStepCell配合AdamWeightDecay优化器学习率设 2e-5batch size 32跑 5 个 epoch。from mindspore import nn, Model from mindspore.train.callback import LossMonitor optimizer nn.AdamWeightDecay(classifier.trainable_params(), learning_rate2e-5) loss_fn nn.SoftmaxCrossEntropyWithLogits(sparseTrue, reductionmean) model Model(classifier, loss_fn, optimizer, metrics{acc: nn.Accuracy()}) model.train(5, train_dataset, callbacks[LossMonitor()])推理阶段把模型设成 eval 模式对每条数据打分保留高分样本。这里有个技巧不要只保留最高分而是按分数排序后取前 70%这样能保证数据多样性避免模型只学到某一类文本。4.5 过滤效果评估与迭代过滤完不是就结束了一定要做效果评估。我的评估方法有两种一是人工抽样检查从过滤后的数据里随机抽 200 条人工判断质量看误杀率和漏杀率。我一般要求误杀率低于 5%漏杀率低于 10%。二是下游任务验证用过滤前后的数据分别训练小模型对比生成质量和 loss 曲线。实测下来经过完整过滤的数据训练出的模型在生成连贯性和多样性上都有明显提升。5. 常见问题与排查技巧实录5.1 过滤后数据量骤降怎么办这是最常见的问题。我一开始也遇到过1TB 数据过滤完只剩 50GB差点以为代码写错了。排查下来发现两个原因一是规则阈值设得太严二是去重把大量正常数据误判为重复。解决办法先统计各层过滤的丢弃率定位是哪一层丢得最多。如果是规则层放宽阈值如果是去重层调高 MinHash 的 threshold。另外可以引入分级保留机制中等质量数据不要全丢按比例采样保留。5.2 质量分类模型误判严重怎么调模型误判通常有两个原因标注数据质量不高或者模型过拟合。我的处理步骤是先检查标注数据看有没有标错的如果标注没问题就增加数据量或者用数据增强扩充样本还可以尝试用更大的预训练模型做 backbone比如从 BERT-base 换成 BERT-large。5.3 MindSpore 数据管道速度慢的优化数据管道慢通常卡在map和filter操作上。优化手段有几个提高num_parallel_workers让多个进程并行处理把 Python 操作改成 MindSpore 原生算子比如用ds.transforms里的算子替代自定义 Python 函数预取数据用dataset.prefetch提前加载下一批数据。问题排查方向解决方案过滤后数据量骤降统计各层丢弃率放宽阈值分级保留模型误判严重检查标注数据增加标注量换更大模型管道速度慢看 CPU 利用率提高并行度用原生算子去重内存溢出看数据规模分片处理用布隆过滤器编码错误中断看报错日志用忽略策略统一转 UTF-8最后分享一个小技巧过滤流程一定要做日志记录每条数据在哪一层被丢弃、丢弃原因是什么都要记下来。这样出问题时能快速定位也方便后续调参。我一开始没做日志出了问题只能从头跑一遍浪费了大量时间。这套方案我在多个预训练项目里反复打磨过从几十 GB 到 TB 级数据都跑过整体稳定性不错。核心就一句话分层过滤逐级收紧规则先行模型兜底评估闭环。你如果刚开始做建议先用小规模数据跑通全流程再逐步放大不要一上来就怼 TB 级数据那样调试成本太高。
返回列表