在实际应用中,我们所面对的数据是海量的,并且有着很高的维度。在对数据的各种操作中,查询操作是最常见的一种,这里的查询是指输入一个数据,查找与其相似的数据,那么怎样快速地从海量高维数据中,找到与某个数据最相似的数据,成为了一个难点和问题。
低维的小数据集,可通过线性查找来解决,但如果是对一个海量的高维数据集采用线性查找的话,时间代价非常大,因此,为了解决该问题,我们需要采用一些类似索引的技术来加快查找过程,通常这类技术称为最近邻查找或近似最近邻查找。局部敏感哈希就可以视为一种“近似最近邻查找”。
在介绍局部敏感哈希之前,需要先介绍传统的哈希算法。
传统哈希算法通过哈希函数建立哈希表,由哈希表我们能够得到O(1)的查找时间性能,传统哈希算法的关键在于,找到合适的哈希函数,将原始数据映射到相对应的桶内,如果不同的数据,映射到了同一个位置,就是发生了冲突,这是传统哈希算法所要避免的。
而局部敏感哈希的思路恰恰想法,LSH渴望冲突,但是,不是没有限制的胡乱冲突,而是希望原先相邻的两个数据能够被映射到相同的桶内,具有相同的桶号,也就是说,将相似的数据聚到一起。
LSH算法基于一个假设,如果两个数据在原有的数据空间中是相似的,那么分别经过哈希函数映射以后的它们也具有很高的相似度;相反,如果它们本身是不相似的,那么经过映射后它们仍不具有相似性。
也就是说,将原始数据空间中的两个相邻数据点通过相同的映射后,这两个数据点在新的数据空间中仍然相邻的概率很大,而不相邻的数据点被映射到同一个桶的概率很小。
那么在实际使用中,我们只需要将查询数据进行哈希映射得到其桶号,然后取出该桶号对应桶内的所有数据,再进行线性匹配即可查找到与查询数据相邻的数据,极大的减少了时间代价。
局部敏感哈希的最大特点就在于保持数据的相似性。
我们可以看一个反例:
假设一个哈希函数为Hash(x) = x%9,那么我们现在有三个数据分别为356、359和814,我们将上述的三个数据通过Hash函数转换为:
Hash(356) = 356%9 =5 ;
Hash(359) = 359%9= 8;
Hash(814) = 814%9 = 4;
在未经过映射前,数据356和359比较接近,和814相差较远,但是在经过哈希映射之后,814的哈希值和356的哈希值接近,359的哈希值和356的哈希值相差较远,也就是说,经过这种哈希计算后,数据之间原有的相似度消失,所以他不是一个局部敏感哈希。
那么,局部敏感哈希的哈希函数需要遵循什么样的原则呢?
局部敏感哈希函数需要满足以下两个条件:
1)如果d(x,y) ≤ d1, 则h(x) = h(y)的概率至少为p1;
2)如果d(x,y) ≥ d2, 则h(x) = h(y)的概率至多为p2;
其中d(x,y)表示x和y之间的距离,d1 < d2, h(x)和h(y)分别表示对x和y进行hash变换。
满足以上两个条件的hash functions称为(d1,d2,p1,p2)-sensitive。而通过一个或多个(d1,d2,p1,p2)-sensitive的hash function对原始数据集合进行hashing生成一个或多个hash table的过程称为Locality-sensitive Hashing 局部敏感哈希。
下面我们通过一个具体的实例,来介绍一下LSH的具体用法,“使用LSH实现文档相似度计算”。
假设现在有4个网页,我们将它们分别进行Shingling(将待查询的字符串集进行映射,映射到一个集合里。)得到如下的特征矩阵,每一列代表一个网页(文档),每一行可以视为一个字符,例如a,b,c,d,e,f,g。
其中“1”代表对应位置的Shingles在文档中出现过,“0”则代表没有出现过。
运用Jaccard相似度来衡量文档之间的相似性。接下来我们就要去找一种哈希函数,使得在hash后尽量还能保持这些文档之间的Jaccard相似度,也就是说,这种哈希可以保持数据之间的相似性,那就可以视为局部敏感哈希。
顺道提一下Jaccard(杰卡德)相似度。
Jaccard相似指数用来度量两个集合之间的相似性,它被定义为两个集合交集的元素个数除以并集的元素个数。
Jaccard距离用来度量两个集合之间的差异性,它是Jaccard的相似系数的补集,被定义为1减去Jaccard相似系数。
接下里,我们就要选择一个适当的哈希函数,令其满足局部敏感哈希的条件,在此处我们选用的哈希函数是MinHash,也就是最小哈希。
MinHash 是用于快速检测两个集合相似性的方法。该方法由 Andrei Broder (1997) 发明,最初用于AltaVista搜索引擎中来检测重复的网页。
MinHash定义为:特征矩阵按行进行一个随机的排列后,第一个列值为1的行的行号。
比如我们原先有一个特征矩阵如下:S1,S2,S3为三个文档,A,B,C,D为四个字符。
接下来,我们按行做一个随机排列:
哈希值:排列转换后的行排列次序下第一个列值为1的行的行号,例如h(S1)=D,h(S2)=B。当然,你也可以记为h(S1)=2,h(S2)=1,h(s3)=2,两种表示方法应该都可行。
事实上,两个集合经随机排列之后得到的两个最小哈希值相等的概率等于这两个集合的Jaccard相似度。即P(h(Si)=h(Sj)) = sim(Si,Sj)。
MinHash的基本原理:在A∪B这个大的随机域里,选中的元素落在A∩B这个区域的概率,这个概率就等于Jaccard的相似度
P(h(Si)=h(Sj)) 为什么会等于sim(Si,Sj)?
我们考虑Si和Sj这两列,它们所在行的所有可能结果可以分成如下三类:
(1)A类:两列的值都为1;
(2)B类:其中一列的值为0,另一列的值为1;
(3)C类:两列的值都为0.
特征矩阵相当稀疏,导致大部分的行都属于C类,但只有A、B类行决定sim( Si , Sj ),假定A类行有a个,B类行有b个,那么sim( si,sj )=a/(a+b)。
如果我们把C类行都删掉,那么第一行不是A类行就是B类行,如果第一行是A类行那么h(Si)=h(Sj),因此P( h(Si)=h(Sj) )=P(删掉C类行后,第一行为A类)=A类行的数目/所有行的数目=a/(a+b)
所以,P(h(Si)=h(Sj)) = sim(Si,Sj)。
介绍完了jaccard相似度和MinHash,我们回到我们最初的工作,对原始特征矩阵做随机排序,计算每一个排序后每列的哈希值,一共做三次。
比如,第一次随机排序后,特征矩阵变成如下所示,可以求得该次随机排序后的最小哈希值。
那么,三次之后,我们得到如下一个矩阵,我们可以把它视为原始特征矩阵的一个压缩,或者说是降维。
最后我们需要验证的是,哈希过后的特征矩阵,是否保持了数据的相似性。相似度的计算结果如下表,所示,可以看出,极好的保留了相似性。
注:在原始数据相似度的计算中,去掉了C类数据,也就是都为0的数据。
这就是一个局部敏感哈希的例子,进行了数据的降维,之后查找所消耗的代价就大大减少了。
本文完。