局部敏感哈希(Locality Sensitive Hashing)

比较不同的文章、图片啊什么的是否相似,如果一对一的比较,数据量大的话,以O(n2)的时间复杂度来看,计算量相当惊人。所以如果是找相同就好了,直接扔到一个hashmap中即可。这样就是O(n)的复杂度了。
不过相同的字符串一定会得到相同的hash,而不同的字符串,哪怕只有一点点不同,也极可能得到完全不同hash。很自然的想到,要是相似的object能够得到相似的hash就好了。局部敏感哈希就是这样的hash,实现了相似的object的hash也是相似的。


定义相似

要找相似,首先是要定义什么事相似。对于高位空间中的相似,一般认为距离越近越相似。一般用Jaccard distance/similarity来描述:
sim(C1,C2)=|C1C2|/|C1C2|d(C1,C2)=1|C1C2|/|C1C2|

例如,该图sim=4/11, d=1 - 4/11=7/11


寻找相似的关键三步

  1. Shingling:把文档转化为集合
  2. MinHashing:把较大的集合转换为较短的签名,同时保留相似性
  3. Locality-sensitive-hashing:找出相似的signature,对应的pair既是要找的相似pair

Shingling

直接举例子说明:
k=2,D1=abcab,那么2-shingles set:S(D1)={ab, bc, ca}
对于较长的shingle,可以将其hash,所以得到:h(D1)={1,5,7},
C1=D1,那么对于D2,他们的相似性为:sim(C1,C2)=|C1C2|/|C1C2|
一般来讲,k越大,相似性判断的就越准确,不过这样来判断相似性依旧效率低下,时间复杂度还是O(n2)(因为没有做任何的简化)

MinHashing

相似矩阵

在使用MinHashing之前,首先要将shingle set写成矩阵形式,看例子:
有5个document:

  1. S1 abcabc
  2. S2 cbacba
  3. S3 acbaba
  4. S4 bcacab
  5. S5 bacbca

那么他们的shingle set:

  1. abcabc = {ab, bc, ca, ab, bc},
  2. cbacba = {cb, ba, ac, cb, ba},
  3. acbaba = {ac, cb, ba, ab, ba},
  4. bcacab = {bc, ca, ac, ca, ab},
  5. bacbca = {ba, ac, cb, bc, ca}

将集合中出现过的shingle放入一个集合,并将其编号,由此可以得到一个矩阵。这个矩阵的行是shingle元素,列是文档

shingle C1 C2 C3 C4 C5
0 (ab) 1 0 1 1 0
1 (ac) 0 1 1 1 1
2 (ba) 0 1 1 0 1
3 (bc) 1 0 0 1 1
4 (ca) 1 0 0 1 1
5 (cb) 0 1 1 0 1

实际上,真正的matrix会相当稀疏,不过有sim的公式可以知道,我们关注的是非零项,因此矩阵只保留那些非全零行。

MinHashing

虽然将shingle set转换成了matrix,但实际上,运算量并没有减少
如果存在一个能够hash document,使每一列的C成为一个较小的signature h(C)。并且满足以下两个条件:

  1. h(c)足够小,使得可以放在RAM中
  2. sim(C1,C2) =P(h(C1)=h(C2))
    即在进行hash时,我们将最相似的对象hash到同一个bucket中

因此,我们的目标是找到这样一个hash函数,使得

  1. 如果sim(C1,C2)很大,则h(C1)=h(C2)
  2. 如果sim(C1,C2)很小,则h(C1)h(C2)

很显然,hash function取决于我们如何选取相似的尺度。对于Jaccard similarity,这个hash function就是MinHashing
MinHashing定义:特征矩阵按行进行一个随机的排列后,第一个列值为1的行的行号
我们还是用接着上述的例子来说明,如果我们使用三个hash函数来生成随机排列:

  • h1 = (2x + 1) mod 6 -> 1 3 5 1 3 5
  • h2 = (3x + 2) mod 6 -> 2 5 2 5 2 5
  • h3 = (5x + 2) mod 6 -> 2 1 0 5 4 3

x是shingle,hash后我们得到了新的排列。不过我们看到,如果hash函数选得不好,有些shingle就会无法出现。这里只有h3才是真正的重新排列(没有缺失)
根据这个重新的排列,我们按照定义找出每一个hash函数对应的最小hash值(特征矩阵按行进行一个随机的排列后,第一个列值为1的行的行号),我们可以得到一个hash的matrix:

根据random hash中的重新排序,我们可以找到对应的第一行为1的数值(MinHashing value),填入MinHashing这个表格中,我们得到了最小hash签名(MinHashing signature)
这里要注意的就是,由于random hash选取不够好,会使得出现重复的元素,这种情况情况下,按照从小到大来看即可。即h2只有2和5,先把2检查完,若没有1,才继续看5.

但是,我们为什么可以这样做?为什么MinHash保留了相似性呢?因为两列的最小hash值就是这两列的Jaccard相似度的一个估计,换句话说,两列最小hash值同等的概率与其相似度相等,即P(h(Si)=h(Sj)) = sim(Si,Sj)简单证明下:
对于任意i, j两列,他们可能:

  • 都是1——有A行
  • 既有1,又有0——有B行
  • 都是0——有C行

对于i, j的Jaccard similarity:
sim = A/B
那么对于MinHash来说, 由于是随机排列后:
两个最小hash值相等的概率是A/B
所以,算出了MinHash,并得到signature,原先的相似性也保留在了signature当中

为什么我们需要MinHashing Signature呢?因为中心极限定理。一次hash只是偶然,多次hash才能接近概率。这也是为什么我们用了h1,h2,h3三个hash函数。在实际应用中,会使用更多的hash函数

可以发现,原先一个可能非常大的文章,被我们摘要成了一个签名矩阵,同时不同文章之间的相似性却保留下来了。

Locality Sensitive Hashing

我们的目标是:找到那些相似度至少为S的文档。一般来讲,会用一个hash function(x, y)来指出x和y是否相似

思路是,对于最小哈希尺度

  • 签名矩阵的列被hash到很多buckets中
  • 进入相同bucket的一对说明他们是candidate(备选)的,是否是相似还需要未来进一步检查

具体措施:将签名矩阵分为 b组,每组有r行,然后对每一组进行hash,如果两列的某一组的hash相同,则将其hash到同一个bucket,一般会有k个buckets(k 尽可能的大)。这些进入同一个bucket的列就是candidate pair(尽管可能只有一个组相同)。但是那些完全没有相同的组,一定是不相似的,基于此,进行剪枝。

对于候选相似项我们知道对于某一具体的行,两个签名相同的概率p =两列的相似度= sim(S1,S2),那么我们可以计算:

  • 在某个组中所有行的两个签名值都相等概率是pr;
  • 在某个组中至少有一对签名不相等的概率是1pr;
  • 在每一组中至少有一对签名不相等的概率是(1pr)b;
  • 至少有一个组的所有对的签名相等的概率是1(1pr)b;

举例来说明:

  • 假设有10w个文档,M = 100000
  • 签名有100个integers组成(100个random hash 函数),合计40Mb=100*10w*4byte
  • b=20,r=5
  • 寻找那些相似性至少在0.8以上的文档

sim(C1,C2) = 0.8意味着任意一组中每行两列(C1,C2)相同的概率是0.8,r=5,那么这一组完全相同的概率是:0.85=0.328
那么对于所有的组b=20来说,两列不相似的概率是:(10.328)20=0.00035,所以可以说两列99.965%都是相似的
同理也可证明,相似度为30%的是真的不相似,它们相似的概率只有0.0475

因此我们看到,如果80%的组都被hash到同一个bucket中,那这两列的相似度为0.8,而真正可能相似的概率则为99.965%。这就是局部敏感hash,保证了相似的文档始终会被hash到同一个bucket中。

另外就是,b和r的取值会导致相似曲线的变化,理想中的相似曲线应该是:

可以想象一下,如果b和r都取1,那么红线应该是一条斜率为1的直线
这根红线实际上是至少有一个组的所有对的签名相等的概率:1(1tr)b,t是相似度

posted @ 2015-10-11 18:19  一路绝尘  阅读(1480)  评论(0编辑  收藏  举报