基于局部敏感哈希(Locality-Sensitive Hashing, LSH)的近似最近邻搜索:哈希函数族构造、多桶机制与搜索过程
题目描述
在高维空间中,精确地找到与一个查询点距离最近的点(即最近邻搜索,Nearest Neighbor Search, NNS)常常因为“维数灾难”而变得计算成本极高。近似最近邻搜索(Approximate Nearest Neighbor, ANN)旨在牺牲少量精度来极大地提高搜索速度。局部敏感哈希 是ANN的核心技术之一。它的核心思想是:如果两个数据点在原始高维空间中相似(距离近),那么经过特定的哈希函数映射后,它们有很大概率被映射到相同的哈希桶中;反之,不相似的点(距离远)被映射到同一桶的概率很低。本题将详细讲解LSH算法的全过程,包括核心的哈希函数族构造、实现多桶查找的机制,以及完整的索引构建与查询搜索流程。
解题过程讲解
让我们一步步拆解这个算法。
第一步:理解核心思想与“局部敏感”的定义
- 目标:我们希望找到一个哈希函数族 H。对于一个给定的距离度量 D(如欧氏距离、余弦距离),如果两个点 p 和 q 的距离 D(p, q) 很小,那么它们发生碰撞(即哈希值相同)的概率 Pr[h(p) = h(q)] 就很高;反之,如果距离很大,碰撞概率就很低。
- 形式化定义:对于一个哈希函数族 H 和距离度量 D,如果存在距离阈值 R,以及概率 P1 和 P2 (P1 > P2),使得对于任意两点 p, q 满足:
- 如果 D(p, q) ≤ R,则 Pr[h(p) = h(q)] ≥ P1。
- 如果 D(p, q) > cR (c > 1 是近似因子),则 Pr[h(p) = h(q)] ≤ P2。
那么这个哈希函数族 H 就被称为是 (R, cR, P1, P2)-敏感的。
- 直观理解:理想的哈希函数像一个“相似性放大器”。相近的点大概率进入同一个“房间”(哈希桶),而远离的点大概率进入不同的房间。这样,搜索时我们只需检查与查询点 q 落在同一个或邻近房间里的点,而不必检查整个数据集,从而加速搜索。
第二步:为不同距离度量构造LSH函数族
不同的距离度量需要设计不同的哈希函数。我们以最常用的欧氏距离(L2范数)为例,讲解经典的p-stable LSH(具体是当p=2时,对应高斯分布的E2LSH或更常用的随机投影法)。
- 核心技术:随机投影+量化。
- 思路:在高维空间中随机画一条直线(随机向量),并将数据点投影到这条直线上。相似的点,其投影位置也应该接近。我们将这条投影轴划分为若干等宽的“段”(或称“窗口”),投影落入同一段的数据点,就认为它们哈希到了一起。
- 单个哈希函数 h_{a, b}(v) 的构造:
- 步骤a:生成随机超平面。对于一个d维数据点 v,我们随机采样一个d维向量 a,其每一维分量独立同分布地从一个标准正态分布 N(0, 1) 中抽取。这相当于随机选择了一个投影方向。
- 步骤b:计算投影。计算点 v 在 a 方向上的投影(点积):
a · v。这个值是实数。 - 步骤c:量化(分桶)。为了将连续的投影值映射到离散的桶编号,我们引入一个随机偏移 b。b 是一个在区间 [0, w) 上均匀分布的随机数,其中 w 是“窗口”的宽度,一个关键的超参数。
- 哈希函数公式:最终的哈希值为:
h_{a, b}(v) = floor((a · v + b) / w)。 - 几何解释:想象一条数轴(a方向),我们将其用宽度为 w 的“刻度尺”划分。偏移 b 决定了刻度尺的起点。
(a·v + b)/w计算了点 v 的投影落在第几个“大格”里,floor函数取整得到格子的编号。如果两个点的投影值很接近(距离小于 w),它们有高概率落入同一个格子。
第三步:放大效果——从单个哈希函数到哈希键(多级哈希)
单个哈希函数 h 的区分能力有限,碰撞概率曲线可能不够陡峭(P1 和 P2 的差距不够大)。为了放大“相近”和“远离”点之间的碰撞概率差距,我们采用两种级联技术:
-
AND 构造:将 k 个独立的LSH函数
(h1, h2, ..., hk)组合起来,形成一个新的“复合”哈希函数g(v)。通常我们将其连接成一个字符串或向量:g(v) = [h1(v), h2(v), ..., hk(v)]。- 规则:两个点 v 和 q 只有在所有 k 个哈希函数上都发生碰撞时,
g(v)才等于g(q)。 - 效果:这会同时降低碰撞概率 P1 和 P2。因为
P(g(v)=g(q)) = P1^k。由于 P1 > P2,所以 P1^k 下降得比 P2^k 慢(相对差距变大),但这使得总体碰撞概率变得非常小,桶会分得很细,可能过于稀疏。
- 规则:两个点 v 和 q 只有在所有 k 个哈希函数上都发生碰撞时,
-
OR 构造:为了弥补AND构造导致的候选集太小(可能漏掉真正近邻)的问题,我们再创建 L 个这样的复合哈希函数
g1, g2, ..., gL。每个 gj 由 k 个不同的LSH函数按AND规则组成。- 规则:只要 v 和 q 在任意一个复合哈希函数 gj 上发生碰撞,我们就认为它们是候选对。
- 效果:这会同时提高碰撞概率。最终的碰撞概率为
1 - (1 - P1^k)^L。通过精心选择 k 和 L,我们可以实现:真正的近邻点(距离≤R)以高概率(如≥90%)被检索到,而远点被检索到的概率极低。
第四步:算法流程——索引构建与查询
假设我们有一个包含 N 个高维数据点的数据集 S。
-
预处理(索引构建)阶段:
- 步骤1:选择 L 组哈希函数,每组由 k 个独立的
h_{a, b}函数构成。即,创建 L 个复合哈希函数g1, g2, ..., gL。 - 步骤2:创建 L 个哈希表(字典)。
- 步骤3:对于数据集 S 中的每一个数据点 v:
- 对每一组 j (j=1 到 L),计算其复合哈希键
gj(v)。 - 将数据点 v 的标识符(如索引或指针)插入到第 j 个哈希表中,键为
gj(v)。
- 对每一组 j (j=1 到 L),计算其复合哈希键
- 最终,我们得到了 L 个哈希表,每个表将数据点根据其
gj(v)的值分到了不同的桶里。
- 步骤1:选择 L 组哈希函数,每组由 k 个独立的
-
在线查询阶段:
- 输入:一个查询点 q。
- 步骤1:计算查询点 q 在所有 L 个复合哈希函数上的哈希键:
g1(q), g2(q), ..., gL(q)。 - 步骤2:收集候选集。对于每一个哈希表 j,取出所有与 q 共享同一个哈希键
gj(q)的桶中的所有数据点,将它们加入候选集 C。(为了避免重复,可以使用集合来存储)。 - 步骤3:在候选集 C 中进行精确的距离计算。计算 q 与 C 中每个点的实际距离(如欧氏距离)。
- 步骤4:返回候选集中距离 q 最近的点(或前K个最近的点),作为近似最近邻。
第五步:关键参数与总结
- 关键参数:
- k (复合哈希函数的长度):决定了哈希桶的“粒度”。k 越大,AND规则越严格,每个桶里的点越少、越相似,但真正近邻被分到同一个桶的概率 P1^k 也会降低。需要权衡召回率与计算量。
- L (哈希表的数量):决定了“广度”。L 越大,通过 OR 构造,真正近邻被至少一个哈希表捕获的概率就越大,召回率提高,但索引大小和查询时间也会线性增加。
- w (窗口宽度):影响碰撞概率曲线的形状。w 越大,碰撞概率越高,桶更“粗糙”;w 越小,桶更“精细”。
- 算法优势:
- 将高维空间中的近邻搜索问题,转化为高效的哈希表查找问题,极大地加速了搜索过程,尤其适合大规模高维数据。
- 有坚实的概率理论保证。
- 算法局限性:
- 是一种近似算法,无法保证100%找到精确的最近邻。
- 效果严重依赖于参数 (k, L, w) 的选择,通常需要根据数据和经验调整。
- 索引构建需要存储 L 个哈希表,占用较多内存。
至此,你已经了解了局部敏感哈希(LSH)从核心思想、哈希函数设计、到多级哈希放大效果,再到完整构建和查询流程的全部细节。其精髓在于利用概率将“空间相近性”编码为“哈希值相等性”,从而用高效的哈希检索替代昂贵的距离计算。