彻底弄懂LSH之simHash算法
马克·吐温曾经说过,所谓经典小说,就是指很多人希望读过,但很少人真正花时间去读的小说。这种说法同样适用于“经典”的计算机书籍。
最近一直在看LSH,不过由于matlab基础比较差,一直没搞懂。最近看的论文里几乎都是用simHash来实现LSH,从而进行ANN。
有空看看基于滑动窗口的论文相似性检测。
如何用matlab画出一个数列(函数)的收敛过程(菱形收敛、圆形收敛)?
学完分布式了,我打算自己学WordPress,建立自己的独立博客,放在云平台或者服务器空间,然后学着分析流量和负载均衡这一类,这也算是数据挖掘了吧。
我的学习目标:像吴军博士一样深入浅出地讲解出来一个知识点,这需要很深厚的积累,我以前写的《彻底弄懂最短路径问题》,自己感觉挺不错的,网友反馈也不错;虽然说实践和理论相辅相成,笔者个人觉得鲜血little理论,再搞many实践,最后在学much理论,进而继续指导实践,螺旋递增式学习。
一.基础知识
1.1 Java位运算
位运算只适合整数哦。。。因为浮点的存储方案决定不能位运算,如果非要位运算,就需要Float.floatToIntBits,运算完,再通过Float.intBitsToFloat转化回去。(java默认的float,double的hashcode其实就是对应的floatToIntBits的int值)
1.2 Java中浮点数比较大小
C++用fabs函数,Java中用Double.doubleToLongBits函数,然后直接比较大小,内部原理不做探讨。
1.3 StringTokenzier
Java中substring方法可以分解字符串,返回的是原字符串的一个子字符串。如果要讲一个字符串分解为一个一个的单词或者标记,StringTokenizer可以帮你。
StringTokenizer确实更快些,至于为什么jdk里不推荐使用了,还要再研究(现在是split结合正则表达式)。
测试方法:用StringBuilder的append方法,构造100W字符串,然后分别分别测试并算时间就ok了。
1.4 偶然所得
final可以不再定义时候初始化,好像可以再构造方法里初始化。
二.simHash算法简介
以前写的一个介绍simHash的。
1、分词,把需要判断文本分词形成这个文章的特征单词。最后形成去掉噪音词的单词序列并为每个词加上权重,我们假设权重分为5个级别(1~5)。比如:“ 美国“51区”雇员称内部有9架飞碟,曾看见灰色外星人 ” ==> 分词后为 “ 美国(4) 51区(5) 雇员(3) 称(1) 内部(2) 有(1) 9架(3) 飞碟(5) 曾(1) 看见(3) 灰色(4) 外星人(5)”,括号里是代表单词在整个句子里重要程度,数字越大越重要。
2、hash,通过hash算法把每个词变成hash值,比如“美国”通过hash算法计算为 100101,“51区”通过hash算法计算为 101011。这样我们的字符串就变成了一串串数字,还记得文章开头说过的吗,要把文章变为数字计算才能提高相似度计算性能,现在是降维过程进行时。
3、加权,通过 2步骤的hash生成结果,需要按照单词的权重形成加权数字串,比如“美国”的hash值为“100101”,通过加权计算为“4 -4 -4 4 -4 4”;“51区”的hash值为“101011”,通过加权计算为 “ 5 -5 5 -5 5 5”。
4、合并,把上面各个单词算出来的序列值累加,变成只有一个序列串。比如 “美国”的 “4 -4 -4 4 -4 4”,“51区”的 “ 5 -5 5 -5 5 5”, 把每一位进行累加, “4+5 -4+-5 -4+5 4+-5 -4+5 4+5” ==》 “9 -9 1 -1 1 9”。这里作为示例只算了两个单词的,真实计算需要把所有单词的序列串累加。
5、降维,把4步算出来的 “9 -9 1 -1 1 9” 变成 0 1 串,形成我们最终的simhash签名。 如果每一位大于0 记为 1,小于0 记为 0。最后算出结果为:“1 0 1 0 1 1”。
三.算法几何意义及原理
3.1 几何意义
这个算法的几何意义非常明了。它首先将每一个特征映射为f维空间的一个向量,这个映射规则具体是怎样并不重要,只要对很多不同的特征来说,它们对所对应的向量是均匀随机分布的,并且对相同的特征来说对应的向量是唯一的就行。比如一个特征的4位hash签名的二进制表示为1010,那么这个特征对应的 4维向量就是(1, -1, 1, -1)T,即hash签名的某一位为1,映射到的向量的对应位就为1,否则为-1。然后,将一个文档中所包含的各个特征对应的向量加权求和,加权的系数等于该特征的权重。得到的和向量即表征了这个文档,我们可以用向量之间的夹角来衡量对应文档之间的相似度。最后,为了得到一个f位的签名,需要进一步将其压缩,如果和向量的某一维大于0,则最终签名的对应位为1,否则为0。这样的压缩相当于只留下了和向量所在的象限这个信息,而64位的签名可以表示多达264个象限,因此只保存所在象限的信息也足够表征一个文档了。
3.2 算法原理描述性证明
明确了算法了几何意义,使这个算法直观上看来是合理的。但是,为何最终得到的签名相近的程度,可以衡量原始文档的相似程度呢?这需要一个清晰的思路和证明。在simhash的发明人Charikar的论文中并没有给出具体的simhash算法和证明,以下列出我自己得出的证明思路。
Simhash是由随机超平面hash算法演变而来的,随机超平面hash算法非常简单,对于一个n维向量v,要得到一个f位的签名(f<<n),算法如下:
1,随机产生f个n维的向量r1,…rf;
2,对每一个向量ri,如果v与ri的点积大于0,则最终签名的第i位为1,否则为0.
这个算法相当于随机产生了f个n维超平面,每个超平面将向量v所在的空间一分为二,v在这个超平面上方则得到一个1,否则得到一个0,然后将得到的 f个0或1组合起来成为一个f维的签名。如果两个向量u, v的夹角为θ,则一个随机超平面将它们分开的概率为θ/π,因此u, v的签名的对应位不同的概率等于θ/π。所以,我们可以用两个向量的签名的不同的对应位的数量,即汉明距离,来衡量这两个向量的差异程度。
Simhash算法与随机超平面hash是怎么联系起来的呢?在simhash算法中,并没有直接产生用于分割空间的随机向量,而是间接产生的:第 k个特征的hash签名的第i位拿出来,如果为0,则改为-1,如果为1则不变,作为第i个随机向量的第k维。由于hash签名是f位的,因此这样能产生 f个随机向量,对应f个随机超平面。下面举个例子:
假设用5个特征w1,…,w5来表示所有文档,现要得到任意文档的一个3维签名。假设这5个特征对应的3维向量分别为:
h(w1) = (1, -1, 1)T
h(w2) = (-1, 1, 1)T
h(w3) = (1, -1, -1)T
h(w4) = (-1, -1, 1)T
h(w5) = (1, 1, -1)T
按simhash算法,要得到一个文档向量d=(w1=1, w2=2, w3=0, w4=3, w5=0) T的签名,
先要计算向量m = 1*h(w1) + 2*h(w2) + 0*h(w3) + 3*h(w4) + 0*h(w5) = (-4, -2, 6) T,然后根据simhash算法的步骤3,得到最终的签名s=001。上面的计算步骤其实相当于,先得到3个5维的向量,第1个向量由h(w1),…,h(w5)的第1维组成:r1=(1,-1,1,-1,1) T;第2个5维向量由h(w1),…,h(w5)的第2维组成:r2=(-1,1,-1,-1,1) T;同理,第3个5维向量为:r3=(1,1,-1,1,-1) T.按随机超平面算法的步骤2,分别求向量d与r1,r2,r3的点积:
d T r1=-4 < 0,所以s1=0;
d T r2=-2 < 0,所以s2=0;
d T r3=6 > 0,所以s3=1.
故最终的签名s=001,与simhash算法产生的结果是一致的。
从上面的计算过程可以看出,simhash算法其实与随机超平面hash算法是相同的,simhash算法得到的两个签名的汉明距离,可以用来衡量原始向量的夹角。这其实是一种降维技术,将高维的向量用较低维度的签名来表征。衡量两个内容相似度,需要计算汉明距离,这对给定签名查找相似内容的应用来说带来了一些计算上的困难;我想,是否存在更为理想的simhash算法,原始内容的差异度,可以直接由签名值的代数差来表示呢?
参考http://blog.sina.com.cn/s/blog_72995dcc010145ti.html
四.算法与网页去重
例如,文本的特征可以选取分词结果,而权重可以用df来近似。
Simhash具有两个“冲突的性质”:
1. 它是一个hash方法
2. 相似的文本具有相似的hash值,如果两个文本的simhash越接近,也就是汉明距离越小,文本就越相似。
因此海量文本中查重的任务转换位如何在海量simhash中快速确定是否存在汉明距离小的指纹。也就是:在n个f-bit的指纹中,查询汉明距离小于k的指纹。
在文章的实验中,simhash采用64位的哈希函数。在80亿网页规模下汉明距离=3刚好合适。
因此任务的f-bit=64 , k=3 , n= 8*10^11
任务清晰,首先看一下两种很直观的方法:
1. 枚举出所有汉明距离小于3的simhash指纹,对每个指纹在80亿排序指纹中查询。(这种方法需要进行C(64,3)=41664词的simhash指纹,再为每个进行一次查询)
2. 所有接近的指纹排序到一起,这至多有41664排序可能,需要庞大的空间。提出的方法介于两者之间,合理的空间和时间的折中。
假设我们有一个已经排序的容量为2d,f-bit指纹集。看每个指纹的高d位。该高低位具有以下性质:尽管有很多的2d位组合存在,但高d位中有只有少量重复的。
现在找一个接近于d的数字d’,由于整个表是排好序的,所以一趟搜索就能找出高d’位与目标指纹F相同的指纹集合f’。因为d’和d很接近,所以找出的集合f’也不会很大。
最后在集合f’中查找 和F之间海明距离为k的指纹也就很快了。
总的思想:先要把检索的集合缩小,然后在小集合中检索f-d’位的海明距离
按照例子,80亿网页 有2^34 个,那么理论上34位就能表示完80亿不重复的指纹。我们假设最前的34位的表示完了80亿指纹,假设指纹在前30位是一样的,那么后面4位还可以表示24个, 只需要逐一比较这16个指纹是否于待测指纹汉明距离小于3。
假设:对任意34位中的30位都可以这么做。
因此在一次完整的查找中,限定前q位精确匹配(假设这些指纹已经是q位有序的,可以采用二分查找,如果指纹量非常大,且分布均匀,甚至可以采用内插搜索),之后的2d-q个指纹剩下64-q位需要比较汉明距离小于3。
于是问题就转变为如何切割64位的q。
将64位平分成若干份,例如4份ABCD,每份16位。
假设这些指纹已经按A部分排序好了,我们先按A的16位精确匹配到一个区间,这个区间的后BCD位检查汉明距离是否小于3。
同样的假设,其次我们按B的16位精确匹配到另一个区间,这个区间的所有指纹需要在ACD位上比较汉明距离是否小于3。
同理还有C和D,所以这里我们需要将全部的指纹T复制4份, T1 T2 T3 T4, T1按A排序,T2按B排序… 4份可以并行进行查询,最后把结果合并。这样即使最坏的情况:3个位分别落在其中3个区域ABC,ACD,BCD,ABD…都不会被漏掉。
只精确匹配16位,还需要逐一比较的指纹量依然庞大,可能达到2d-16个,我们也可以精确匹配更多的。
例如:将64位平分成4份ABCD,每份16位,在BCD的48位上,我们再分成4份,WXZY,每份12位, 汉明距离的3位可以散落在任意三块,那么A与WXZY任意一份合起来做精确的28位…剩下3份用来检查汉明距离。 同理B,C,D也可以这样,那么T需要复制16次,ABCD与WXYZ的组合做精确匹配,每次精确匹配后还需要逐一比较的个数降低到2d-28个。不同的组合方式也就是时间和空间上的权衡。
最坏情况是其中3份可能有1位汉明距离差异为1。
算法的描述如下:
1)先复制原表T为Tt份:T1,T2,….Tt
2)每个Ti都关联一个pi和一个πi,其中pi是一个整数, πi是一个置换函数,负责把pi个bit位换到高位上。
3)应用置换函数πi到相应的Ti表上,然后对Ti进行排序
4)然后对每一个Ti和要匹配的指纹F、海明距离k做如下运算:
a) 然后使用F’的高pi位检索,找出Ti中高pi位相同的集合
b) 在检索出的集合中比较f-pi位,找出海明距离小于等于k的指纹
5)最后合并所有Ti中检索出的结果
由于文本已经压缩成8个字节了,因此其实Simhash近似查重精度并不高:
笔者注:这个方法是第二次看了,还是不甚理解........讲解不够直观明了..........
五.算法Java实现
想了很久,觉得直接放代码是个不好的习惯,容易依赖别人,所以笔者放在CSDN上,不过只需要1分。
六.结束语及参考文献
6.1 结束语
熬夜感觉并不好,如何才能戒掉这个坏习惯。
谷歌真叼......
笔者会在下一篇博文里探讨simHash和VSM与网页去重。
探讨信息检索与跳跃表。
探讨二分图最大权匹配(这个应用比较广吧,感觉可以用来精确投放广告,灵感来自计算机121教师和课程互选)。
6.2 部分参考文献
http://blog.sina.com.cn/s/blog_72995dcc010145ti.html
http://gemantic.iteye.com/blog/1701101
http://blog.csdn.net/lgnlgn/article/details/6008498
http://blog.csdn.net/meijia_tts/article/details/7928579
论文Detecting near-duplicates for web crawling.