基于局部敏感哈希(Locality-Sensitive Hashing, LSH)的文本相似搜索算法详解
字数 3401 2025-12-17 08:56:03
基于局部敏感哈希(Locality-Sensitive Hashing, LSH)的文本相似搜索算法详解
题目描述
在自然语言处理中,我们经常需要快速地从海量文本集合中找出与目标文本相似的文档,例如新闻去重、推荐系统或抄袭检测。传统的两两比较(如计算余弦相似度)时间复杂度为O(N²),对于大规模数据集是不可行的。基于局部敏感哈希(LSH)的文本相似搜索算法,通过将高维文本向量(如TF-IDF或词向量)映射到低维哈希签名,并利用哈希碰撞的概率性质,使得相似的文本有高概率落入相同的哈希桶中,从而实现亚线性时间的近似相似搜索。
解题过程
第一步:问题建模与核心思想
- 目标:给定一个海量文档集合 D(例如100万篇新闻),对于任意查询文档 q,快速找出 D 中所有与 q 相似度超过阈值 t 的文档。
- 挑战:暴力比较需要为 q 计算它与 D 中每个文档的相似度(如余弦相似度),成本过高。
- 核心思想:LSH的核心是设计一组哈希函数,使得相似的文本以高概率哈希到相同的桶(bucket)中,不相似的文本以低概率哈希到相同的桶中。这样,我们只需在 q 落入的少数几个桶内进行精确比较,从而大幅减少计算量。
第二步:文本表示与相似度度量
-
文本向量化:
- 首先,将每个文档表示为高维向量。常用方法有:
- 词袋模型 + TF-IDF:每个维度对应一个词,值为该词的TF-IDF权重。
- 词向量平均:使用预训练词向量(如Word2Vec、GloVe)对文档中的词向量取平均或加权平均。
- 句子/文档嵌入:直接使用BERT等模型获得文档的稠密向量。
- 假设每个文档被表示为
d维向量,例如d=10000(TF-IDF)或d=300(词向量平均)。
- 首先,将每个文档表示为高维向量。常用方法有:
-
相似度度量:
- 对于向量表示,通常使用余弦相似度来衡量文档相似性:
sim(u, v) = (u·v) / (||u|| ||v||)。 - 我们的目标就是快速找到所有与
q余弦相似度大于阈值t的文档。
- 对于向量表示,通常使用余弦相似度来衡量文档相似性:
第三步:局部敏感哈希(LSH)的基本原理
-
LSH函数族:
- LSH依赖于一个哈希函数族
H,满足:对于任意两个向量u和v,有- 如果
sim(u, v)很高,则Pr[h(u) = h(v)]很高。 - 如果
sim(u, v)很低,则Pr[h(u) = h(v)]很低。
- 如果
- 对于余弦相似度,一个经典的LSH函数族是随机投影(Random Projection),也称为SimHash。
- LSH依赖于一个哈希函数族
-
随机投影(SimHash)的工作原理:
- 生成一个随机向量
r,其每个分量从标准正态分布N(0,1)中独立采样。 - 定义哈希函数
h_r(u) = sign(r·u),其中sign是符号函数:- 如果
r·u ≥ 0,则h_r(u) = 1。 - 如果
r·u < 0,则h_r(u) = 0。
- 如果
- 关键性质:两个向量
u和v的哈希值相同的概率为:
这意味着,夹角越小(相似度越高),哈希碰撞的概率越大。Pr[h_r(u) = h_r(v)] = 1 - (θ / π) 其中 θ = arccos(sim(u, v)),即两者之间的夹角。
- 生成一个随机向量
第四步:从单个哈希函数到哈希签名
-
单个哈希函数的问题:
- 仅使用一个随机向量
r,碰撞概率随相似度线性变化,但无法提供足够的选择性(例如,相似度为0.8和0.3的碰撞概率差异不够大)。 - 我们需要一种方式,使得高相似文档几乎肯定碰撞,低相似文档几乎肯定不碰撞。
- 仅使用一个随机向量
-
构造哈希签名(Signature):
- 我们生成
k个随机向量r1, r2, ..., rk,每个对应一个独立的哈希函数。 - 对于文档向量
u,计算它的k位哈希签名s(u) = [h_{r1}(u), h_{r2}(u), ..., h_{rk}(u)]。 - 这个签名可以看作一个长度为
k的二进制串。
- 我们生成
-
签名相似度与原始相似度的关系:
- 两个文档签名中,对应位相同的比例(即签名间的汉明距离)是原始余弦相似度的无偏估计。
- 具体地,
Pr[s_i(u) = s_i(v)] = 1 - (θ/π),所以签名中相同位的期望比例为1 - (θ/π)。 - 因此,我们可以通过比较签名来快速估计文档相似度。
第五步:哈希桶(Bucket)构建与搜索
-
直接使用签名的局限性:
- 即使使用
k位签名,如果k很大(例如1000位),两个文档签名完全相同的概率仍然很低,除非它们几乎完全相同。 - 我们需要进一步“放大”相似与不相似文档之间的碰撞概率差异。
- 即使使用
-
引入波段(Banding)技术:
- 将
k位的签名分成b个波段(band),每个波段包含r位(k = b * r)。 - 对于每个波段
i,我们使用一个更高级的哈希函数(如常规字符串哈希)将整个波段映射到一个哈希桶。 - 这样,每个文档在
b个哈希表中各有b个桶位置。
- 将
-
索引构建:
- 预处理阶段:对文档集合 D 中的每个文档,计算其
k位 SimHash 签名,应用波段技术,将其插入b个哈希表中对应的桶。 - 存储时,只需存储文档ID(或指针),而不是完整向量。
- 预处理阶段:对文档集合 D 中的每个文档,计算其
-
查询过程:
- 对于查询文档
q,同样计算其k位签名和b个波段哈希值。 - 对于每个波段
i,取出该波段哈希值对应桶中的所有候选文档。 - 合并所有波段的候选文档,去除重复。
- 最后,对候选文档子集(远小于N)进行精确的相似度计算(如计算余弦相似度),返回超过阈值
t的文档。
- 对于查询文档
第六步:参数选择与概率分析
-
相似度阈值
t与参数(b, r)的关系:- 假设我们希望找到相似度 > t 的文档。
- 对于两个相似度为
s的文档:- 在一个波段(r位)内,签名完全相同的概率为
p = (1 - arccos(s)/π)^r。 - 在
b个波段中,至少有一个波段碰撞的概率为P(s) = 1 - (1 - p)^b。
- 在一个波段(r位)内,签名完全相同的概率为
- LSH的S曲线效应:通过调整
(b, r),我们可以使P(s)在s=t附近急剧变化,即:- 当
s > t时,P(s)接近1(高召回率)。 - 当
s < t时,P(s)接近0(低误报率)。
- 当
-
示例计算:
- 设定阈值
t=0.8(余弦相似度)。 - 选择
k=1000,b=50,r=20。 - 对于
s=0.85的文档对:p ≈ (1 - arccos(0.85)/π)^20 ≈ (1 - 0.554/π)^20 ≈ 0.327P ≈ 1 - (1-0.327)^50 ≈ 0.999,几乎肯定被检索到。
- 对于
s=0.3的文档对:p ≈ (1 - arccos(0.3)/π)^20 ≈ (1 - 1.266/π)^20 ≈ 0.00034P ≈ 1 - (1-0.00034)^50 ≈ 0.017,误报率很低。
- 设定阈值
第七步:算法总结与优化
-
算法步骤总结:
- 离线索引构建:
- 将文档集 D 中每个文档表示为向量。
- 为每个向量生成 k 位 SimHash 签名。
- 将签名分成 b 个波段,用每个波段在 b 个哈希表中索引文档ID。
- 在线查询:
- 计算查询 q 的向量和 SimHash 签名。
- 对每个波段,查找对应哈希桶,收集候选文档ID。
- 合并候选集,去重。
- 对候选文档计算精确相似度,过滤并返回结果。
- 离线索引构建:
-
优化与变体:
- 多哈希表(L个哈希函数组):为了进一步提高召回率,可以独立构建 L 套哈希表(每组使用不同的随机向量集)。查询时合并 L 套哈希表的候选集。
- 动态调整阈值:通过调整
(b, r)或 L,可以在召回率与计算效率之间取得平衡。 - 适用于其他相似度度量:LSH 有多种函数族,例如对于 Jaccard 相似度可以使用 MinHash,对于欧氏距离可以使用 p-stable LSH。
总结
基于局部敏感哈希(LSH)的文本相似搜索算法,通过巧妙的概率哈希将高维相似搜索问题转化为低维哈希碰撞问题,利用波段技术放大相似与不相似文档的碰撞概率差异,从而实现了海量文本集合中高效的近似相似搜索。其核心是在可接受的小误差(漏报或误报)下,将时间复杂度从 O(N) 降至亚线性,是大规模文本去重、检索和聚类的关键技术之一。