大厨老张要为三天后的千人宴席备菜。货车刚卸下一筐筐食材,他打开一看却笑不出来:土豆糊着泥,白菜烂了半截,明明只要五斤葱,市场却塞进来五十斤——还全是同一批货;更头疼的是,机器助厨照着菜谱“自动研发”的新菜,端上来一看有三十盘几乎一模一样。老张心里清楚,直接下锅这宴席就砸了。他必须先做两件事:去重——把买重、做重的东西只留一份;清洗——洗掉泥、摘掉烂叶、扔掉不能吃的部分。 大模型(LLM)训练也是这样。喂给模型的数据就是“食材”,训练出一个聪明模型就是一场“千人宴”。原始语料里堆满了重复文本、噪声垃圾、残缺片段,如果不经过去重与清洗就开训,模型会死记硬背、说胡话、学坏习惯。这篇文章就用老张的备菜故事,把“LLM 数据生成中的去重与清洗”讲明白:从认识脏数据,到合成数据的特殊麻烦,再到 MinHash、SimHash 这些去重算法,以及清洗、加速和工程落地的完整流程。
对 AI 从业者而言,数据质量直接决定模型天花板,去重与清洗是数据工程岗位的核心基本功,掌握它能让你的训练数据管道又快又干净。
可视化演示
展示精确哈希去重检测、MinHash模糊去重近似匹配、以及文本清洗质量过滤流水线三个核心环节。
本节配有交互动效(进入视口后自动加载,约 3KB)。
精确去重:基于哈希的完全重复检测 对每条数据计算哈希值(MD5/SHA),相同哈希即为完全重复,直接剔除 哈希表O(1)查重,速度快但只能检测完全一致的重复,无法处理近似重复 hash(doc) -> bucket | collision = duplicate 精确去重是数据清洗的第一道关卡
认识脏数据
清晨的菜市场,大厨老张掀开货筐盖子:土豆带着泥,白菜烂了边,葱有三十斤是同一批,角落里还混着几块石头。他叹了口气——“这筐菜,不能直接下锅。”在 LLM 数据工程里,“菜筐”是爬虫抓来的网页语料、公开数据集、以及模型自己吐出来的合成文本,它们同样泥沙俱下:有噪声(广告、乱码、残缺 HTML),有重复(同一条新闻在几百个网站出现),还有低质量文本(答非所问的对话、AI 生成的废话)。
我们把脏数据问题拆成两类动作去解决。去重针对“重复样本”:完全重复(一模一样的字符串)好办,用哈希比对即可;近似重复(改写、加空格、换说法的同义文本)难办,需要 MinHash、SimHash 这类近似算法。清洗针对“噪声与低质量”:去掉 HTML 标签、统一编码、过滤垃圾、检查前后矛盾。两者一个管“多出来的”,一个管“不对的”。如何度量数据有多脏?常用指标包括重复率、清洗后干净样本占比、数据多样性、困惑度(PPL)等,其中最直观的是重复率:
老张把重复的葱挑成一把,烂叶摘掉,石头扔掉,筐里顿时清爽。去重与清洗不是一回事:葱多了是“重复”,葱烂了是“脏”。分清楚它们,后面的菜才能炒对。
- 分子 N_重复语料库中被判定为重复或近似重复的样本数量,比如三份相同的新闻稿要算两份重复。
- 分母 N_全部语料库中的全部样本总数,包括重复样本与正常样本。
- 比值与百分比分子除以分母并乘上 100%,把重复占比换算成百分比,便于跨数据集对比。
合成数据特点
宴席上有一道特殊的菜——机器助厨(大语言模型)照着菜谱自动“研发”的新菜。它一小时能出一百道,可老张越看越不对:三十道红烧味的几乎一模一样,只是换了配菜名字。合成数据正是模型生成的文本数据,它解决了人工标注太贵的难题,却也带来了新的重复危机。
常见的合成数据生成方法有:Self-Instruct(让模型照着种子指令自我提问)、反向翻译(翻译往返制造平行语料)、思维链采样(让模型展示解题过程)等。问题在于,模型在解码时倾向于输出高概率的高频模式,温度一调低更是“复读机”附身,于是整批生成结果呈现严重的模式集中——这正是“重复模式来源”。与真实数据相比,人类写作各说各话,模型却总在相同分布上反复采样,近似重复率远高于人工语料。我们可以用“冗余率”量化这个缺陷:
老张恍然大悟:机器助厨不是手艺差,是“懒”——老照着拿手菜翻来覆去。合成数据也一样,不是模型笨,而是采样机制天生爱走老路。所以对合成数据的去重,要比真实数据更严格。
- N_总生成器输出的全部样本数,对应机器助厨端上来的所有菜。
- N_唯一经过去重后剩余互不重复的样本数(Unique 样本),类似“真正不同的菜”。
- 比例与取补先算唯一样本占比,再用 1 减去它,得到被浪费的冗余比例。
去重核心算法
去重就是“挑重复”。完全一样的菜,老张一眼认出:复制粘贴的文本,算个哈希指纹即可搞定,这叫精确匹配去重。可“红烧肉”和“红烧五花肉”呢?这时就得请出三位帮厨:MinHash、SimHash、语义去重。
MinHash 的思路是把每篇文本看成“词的集合”,用多个随机哈希函数为集合生成一串短签名,两个集合的签名相同位置越多,越可能重复。它估计的是 Jaccard 相似度——交集占并集的比例。SimHash 则把文本压缩成一个 64 位/128 位的二进制指纹,两篇文本的相似度用指纹的“海明距离”(不同位的个数)度量。语义去重更进一步,用嵌入模型把文本变成向量、比较余弦相似度,能抓住“明天要下雨”和“预计明日有雨”这类改写型重复。三位帮厨的数学根基,是 MinHash 的著名性质:
老张叫来三位帮厨分工:一眼认出的(精确匹配)当场挑走;大致相同的(MinHash/SimHash)快速过筛;“换了个说法”的高级重复交给最费力的品菜师。各司其职,重复菜一网打尽。
- h_min(A)(集合最小哈希值)对集合中的每个元素施加随机哈希函数后取最小值,相当于给集合订一个“最小号牌”。
- P(号牌相等)两个集合的最小号牌相同的概率,这是算法可以反复试验的事件。
- 交集 |A∩B|两篇文本共有的词(或 n-gram)数量,对应“两道菜共用的配料”。
- 并集 |A∪B|两篇文本所有词合并去重后的数量,对应“两道菜用过的所有配料”。
- 等号连接把“概率相等”与“交集/并集”用等号连起来,就能用少量哈希签名代替全量比对,实现海量文本的近似去重。
清洗核心技术
菜挑完重复,剩下的还得洗。老张把洗菜分成四步:先把菜切成统一大小(格式规范化),再扔掉烂叶(噪声过滤),接着检查配料表有没有自相矛盾(一致性校验),最后把顾客的私人定制标签撕掉(敏感信息处理)。
对应到数据工程:格式规范化要统一编码(UTF-8)、修复非法字符、规整换行空白、把网页转成纯文本;噪声过滤要移除广告脚本、验证码、乱码,以及模型生成的答非所问的垃圾;一致性校验要检查 JSON/CSV 结构是否完整、日期数值范围是否合理、文本开头结尾是否残缺;敏感信息处理则对身份证号、邮箱、手机号做脱敏(PII 移除),既为合规,也为防止模型“背出”用户隐私。合成文本的清洗还有个利器:用一个小语言模型计算困惑度(PPL),困惑度低说明文本流畅像人写的,困惑度极高的往往是乱码或胡言乱语,直接丢弃:
老张的四步洗菜走完,筐里根根清爽水灵。清洗的本质是“减噪不减量”——把坏的挑出去,好的原样留下,而不是把整筐菜都剁碎了重来。
- p_θ(w_t | w_<t)模型在前文条件下预测第 t 个词的概率,概率越小意味着这个词越“意外”。
- 对数求和把所有词的预测概率取对数并累加,得到整句话的“惊讶度总量”。
- 平均并取负除以总词数 T 再取负号,得到每个词平均的负对数似然。
- 指数化对平均值取指数,把结果映射回正常尺度,最终 PPL 越小文本越流畅。
性能优化策略
几百万条文本,一个人洗要洗到明年。老张的解法是:多请几个帮厨,每人负责一筐(并行与分块);不必每根菜都精洗,先抽查一把看看脏不脏(采样估计);再让记性好又省地方的看门人帮忙记“哪些菜来过”(布隆过滤器,省内存)。
具体技术包括:并行计算——把语料按文件或 ID 分片,用多进程或 Spark 分布式同时去重,难点在于事后合并全局指纹;分块处理——按时间戳、来源、哈希前缀分桶,让数据在各桶内独立去重,天然适合分布式;采样估计——全量比对太贵,先随机抽 1% 估算重复率,再决定要不要投入重资源;内存压缩——指纹集合很大,用 Bloom Filter 的位数组代替哈希集合,以极小误判率换几十倍内存节省。不过并行不是人越多越快,加速比受制于串行部分,这就是 Amdahl 定律:
老张的厨房有条铁律:先小锅试菜,再大锅出餐。大规模去重也一样——先采样摸清数据,再并行分块上量,最后用省内存的结构兜底。
- P(可并行比例)算法中能被并行执行的时间占比,比如 90% 的去重比对都可以并行。
- N(处理器数量)并行执行的进程 / worker 数量,相当于帮厨的人数。
- 1-P(串行比例)必须串行的部分(如最终合并去重表),这是加速的“天花板”。
- 加速比 S整体提速倍数;当 N 无限增大时,S 趋近于 1/(1-P)。
工程实践落地
备完菜,老张把经验固化成流水线:进货(采集)→ 初拣(清洗)→ 挑重(去重)→ 质检(评估)→ 入库(进入训练集),每一步都有明确的人和标准,这就是数据管道设计。
管道设计有个细节:先清洗再去重,因为噪声会影响指纹质量;中间用 Parquet/Arrow 承载中间结果;最后抽样质检。质量监控指标包括重复率、去重保留率、噪声比例、PPL 分布、格式合法率,出现波动要能告警。工具链选择上:小规模用 pandas + datasketch(MinHash 实现)、精确去重用哈希集合;大规模用 Spark、Dask;语义去重用 Faiss 等近似近邻库。看一个案例:某团队用 LLM 生成 500 万条指令数据,去重后发现 30% 与种子样例近似重复,用 MinHash 粗筛加语义去重后,微调模型评测分数明显回升。那么,怎么评估去重本身干得好不好?套用分类任务的精确率与召回率:
老张的流水线一开,出菜又快又稳。数据管道也一样:用指标盯住每道工序,用工具支撑每个环节,才能让“干净数据”从理想变成日常。
- TP(真正例)系统判定重复、人工确认确实重复的样本对数,是去重命中的部分。
- FP(假正例)系统误判为重复、实际并不重复的样本对数,误删风险来自这里。
- FN(假负例)漏掉的重复样本对数,它们会让模型把同一内容重复学一遍。
- Precision 与 Recall 并排Precision 高代表删得准,Recall 高代表漏得少,工程上常需二者权衡。
AI 应用场景
场景一:互联网语料预训练清洗流水线
阶段:数据准备与清洗
解决的问题:爬虫抓取的网页语料重复率常高达 30%-40%,直接训练会让模型死记硬背高频文本。传统做法靠人工抽样看,根本看不过来;用 SimHash 对天量网页计算指纹并按海明距离聚类,可在几小时内完成十万亿级 token 的去重。
具体案例:某大模型团队抓取 800 亿网页,先用 SimHash 聚类去重,再按 PPL 过滤低质页面,最终语料重复率降到 5% 以下,训练出的模型困惑度明显下降。
场景二:合成指令数据微调前去重
阶段:合成数据生成与质量控制
解决的问题:用 LLM 批量生成 SFT 指令数据,模式集中导致的近似重复会让微调模型过拟合、泛化变差。传统人工清洗成本高,用 MinHash 指纹粗筛加嵌入向量语义细筛,双管齐下删除改写型重复。
具体案例:某算法团队生成 50 万条数学题解,MinHash 粗筛后剩 32 万条,再经语义去重剩 20 万条,微调后模型在未见过的题型上准确率提升 8 个百分点。
场景三:隐私合规驱动的数据脱敏
阶段:数据安全与合规
解决的问题:语料里混有身份证、电话、邮箱等隐私信息,直接训练有泄露风险。用正则匹配加实体识别(NER)把 PII 替换成占位符,再在评测集上验证模型不会“背出”隐私。
具体案例:某客服对话数据集上线前做全量脱敏,自动识别 12 万处敏感实体,复查零遗漏后通过合规审查。
Python 演示
# -*- coding: utf-8 -*-"""MinHash 近似去重 + 基础清洗的演示。"""import reimport hashlib def clean_text(text): text = re.sub(r"<[^>]+>", "", text) text = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f]", "", text) return re.sub(r"\s+", " ", text).strip() def shingles(text, k=3): tokens = re.findall(r"[\w\u4e00-\u9fff]+", text.lower()) return {tuple(tokens[i:i + k]) for i in range(len(tokens) - k + 1)} def minhash_signature(gram_set, num_hashes=64): sig = [] for seed in range(num_hashes): min_val = float("inf") for gram in gram_set: h = int(hashlib.md5((str(seed) + str(gram)).encode("utf-8")).hexdigest()[:8], 16) min_val = min(min_val, h) sig.append(min_val) return sig def jaccard_estimate(sig_a, sig_b): same = sum(1 for a, b in zip(sig_a, sig_b) if a == b) return same / len(sig_a) class DedupReporter: def __init__(self): self.keep_count = 0 self.drop_count = 0 def report(self): print("保留 %d 条,删除 %d 条" % (self.keep_count, self.drop_count)) def main(): docs = [ "机器学习是人工智能的一个分支。", "机器学习是人工智能的一个分支。", "机器学习是人工智能的重要分支。", "今天天气很好,适合出去散步。", "<html>明天要下雨,记得带伞。</html>", "明天要下雨,记得带伞。", ] reporter = DedupReporter() seen_sig = [] threshold = 0.6 for raw in docs: text = clean_text(raw) if not text: continue sig = minhash_signature(shingles(text)) is_dup = False for old_sig in seen_sig: if jaccard_estimate(sig, old_sig) >= threshold: is_dup = True break if is_dup: reporter.drop_count += 1 print("[删除] %s" % text) else: seen_sig.append(sig) reporter.keep_count += 1 print("[保留] %s" % text) reporter.report() if __name__ == "__main__": main()
text = clean_text(raw)先清洗:去掉 HTML 标签、控制字符,规整空白,避免脏文本污染指纹。shingles(text)把文本切成 k-gram 集合,集合相似度比字符串相等更能体现“近似重复”。minhash_signature(gram_set, num_hashes=64)用 64 个随机哈希种子给集合生成指纹签名。jaccard_estimate(sig_a, sig_b)用两串签名中同位相同的比例估计 Jaccard 相似度。if jaccard_estimate(sig, old_sig) >= threshold:相似度达到阈值 0.6 判定为重复,直接丢弃。
常见误区
误区数据去重很简单,字符串完全相等才算重复,用集合判重就完事了。
正解LLM 语料里最可怕的是“近似重复”——同一篇新闻被各网站改写、加广告、换标点,字符串不相等但语义相同。MinHash/SimHash 就是为抓这种“换了个马甲”的重复而生的。只做精确去重,近似重复照样让模型反复背诵同一句话。
为何常见:初学者常从编程里的“数组去重”入门,直觉上认为必须完全相等才算重复。
误区把清洗规则拉满,可疑文本全删掉,删得越多越干净,模型一定越好。
正解过度清洗会伤害数据多样性。有些“低质量”文本(口语、方言、调侃)反而是模型理解人类语言的关键。清洗的本质是“减噪不减量”:只去掉噪声、重复、有害内容,保留多样化表达,否则模型会变得呆板。
为何常见:“干净等于好”来自日常经验,但机器学习要的是“多样且准确”,不是无菌病房。
误区MinHash 签名算出来就是铁板钉钉的相似度,跟暴力比对 Jaccard 完全一样。
正解MinHash 是概率近似算法:签名越长(哈希函数越多),估计越接近真实 Jaccard,但永远只是估计。它能胜出,是因为海量文本上暴力两两比对不可行,用少量哈希换来可计算性。
为何常见:把“指纹”想象成 MD5 这类精确哈希,忽视了 MinHash 的“抽样估计”本质。
误区LLM 每次生成都是重新采样,每条数据都独一无二,根本不会有重复。
正解模型解码时总往高概率区域跑,温度一低就“复读”,所以合成数据的近似重复率往往高于真实语料。合成数据恰恰最需要严格去重,否则微调直接过拟合。
为何常见:把“生成过程随机”误解成“生成结果必然多样”,忽略了采样分布高度集中。
误区数据清洗等于写一堆正则把乱码、特殊字符删掉,完事。
正解特殊字符过滤只是“格式规范化”的一小步。清洗还包括噪声过滤(去广告、去垃圾)、一致性校验(结构完整、数值合理)、PPL 质量过滤(删胡言乱语)、敏感信息脱敏(合规)。正则只是洗菜的第一步,后面还有摘叶、验菜、贴标。
为何常见:教程里的清洗例子常从正则开始,容易让人以为正则就是全部。
面试题
Q1. 下列哪一项属于“近似重复”数据?
- A.两条完全相同的新闻稿
- B.同一事件的两篇改写稿← 正确答案
- C.两份不同主题的文档
- D.两个不同语言的翻译
答案:B。近似重复指“换了个马甲”的重复——同一事件被不同网站改写,字符串不同但语义相同。完全相同的新闻稿是“完全重复”,不同主题、不同语言都不构成重复。好比厨房里“同一道菜换个盘子端上来”就是近似重复。
Q2. 下列哪一项不属于数据清洗操作?
- A.去除文本中的HTML标签
- B.统一语料文本编码格式
- C.增加模型训练参数量← 正确答案
- D.过滤答非所问的噪声
答案:C。数据清洗管的是数据本身:格式规范化、噪声过滤、一致性校验、脱敏。增加模型参数量是模型架构层面的动作,与清洗无关。好比洗菜时可以摘叶、削皮、泡水,但“换一口更大的锅”不属于洗菜。
Q3. MinHash 算法估计的是两个集合的哪个指标?
- A.文本的余弦相似度
- B.集合的杰卡德相似度← 正确答案
- C.字符串的编辑距离
- D.二进制的海明距离
答案:B。MinHash 的核心性质是“两个集合最小哈希值相同的概率等于 Jaccard 相似度(交集/并集)”。余弦相似度用于嵌入向量,编辑距离用于字符串,海明距离用于 SimHash 指纹,都不是 MinHash 的估计目标。
Q4. 使用 SimHash 指纹去重时,判定两篇文本相似通常看什么?
- A.两段签名的海明距离← 正确答案
- B.两段签名的MD5值
- C.两段签名的编码长度
- D.两段签名的哈希种子
答案:A。SimHash 把文本压缩成固定长度的二进制指纹,两篇文本越相似,指纹对应位相同的越多,即海明距离越小。MD5 是精确哈希,编码长度固定、哈希种子是算法参数,都不用于判定相似。
Q5. 为什么 LLM 生成的合成数据也需要去重?
- A.生成器输出高度模式化← 正确答案
- B.生成数据一定含有毒内容
- C.生成数据无法用于训练
- D.生成数据的体积过于庞大
答案:A。模型在解码时偏好高概率输出,整批生成结果常常“复读”同一模式,近似重复率很高,不去重会导致模型过拟合。“生成数据有毒”“无法训练”是夸大,体积大也不是核心原因。
Q6. 下列哪些属于数据清洗的常见手段?
- A.统一编码与格式规范化← 正确答案
- B.过滤广告脚本与乱码← 正确答案
- C.对身份证号等隐私脱敏← 正确答案
- D.调整随机数种子重采样
答案:ABC。清洗包含格式规范化(A)、噪声过滤(B)、一致性校验和敏感信息处理(C)。调整随机数种子属于上游采样环节,不是清洗操作。
Q7. 关于 MinHash 签名的说法,正确的是?
- A.签名可以精确还原原文
- B.签名越长估计结果越准← 正确答案
- C.签名越短估计越准确
- D.签名长度随文本长度增长
答案:B。MinHash 签名是压缩指纹,无法还原原文;签名由固定数量的哈希值组成,与文本长度无关;哈希函数越多,对 Jaccard 的估计越精确,签名越短误差越大。
Q8. 大规模语料去重时,哪些做法可以提升性能?
- A.按哈希前缀分块并行去重← 正确答案
- B.先采样估算整体重复率← 正确答案
- C.用布隆过滤器节省内存← 正确答案
- D.对所有样本做暴力两两比对
答案:ABC。分块并行(A)、采样估计(B)、内存压缩(C)都是工程上常用的加速手段。暴力两两比对(D)复杂度为 O(n²),正是我们要避免的做法。
Q9. Jaccard 相似度的正确计算公式是?
- A.交集大小除以并集大小← 正确答案
- B.并集大小除以交集大小
- C.交集大小乘以并集大小
- D.两集合总和除以交集大小
答案:A。Jaccard = |A∩B| / |A∪B|,即“共同拥有的词”占“总共有过的词”的比例。交集占比越高,两个集合越像。B、C 方向全反,D 的表述同样错误。
Q10. 关于语义去重,下列说法正确的有哪些?
- A.能抓住“改写型”近似重复← 正确答案
- B.相比 SimHash 计算成本更高← 正确答案
- C.需要把文本映射成向量← 正确答案
- D.完全不需要任何阈值设置
答案:ABC。语义去重用嵌入模型把文本映射成向量,再算余弦相似度,因此能抓改写型重复(A、C),但需要跑嵌入模型,成本比 SimHash 高(B);它仍要设定相似度阈值来判断是否重复(D 错误)。