Python局部敏感哈希实战:论文查重与相似度比对
简介这是一份面向计算机专业学生与毕业设计开发者的Python实战资源围绕局部敏感哈希LSH算法实现论文相似性比对。资源从LSH的(r1,r2,p1,p2)敏感性定义出发讲解相似对象映射为同一哈希值概率高、不相似对象概率低的核心原理并给出可运行的完整代码实现适合需要完成文本查重、相似度检测类课题的读者参考。压缩包共85个文件约340KB其中78个txt为爬取的中文论文语料5个py脚本承担主程序与LSH库逻辑另有1个md说明文档目录结构清晰便于按模块阅读与调试。资源中test.txt由单篇论文拼接其他论文片段构成可直接用于验证比对效果main.py串联起分词、哈希与相似度计算流程lshash第三方库则提供底层支持。目前已有176人学习适合希望快速理解LSH原理并落地论文查重demo的读者。1. 论文查重脚本跑不通先搞懂局部敏感哈希到底在比什么手里攒了上百篇 PDF 论文想快速找出内容高度重合的那几篇用 difflib 逐字比对慢到怀疑人生换词序、改标点就完全失效——这是我第一次做论文相似性比对时踩的坑。后来换成基于 Python 的局部敏感哈希算法才把这件事跑通。它的核心思路不是精确匹配而是把每篇论文切成若干 shingle连续词片段再用多组哈希函数把每个片段映射成签名向量内容越接近的论文签名向量重合度越高。这个资源包解决的正是「批量论文去重与相似度排序」这个具体问题适合正在做毕业设计、需要交一份能跑通代码和实验数据的同学也适合想理解 LSH 工程落地的开发者。它不追求查重率精确到小数点后两位而是用可接受的误差换取数量级的速度提升。2. MinHash 签名矩阵从论文文本到可比对指纹2.1 为什么选 MinHash 而不是直接哈希直接对整篇论文做 MD5只能判断两篇是否完全相同改一个字结果就天差地别。论文相似性比对要的是「差不多也算像」所以需要一种能保留集合相似度的哈希方式。MinHash 的做法是先把文档表示成词片段集合然后用 k 个不同的哈希函数分别作用于集合中每个元素每个哈希函数取最小值组成长度为 k 的签名向量。两个集合的 Jaccard 相似度可以用签名向量对应位置相等的比例来估计。k 越大估计越准但计算量也越大。常见做法是 k 取 100 到 200在毕业设计场景下 128 是个平衡点。这里有个容易混淆的点MinHash 估计的是 Jaccard 相似度不是余弦相似度。如果你拿它去和 TF-IDF 余弦值对比数值对不上是正常的因为两者度量的是不同东西。选型时要先想清楚你要的是「集合重合比例」还是「向量方向接近程度」。论文去重通常更关心前者。2.2 文本预处理与 shingle 切分原始 PDF 抽出来的文本带页眉页脚、参考文献编号、公式乱码直接切 shingle 会引入大量噪声。我一般先做四步清洗去掉非中英文和数字的符号、统一转小写、按标点断句、过滤长度小于 2 的词。然后用滑动窗口切 shingle窗口大小 w 取 3 到 5 个词。w 太小普通短语也会撞车w 太大改几个词就完全匹配不上。下面是我常用的预处理函数import re def clean_text(text): # 只保留中英文和数字其余替换为空格 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9], , text) text text.lower() # 合并连续空格 text re.sub(r\s, , text).strip() return text def get_shingles(text, w4): words text.split() if len(words) w: return set() # 滑动窗口生成连续 w 个词的片段 return set( .join(words[i:iw]) for i in range(len(words) - w 1))clean_text里的正则[^\u4e00-\u9fa5a-zA-Z0-9]覆盖了中文区间和英文数字把标点、换行、特殊符号统一替换成空格避免它们影响 shingle 边界。get_shingles用set去重因为同一片段在文中出现多次对集合相似度没有额外贡献。w 默认取 4这是我在中英文混合论文上试出来比较稳的值英文论文可以降到 3中文论文建议升到 5因为中文单字信息量低需要更长窗口才能区分。2.3 构建 MinHash 签名并写入矩阵有了 shingle 集合接下来对每个集合生成签名。这里不自己手写哈希函数直接用datasketch库的MinHash它内部已经用 numpy 做了向量化加速。安装命令是pip install datasketch。核心代码如下from datasketch import MinHash def build_minhash(shingles, num_perm128): m MinHash(num_permnum_perm) for s in shingles: m.update(s.encode(utf-8)) return m # 假设 docs 是 {论文id: 清洗后文本} 的字典 signatures {} for doc_id, text in docs.items(): shingles get_shingles(clean_text(text), w4) signatures[doc_id] build_minhash(shingles, num_perm128)num_perm128就是签名向量长度 k它决定估计精度和内存占用。128 个 32 位整数约 512 字节一万篇论文也就 5MB 左右完全放得下。m.update接收 bytes所以要先encode(utf-8)。如果你要自己实现哈希函数注意别用 Python 内置hash()因为它对字符串有随机盐每次进程启动结果不同会导致签名不可复现——这是新手最容易翻车的地方。3. 相似度查询与批量比对把签名变成可排序的结果3.1 用 LSH 索引加速近邻查询如果只有几十篇论文两两算 Jaccard 也能忍。但上百篇就是上万次比较每次还要遍历签名向量纯 Python 循环会明显变慢。datasketch提供了MinHashLSH它把签名按 band 分组只有落在同一个桶里的候选对才做精确 Jaccard 计算把比较次数降下来。构建索引和查询的代码如下from datasketch import MinHashLSH # threshold 是相似度阈值低于它的对不会被返回 lsh MinHashLSH(threshold0.5, num_perm128) for doc_id, m in signatures.items(): lsh.insert(doc_id, m) def find_similar(doc_id, top_k10): m signatures[doc_id] # query 返回候选集再手动算 Jaccard 排序 candidates lsh.query(m) results [] for cid in candidates: if cid doc_id: continue j m.jaccard(signatures[cid]) results.append((cid, j)) results.sort(keylambda x: x[1], reverseTrue) return results[:top_k]threshold0.5表示只关心相似度超过 0.5 的候选对这个值要根据你的查重严格程度调。毕业设计里如果只是想找出高度重复的论文0.6 到 0.7 更合适如果要做聚类分析可以降到 0.4。lsh.query返回的是候选集不是最终排序结果因为 band 分组会有漏检和误召所以后面还要用m.jaccard精确算一遍再排序。top_k控制返回条数避免结果太多看不过来。3.2 批量比对与结果落盘实际做毕业设计时导师往往要一份完整的相似度矩阵或者 Top-N 列表。我一般会写一个批量处理脚本把所有论文的相似对导出成 CSV方便后续画热力图或写进论文。下面这段代码遍历所有文档用 LSH 查询候选去重后写入文件import csv def batch_compare(signatures, lsh, outputsimilar_pairs.csv, min_jaccard0.5): seen set() rows [] for doc_id in signatures: for cid, j in find_similar(doc_id, top_k20): if j min_jaccard: continue # 无向对去重保证 (a,b) 和 (b,a) 只记一次 pair tuple(sorted([doc_id, cid])) if pair in seen: continue seen.add(pair) rows.append({doc_a: pair[0], doc_b: pair[1], jaccard: round(j, 4)}) rows.sort(keylambda r: r[jaccard], reverseTrue) with open(output, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[doc_a, doc_b, jaccard]) writer.writeheader() writer.writerows(rows) return rowsmin_jaccard是最终输出阈值比 LSH 的 threshold 可以设得更高因为 LSH 阶段宁滥勿缺输出阶段再收紧。tuple(sorted([doc_id, cid]))用来去重无向对否则同一对会出现两次。encodingutf-8-sig是为了 Excel 打开 CSV 不乱码这个细节写论文时很实用。round(j, 4)保留四位小数够用且不会让表格太宽。3.3 参数怎么调一张表说清不同参数对结果和性能的影响差别很大我把常用取值范围整理成表方便你按自己的论文集调整参数含义常用取值调大后果调小后果num_perm签名向量长度64 / 128 / 256精度高内存和计算增加精度下降速度快w (shingle)滑动窗口词数3 / 4 / 5抗改写强但短文本失效易误判普通短语撞车thresholdLSH 召回阈值0.4 / 0.5 / 0.6召回多误报增加漏检增加min_jaccard输出过滤阈值0.5 / 0.6 / 0.7结果少而精结果多而杂我的习惯是先用 num_perm128、w4、threshold0.5 跑一遍看结果分布再决定往哪个方向调。如果发现明显相似的论文没被召回先降 threshold如果结果里一堆不相关的先升 min_jaccard。4. 避坑与排查那些让结果失真的细节4.1 现象同一篇论文两次运行相似度不一样原因用了 Python 内置hash()或者没固定随机种子。内置哈希对字符串有进程级随机盐每次启动结果不同。解决统一用datasketch的 MinHash或者自己实现时用hashlib.md5这类确定性哈希。如果用了 numpy 随机数记得np.random.seed(42)。4.2 现象短论文之间相似度虚高原因shingle 集合太小Jaccard 分母小随便几个共同短语就能把比例拉高。解决对长度低于阈值的文档直接跳过或单独处理比如词数少于 200 的论文不参与比对或者在结果里标注「短文本仅供参考」。4.3 现象PDF 抽取文本全是乱码shingle 全是噪声原因PDF 里嵌入了非标准字体或扫描件pdfminer、PyPDF2抽出来是乱码。解决先用pdfplumber试抽一页看效果扫描件必须走 OCR如pytesseract否则后面所有步骤都是白费。这一步不做后面调参调到天亮也没用。4.4 现象LSH 查询返回空但明明有相似论文原因threshold 设太高或者 num_perm 太小导致 band 分组太粗。解决先把 threshold 降到 0.3 验证流程是否通再逐步升回去。另外检查lsh.insert时用的 MinHash 对象和查询时是不是同一批重新构建过签名就要重新建索引。4.5 现象内存爆掉进程被 kill原因一次性把所有论文的 shingle 集合都留在内存里。解决shingle 集合用完即弃只保留 MinHash 签名或者分批处理每批构建签名后写入磁盘最后统一建索引。一万篇论文的签名也就几 MB真正占内存的是原始文本和 shingle 集合。5. 进阶技巧把相似度结果变成论文里能用的图跑出 CSV 只是第一步毕业设计里通常还要一张相似度热力图或者聚类树。我的做法是用scipy的层次聚类把 Jaccard 距离转成距离矩阵再画树状图。Jaccard 距离就是1 - jaccard注意要保证矩阵对称且对角线为 0。下面这段代码从签名直接算距离矩阵并聚类import numpy as np from scipy.cluster.hierarchy import linkage, dendrogram import matplotlib.pyplot as plt def cluster_docs(signatures, doc_ids): n len(doc_ids) dist np.zeros((n, n)) for i in range(n): for j in range(i1, n): jac signatures[doc_ids[i]].jaccard(signatures[doc_ids[j]]) dist[i][j] dist[j][i] 1 - jac # condensed 距离向量linkage 要求上三角展开 condensed dist[np.triu_indices(n, k1)] Z linkage(condensed, methodaverage) dendrogram(Z, labelsdoc_ids, leaf_rotation90) plt.tight_layout() plt.savefig(dendrogram.png, dpi150) return Znp.triu_indices(n, k1)取上三角索引因为linkage只接受压缩后的距离向量传完整矩阵会报错。methodaverage是平均连接法对论文聚类比较稳single容易产生链状效应complete又太保守。dpi150保证图放进论文里不糊。如果论文数量超过 50树状图标签会挤成一团这时候改用热力图更合适用seaborn.heatmap配mask遮住下三角即可。还有一个实用技巧把 Jaccard 相似度转成「重复率」写进论文时别直接说「相似度 0.73」而是说「按 4-gram shingle 和 128 位 MinHash 估计Jaccard 相似度为 0.73」。把参数写清楚答辩时老师问起来你能答得上也显得实验可复现。我吃过亏第一次答辩只写了个相似度数字被追问「怎么算的、参数多少」时卡住了。从那以后我每次导出结果都强制把 num_perm、w、threshold 三个参数写进 CSV 表头或者实验记录里再也没被问倒过。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

日化广告投放数据分析报告:从数据归集到PPT模板的完整流程

日化广告投放数据分析报告:从数据归集到PPT模板的完整流程

简介:这份日化广告投放数据分析报告PPT模板面向日化行业营销人员、广告投放策略制定者及希望提升数据分析能力的市场从业者,帮助其快速搭建专业的数据汇报框架,理清广告效果评估与策略优化思路。压缩包共3个文件,以一份可直接编辑…

2026/10/11 22:21:04 阅读更多 →
evlog CLI 可观测性评分实战:用 evlog map 揪出应用里的暗区日志入口

evlog CLI 可观测性评分实战:用 evlog map 揪出应用里的暗区日志入口

【免费下载链接】evlog Digging through logs is not observability. Its hope — wide events, structured errors, TypeScript-first, every runtime. 项目地址: https://gitcode.com/gh_mirrors/ev/evlog 点击查看 免费下载 为什么需要 evlog 可观测性评分 翻日…

2026/10/11 22:21:04 阅读更多 →
agent-skills:面向生产环境的智能体能力治理范式

agent-skills:面向生产环境的智能体能力治理范式

1. 项目概述:一个被严重低估的“技能容器”概念“agent-skills”这个词组乍看像技术黑话,但拆开来看——它既不是某个具体开源库的官方命名,也不是某家大厂刚发布的API产品名,而是一个正在快速凝聚共识的设计范式代号。我在过去三…

2026/10/11 22:21:04 阅读更多 →

最新新闻

MATLAB船舶航向PID控制仿真:Nomoto模型与参数整定详解

MATLAB船舶航向PID控制仿真:Nomoto模型与参数整定详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 1:04:48 阅读更多 →
Java实现图片与Base64互转及保存的完整指南

Java实现图片与Base64互转及保存的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 1:04:48 阅读更多 →
STM32寄存器操作实战:从点灯到定时器与串口,彻底搞懂底层开发

STM32寄存器操作实战:从点灯到定时器与串口,彻底搞懂底层开发

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 1:04:48 阅读更多 →
优先队列PriorityQueue实战:原理、应用场景与避坑指南

优先队列PriorityQueue实战:原理、应用场景与避坑指南

大概很多年前我第一次做任务调度模块时,把所有待处理任务都塞进了一个普通队列,先进先出,看起来毫无问题。直到系统里突然混进来一个必须在几秒内给出结果的紧急请求,而它前面还堵着几百个耗时任务,那一刻我才真正意识…

2026/10/13 1:04:48 阅读更多 →
Agent Skills 操作指南:从 SKILL.md 到 MCP 自定义进阶

Agent Skills 操作指南:从 SKILL.md 到 MCP 自定义进阶

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 1:04:48 阅读更多 →
iOS TableView 异步加载图片:从原理到实践,告别列表卡顿

iOS TableView 异步加载图片:从原理到实践,告别列表卡顿

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 1:03:48 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →