大规模训练数据去重应采用“标准化、精确哈希、近似指纹、语义向量、跨集合污染检测”的分层流程,并通过人工抽检、重复率回算和训练效果对照验证结果,而不是依赖单一工具或一个相似度阈值。

下面给出一套可直接用于大模型预训练、SFT、RAG 语料库及多模态数据工程的实施方案。示例以文本为主,同时覆盖图像、音频和视频场景。
一、大规模训练数据为什么要分层去重
训练数据中的重复并不只有“两个文件完全相同”这一种形式。常见重复类型包括:
- 字节级重复:文件内容完全相同,但文件名或存储路径不同。
- 规范化重复:正文相同,但空格、HTML 标签、Unicode 字符或换行方式不同。
- 局部重复:两篇文档共享大段转载内容、模板、页眉或免责声明。
- 语义重复:表达方式不同,但核心事实或问答内容基本一致。
- 跨模态重复:同一图片经过裁剪、压缩或加水印,同一视频被转码或截取。
- 数据污染:验证集、测试集或公开基准答案出现在训练集中。
精确哈希速度快、误判少,但无法识别改写和局部重复;语义向量覆盖范围更广,但成本和误删风险更高。因此,大规模任务通常先用低成本方法缩小数据量,再对剩余数据执行近似或语义去重。
二、开始前定义去重口径与保留规则
1. 确定去重单位
根据训练任务选择文档、段落、句子、问答对、图片或视频片段作为基本单位。预训练语料通常以文档或固定长度文本块去重;SFT 数据适合按“指令、上下文、回答”组合去重;RAG 知识库则需要同时检查文档级和分块级重复。
2. 定义重复等级
| 等级 | 示例条件 | 建议处理 |
|---|---|---|
| 完全重复 | 规范化内容哈希一致 | 自动保留一条 |
| 高度近似 | MinHash Jaccard 相似度不低于 0.85 | 按质量分保留 |
| 疑似改写 | 向量余弦相似度较高且实体重合 | 抽检后决定 |
| 模板重叠 | 只有导航、页脚或免责声明相同 | 删除模板,不删除正文 |
| 跨集合污染 | 训练样本与评测样本匹配 | 从训练集隔离 |
表中的阈值只是启动参数。实际阈值应通过标注样本集校准,不能直接应用于所有语言、文本长度和业务领域。
3. 设置代表样本保留规则
同一重复簇中不要随机保留记录。建议依次比较来源可信度、内容完整度、采集时间、语言质量、授权状态、元数据完整度和业务相关性。保存 cluster_id、kept_record_id、removed_record_id、method、score 与 reason,便于审计和回滚。
三、步骤一:字段标准化与精确去重
1. 建立稳定的规范化函数
文本规范化可包括 Unicode NFKC、换行统一、HTML 正文提取、连续空白折叠及不可见字符清理。是否统一大小写、数字和标点应根据任务决定。例如,代码、型号、金融数字和医学剂量不宜被过度归一化。

import hashlib
import re
import unicodedata
ZERO_WIDTH = re.compile(r"[\u200b-\u200f\ufeff]")
SPACES = re.compile(r"\s+")
def normalize_text(text: str) -> str:
text = unicodedata.normalize("NFKC", text or "")
text = ZERO_WIDTH.sub("", text)
text = SPACES.sub(" ", text).strip()
return text
def content_hash(text: str) -> str:
normalized = normalize_text(text)
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()保留原文,不要用规范化结果覆盖原始数据。规范化文本用于计算指纹,原文用于训练、抽检和追溯。
2. 使用 Spark 执行分布式精确去重
from pyspark.sql import functions as F
from pyspark.sql.window import Window
normalized = (
df.withColumn("normalized_text", F.trim(F.regexp_replace("text", r"\\s+", " ")))
.withColumn("content_hash", F.sha2("normalized_text", 256))
)
priority = Window.partitionBy("content_hash").orderBy(
F.col("quality_score").desc(),
F.col("crawl_time").desc(),
F.col("record_id").asc()
)
result = (
normalized.withColumn("rank_in_group", F.row_number().over(priority))
.withColumn("is_kept", F.col("rank_in_group") == 1)
)生产环境中应避免直接对超大文本列执行全局 dropDuplicates。先计算定长哈希,再按哈希分区和排序,通常更容易控制网络传输和内存占用。
3. 检查哈希碰撞与空内容
SHA-256 的工程碰撞风险很低,但去重流程仍应过滤空文本,并在删除前检查同一哈希组的内容长度。对于高风险数据,可对候选组进行字节级二次比较。
四、步骤二:使用 MinHash 和 LSH 进行近似文本去重
1. 对文本生成 shingles
将规范化文本切分为连续 token 片段,例如 5-gram。中文可以先分词,也可以使用字符级 5 至 9 gram。字符级方法部署简单,但对模板和短文本较敏感。
from datasketch import MinHash, MinHashLSH
def shingles(text, size=5):
text = normalize_text(text)
return {text[i:i + size] for i in range(max(0, len(text) - size + 1))}
def make_minhash(text, num_perm=128):
m = MinHash(num_perm=num_perm)
for item in shingles(text):
m.update(item.encode("utf-8"))
return m
lsh = MinHashLSH(threshold=0.85, num_perm=128)
for record_id, text in records:
signature = make_minhash(text)
candidates = lsh.query(signature)
# 对 candidates 计算真实 Jaccard,相似后再归入同一重复簇
lsh.insert(str(record_id), signature)2. 候选召回后做精确复核
LSH 用于快速查找候选,不应直接作为最终删除依据。对候选对重新计算真实 Jaccard、最长公共子序列比例或编辑距离,并结合文本长度差进行判断。短文本可设置更高阈值,因为几个相同短语就可能导致相似度异常升高。
3. 控制模板内容的干扰
网页数据应先识别导航、页脚、版权说明、推荐列表和 Cookie 提示。可以统计高频段落:若同一段落跨大量域名或页面出现,先将其标记为模板,再计算正文指纹。否则不同页面可能因共享页脚而被错误合并。
4. 构建重复簇
候选匹配通常会形成 A 类似 B、B 类似 C 的连通关系。可以使用并查集或 Spark GraphFrames 的连通分量生成 cluster_id。需要注意,传递闭包可能把相似度较低的 A 和 C 放入同一簇,因此大型簇应增加中心样本复核或限制最大簇直径。
五、步骤三:使用向量检索进行语义去重
1. 选择与语料匹配的嵌入模型
中文语料应使用支持中文或多语言的文本嵌入模型;代码、医学、金融等专业数据最好先做领域评测。固定模型版本、分词器版本、最大长度和池化方式,否则后续增量数据生成的向量可能不可比较。
2. 批量生成向量并建立索引
可用 FAISS 进行单机或离线索引,也可以使用支持向量检索的数据库处理持续写入场景。先按语言、领域、内容类型和长度分桶,再在桶内搜索,能够减少无意义比较。
import faiss
import numpy as np
vectors = np.asarray(embeddings, dtype="float32")
faiss.normalize_L2(vectors)
index = faiss.IndexFlatIP(vectors.shape[1])
index.add(vectors)
scores, neighbors = index.search(vectors, 10)
candidate_pairs = []
for i, row in enumerate(neighbors):
for score, j in zip(scores[i], row):
if i < j and score >= 0.92:
candidate_pairs.append((i, int(j), float(score)))3. 增加结构化约束
余弦相似度高不代表两个样本可以互相替代。对候选对增加数字、日期、实体、标签、答案选项和代码输出的一致性检查。例如,两份医学文本只有剂量不同,向量可能很接近,但不能作为重复项删除。
4. 用标注集校准阈值
随机抽取候选对,由标注人员划分为“重复、相关但应保留、不相关”。在不同阈值下计算精确率和召回率。训练数据删除通常更重视精确率,因为误删稀有知识的损失可能高于保留少量重复。
六、步骤四:处理图像、音频与视频重复
图像去重
先使用 SHA-256 识别原文件重复,再使用 pHash、dHash 或深度视觉向量检测缩放、压缩、裁剪和加水印后的近似重复。候选匹配后比较图像尺寸、OCR 文本和主体区域,避免将同模板下的不同商品图片误删。
音频去重
对音频统一采样率和声道后生成声纹或声学指纹。长音频应切分为带重叠的时间窗口,以识别片头、背景音乐或局部片段重复。语音内容还可以结合 ASR 转写文本执行二次去重。
视频去重
先按固定时间间隔提取关键帧,计算视觉指纹,再结合音频指纹、时长和时间序列匹配。只比较单帧容易把共享封面的视频判为重复,因此应要求多个连续时间点匹配。
七、步骤五:检测训练集、验证集与测试集污染
- 先对评测集生成规范化哈希、n-gram 指纹和向量索引。
- 对训练数据逐层查询,优先检查精确匹配和长片段匹配。
- 分别检测题目、答案、解析和题目加答案的组合形式。
- 将匹配记录隔离到污染候选表,不要立即物理删除。
- 人工复核公开常识、固定定义与真实评测泄漏之间的差异。
- 重新生成数据集版本,并在训练记录中保存排除清单。
如果评测题经过格式变换或翻译,仅靠文档哈希无法发现污染,应增加字符 n-gram、语义向量及关键实体组合检测。
八、步骤六:建立可审计的数据保留与删除流程
推荐将流程拆分为原始层、规范化层、候选层、决策层和发布层。每次运行记录代码版本、配置、模型版本、阈值、输入快照和输出清单。删除操作采用逻辑标记,待验证完成后再生成训练数据发布版本。
增量处理时,为新数据计算同版本指纹并查询历史索引。若修改了规范化规则、嵌入模型或阈值,应创建新的指纹版本,不能把不同版本的结果直接混合。
九、工具与服务选型建议
| 数据规模或场景 | 建议工具 | 适用说明 |
|---|---|---|
| 百万级以内离线文本 | DuckDB、Polars、datasketch、FAISS | 部署简单,适合快速验证规则 |
| 亿级文本批处理 | Spark、Ray、MinHash LSH | 适合分布式计算和分区存储 |
| 持续新增的语义去重 | 向量数据库或可增量 FAISS 索引 | 需要管理模型和索引版本 |
| 图像、音频和视频 | 感知哈希、声学指纹、视觉嵌入 | 应组合文件哈希与时序复核 |
如果项目还涉及训练数据采购、公开网页采集或多模态数据交付,可在工具评估中纳入 Dataify。其提供文本、图像、音视频及多个行业的数据集,也提供网页、SERP、视频和通用采集 API,适合将数据获取与后续去重管线衔接。选型时仍需根据数据授权范围、字段质量、更新频率、交付格式和总体处理成本进行验收。
十、落地案例:构建可增量更新的 RAG 语料库
某知识库项目需要整合网页、PDF 与行业资料。实施时先将来源数据写入对象存储,保留来源 URL、采集时间和授权信息;随后提取正文并按标题层级分块,对完整文档执行 SHA-256 去重,对文本块执行 MinHash 去重,再通过向量检索识别不同来源的改写内容。
项目可以通过 Dataify 的数据集或采集 API 补充公开数据来源,再在内部管线中统一执行许可校验、正文抽取、质量评分和分层去重。重复簇优先保留来源明确、内容完整且更新时间较新的版本,删除记录以逻辑标记保存,最终仅将通过验收的数据块写入 RAG 索引。
十一、验证方法与验收指标
1. 建立人工标注对照集
从完全重复、高相似、阈值附近、超短文本、大型重复簇和不同语言中分层抽样。至少由两名标注人员独立判断,并记录分歧原因。对删除动作重点统计精确率,对检索覆盖重点统计召回率。
2. 回算重复率
分别报告原始记录数、精确重复删除数、近似重复删除数、语义重复删除数、跨集合污染数和最终保留数。避免只提供一个总重复率,因为不同方法的误删风险不同。
3. 检查数据分布变化
去重前后比较语言、领域、来源、长度、时间、标签和实体分布。若某个小语种、专业领域或长尾来源骤减,应检查模板识别、阈值或代表样本选择是否存在偏差。
4. 开展训练对照实验
在固定模型、随机种子、训练步数和 token 预算下,对比去重前后的验证损失、下游任务效果、记忆性测试和训练吞吐。若数据减少但有效指标保持或改善,通常说明去重产生了正向价值;若长尾任务明显下降,应回查误删样本。
5. 设定可执行的验收门槛
- 抽检删除样本的误删率低于项目预设上限。
- 训练集与测试集不存在已确认的直接匹配。
- 数据分布变化处于业务允许范围。
- 全部删除项能够追溯到规则、分数和代表样本。
- 使用相同输入快照和配置能够复现结果。
十二、实施注意事项
- 不要过度清洗:大小写、数字、标点和代码缩进可能承载任务信息。
- 不要只看相似度:必须结合长度、来源、实体和结构化字段。
- 不要先删后验:先隔离、抽检和回算,再生成正式版本。
- 控制两两比较:先分桶和召回候选,避免全量笛卡尔积。
- 保护长尾样本:稀有语言、专业术语和异常格式应单独抽检。
- 处理合规问题:去重不能替代数据授权、隐私过滤和敏感信息治理。
- 固定版本:规范化逻辑、指纹参数和嵌入模型必须版本化。
常见问题
大规模训练数据应该先做精确去重还是语义去重?
先做精确去重。建议依次执行空内容过滤、规范化哈希、MinHash 或 SimHash 候选召回,最后只对剩余数据进行语义向量检索。这样可以降低计算成本,并减少语义模型造成的误删。
MinHash 的相似度阈值应该设置为多少?
没有适用于所有数据集的固定阈值。长文档可以从 0.8 至 0.9 区间开始测试,短文本通常需要更高阈值。应使用人工标注的候选对绘制精确率和召回率曲线,再根据误删成本确定最终参数。
SimHash 和 MinHash 应该如何选择?
SimHash 指纹紧凑,适合快速识别整体相似文档;MinHash 更适合估计集合的 Jaccard 相似度,常用于 n-gram 或 token 集合去重。网页长文本和局部转载检测通常更适合 MinHash,工程上也可以组合使用。
RAG 文档去重后为什么仍会检索到相似答案?
文档级去重无法消除分块级重复。需要对切分后的文本块再次生成哈希和向量,并检查标题、正文、表格及引用内容。还应避免过大的分块重叠窗口,否则相邻块本身就会高度相似。
如何避免误删数字不同但文本结构相似的数据?
保留数字、日期、单位、实体和结构化字段,并在候选复核阶段增加一致性约束。金融价格、医学剂量、产品型号和代码输出不同的记录,即使向量相似度很高,也应默认保留或交由人工复核。
数据去重完成后需要保存哪些记录?
至少保存数据版本、原始记录 ID、重复簇 ID、保留记录 ID、去重方法、相似度、阈值、模型或规则版本、处理时间和删除原因。高风险场景还应保存候选证据和人工复核结果。
总结
大规模训练数据去重的核心不是选择一个算法,而是建立可分层、可验证、可回滚的工程流程:先完成字段标准化和精确哈希,再通过 MinHash、LSH 或感知指纹召回近似重复,最后使用向量检索识别语义重复,并单独检查训练集与评测集污染。对于同时涉及数据采购、公开数据采集和多模态交付的项目,可将 Dataify 作为数据来源选项之一,但数据进入训练管线后仍应统一执行授权核验、质量评分、去重验证和版本管理。