想象你管理着一座巨型快递分拣中心,每天有上亿件包裹进出。现在接到一项新业务:客户报出“我家在北京市朝阳区望京街道”,要你找出所有地址类似的包裹。最笨的办法是把包裹全部翻一遍(暴力搜索),但上亿件根本翻不完。而传统快递分拣要求“运单号完全一致”才走同一传送带(精确哈希),可“类似地址”不是“相同地址”,精确分拣直接失灵。这时,你引入了一个新思路:按片区模糊分拣——地址相近的包裹大概率进同一个分拣口,查询时只需翻对应分拣口的那一堆包裹。这就是局部敏感哈希(LSH)的智慧。本文将以这座快递中心为舞台,带你理解LSH的核心原理、MinHash签名、欧氏空间哈希、检索全流程、参数调优与工程实战。

岗位关联

掌握LSH,你就拿到了百万级向量快速检索的入场券——这是推荐系统、语义搜索、向量数据库落地时最关键的召回加速器。

目录
  1. 可视化演示
  2. LSH核心思想
  3. 哈希家族对比
  4. MinHash原理
  5. 欧式哈希族
  6. 检索全流程
  7. 参数与调优
  8. 工程实战
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示LSH哈希投影桶划分、SimHash符号编码与AND-OR级联构造的召回率调节过程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

LSH投影与桶划分 随机超平面将高维向量投影到桶中,近邻点大概率落入同桶 距离越近的点碰撞概率越高,远点落入不同桶实现快速过滤 P(h(p)=h(q))=1-θ/π(角度距离SimHash) LSH将近邻搜索从O(N)降至O(1)期望桶内查找

LSH核心思想

快递中心的老张管着分拣线。传统分拣按“运单号”操作:运单号差一位数字,包裹就被甩到完全不同的传送带,永远不会出现在同一个分拣口。这套体系做“精确查找”很高效,但面对“找出所有地址相似的包裹”就彻底失灵了——因为相似不是相等。

向量检索面临的正是同样的困境。在Embedding检索中,每个用户、商品、文档都被表示成一个几百维的浮点向量。查询到来时,我们要找出与它“相似”(距离近)的TopK个向量。暴力线性扫描要比较全部数据,亿级规模下无法实时响应;普通哈希函数(如MD5)对输入极其敏感,向量稍微变化一点,哈希值就面目全非,相似的向量根本不会落入同一个桶。

LSH(Locality-Sensitive Hashing,局部敏感哈希)正是为解决“相似查找”而生的哈希函数族。它的核心特征是局部敏感:原始空间中距离近的点,哈希后以高概率落入同一个桶;距离远的点,落入同一桶的概率很低。形式化地说:

查询时只需对查询向量做同样的哈希,再只搜索它所在桶内的候选点,搜索范围就从全量压缩到一个小分拣口。老张终于松了口气:上亿件包裹不需要全翻,只要去对应片区找,速度提升了几个数量级。

Pr⁡[h(a)=h(b)]≥p1, ∀d(a,b)≤RPr⁡[h(a)=h(b)]≤p2, ∀d(a,b)≥cR\displaystyle \Pr[h(a)=h(b)] \ge p_{1}, \ \forall d(a,b) \le R \\\displaystyle \Pr[h(a)=h(b)] \le p_{2}, \ \forall d(a,b) \ge cR
通俗理解:近邻点对撞桶概率高,远邻点对撞桶概率低,这就是“局部敏感”的数学表达。
  1. 碰撞概率Pr表示两个点哈希后落入同一桶的概率
    Pr⁡[h(a)=h(b)]\Pr[h(a)=h(b)]
  2. 近距离阈值R距离不超过R时视为“相似”的临界半径
    d(a,b)≤Rd(a,b) \le R
  3. 远距离阈值cR用放大因子c区分“不相似”场景
    d(a,b)≥cRd(a,b) \ge cR
  4. 核心条件p1>p2相似对的撞桶概率必须显著高于远距对
    p1>p2p_{1} > p_{2}

哈希家族对比

老张的快递中心有不同的业务线:生鲜要按温度分拣,重货要按楼层分拣,加急件要按时效分拣。一套规则不可能满足所有需求。LSH同样如此——不同的距离度量,要选不同的哈希家族。

  • 普通哈希:要求输入完全相同才映射到同一位置,适合精确查找与去重,无法承载“相似”语义。
  • MinHash:面向Jaccard相似度,适合集合型数据,如文本去重、购物记录比对。
  • SimHash:面向余弦相似度,适合高维稠密文本向量,常用于网页去重。
  • p-stable LSH:面向欧氏距离,适合通用Embedding向量,是向量检索的主力。

选错哈希家族,就像用温度分拣标准去分拣重货,毫无意义。比如两台手机用户向量用欧氏距离衡量更自然,套用MinHash的集合视角反而不合适。最经典的Jaccard相似度定义是:

所以工程上的第一课是:先看你的数据用什么距离度量,再选对应的LSH哈希族。老张总结道:包裹按什么属性分拣,取决于客户在乎什么属性。

J(A,B)=∣A∩B∣∣A∪B∣\displaystyle J(A,B) = \frac{|A \cap B|}{|A \cup B|}
通俗理解:两袋商品重合部分占全部商品的比例,比例越高越像“同好”。
  1. 交集基数两个集合共享的元素总数
    ∣A∩B∣|A \cap B|
  2. 并集基数两个集合全部元素去重后的总数
    ∣A∪B∣|A \cup B|
  3. 比值含义共享比例落在0到1之间,越接近1越相似
    ∣A∩B∣∣A∪B∣∈[0,1]\frac{|A \cap B|}{|A \cup B|} \in [0,1]

MinHash原理

两个购物袋,想知道里面商品重叠了多少。逐个比对(精确计算Jaccard)费时费事。一个巧妙办法:给所有可能商品随机排一个顺序(随机置换),然后各自取“袋子里排在最前面的商品”作为袋子的标签。如果两个袋子的标签经常相同,说明它们内容高度重叠。

MinHash正是这个“标签法”的数学化。对集合S,施加随机置换π,定义最小哈希值为集合中π(x)最小的元素。它的关键定理非常漂亮:

实际操作中,我们用k个独立置换生成k维签名向量,再用签名中相同位置的比例估计相似度:

这就像老张给每个分拣口挂上“代表商品标签”,几百个标签一对比,两个袋子像不像一目了然。标签越多,估计越准,这就是MinHash的无偏估计特性。

Pr⁡[h(A)=h(B)]=J(A,B)=∣A∩B∣∣A∪B∣\displaystyle \Pr[h(A) = h(B)] = J(A,B) = \frac{|A \cap B|}{|A \cup B|}
通俗理解:两袋商品随机排序后,取各自最靠前的商品当标签;标签相同的概率恰好等于Jaccard相似度。
  1. 指示函数第i个哈希函数对两集合的结果相同时计1
    I[hi(A)=hi(B)]\mathbb{I}[h_i(A) = h_i(B)]
  2. 频率平均k维签名中相同位置的占比逼近真实Jaccard值
    1k∑i=1k(⋯ )\frac{1}{k}\sum_{i=1}^{k}(\cdots)

欧式哈希族

老张遇到新挑战:有些包裹是用GPS坐标定位的,地图上没法天然划出“片区”。怎么办?答案很简单——随机画平行线。把地图切成等宽的条形区域,两个包裹落在同一条形区的概率,随它们的实际距离增大而衰减。多画几组不同方向的平行线,就构成多个哈希表。

这就是p-stable LSH的核心思想。对d维向量v,哈希函数定义为:

其中a是服从标准正态分布N(0,1)的d维随机向量,b在[0,w)上均匀随机,w是带宽。直观理解:a·v把v投影到随机方向a上,投影值再除以带宽w取整,就落到了某个“条形区”。

碰撞概率随距离c的增大而衰减,精确表达式为:

老张的地图被切成一条条“分拣带”,两个点若落进同一条带,说明它们在某个随机方向上位置相近。多组随机方向叠加,相似度判断就更可靠。

ha,b(v)=⌊a⋅v+bw⌋\displaystyle h_{a,b}(v) = \left\lfloor \frac{a \cdot v + b}{w} \right\rfloor
通俗理解:两个GPS点离得越远,被同一条形区域同时罩住的概率越低,这就是“局部敏感”在连续空间中的体现。
  1. 随机向量a定义了一次随机投影的方向
    a∼N(0,Id)a \sim \mathcal{N}(0, \mathbf{I}_{d})
  2. 投影加偏移a·v得到标量投影,b让分桶边界随机抖动
    z=a⋅v+bz = a \cdot v + b
  3. 量化取整带宽w决定桶的粗细,w越大桶越宽
    pcoll(c)=∫0w1cfp(tc)(1−tw)dt\displaystyle p_{coll}(c) = \int_{0}^{w} \frac{1}{c} f_{p}\left(\frac{t}{c}\right)\left(1 - \frac{t}{w}\right) dt

检索全流程

用户拿着查询单来了。老张的完整流程分四步:先查分拣表确定去哪个分拣口(哈希),再从分拣口搬出那一堆包裹(候选集),然后逐个仔细核对地址(精确距离),最后挑出最相近的包裹交给用户(TopK)。

离线阶段:对所有向量计算L个哈希函数的值,放入L张哈希表。每张表的key是哈希值,value是落在这个桶里的向量ID列表。

在线查询阶段:

1. 对查询向量q分别计算L个哈希值

2. 从L张表中取出对应桶,合并去重得到候选集C

3. 对C中的向量用原始度量(如欧氏距离)精确计算

4. 排序并返回TopK结果

召回率是衡量候选集质量的核心指标:

其中G是暴力搜索得到的真实TopK集合,C是LSH候选集。

老张常说:“分拣口捞得快不快是速度,捞得全不全就是召回率。” LSH用小小的概率损失换来了惊人的速度提升,这正是它在工业界被广泛接受的原因。

Recall=∣C∩G∣∣G∣\displaystyle Recall = \frac{|C \cap G|}{|G|}
  1. 真实最近邻集合G用暴力搜索找到的金标准答案
    G=TopK(q,D)G = \text{TopK}(q, D)
  2. 候选集CLSH从L张哈希表的对应桶中捞出的全部点
    C=⋃i=1Lbucketi(q)C = \bigcup_{i=1}^{L} \text{bucket}_{i}(q)
  3. 召回率定义真实近邻中被候选集覆盖的比例
    Recall=∣C∩G∣∣G∣Recall = \frac{|C \cap G|}{|G|}

参数与调优

分拣线太粗(一个口容纳半个城区),找得全但翻找慢;太细(每个小区一个口),翻得快但容易漏。老张的解法是“AND-OR级联”:先做k次精确匹配(AND),再用L张表重复(OR)。

AND操作:使用k个哈希函数,要求k个值全部相等才认为碰撞。这让桶更精细,候选更少,但碰撞概率从p降为p^k。OR操作:构建L张独立哈希表,任何一张表命中即可进入候选。这给了相似对“L次补救机会”。两级合并后的整体命中概率为:

实际工程中常用k=8~12、L=32~128。画出“候选规模-召回率”的S曲线:相似对概率接近1,不相似对概率接近0,两者之间出现明显分界。老张的调参心得:先定可接受延迟,再选k保精度,最后用L拉召回,三者是跷跷板。

Phit=1−(1−p1k)L\displaystyle P_{hit} = 1 - \left(1 - p_{1}^{k}\right)^{L}
通俗理解:先把分拣口切得很细(AND保证精度),再开多条一模一样的细线(OR保证召回),只要有一条线命中就算找到。
  1. 单次碰撞概率p1LSH哈希族保证的近邻碰撞下限
    p1p_{1}
  2. AND级联k位哈希全部相同才放行,概率指数下降
    p1kp_{1}^{k}
  3. OR级联L张表至少一张命中的补集概率
    1−(1−p1k)L1 - (1 - p_{1}^{k})^{L}

工程实战

现实中的快递中心,周一处理生鲜订单,周二应对大促爆仓——不同业务、不同量级、不同SLA。LSH工程落地同样要围绕场景定制方案。

  • 广告推荐:用户向量与广告向量在亿级规模下做相似召回。离线把广告向量全部哈希入桶,在线对用户向量查桶取候选,再精排出TopK广告。
  • 语义搜索:查询与文档用BERT映射到同一向量空间。LSH做第一层粗筛,把候选从百万压到几千,再交给重排序模型精细打分。
  • 混合索引:LSH负责粗筛候选,HNSW等图索引负责在候选内做更精确的近邻搜索,两者组合兼顾速度与精度。
  • 内存压缩:用乘积量化(PQ)压缩向量存储,或用布隆过滤器压缩桶ID,减少哈希表内存占用。

候选集规模可以用期望公式估算:

老张在季度总结会上说:LSH不是银弹,它解决的是“海里捞针第一步”的问题。把候选范围缩小到可以精确计算之后,后面的精排、重排序才能发挥价值。

Candidates=L×(n×p1k)\displaystyle \text{Candidates} = L \times \left(n \times p_{1}^{k}\right)
  1. 单表期望候选数n个向量在k位AND下落入同一桶的期望数量
    n×p1kn \times p_{1}^{k}
  2. L表叠加总候选数随表数量近似线性放大
    L×(⋯ )L \times (\cdots)

AI 应用场景

电商语义搜索召回

阶段:离线索引构建 + 在线召回

解决的问题:传统倒排索引依赖关键词精确匹配,用户搜“耐穿不累脚的运动鞋”时,含“舒适”“慢跑”等同义词的商品无法召回。AI把查询和商品用Embedding映射到同一向量空间,但上千万商品暴力遍历无法实时响应,LSH让“语义相似”变成“哈希同桶”,毫秒级完成候选召回。

具体案例:某电商平台将商品标题、品类、属性描述用BERT编码成128维向量,离线构建LSH哈希表;用户查询时先编码成向量,落桶取出数百个候选,再精排展示Top50,整体时延从秒级降到几十毫秒。

新闻资讯近似去重

阶段:离线批处理去重

解决的问题:新闻聚合平台每天抓取上百万篇文章,其中大量转载、洗稿的近似重复内容。逐对比较Jaccard相似度的计算量是O(n²),完全不可行。用MinHash先把每篇文章压缩成200维签名,再按签名分桶,只有同桶的文档才做精确比较,计算量下降几个数量级。

具体案例:某资讯App用MinHash检测近似文章,把重复新闻聚成一簇,簇内只保留首发源,垃圾稿件清洗率提升40%以上,实现“签名相同即疑似同源”的高效过滤。

向量数据库混合索引

阶段:索引构建与查询加速

解决的问题:向量数据库在千万级以上数据量时,直接用HNSW建图内存开销大、写入慢。工程上常采用“IVF/LSH粗筛 + 精确精排”的分层架构:LSH把向量分入桶中,查询时只加载少量桶,再对桶内向量精确排序,兼顾内存占用与查询质量。

具体案例:某RAG知识库系统存储千万级文档切片向量,使用LSH做第一级分桶,召回率维持在95%以上,同时将单次查询的内存读取量压缩到原来的1/50。

Python 演示

python41 行
import random PRIME = 2**31 - 1 def item_id(item):    """把字符串稳定映射为整数, 保证可复现"""    code = 0    for ch in item:        code = (code * 127 + ord(ch)) % PRIME    return code def make_hashes(num_hashes, seed=42):    rng = random.Random(seed)    hashes = []    for _ in range(num_hashes):        a = rng.randint(1, PRIME - 1)        b = rng.randint(0, PRIME - 1)        hashes.append((a, b))    return hashes def minhash_signature(items, hashes):    ids = [item_id(x) for x in set(items)]    sig = []    for a, b in hashes:        vals = [((a * x + b) % PRIME) for x in ids]        sig.append(min(vals))    return sig def estimate_jaccard(set_a, set_b, hashes):    sig_a = minhash_signature(set_a, hashes)    sig_b = minhash_signature(set_b, hashes)    same = sum(1 for x, y in zip(sig_a, sig_b) if x == y)    return same / len(hashes) if __name__ == "__main__":    set_a = {"苹果", "香蕉", "樱桃", "葡萄", "芒果"}    set_b = {"苹果", "香蕉", "西瓜", "草莓", "柠檬"}    truth = len(set_a & set_b) / len(set_a | set_b)    est = estimate_jaccard(set_a, set_b, make_hashes(300))    print(f"真实 Jaccard: {truth:.3f}")    print(f"MinHash 估计: {est:.3f}")
目的:用300个随机哈希函数生成两个集合的MinHash签名,统计签名相同位置占比来估计Jaccard相似度。
  1. hashes.append((a, b))用一条(a,b)直线模拟一次随机置换,种子固定保证可复现
  2. ids = [item_id(x) for x in set(items)]先把集合内元素稳定编码成整数,消除Python hash随机性
  3. vals = [((a * x + b) % PRIME) for x in ids]对每个元素施加随机哈希变换并取模防止溢出
  4. sig.append(min(vals))集合内最小哈希值就是该维签名,对应“随机置换下最靠前的商品”
  5. same = sum(1 for x, y in zip(sig_a, sig_b) if x == y)对齐比较两个签名向量,统计相同位数
  6. return same / len(hashes)相同位置占比即Jaccard相似度的无偏估计

常见误区

误区以为LSH和暴力搜索一样,一定能返回“真正最近”的K个邻居。如果漏掉了某个真实近邻,就认为代码有bug或参数没调好。

正解LSH是概率型近似检索,设计目标是“大概率”召回近邻,而非保证100%准确。工程上接受小概率漏检,换取数量级的速度提升。就像按片区模糊分拣,偶尔会把望京的包裹分到酒仙桥,边界误差是天然的。

为何常见:“哈希等于确定性映射”的印象太深,忽略了“局部敏感”四个字恰恰引入了概率性。

误区哈希位数k越大,桶越精细,候选越少,查询速度越快,所以k越大越好。

正解k增大使桶变小、候选变少,但相似对的碰撞概率p^k急剧下降,真实近邻被拆散到不同桶的概率上升,召回率骤降。必须配合增大L来“OR“补救。就像把分拣口细到”门牌号”,相似的包裹很容易被拆开。

为何常见:只看到“桶越小越精确”的一面,忽略了LSH的首要目标是“相似点不被漏掉”。

误区MinHash名声主要在文档去重领域,就以为LSH只能处理集合或文本,处理不了连续向量。

正解LSH是一个哈希函数族框架:集合数据用MinHash,稠密向量用p-stable或SimHash,每种距离度量都有对应哈希族。Embedding检索的工业实现大量使用这些变体。

为何常见:接触的第一个LSH应用是文本去重,把一个具体应用当成了整个方法的边界。

误区既然OR表能提高召回,那不断加L表或多探针就能解决所有召回不足问题。

正解加L表会线性增加内存和候选集规模,候选过大会拖慢精排阶段,边际收益递减。正确做法是系统权衡(k,L)与精排能力,必要时引入PQ压缩、分层索引,而非无限堆L。

为何常见:调参时发现“加到某个L值后召回不再增长”,仍凭直觉继续加,忽略了速度-内存-召回的三维约束。

面试题

Q1. LSH中“局部敏感”的正确含义是什么?

单选题 · easy

  • A.哈希只作用于数据局部区域
  • B.相似数据哈希到同桶概率高← 正确答案
  • C.每次只处理部分哈希值
  • D.哈希结果随数据位置变化

答案:B。“局部敏感”指相近的输入经过哈希后以高概率落到同一个桶。好比快递按“片区”分拣,同城包裹大概率在同一传送带。选项A是对“局部”二字的字面误解,C和D与LSH定义无关。

Q2. 普通哈希与LSH的核心区别是什么?

单选题 · easy

  • A.普通哈希严格精确,LSH容忍相似← 正确答案
  • B.普通哈希可逆,LSH不可逆
  • C.LSH比普通哈希计算速度更快
  • D.普通哈希用于加密,LSH用于索引

答案:A。普通哈希(如MD5)输入微变、输出剧变,适合精确匹配;LSH故意设计成“小变化不翻脸”,让相似数据落到相近桶。选项目的是直观类比,B、C、D都偏离了本质区别。

Q3. MinHash估计的是哪种相似度?

单选题 · mid

  • A.余弦相似度指标
  • B.欧氏距离指标
  • C.Jaccard相似度指标← 正确答案
  • D.海明距离指标

答案:C。MinHash理论核心是估计Jaccard相似度,即集合交集占并集的比例。它不估计余弦也不估计欧氏距离。就像用“购物袋标签重合频率”推测两袋商品的重叠率,标签法是Jaccard的替身。

Q4. p-stable LSH中带宽参数w的作用是什么?

单选题 · mid

  • A.决定随机向量的维度
  • B.决定哈希桶的粒度大小← 正确答案
  • C.控制哈希表的数量
  • D.指定数据集的规模

答案:B。带宽w决定投影值量化取整的步长,w大则桶粗、碰撞概率高、候选多;w小则桶细、候选少但易漏检。类比分拣线按“市”还是按“小区”划分,w直接控制切片粗细。

Q5. 以下哪些方法能有效提高LSH的召回率?

多选题 · mid

  • A.增加哈希表数量L← 正确答案
  • B.减小每表哈希位数k← 正确答案
  • C.增大k并减小L
  • D.多探针查询相邻桶← 正确答案

答案:ABD。增加L表给相似对更多“OR机会”,减小k让桶变宽,多探针访问相邻桶都能召回更多候选。选项C同时降低单表碰撞概率并减少补救机会,召回不升反降。就像多开几条粗分拣线,才捞得回相似包裹。

Q6. 两级AND-OR哈希结构的总命中概率公式是哪个?

单选题 · hard

  • A.1-(1-p^k)^L取补集← 正确答案
  • B.(1-p^k)^L失败概率
  • C.1-(1-p^L)^k反置式
  • D.p^k乘以L线性估计

答案:A。AND级联后相似对碰撞概率降为p^k,OR级联用L次独立机会弥补。全部失败概率是(1-p^k)^L,取补集得到总命中概率1-(1-p^k)^L。其余选项把幂指数位置颠倒或做了线性近似,都不正确。

Q7. LSH检索中“候选集”指的是什么?

单选题 · mid

  • A.所有数据点的哈希桶
  • B.查询落桶中的全部点集合← 正确答案
  • C.与查询点完全相同的点
  • D.随机抽样的数据子集

答案:B。候选集是查询向量哈希后,从L张哈希表对应桶中取出的所有点的并集,它是精排阶段的输入。它不要求与查询完全相同,也不是随机抽样。类比用户报出片区后,分拣员从对应分拣口搬出的那堆包裹。

Q8. LSH工程落地时常用的优化手段有哪些?

多选题 · hard

  • A.使用多探针访问相邻桶← 正确答案
  • B.布隆过滤器压缩桶ID← 正确答案
  • C.只用一张哈希表省内存
  • D.与HNSW精排索引结合← 正确答案

答案:ABD。多探针增加候选覆盖,布隆过滤器压缩哈希表内存,与HNSW结合形成“LSH粗筛+图索引精排”的经典流水线。只用一张哈希表会严重损失召回率,不是推荐做法。类比只留一条分拣线,覆盖率太低。

Q9. LSH属于哪种最近邻检索方法?

单选题 · easy

  • A.精确最近邻检索
  • B.近似最近邻检索← 正确答案
  • C.暴力线性扫描
  • D.基于图的深度搜索

答案:B。LSH是典型的近似最近邻(ANN)方法,以概率性召回换取速度。它不保证返回绝对最近的K个点,但通常能召回绝大多数近邻。类比“按片区找包裹”比“逐个翻全城”快得多,偶尔会有遗漏。

Q10. 关于MinHash签名,下列说法正确的有哪些?

多选题 · hard

  • A.签名长度决定估计精度← 正确答案
  • B.签名可直接计算集合大小
  • C.签名越长估计误差越小← 正确答案
  • D.签名可替代集合参与比较← 正确答案

答案:ACD。签名长度k越大,相同位置占比越逼近真实Jaccard,估计误差越小;签名可以作为集合的“压缩指纹”参与两两比较。但签名是相对指纹,不能反推出集合并集的绝对大小。好比购物袋标签卡能判断两袋像不像,但算不出袋里到底装了多少商品。