主要介绍Min Hashing(用于降维)和Locality Sensitive Hashing(简称LSH,局部敏感哈希)(用于查找)
什么是Jaccard相似度?
Jaccard相似度是用来描述两个集合间的相似度的,其计算方法如下(假设有两个集合A,B):
K-Shingle
假如我们把一整篇文章看成一个长的字符串,那么k-shingle就是这篇文档中长度为k的任意字符子串。所以,一篇文章就是很多个不同的k-shingle的集合。
尽管用k-shingle的方式来表示每篇文章,然后再通过判断每篇文章中shingle集合的相同元素的数量,就可以得出文章的相似度;但是,一篇文章得到的shingle集合的元素个数是很多的。假定k=4,那么每个shingle中就会有4个字符,存在内存中就至少需要4个字节;那么要以这种方式存下一篇文章的所有shingle,需要的内存空间大概是原文档大小的4倍(假设原文档大小为100K,那么存下这篇文档的所有shingle则需要400K)内存开销极大。
基于特征矩阵的最小hash
- 我们随机从两个集合中各挑选一个元素s(A)、s(B),刚好这两个无素相同的概率就相当于两个集合重合部分的比例。
假设现在有4个集合,分别为S1,S2,S3,S4;其中,S1={a,d}, S2={c}, S3={b,d,e}, S4={a,c,d},所以全集U={a,b,c,d,e}
\ | S1 | S2 | S3 | S4 |
---|---|---|---|---|
a | 1 | 0 | 0 | 1 |
b | 0 | 0 | 1 | 0 |
c | 0 | 1 | 0 | 1 |
d | 1 | 0 | 1 | 1 |
e | 0 | 0 | 1 | 0 |
构建集合的特征矩阵是为了计算集合的最小哈希。
为了计算最小哈希,首先对特征矩阵的行进行打乱(也即随机调换行与行之间的位置),这个打乱是随机的。然后某一列的最小哈希值
就等于打乱后的这一列第一个值为1的行所在的行号,行号从0开始。
例如,定义一个最小哈希函数h,然后对上面的特征矩阵进行行打乱,原来第一列的顺序为abcde,打乱后为beadc,则新的特征矩阵为:
\ | S1 | S2 | S3 | S4 |
---|---|---|---|---|
b | 0 | 0 | 1 | 0 |
e | 0 | 0 | 1 | 0 |
a | 1 | 0 | 0 | 1 |
d | 1 | 0 | 1 | 1 |
c | 0 | 1 | 0 | 1 |
对于列S1,从这一列的第一行往下走,直到遇到第一个1,所在的行号则为这一列的最小哈希值。所以这4列的最小哈希值依次为h(S1) = 2, h(S2) = 4, h(S3) = 0, h(S4) = 2
在经过行打乱后的两个集合计算得到的最小哈希值相等的概率等于这两个集合的Jaccard相似度。
当然,打乱用permutation太耗时,一般都会用近似的方法生成最小签名。
- 最小签名:
随机生成n个hash函数,比如h1(x)=(x+1) mod 5
,h2(x) = (3*x+1) mod 5
,在spark中是基于((1 + index) * a + b) % HASH_PRIME)
,a和b随机生成,HASH_PRIME是一个非常大的奇素数。
具体计算过程参看:链接
最后得出一个以hash函数值替代集合的矩阵实现了数据压缩。
\ | S1 | S2 | S3 | S4 |
---|---|---|---|---|
h1 | 1 | 3 | 0 | 1 |
h2 | 0 | 2 | 0 | 0 |
- 误差期望为期望误差是,k为函数个数
LSH(Locality Sensitive Hashing)
通过上面的方法处理过后,一篇文档可以用一个很小的签名矩阵来表示,节省下很多内存空间;但是,如果有很多篇文档,那么如果要找出相似度很高的文档,其中一种办法就是先计算出所有文档的签名矩阵,然后依次两两比较签名矩阵的相似度;需要比较次数。那么我们可不可以只比较那些相似度可能会很高的文档,而直接忽略过那些相似度很低的文档。
求相应的平面参数也有相较随机生成更好的方法,比如PCAH(PCA Hash)通过主成分分析方法学习到转置矩阵w