尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
文本去重的工程方法:MinHash 到 SimHash,在精度与速度间选择
文本去重的工程方法MinHash 到 SimHash在精度与速度间选择一、当十亿级文本库的重复率超过预期在大模型训练数据的清洗流程中文本去重是最耗时但最关键的步骤之一。常见的情况是爬取了 50TB 的网页文本经过 HTML 标签清洗、语言检测之后需要做全文去重。Naive 的做法是逐条比对哈希值时间复杂度和空间复杂度迅速爆炸。实际场景中重复文本的形态远比想象中复杂。除了完全相同的文本还有轻微修改的复读文本、翻译后回译的文本、以及模板化生成的内容。这些近重复Near-Duplicate文本对模型训练的影响与完全重复文本一样大——它们会降低训练数据的多样性导致模型过拟合某些低频模式。去重的工程挑战在于三个维度精度能否检测出所有重复、速度十亿级规模下能否在可接受时间内完成、和近重复识别能否识别非完全相同的重复。这三个维度构成一个不可能三角任何单一方法都无法同时满足。见证奇迹的时刻不是发现某个算法能完美解决所有问题而是理解每种方法的适用边界并根据实际场景做出组合选择。二、MinHash 与 SimHash 的算法原理对比两种算法都用于大规模文本的近重复检测但设计思路截然不同。graph LR A[原始文本] -- B{选择算法} B --|侧重召回率| C[MinHash 路径] B --|侧重速度| D[SimHash 路径] C -- C1[Shingling: 生成 n-gram 集合] C1 -- C2[多哈希函数映射到最小签名] C2 -- C3[LSH 分桶: Banding 策略] C3 -- C4[Jaccard 相似度候选对] D -- D1[分词加权: TF-IDF 或词频] D1 -- D2[哈希投影: 每位加权求和] D2 -- D3[生成定长二进制指纹] D3 -- D4[汉明距离筛选] C4 -- E[最终去重结果] D4 -- E style C fill:#e3f2fd style D fill:#fff8e1MinHash 原理将文本转化为 n-gram通常 n3~5集合通过多个哈希函数对集合元素取最小值生成固定长度的签名。两个文本的 MinHash 签名重叠比例近似等于原始集合的 Jaccard 相似度。配合局部敏感哈希LSH的分桶策略可以将 O(N^2) 的比较降低到近似线性。SimHash 原理将文本分词后对每个词计算哈希值并加权将所有词的加权哈希值按位求和最后将每位上的和与阈值比较生成定长的二进制指纹。相似文本的指纹间的汉明距离Hamming Distance较小。SimHash 的优势在于指纹是定长二进制存储和比较的成本极低。两种算法的本质区别MinHash 近似 Jaccard 相似度集合重合度更适合检测内容重叠但顺序可能不同的文本SimHash 近似 Cosine 相似度向量方向一致性更适合检测结构和主题相似的文本。三、MinHash LSH 的完整实现以下代码实现了一个基于 MinHash LSH 的近重复检测器。import hashlib from collections import defaultdict from typing import List, Set, Tuple def n_grams(text: str, n: int 3) - Set[str]: 生成文本的 n-gram 集合 设计原因n3 是经验最优值在中文场景平衡了粒度与性能。 n 太大会丢失重叠信息n 太小会增加误匹配。 text text.lower().strip() return {text[i:in] for i in range(len(text) - n 1)} class MinHash: MinHash 签名生成器 设计原因使用多哈希函数的最小值构成签名签名碰撞概率 近似于 Jaccard 相似度是理论基础扎实的近似算法。 def __init__(self, num_perm: int 128): self.num_perm num_perm # 使用 sha256 模拟多个哈希函数 self.seeds [hashlib.sha256(fseed_{i}.encode()).digest() for i in range(num_perm)] def signature(self, shingle_set: Set[str]) - List[int]: 计算 MinHash 签名 sig [] for seed in self.seeds: min_hash float(inf) for shingle in shingle_set: h int(hashlib.sha256( seed shingle.encode(utf-8) ).hexdigest(), 16) min_hash min(min_hash, h) sig.append(min_hash) return sig class LSH: 局部敏感哈希索引 设计原因Banding 策略将签名切分为 b 个 band 每个 band 包含 r 行。b * r num_perm。 只有当两个文本在至少一个 band 中完全相同时 才进入候选集合。参数 b 控制召回率r 控制精确率。 def __init__(self, b: int 16, r: int 8): self.b b # band 数量 self.r r # 每个 band 的行数 def get_lsh_keys(self, signature: List[int]) - List[Tuple[int, int]]: 生成 LSH 桶键 keys [] for band_idx in range(self.b): start band_idx * self.r end start self.r band_sig tuple(signature[start:end]) bucket_key hash(band_sig) keys.append((band_idx, bucket_key)) return keys def find_candidates(self, signatures: List[List[int]]) - Set[Tuple[int, int]]: 找出候选重复对 buckets defaultdict(list) for doc_id, sig in enumerate(signatures): for band_idx, bucket_key in self.get_lsh_keys(sig): buckets[(band_idx, bucket_key)].append(doc_id) candidates set() for bucket_docs in buckets.values(): if len(bucket_docs) 1: for i in range(len(bucket_docs)): for j in range(i 1, len(bucket_docs)): candidates.add(( min(bucket_docs[i], bucket_docs[j]), max(bucket_docs[i], bucket_docs[j]) )) return candidates上述实现中num_perm128, b16, r8是一个常用的参数组合。在这个配置下对于 Jaccard 相似度为 0.5 的文本对被 LSH 分到同一桶的概率约为 0.996误判率约 0.003。这意味着召回率很高但候选集可能包含一些假阳性需要后续做精确 Jaccard 验证。四、MinHash 与 SimHash 的工程 Trade-off两种算法不存在绝对的优劣选择取决于数据特征和业务目标。MinHash 适用场景原始文本重复率未知需要高召回文本可能被大量编辑插入、删除、重排对精确率要求不过分苛刻可以接受后验证的成本SimHash 适用场景已知大部分是原始文本检测近修改版本对计算和存储资源敏感需要极低延迟海明距离阈值可调可以灵活控制召回/精确平衡组合策略推荐阶段方法目的第一轮SimHash粗筛快速排除完全不同的文本将候选集缩小 100 倍第二轮MinHash LSH精筛在候选集上做高召回近重复检测第三轮精确编辑距离确认对候选对做最终确认清理误匹配见证奇迹的时刻在于三轮组合后计算量从 O(10^9 对) 下降到 O(10^5 对)而检测准确率不降反升。工程优化的关键在于分阶段降低候选集规模而不是在一个阶段追求完美。五、总结文本去重的工程方案需要根据数据规模和重复特征选择算法组合。MinHash 基于 Jaccard 相似度近似适合高召回场景SimHash 基于汉明距离筛选适合低延迟场景。推荐的实践是将两者组合先用 SimHash 粗筛再用 MinHash LSH 精筛最后做精确验证。参数选择上n-gram 的 n 值、MinHash 签名长度、LSH 的 band 和 row 数量都需要根据数据集的规模和期望的重复率来调优没有一套参数能适应所有场景。
RELATED

相关推荐

Git 标签实战:从 v1.0 创建到 origin 推送的 3 步完整流程

Git 标签实战:从 v1.0 创建到 origin 推送的 3 步完整流程

Git 标签实战:从 v1.0 创建到 origin 推送的 3 步完整流程 版本发布是软件开发中至关重要的环节,而 Git 标签(Tag)则是标记发布版本的利器。本文将带你完整走通创建标签到推送远程仓库的全流程,掌握版本发布的标准化操…

📅 2026/7/15 5:10:52
Git 标签 vs 分支:3 个核心差异与 4 种典型应用场景解析

Git 标签 vs 分支:3 个核心差异与 4 种典型应用场景解析

Git 标签与分支深度解析:核心差异与实战应用指南1. 版本控制中的两大支柱:标签与分支的本质差异在Git版本控制系统中,标签(Tag)和分支(Branch)是开发者日常工作中频繁接触的两个核心概念&#x…

📅 2026/9/14 10:39:24
etmem与PSI策略:实现cgroup粒度内存扩展的完整指南

etmem与PSI策略:实现cgroup粒度内存扩展的完整指南

etmem与PSI策略:实现cgroup粒度内存扩展的完整指南 【免费下载链接】etmem the memory vertical expansion technology that can achieve the purpose of memory capacity expansion and memory cost reduction. 项目地址: https://gitcode.com/openeuler/etmem …

📅 2026/9/9 20:24:20
MORE NEWS

更多资讯

📰

Hydra 1.1 到 1.2 迁移指南:`hydra.job.chdir` 与作业运行时工作目录行为变更

Hydra 1.1 到 1.2 迁移指南:hydra.job.chdir 与作业运行时工作目录行为变更 【免费下载链接】hydra Hydra is a framework for elegantly configuring complex applications 项目地址: https://gitcode.com/GitHub_Trending/hyd/hydra 本指南面向从 Hydra 1.…

📰

Open MCT URLIndicator 插件详解:在状态栏中实时监控 Web 服务可用性

Open MCT URLIndicator 插件详解:在状态栏中实时监控 Web 服务可用性 【免费下载链接】openmct A web based mission control framework. 项目地址: https://gitcode.com/GitHub_Trending/ope/openmct 导读 URLIndicator 是 Open MCT 内置的一个轻量级插件…

📰

Python医疗AI助手开发实战:从架构到落地

1. 项目概述:当Python遇上医疗AI三甲医院急诊科的张医生最近多了一位"数字同事"——每天上班第一件事,就是向这个AI助手查询当天重点患者的用药禁忌提醒。这个能理解医学专业术语、会分析检验报告、还能用自然语言解释复杂病理的智能助手&…

📰

Faker::Music::Rush 使用指南:用 Ruby Faker 生成 Rush 乐队成员与专辑名

Faker::Music::Rush 使用指南:用 Ruby Faker 生成 Rush 乐队成员与专辑名 【免费下载链接】faker A library for generating fake data such as names, addresses, and phone numbers. 项目地址: https://gitcode.com/GitHub_Trending/fake/faker 导读 Fake…

📰

macOS 窗口布局不再靠拖拽:Loop 窗口管理完全指南

macOS 窗口布局不再靠拖拽:Loop 窗口管理完全指南 【免费下载链接】Loop Window management made elegant. 项目地址: https://gitcode.com/GitHub_Trending/lo/Loop 窗口一多,靠鼠标来回拖拽整理,光排窗口就要花掉半天。Loop 是一款开…

📰

OpenSRE Telegram 网关部署实战:把聊天机器人变成你的 AI SRE

OpenSRE Telegram 网关部署实战:把聊天机器人变成你的 AI SRE 【免费下载链接】opensre Build your own AI SRE agents. The open source toolkit for the AI era. 项目地址: https://gitcode.com/GitHub_Trending/op/opensre OpenSRE 是一个开源的 AI SRE&a…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬