比较不同的文章、图片啊什么的是否相似,如果一对一的比较,数据量大的话,以O(n2)的时间复杂度来看,计算量相当惊人。所以如果是找相同就好了,直接扔到一个hashmap中即可。这样就是O(n)的复杂度了。
不过相同的字符串一定会得到相同的hash,而不同的字符串,哪怕只有一点点不同,也极可能得到完全不同hash。很自然的想到,要是相似的object能够得到相似的hash就好了。局部敏感哈希就是这样的hash,实现了相似的object的hash也是相似的。
定义相似
要找相似,首先是要定义什么事相似。对于高位空间中的相似,一般认为距离越近越相似。一般用Jaccard distance/similarity来描述:
sim(C1,C2)=|C1∩C2|/|C1∪C2|,d(C1,C2)=1?|C1∩C2|/|C1∪C2|
例如,该图sim=4/11, d=1 - 4/11=7/11
寻找相似的关键三步
- Shingling:把文档转化为集合
- MinHashing:把较大的集合转换为较短的签名,同时保留相似性
- 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)=|C1∩C2|/|C1∪C2|
一般来讲,k越大,相似性判断的就越准确,不过这样来判断相似性依旧效率低下,时间复杂度还是O(n2)(因为没有做任何的简化)
MinHashing
相似矩阵
在使用MinHashing之前,首先要将shingle set写成矩阵形式,看例子:
有5个document:
S1 abcabc
S2 cbacba
S3 acbaba
S4 bcacab
S5 bacbca
那么他们的shingle set:
abcabc = {ab, bc, ca, ab, bc},
cbacba = {cb, ba, ac, cb, ba},
acbaba = {ac, cb, ba, ab, ba},
bcacab = {bc, ca, ac, ca, ab},
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)。并且满足以下两个条件:
- h(c)足够小,使得可以放在RAM中
- sim(C1,C2) =P(h(C1)=h(C2))
即在进行hash时,我们将最相似的对象hash到同一个bucket中
因此,我们的目标是找到这样一个hash函数,使得
- 如果sim(C1,C2)很大,则h(C1)=h(C2)
- 如果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;
- 在某个组中至少有一对签名不相等的概率是1?pr;
- 在每一组中至少有一对签名不相等的概率是(1?pr)b;
- 至少有一个组的所有对的签名相等的概率是1?(1?pr)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来说,两列不相似的概率是:(1?0.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?(1?tr)b,t是相似度