在数据挖掘中,一个最基本的问题就是比较两个集合的相似度。通常通过遍历这两个集合中的所有元素,统计这两个集合中相同元素的个数,来表示集合的相似度;这一步也可以看成特征向量间相似度的计算(欧氏距离,余弦相似度)。当这两个集合里的元素数量异常大(特征空间维数很大),同时又有很多个集合需要判断两两间的相似度时,传统方法会变得十分耗时,最小哈希(minHash)可以用来解决该问题。
Jaccard相似度
首先看看Jaccard相似度。假设有两个集合A,B,则
最小哈希(minHash)
一个很大的集合进行哈希处理的过程其实是由很多小的哈希过程组成。而这些最小的哈希过程就被称为是最小哈希。最小哈希的具体内容就是把一个集合映射到一个编号上。
比如对于集合U={a,b,c,d,e},S1:{a,d},S2:{c},S3:{b,d,e},S4:{a,c,d},用一个矩阵形式来表示他们:
那么,对他进行一次最小哈希就是在经过随机的行排列之后,对于每个集合,从上到下取第一个数值为1的那一行的行号。
比如对上面的矩阵进行随机行排列后变成:
那么每个集合的minhash结果就应该是h(S1)=2,h(S2)=4,h(S3)=0,h(S4)=2。
在经过随机行打乱后,两个集合的最小哈希值相等的概率等于这两个集合的Jaccard相似度,证明如下:
现仅考虑集合S1和S2,那么这两列所在的行有下面3种类型:
1、S1和S2的值都为1,记为X
2、只有一个值为1,另一个值为0,记为Y
3、S1和S2的值都为0,记为Z
S1和S2交集的元素个数为x,并集的元素个数为x+y,所以sim(S1,S2) = Jaccard(S1,S2) = x/(x+y)。接下来计算h(S1)=h(S2)的概率,经过随机行打乱后,从上往下扫描,在碰到Y行之前碰到X行的概率为x/(x+y),即h(S1)=h(S2)的概率为x/(x+y)。
这就是minhash的基本方法。
最小哈希签名
其实得到上面的签名矩阵之后,我们就可以用签名矩阵中列与列之间的相似度来计算集合间的Jaccard相似度了;但是这样会带来一个问题,就是当一个特征矩阵很大时(假设有上亿行),那么对其进行行打乱是非常耗时,更要命的是还要进行多次行打乱。
为了解决这个问题,可以通过一些随机哈希函数来模拟行打乱的效果。就是再用一个哈希函数,将行号进行哈希变换。比如当n=5时,对0,1,2,3,4这5个数,可以用h(x)=(3x+1)的方法进行映射,得到1,4,2,0,3。当然,随便找的h(x)=ax+b这种哈希函数显然可能会冲突,不过只要n和a互素,那么生成的一定是一个排列,这一点用同余类的知识很好证明。
以(x+1)mod5,(3x+1)mod5为例,处理过程如下:
s1 | s2 | s3 | s4 | h1(x+1mod5) | h2(3x+1mod5) | |
0 | 1 | 0 | 0 | 1 | 1 | 1 |
1 | 0 | 0 | 1 | 0 | 2 | 4 |
2 | 0 | 1 | 0 | 1 | 3 | 2 |
3 | 1 | 0 | 1 | 1 | 4 | 0 |
4 | 0 | 0 | 1 | 0 | 0 | 3 |
我们用h1、h2两个hash函数产生了两个行号顺序,那么接下来就是关键步骤了:
初始化都是inf:
\ | s1 | s2 | s3 | s4 |
h1 | inf | inf | 0 | inf |
h2 | inf | inf | inf | inf |
比如求文档s1的值。遍历s1相应的单词,从第0行到第4行:
- 第0行为1,看一下h1计算出来的行号为1<inf,赋值h1为1(就是行号)。继续遍历
- 第1行为0,不关心,跳过
- 第2行为0,不关心。跳过
- 第3行为1, 看一下h1计算出来的行号为4。4大于此时h1的值,h1的值不变。假设小于h1此时的值,将值付给h1
- 第4行为0。不关心,跳过
遍历完了之后此时h1的值就是1,能够看到。我们事实上在做的就是遍历矩阵中的值,对0的不关心。跳过。对1的。看一下hash函数产生的行号,找到行号最小的值作为h1输出的值。同理,h2也一样,最后得到例如以下的签名矩阵矩阵
\ | s1 | s2 | s3 | s4 |
h1 | 1 | 3 | 0 | 1 |
h2 | 1 | 2 | 0 | 0 |
这个时候就能够计算相似度了:
- sim(s1,s2)=0
- sim(s1,s4)=0.5
- sim(s3,s4)=0.5
局部敏感哈希算法(LSH)
通过上面的方法处理过后,一篇文档可以用一个很小的签名矩阵来表示,将文档进行了压缩,比如使用了30个hash函数。那么就将一篇文档压缩成了30位表示,节省下很多内存空间;但是,还有一个问题没有解决,那就是如果有很多篇文档,那么如果要找出相似度很高的文档,其中一种办法就是先计算出所有文档的签名矩阵,然后依次两两比较签名矩阵的两两;这样做的缺点是当文档数量很多时,要比较的次数会非常大。那么我们可不可以只比较那些相似度可能会很高的文档,而直接忽略过那些相似度很低的文档。
首先,我们可以通过上面的方法得到一个签名矩阵,然后把这个矩阵划分成b个行条(band),每个行条由r行组成。对于每个行条,存在一个哈希函数能够将行条中的每r个整数组成的列向量(行条中的每一列)映射到某个桶中。可以对所有行条使用相同的哈希函数,但是对于每个行条我们都使用一个独立的桶数组,因此即便是不同行条中的相同列向量,也不会被哈希到同一个桶中。这样,只要两个集合在某个行条中有落在相同桶的两列,这两个集合就被认为可能相似度比较高,作为后续计算的候选对;下面直接看一个例子:
例如,现在有一个12行签名矩阵,把这个矩阵分为4个行条,每个行条有3行;为了方便,这里只写出行条1的内容。
可以看出,行条1中第2列和第4列的内容都为[0,2,1],所以这两列会落在行条1下的相同桶中,因此不论在剩下的3个行条中这两列是否有落在相同桶中,这两个集合都会成为候选对。在行条1中不相等的两列还有另外的3次机会成为候选对,因为他们只需在剩下的3个行条中有一次相等即可。
对于S2,我们仅需要寻找那些桶相同的集合来计算相似度,例如:
我们仅需要计算sim(S2, S3),sim(S2, S4),sim(S2, S5),因为这些集合出现过与S2桶相同的情况。
两者的应用
1.谷歌新闻推荐论文中使用到了Min hash。这里涉及推荐,使用用户看过的所有的新闻集合表示用户。这里每个用户可以用一个取值0/1的向量表示(每个维度代表新闻,取值代表是否看过该新闻)。这里选用的方法是Min Hash。文章将Min Hash看作是聚类算法,因为哈希后的不同桶可以看做不同的簇。
具体算法是对用户看过的新闻集合进行随机排列,第一个新闻的序号就是最小哈希的结果。但是由于新闻集合太大,显示的随机排列是不可取的。文章里面用哈希的方法模拟随机排列的过程,将每个新闻哈希到0到N,取哈希结果最小的那篇新闻的序号即可。
2.谷歌网页去重论文中使用到了Sim hash。这里用一个高维向量表示表示一个网页。这里每维度的值是一个实数(不是binary)故选用SimHash。使用SimHash将高维向量降维成64位0/1的指纹。之后文章提出了如何找出指纹库中与给定指纹相差最多3位的所有指纹,查询效率关于时间和空间的权衡,非常有意思。
参考: