局部敏感哈希深度解析(locality-sensetive hashing, LSH)(一)

一. 近邻搜索

  从这里开始我将会对LSH进行一番长篇大论。因为这只是一篇博文,并不是论文。我觉得一篇好的博文是尽可能让人看懂,它对语言的要求并没有像论文那么严格,因此它可以有更强的表现力。

  局部敏感哈希,英文locality-sensetive hashing,常简称为LSH。局部敏感哈希在部分中文文献中也会被称做位置敏感哈希。LSH是一种哈希算法,最早在1998年由Indyk在[1]上提出。不同于我们在数据结构教材中对哈希算法的认识,哈希最开始是为了减少冲突方便快速增删改查,在这里LSH恰恰相反,它利用的正式哈希冲突加速检索,并且效果极其明显。LSH主要运用到高维海量数据的快速近似查找。近似查找便是比较数据点之间的距离或者是相似度。因此,很明显,LSH是向量空间模型下的东西。一切数据都是以点或者说以向量的形式表现出来的。在细说LSH之前必须先提一下K最近邻查找 (kNN,k-Nearest Neighbor)与c最近邻查找 (cNN,c-Nearest Neighbor )。
  kNN问题就不多说了,这个大家应该都清楚,在一个点集中寻找距离目标点最近的K个点。我们主要提一下cNN问题。首先给出最近邻查找(NN,Nearest Neighbor)的定义。

《局部敏感哈希深度解析(locality-sensetive hashing, LSH)(一)》
  
  定义 1: 给定一拥有 n 个点的点集 P ,在此集合中寻找距离 q 点最近的一个点。

  这个定义很容易被理解,需要说明的是这个距离是个广义的概念,并没有说一定是欧式距离。随着需求的不同可以是不同的距离度量手段。那么接下来给出cNN问题的定义。
《局部敏感哈希深度解析(locality-sensetive hashing, LSH)(一)》
  定义 2: 给定一拥有 n 个点的点集 P ,在点集中寻找点 p ,这个 p 满足 d(q,p)(1+c)d(q,P) ,其中 d(q,P) P 中距离 q 点最近一点到 q 的距离。

  cNN不同于kNN,cNN和距离的联系更加紧密。LSH本身设计出来是专门针对解决cNN问题,而不是kNN问题,但是很多时候kNN与cNN有着相似的解集。因此LSH也可以运用在kNN问题上。这些问题若使用一一匹配的暴力搜索方式会消耗大量的时间,即使这个时间复杂度是线性的。也许一次两次遍历整个数据集不会消耗很多时间,但是如果是以用户检索访问的形式表现出来可以发现查询的用户多了,每个用户都需要消耗掉一些资源,服务器往往会承受巨大负荷。那么即使是线性的复杂度也是不可以忍受的。早期为了解决这类问题涌现出了许多基于树形结构的搜索方案,如KD树,SR树。但是这些方法只适用于低维数据。自从LSH的出现,高维数据的近似查找便得到了一定的解决。
  

二. LSH的定义

  
  LSH不像树形结构的方法可以得到精确的结果,LSH所得到的是一个近似的结果,因为在很多领域中并不需非常高的精确度。即使是近似解,但有时候这个近似程度几乎和精准解一致。
  LSH的主要思想是,高维空间的两点若距离很近,那么设计一种哈希函数对这两点进行哈希值计算,使得他们哈希值有很大的概率是一样的。同时若两点之间的距离较远,他们哈希值相同的概率会很小。给出LSH的定义如下:
  
  定义 3: 给定一族哈希函数 H H 是一个从欧式空间 S 到哈希编码空间 U 的映射。如果以下两个条件都满足,则称此哈希函数满足 (r1,r2,p1,p2) 性。
   pB(q,r1) PrH[h(q)=h(p)]p1
   pB(q,r2) PrH[h(q)=h(p)]p2

  定义3中 B 表示的是以 q 为中心, r1 r2 为半径的空间。其实还有个版本的定义,用的是距离的方式,其实都是一样的。(至于说为什么是≥≤同时出现,如果要严密的说这确实是个问题,但是人家大牛的论文下的定义,不要在意这些细节 O(∩_∩)O)
  我绘制了一幅图来说明一下这个定义。
《局部敏感哈希深度解析(locality-sensetive hashing, LSH)(一)》
  可以看出这个定义其实是更关注 pB(q,r1) pB(q,r2) 两个部分的概率。从定义的角度上并没有说概率随着距离的变化一定要单调。但事实上目前所有的LSH哈希函数都是单调的。这也是我困惑的一点。

三. 曼哈顿距离转换成汉明距离

  从理论讲解的逻辑顺序上来说,现在还没到非要讲具体哈希函数的时候,但是为了方便理解,必须要举一个实例来讲解会好一些。那么就以曼哈顿距离下(其实用的是汉明距离的特性)的LSH哈希函数族作为一个参考的例子讲解。
  曼哈顿距离又称 L1 范数距离。其具体定义如下:
  
  定义 4: n 维欧式空间 Rn 中任意两点 A=(a1,a2,...,an) B=(b1,b2,...,bn) ,他们之间的买哈顿距离为:
  

Manhattan(A,B)=i=1n|aibi|

  

  其实曼哈顿距离我们应该并不陌生。他与欧式距离(

L2 范数距离)的差别就像直角三角形两边之和与斜边的差别。文[2]介绍了具体如何构造曼哈顿距离LSH哈希函数的过程。其实在这篇论文发表的时候欧式距离的哈希函数还没有被探究出来,原本LSH的设计其实是想解决欧式距离度量下的近似搜索。所以当时这个事情搞得就很尴尬,然后我们的大牛Indyk等人就强行解释,大致意思是:不要在意这些细节,曼哈顿和欧式距离差不多。他在文[2]中提出了两个关键的问题。

  1.使用

L1 范数距离进行度量。

  2.所有座标全部被正整数化。

  对于第一条他解释说

L1 范数距离与

L2 范数距离在进行近似查找时得到的结果非常相似。对于第二条,整数化是为了方便进行01编码。

  对数据集

P 所有的点,令

C 作为所有点中座标的最大值m,也就是上限。下限是0,这个很明显。然后就可以把

P 嵌入汉明空间

Hd 。其中

d=nC ,此处

n 是数据点在原来欧式空间的维度。对于一个点

p=(x1,x2,...,xn) 如果用

Hd 空间的座标表示就是:

  

v(p)=UnaryC(x1)UnaryC(x2)...UnaryC(xn)

  

UnaryC(x) 是一串长度为

C 二进制的汉明码,其意思是前

x 位为1,后

Cx 位为0。举个例子,

C 若为5,

x 为3,则

Unary5(3)=11100

v(p) 是多个

UnaryC(x) 拼接而成。此时可以发现对于两点

p ,

q 他们之间的曼哈顿距离和通过变换座标后的汉明距离是一样的。到此处,我们可以针对汉明距离来定义一族哈希函数。

四. 汉明距离下的LSH哈希函数

  定义 5: 给定一族哈希函数 H ,对其中任意一个 hH ,

h(p)={0,1,v(p)r 0v(p)r 1

  
其中r是一个从1到 d 之间产生服从均匀分布的随机整数。

  对于这个哈希函数我们可以发现若 p q 的曼哈顿距离为 d ,则他们被哈希成相同哈希值的概率为 PrH[h(q)=h(p)]=nCdnC ,从而这个哈希函数是具有 (r1,r2,nCr1nC,nCr2nC) 敏感性的。这个应该很容易理解。需要说明一下的是,这个哈希的方式准确的说应该是针对汉明距离的。然而我们刚刚其实耍了一个小聪明,把曼哈顿距离转换成了汉明距离。真正针对曼哈顿距离下的LSH将会在几年后的一篇论文中与欧式距离下的LSH同时被提出。
  下图是这个哈希函数概率和距离之间的关系示意图。
《局部敏感哈希深度解析(locality-sensetive hashing, LSH)(一)》

五. LSH的重要参数

  接下来需要讲的就是哈希函数的组合判断。试想如果 nC 确定了,在不同的距离下的概率就被确定了。有时候我们发现对于一些比较接近的 r1 r2 我们很难区分开来。这个时候我们就要放缩相关的哈希概率。放缩概率的手段比较简单,通过增加哈希表的数量来增加哈希概率,在同一张哈希表中增加哈希函数可以缩小哈希概率。我们通常用 l 表示哈希表的数量,用 k 表示每个哈希表中哈希函数的个数。
  假设 h1 是一个函数族 H 中随机挑选的哈希函数,对 q p h1 满足 PrH(h1(q)=h1(p))=p0 。然后我们再独立的挑选 h2 h3 ,…, hk h1 构造成一个整体哈希函数 g 。令 g(p)=[h1(p),h2(p),...,hk(p)]T (这种列向量的表示形式是为了方便理解。这是我的写法。原文不是这么表述的,但是意思差是一样)。

PrH(g(q)=g(p))=PrH(h1(q)=h1(p),h2(q)=h2(p),...,hk(q)=hk(p))=PrH(h1(q)=h1(p))PrH(h2(q)=h2(p))...PrH(hk(q)=hk(p))=pk0

  很显然,因为

p0 是概率,是0到1之间的数,因此

pk0p0 。函数组合判断不光降低了

p2 ,同时也降低了

p1

  假设现在有

l 个哈希表,每个哈希表都使用由刚才方法产生的

g 作为组合哈希函数。为了方便表示,我们记每张哈希表对应的组合哈希函数分别为

g1 ,

g2 ,…,

gl 。我们的判断标准是只要有一个

g 使得

q

p 哈希值相同,那么

q

p 就被哈希到一起。这儿,我令这种判别方式为函数

G

  


PrH(G(q)=G(p))=1[1PrH(g1(q)=g1(p))]...[1PrH(gl(q)=glp))]=1(1pk0)l

  有没有觉得这两种方法有点像与和或运算。改变

k

l 的大小可以放缩哈希概率的大小。可能你会说,放大了都会放大,缩小了都会缩小,也没有取到区分相近的

r1

r2 的效果。事实上效果很明显。我画了几个示意图。

《局部敏感哈希深度解析(locality-sensetive hashing, LSH)(一)》

《局部敏感哈希深度解析(locality-sensetive hashing, LSH)(一)》

《局部敏感哈希深度解析(locality-sensetive hashing, LSH)(一)》

  当基本哈希函数确定,理论上讲只要 p1>p2 ,通过改变 k l 都可以将 r1 r2 时的哈希概率差距拉的很大。代价是要足够大的 k l 。这也是LSH一个致命的弊病。
  说了这么多我们来举一个实例帮助理解。
  
   例1: 数据点集合 P 由以下6个点构成:
   A=(1,1)   B=(2,1)   C=(1,2)
   D=(2,2)   E=(4,2)   F=(4,3)
《局部敏感哈希深度解析(locality-sensetive hashing, LSH)(一)》
   可知座标出现的最大值是4,则 C=4 。维度为2,则 n=2 ,显然 nC=8 。我们进行8位汉明码编码。
   v(A)=10001000
   v(B)=11001000
   v(C)=10001100
   v(D)=11001100
   v(E)=11111100
   v(F)=11111110
   若我我们现在采用 k=2 l=3 生成哈希函数。
   G g1 g2 g3 构成。每个 g 由它对应的 h1 , h2 构成。
   假设有如下结果。
   g1 分别抽取第2,4位。
   g2 分别抽取第1,6位。
   g3 分别抽取第3,8位。
   哈希表的分布如下图所示。
《局部敏感哈希深度解析(locality-sensetive hashing, LSH)(一)》
   若此时我们的查询点 q=(4,4) ,可以计算出 g1(q)=[1,1]T , g2(q)=[1,1]T , g3(q)=[1,1]T 。则分别取出表1,2,3的11,11,11号哈希桶的数据点与 q 比较。依次是C,D,E,F。算出距离 q 最近的点为F。当然这个例子可能效果不是很明显。原始搜索空间为6个点,现在搜索空间为4个点。对于刚接触LSH的人会有个疑问。如果不同哈希表的数据点重复了怎么办,会不会增加搜索空间的大小。首先要说的是这个概率很小,为什么呢。试想假设两个不同哈希表的哈希桶对 q 的查询有相同点,这意味着在两张哈希表中这个点与 q 都有相同哈希值。如果使用单个哈希函数 q 和此点被哈希到一起的概率为 p0 。则刚才那个事件发生的概率为 p2k0 ,这个概率是很小的。当然也有很多办法可以解决这个问题。这不是一个大问题。我在实际运用时 k 大概总是取10-20之间的数, l 大致20-100左右。每次对 q 进行候选点匹配时,候选的样本点数量已经是 P 的十分之一到百分之一了。就好比 P 有10000个数据点,使用暴力匹配要遍历整个数据集,使用LSH可能只要匹配100到1000个点就可以了。而且往往我都能找到最近点。即使找不到最近的,总体成功率也在90%-98%左右。
   文中[2]对LSH的描述非常详细,给出了LSH具体实施步骤。之前讲解的是一个大致思想,有很多细节没有说明白。比如哈希表和哈希桶的具体表现形式。就好比我给出的是个逻辑结构,并没有说清楚它的物理结构。现在说说文[2]是怎么具体实施的。其实我想说的是物理结构这个东西每个人都可以设计一个自己习惯的,不一定非要按某个标准来。重要的是思想。
   当 k 的数值很大时,对于拥有大量数据点的 P ,产生不同的哈希值会很多。这种二进制的编码的哈希值最多可以有 2k 种。这样一来可能会产生大量哈希桶,于是乎我们可以采用一种方法,叫二级哈希。首先我们可以将数据点 p 哈希到编码为 g(p) 的哈希桶。然后我们可以用一个普通的哈希将哈希桶 g(p) 哈希到一张大小为 M 的哈希表中。注意这里的 M 是针对哈希桶的数目而不是针对点的数量。至于第二个哈希具体这么做,我想学过数据结构的同学都应该知道。对于哈希桶而言,我们限制它的大小为 B ,也就是说它最多可以放下 B 个点。当它的点数量达到 B 时,原本我们可以重新开辟一段空间放多余的点,但是我不放入,舍弃它。这个是文[2]的观点,貌似说放不下就不放了,反正我们不止一张哈希表嘛,说不定这些点就被别的哈希表收录了。这个仁者见仁智者见智,我从来不按他的这个方法来。
   假设点集 P n 个点, M B 有如下关系:

M=αnB

  

α 是内存利用率。对查询

q 的近邻点时,我们要搜索所有哈希表至少

cl 个点。(我也不知道这个

c 是个什么意思,想知道的同学去读下原文然后告诉我)。因此磁盘的访问是有上界的,上界便是

l

   我们现在来分析下

l

k 的取值问题。首先我们列出两个事件。

   假如我们的哈希函数是满足

(r1,r2,p1,p1) 性的。

   P1:如果存在一个点

p

pB(q,r1) ,至少存在一个

gj 满足

gj(q)=gj(p)

j=1,2,...,l

   P2:

q 通过

g ,哈希到的块中仅包含

r2 以外的点的块数小于

cl

  

  
定理 1:
k=log1p2nB l=(nB)ρ ,则P1,P2可以保证至少 121e0.132 的概率。其中 ρ=ln(p1)ln(p2)

  这个定理为什么成立,作者的证明我没有看懂。哪天我读懂了我会更新这个博文。

总结:
  以上便是LSH的基本理论,我总结一下。对于LSH算的主要流程分为两个部分,一个是建立哈希结构,另一个便是检索。在知道具体度量方式的情况下,利用该度量下的LSH哈希函数,建立哈希结构。首先选取合适的 k l 参数,然后建立 l 张哈希表,每张哈希表用 k 个独立抽取的基本哈希函数联合判断,建立哈希表的内部结构。哈希值相同的点放在一起,哈希值不同的放在不同的地方。至于查询,当 q 成为我们的查询点,首先计算 q 在每张哈希表的哈希值,取出对应哈希值的哈希桶内所有点,与 q 做距离计算。找到满足我们条件的点作为查询结果。

References:
[1]. Indyk, P. and R. Motwani. Approximate nearest neighbors: towards removing the curse of dimensionality. in Symposium on Theory of Computing. 1998.
[2]. Gionis, A., P. Indyk and R. Motwani. Similarity Search in High Dimensions via Hashing. in International Conference on Very Large Data Bases. 2000.

点赞