Skip to content

GuohuaZhuang/deduplication-detecting

Repository files navigation

(Written by firstboy0513)

guohua0594@sina.com

(结合Charikar的simhash指纹编码与Google的Hamming distance拆分算法予以实现)

说起这个实现,还是先说说需求吧。搜索引擎中常常要对新进来的文档(一般指网页,这里统一以文档称之)进行重复性判断,判断这个文档是否已经在已有数据库中存在了没有,如果存在则不予插入。这也就是通用互联网搜索引擎对整个互联网的网页进行不间断更新的处理过程,当然这个不间断的间隔时间是根据不同站点等其它因素决定的,这里不重点分析这个。 那么怎么判断一个新文档与哪些旧文档是重复的呢?通过数据库中设置的唯一ID来识别,OK,那这个ID怎么来的呢,像上面那种互联网网页的话,你可能一开始想到使用URL作为唯一标识,错!如果那个网页URL没变,但是内容修改了呢?博客、论坛BBS之类的网页常常是这个情况,那么有这些网页里面有更新的数据到时就没有入库建索引,也就搜索不到了。 好,你可能会说那就把网页内容加上去总行了吧,好的,你应该会把URL加上网页内容做一个md5之类的编码作为ID,上面这问题是解决了,但是这个网页里面如果有一个时间计时器什么的,或者访问数什么的,你每次去抓取访问的时候这个数值都会不一样,这样你的这个md5编码的唯一ID就不一样了。(其实我们真正解决的需求还不仅仅是这个网页去重的问题,在搜索引擎内容搜索中,我们经常对于极相近的内容做一个归并呈现出来比较友好,像百度的新闻搜索里出现的“X条相同新闻”就是很典型的应用。)

呜,看来这样不行了。这样我们就想到计算两个网页的语义相似度了,如果两个文档从语义上看差别很小的,那就算这两个文档是相同重复的。哦?真得要这样做吗?诸不知,计算两个语义相似度常常使用的是vsm(Vector space model 向量空间模型)【0】来计算的,可想而知,对于刚刚直接匹配ID的计算量不是一个数量级的。有人说怎么滴不行呀,多一些性能好的配置可以再搞个分布式神马的怎么不能解决呢。的确有XX院的人做一些什么研究性系统或者报告的时候使用这样的方法,不过对于商业化数据量,特别是整个互联网数据,差之毫厘谬以千里啊。

来看看互联网的数据量:截至2013年3月,国内.cn域名总数为7,489,764个。【1】截至2013年5月5日,全球gTLD域名注册总量为145,123,706个。【2】而在2013年5月的调查中,Netcraft在侦测时收到了672,837,096个站点的反馈信息!【3】上帝呀,这还不是网页数量,这只是站点数量!如下图所示是Netcraft的侦测数据。据2008年的传言,谷歌网页索引数量突破1万亿个,Google一直不透漏这方面数据,不知道是什么来的。但无论怎么,这个数据量可以用海量来称呼。

netcraft_website_count

眼看这种vsm去计算文档的相似度不实际,怎么办呢,关键的地方是这个唯一ID的构建,我们知道,如果md5编码的对象差别极小,也会导致两个编码结果相差甚远。你应该在想能不能有一种异于md5这样的编码方式,如果两个网页内容相差极小的情况下对应的编码也很相近,这样不就可以轻松地通过ID来识别出来相似文档了吗?是的,这就是Charikar的simhash指纹编码【4】的厉害之处,使用这个方法生成的ID就达到的内容相差极小情况下对应的编码就越相近的效果,不过差别大一些就不能保证了,而且这里面还有一个理论误差,据论文所说,误差率极小,说实际情况下可以忽略。

那么,我们来看一下,什么是simhash,和以前的hash有什么不同。 Simhash其目的就是计算这样一个文档的指纹编码,它的计算过程应用到了普通的hash编码,如下图所示,先把文档拆解成一个个特征(一般以词作为特征)及其权重(一般以词频或者其tf-idf值【6】作为权重)。然后对每一个特征进行hash编码得到一组hash值及对应权重值,这些hash值都是固定位数的编码,每位数上面的值是按这些hash值对应位如果是1就累加正的权重值,如果是0就累加负的权重值,最好按位累加整个hash数组,得到最终结果后对每位上面的数值取符号函数,如果为正就取1,如果为负就取0,这样就生成了所谓的指纹编码。

simhash

从直观来看,如果差别个别特征的话,最后那个符号函数生成的指纹编码应该不会改变很多,也就是改变的位数应该很小或者极小。当然,这是Charikar论文里面说的,诚然,运气不佳,可能刚好好几位都因个别特征的差异而全部变化也是可能的,但实际情况应该是个小概率事件。

好了,既然知道怎么生成这个simhash,那么新来一个文档也就是新来一个simhash,怎么在海量的旧simhash中打出与其差异不大的编码值呢?这个差异不大我们可以定一个经验值,比如对于64bit的simhash,位数相差不超过3bit的就算差异不大,反应到文档也就是这两个文档相似相近的意思【5】。至于为什么在64bit的simhash下取相差不超过3bit来表示两个文档相近的试验,在论文【5】中给予了实际数据结果,如下图所示,取召回率和准确率折衷的值,所以在64bit的情况下取相差3bit比较合适。

recall_vs_presious

似乎simhash直接拿去和每个旧simhash对比不是很好,假设有100亿个旧的simhash,那么这种对比岂不是要100亿次?假设每次比对只需要10个CPU周期,这样,2.4GHz的CPU就需要2.5亿次对比。什么,你说要对比100亿次,那么就需要40秒!那一台机器一天能进来几个新的文档呢。而这还是估算只需要10个CPU周期,看看下面的hamming_hist计算函数,不止吧。原来每个对比时间是很短,不过数据量大起来计算量就惊人了。想想如果全世界每人给你1美元,你就会变成70亿美元的富翁,你就可以轻松成为2013年福布斯全球亿万富豪排行榜的第166名,比李彦宏和马化腾都要有钱,比世界上86个国家或地区的2012年全年GDP还高!

如下计算hamming_dist日前来看是最快的计算方法了,当然你如果有更快的方法非常欢迎提出来。

int hamming_dist( uint64_t a1, uint64_t a2) { uint32_t v1 = a1^a2; uint32_t v2 = (a1^a2)>>32;

_v1 = v1 - ((v1>>1) & 0x55555555);_
_v2 = v2 - ((v2>>1) & 0x55555555);_
_v1 = (v1 & 0x33333333) + ((v1>>2) & 0x33333333);_
_v2 = (v2 & 0x33333333) + ((v2>>2) & 0x33333333);_
_int c1 = (((v1 + (v1>>4)) & 0x0F0F0F0F) * 0x01010101) >> 24;_
_int c2 = (((v2 + (v2>>4)) & 0x0F0F0F0F) * 0x01010101) >> 24;_

_return c1+c2;_
}
OK,言归正传,现在就说说Google论文提到的快速hamming_dist查找算法,先看看这种想法是怎么产生的。

想要不比对,就自然想到通过索引来取,最笨的方法是新来一个文档的simhash编码,把与这个编码所有可能距离不超过3bit的编码都罗列出来,然后拿这些编码去从已经建好完整索引库的旧simhash编码中查询就可以得到结果了。不过按64bit要取距离不超过3bit的话这需要罗列出 种这样的编码,这样的计算量就会很大,不切合实际。

attemp_one

那么可不可以在建立索引的时候就构建这样的索引,这样搜索速度不就非常快了吗?也就是根据已经存在的旧编码都遍历出来所有可能的hashcode,然后查询的时候,直接拿新文档的hashcode去索引库里面搜索。虽然这样查询新文档的hashcode速度很快,但是建立旧索引的价格的巨大的,这个计算量和前面的 是差不多的。

attemp_two

好了,下面的步骤已经可以猜出来大概是什么的情况了。思路的重点就在于这两个想法的折衷。 注意看了,这里面的思路是这样的。理论上来说,这些hashcode的指纹编码值分布是随机的,这样是不是可以以不同的 bit段做为索引呢?如果取前面一部分bit做索引的话,后面的比对计算量从理论的随机上来说计算量不会很大的,毕竟这个bit数是以指数级增长的。OK?是的,思路确实是这样的。但这个索引位数应该取多少呢?这个就需要按实际的数据量按理论随机性来预估的。因为假设这些实际数据量按理论上的随机性来算的话,应该都可以通过索引来识别的,这样尽量分散的情况下,被索引后的hashcode,也就是需要比对的hashcode就很少了,如果数据量完全由这些索引位来指定的话,那么需要比对的hashcode是不是只需要比对1个?呵呵,理论上是这样的,实际上其实只是尽量减少这样的比对次数而已。

下面以实际数据在100亿的数量级为例,因为 ,所以取34位做索引比较好,那么我们可以把64bit的hashcode拆解成4段,每段16bit,这样,索引的位数可以是32bit,比较接近34bit,这样只需要构建4个索引表,也就是把4种可能的不同16bit都来做索引。这样,从理论上的随机性来看,到时需要比对的数量差不多是 次比对,这个26万比对次数相对来说还是可以接受的。至少比起前面预估的对比100亿次需要40秒的话,这26万次只需要0.001048576秒。具体切割及索引如下摘录自论文【5】的PPT图示。

64bit_4pieces

上面这个思路清楚了,下一步就是怎么实现它,Google的做法是加入了MapReduce去做分布式,因为上面提到分不同的索引表,其实这个时候就可以直接把不同索引表放在不同的机器分布式处理达到快速实现这些simhash fingerprint的插入与查询。不过鉴于实际数据量,我们可能比较简单地直接配置这样的索引表,通过socket实现这样的不同机器服务端处理的压力。

末了,有兴趣参考一下上述思路的一个具体实现程序:

https://code.google.com/p/deduplication-detecting

https://github.com/firstboy0513/deduplication-detecting

参考文献

【0】http://en.wikipedia.org/wiki/Vector_space_model

【1】http://www.cnnic.net.cn/hlwfzyj/hlwfzzx/qwfb/201304/W020130424624101060588.pdf

【2】http://www.whois.sc/internet-statistics/

【3】http://news.netcraft.com/archives/category/web-server-survey/

【4】M. Charikar. Similarity estimation techniques from rounding algorithms. In Proc. 34th Annual Symposium on Theory of Computing (STOC 2002), pages 380-388, 2002.

【5】Gurmeet Singh, Manku; Das Sarma, Anish (2007), "Detecting near-duplicates for web crawling", Proceedings of the 16th international conference on World Wide Web. ACM,.

【6】http://en.wikipedia.org/wiki/TFIDF

About

文档去重功能是为了解决搜索引擎的文档语义重复的问题,方法是多重哈希下的语义指纹算法。

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages