在实际应用的过程中。相似性度量和计算是很经常使用的一个方法。比如网页去重、推断帖子是否相似、推荐系统衡量物品或者用户的相似度等等。当数据量大的时候,计算的时间和空间复杂度就会是一个很重要的问题,比如在推断相似发帖的时候。我们能够用kmeans来进行聚类。可是资源的消耗是巨大的。所以本文推荐一种方法,minhash+lsh(局部敏感hash),用minhash来降维。用lsh来做近似查询,本文主要介绍一下minhash。
在介绍minhash之前,先给出相似性的度量方法。
1. 相似性的度量
相似性度量有非常多方法,欧氏距离是比較经常使用的。这里我们用一下Jaccard相似性系数,公式例如以下
计算方法非常easy。文档A和文档B共同拥有的单词数除以A和B单词的集合。比如A={a,b,c,d},B={c,d,e,f},那么相似性系数就是2/6=0.33。
2. minhash
刚才我们知道在求相似度的时候我们用到了文档和单词。通常情况下,我们都会将文档和单词表示成doc-term矩阵的形式,能够看到term详细的是什么对最后的结果没有不论什么影响。所以我索性用行号来代表term,行号跟term是一一相应的。比如
s1 | s2 | s3 | |
0 | 1 | 0 | 0 |
1 | 0 | 0 | 1 |
2 | 0 | 1 | 0 |
3 | 1 | 0 | 1 |
4 | 0 | 0 | 1 |
第一行中的S1,、S2、S3表示文档,第一列的01234表示行号。也即单词。其它部分1表示文档S中有这个单词,0表示没有这个单词,有了这个集合,我们看一下minhash是怎么做的
随机确定一个顺序。比如上面的顺序是01234。随机确定一个顺序,比如12340。注意这里是随机。目的就是不让最后的结果受人为的干扰。结果例如以下
s1 | s2 | s3 | |
0 | 0 | 0 | 1 |
1 | 0 | 1 | 0 |
2 | 1 | 0 | 1 |
3 | 0 | 0 | 1 |
4 | 1 | 0 | 0 |
我们看到,行号是不变的,行号还是那个行号,变化的是矩阵的内容。找到排在最前面的1代表这个文档,比如S1排在最前面的1行号为2,那么就用2代表文档S1,同理,用1代表S2,那么能够计算S1和S2的相似性系数了,1交2除以1并2等于0。
后面会给出为什么用这样的方法是合理的证明。我们临时先跳过。能够想象一下,用一个单词来代表一个文档偶然性会比較大,那么这个时候我们的想法可能是,能够随机的产生多次变换,取出多个单词来进行比較。这个时候问题就来了,在实际应用的过程中,文档可能有几百万,单词也会有几万,对如此庞大的矩阵做变换时间和空间的代价都会比較大。是不是有别的方法呢,答案是肯定的,我们知道运动是相对的。之前是变换矩阵内容不变行号。我们如今不变矩阵,仅仅变换行号,是不是计算量少了许多。
所以问题转换为怎样产生随机的行号,我们能够用hash函数来产生行号的顺序,两个函数能够自定义。最好保证hash后的值均匀。比如x+1mod5,3x+1mod5。我们选用这两个hash函数来产生行号的顺序。看一下我们如今的情况
s1 | s2 | s3 | h1(x+1mod5) | h2(3x+1mod5) | |
0 | 1 | 0 | 0 | 1 | 1 |
1 | 0 | 0 | 1 | 2 | 4 |
2 | 0 | 1 | 0 | 3 | 2 |
3 | 1 | 0 | 1 | 4 | 0 |
4 | 0 | 0 | 1 | 0 | 3 |
我们用h1、h2两个hash函数产生了两个行号顺序,那么接下来就是关键步骤了
比如求文档s1的值。遍历s1相应的单词
从第0行到第四行
1. 第0行为1,看一下h1计算出来的行号为1。赋值h1为1(就是行号)。继续遍历
2. 第1行为0,不关心,跳过
3. 第2行为0,不关心。跳过
4. 第3行为1, 看一下h1计算出来的行号为4。4大于此时h1的值,h1的值不变。假设小于h1此时的值,将值付给h1
5. 第4行为0。不关心,跳过
遍历完了之后此时h1的值就是1,能够看到。我们事实上在做的就是遍历矩阵中的值,对0的不关心。跳过。对1的。看一下hash函数产生的行号,找到行号最小的值作为h1输出的值。同理,h2也一样,最后得到例如以下的矩阵
s1 | s2 | s3 | |
h1 | 1 | 3 | 0 |
h2 | 1 | 2 | 0 |
这个时候就能够计算s1、s2的相似度了,J=0/3=0
3. 为什么minhash的方法是合理的
问题:两个集合的随机的一个行排列的minhash值相等的概率和两个集合的Jaccard相似度相等
证明例如以下:
两个集合。A、B。对一行来说。他们的状态有三种
X:A、B都为1,即表示A、B集合中都有这个单词
Y:A、B当中一个为1,当中一个不为1,即一个有这个单词,一个没有
Z:A、B都为0,即表示A、B中都没有这个单词。
如果有x行为X,y行为Y,z行为z。这是jaccard系数为x/(x+y)。再看minhash,由于排列是随机的,在遇到Y之前遇到X的概率是x/(x+y)。是不是正好等于jaccard系数的值。
4. 怎样进行相似查询比較
通过前面的方法。我们将文档进行了压缩,比如使用了30个hash函数。那么就将一篇文档压缩成了30位表示。接下来的问题是怎样进行查询。
一种思路是:建立倒排,term是一个单词,doclist就是拥有这个单词的其它文档。
还有一种思路是:不是建立单个单词的倒排,而是建立多个单词的联合倒排,比如
一篇文档:通过前面的方式用30位进行了表示,将这30为进行分成m个桶,每桶r个单词,即m*r=30,这个倒排建立的是:term是r个单词(或者将这r个单词求hashcode),doclist就是拥有这r个单词的文档。
那么这里的问题就是。m、r怎样求解,m等于几好。r等于几好。
假设两个文档相似度为p,那么相应位数相似的概率也是p,那么一个桶里全然同样的概率是p^r,不同样的概率是1-p^r,那么m个桶都不同样的概率是(1-p^r)^m。所以至少有一个桶同样的概率是1-(1-p^r)^m,我们能够依据我们想要的概率p去分配m和r。
最后建立倒排是这种。
桶1——>doc1,doc2,doc3,doc4
桶2——>doc2,doc5,doc9,doc10
索引建立完毕了之后,下一步就是检索,一篇新的文档。也要经过全面的步骤,得到对应的桶。比如桶1,桶3,那么接下来就是用桶1查询,得到跟这篇文档相似的文档。为了保证确实相似。还能够对候选文档计算一下跟本片文档的相似度