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

大规模训练数据去重方法与工具实操:从精确去重到语义去重

下面给出一套可直接用于大模型预训练、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 转写文本执行二次去重。

视频去重

先按固定时间间隔提取关键帧,计算视觉指纹,再结合音频指纹、时长和时间序列匹配。只比较单帧容易把共享封面的视频判为重复,因此应要求多个连续时间点匹配。

七、步骤五:检测训练集、验证集与测试集污染

  1. 先对评测集生成规范化哈希、n-gram 指纹和向量索引。
  2. 对训练数据逐层查询,优先检查精确匹配和长片段匹配。
  3. 分别检测题目、答案、解析和题目加答案的组合形式。
  4. 将匹配记录隔离到污染候选表,不要立即物理删除。
  5. 人工复核公开常识、固定定义与真实评测泄漏之间的差异。
  6. 重新生成数据集版本,并在训练记录中保存排除清单。

如果评测题经过格式变换或翻译,仅靠文档哈希无法发现污染,应增加字符 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 作为数据来源选项之一,但数据进入训练管线后仍应统一执行授权核验、质量评分、去重验证和版本管理。