【转】海量数据类似度计算之simhash和海明距离

 经过 采集系统 咱们采集了大量文本数据,可是文本中有不少重复数据影响咱们对于结果的分析。分析前咱们须要对这些数据去除重复,如何选择和设计文本的去重算法?常见的有余弦夹角算法、欧式距离、Jaccard类似度、最长公共子串、编辑距离等。这些算法对于待比较的文本数据很少时还比较好用,若是咱们的爬虫天天采集的数据以千万计算,咱们如何对于这些海量千万级的数据进行高效的合并去重。最简单的作法是拿着待比较的文本和数据库中全部的文本比较一遍若是是重复的数据就标示为重复。看起来很简单,咱们来作个测试,就拿最简单的两个数据使用Apache提供的 Levenshtein for 循环100w次计算这两个数据的类似度。代码结果以下:html

            String s1 = "你妈妈喊你回家吃饭哦,回家罗回家罗" ;
             String s2 = "你妈妈叫你回家吃饭啦,回家罗回家罗" ;

             long t1 = System.currentTimeMillis();

             for (int i = 0; i < 1000000; i++) {
                    int dis = StringUtils .getLevenshteinDistance(s1, s2);
             }

             long t2 = System.currentTimeMillis();

             System. out .println(" 耗费时间: " + (t2 - t1) + "  ms ");

  耗费时间: 4266 ms算法

  大跌眼镜,竟然计算耗费4秒。假设咱们一天须要比较100w次,光是比较100w次的数据是否重复就须要4s,就算4s一个文档,单线程一分钟才处理15个文档,一个小时才900个,一天也才21600个文档,这个数字和一天100w相差甚远,须要多少机器和资源才能解决。数据库

  为此咱们须要一种应对于海量数据场景的去重方案,通过研究发现有种叫 local sensitive hash 局部敏感哈希 的东西,听说这玩意能够把文档降维到hash数字,数字两两计算运算量要小不少。查找不少文档后看到google对于网页去重使用的是simhash,他们天天须要处理的文档在亿级别,大大超过了咱们如今文档的水平。既然老大哥也有相似的应用,咱们也赶忙尝试下。simhash是由 Charikar 在2002年提出来的,参考《Similarity estimation techniques from rounding algorithms》 。 介绍下这个算法主要原理,为了便于理解尽可能不使用数学公式,分为这几步:数据结构

  • 一、分词,把须要判断文本分词造成这个文章的特征单词。最后造成去掉噪音词的单词序列并为每一个词加上权重,咱们假设权重分为5个级别(1~5)。好比:“ 美国“51区”雇员称内部有9架飞碟,曾看见灰色外星人 ” ==> 分词后为 “ 美国(4) 51区(5) 雇员(3) 称(1) 内部(2) 有(1) 9架(3) 飞碟(5) 曾(1) 看见(3) 灰色(4) 外星人(5)”,括号里是表明单词在整个句子里重要程度,数字越大越重要。函数

  • 二、hash,经过hash算法把每一个词变成hash值,好比“美国”经过hash算法计算为 100101,“51区”经过hash算法计算为 101011。这样咱们的字符串就变成了一串串数字,还记得文章开头说过的吗,要把文章变为数字计算才能提升类似度计算性能,如今是降维过程进行时。性能

  • 三、加权,经过 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”,“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”。这里做为示例只算了两个单词的,真实计算须要把全部单词的序列串累加。google

  • 五、降维,把4步算出来的 “9 -9 1 -1 1 9” 变成 0 1 串,造成咱们最终的simhash签名。 若是每一位大于0 记为 1,小于0 记为 0。最后算出结果为:“1 0 1 0 1 1”。spa

  整个过程图为:线程

simhash计算过程图

  你们可能会有疑问,通过这么多步骤搞这么麻烦,不就是为了获得个 0 1 字符串吗?我直接把这个文本做为字符串输入,用hash函数生成 0 1 值更简单。其实不是这样的,传统hash函数解决的是生成惟一值,好比 md五、hashmap等。md5是用于生成惟一签名串,只要稍微多加一个字符md5的两个数字看起来相差甚远;hashmap也是用于键值对查找,便于快速插入和查找的数据结构。不过咱们主要解决的是文本类似度计算,要比较的是两个文章是否相识,固然咱们降维生成了hashcode也是用于这个目的。看到这里估计你们就明白了,咱们使用的simhash就算把文章中的字符串变成 01 串也仍是能够用于计算类似度的,而传统的hashcode却不行。咱们能够来作个测试,两个相差只有一个字符的文本串,“你妈妈喊你回家吃饭哦,回家罗回家罗” 和 “你妈妈叫你回家吃饭啦,回家罗回家罗”。

  经过simhash计算结果为:

  1000010010101101111111100000101011010001001111100001001011001011

  1000010010101101011111100000101011010001001111100001101010001011

  经过 hashcode计算为:

  1111111111111111111111111111111110001000001100110100111011011110

  1010010001111111110010110011101

  你们能够看得出来,类似的文本只有部分 01 串变化了,而普通的hashcode却不能作到,这个就是局部敏感哈希的魅力。目前Broder提出的shingling算法和Charikar的simhash算法应该算是业界公认比较好的算法。在simhash的发明人Charikar的论文中并无给出具体的simhash算法和证实,“量子图灵”得出的证实simhash是由随机超平面hash算法演变而来的

  如今经过这样的转换,咱们把库里的文本都转换为simhash 代码,并转换为long类型存储,空间大大减小。如今咱们虽然解决了空间,可是如何计算两个simhash的类似度呢?难道是比较两个simhash的01有多少个不一样吗?对的,其实也就是这样,咱们经过海明距离(Hamming distance)就能够计算出两个simhash到底类似不类似。两个simhash对应二进制(01串)取值不一样的数量称为这两个simhash的海明距离。举例以下: 10101 和 00110 从第一位开始依次有第一位、第4、第五位不一样,则海明距离为3。对于二进制字符串的a和b,海明距离为等于在a XOR b运算结果中1的个数(广泛算法)。

  为了高效比较,咱们预先加载了库里存在文本并转换为simhash code 存储在内存空间。来一条文本先转换为 simhash code,而后和内存里的simhash code 进行比较,测试100w次计算在100ms。速度大大提高。

  未完待续:

  一、目前速度提高了可是数据是不断增量的,若是将来数据发展到一个小时100w,按如今一次100ms,一个线程处理一秒钟 10次,一分钟 60 * 10 次,一个小时 60*10 *60 次 = 36000次,一天 60*10*60*24 = 864000次。 咱们目标是一天100w次,经过增长两个线程就能够完成。可是若是要一个小时100w次呢?则须要增长30个线程和相应的硬件资源保证速度可以达到,这样成本也上去了。可否有更好的办法,提升咱们比较的效率?

  二、经过大量测试,simhash用于比较大文本,好比500字以上效果都还蛮好,距离小于3的基本都是类似,误判率也比较低。可是若是咱们处理的是微博信息,最多也就140个字,使用simhash的效果并不那么理想。看以下图,在距离为3时是一个比较折中的点,在距离为10时效果已经不好了,不过咱们测试短文本不少看起来类似的距离确实为10。若是使用距离为3,短文本大量重复信息不会被过滤,若是使用距离为10,长文本的错误率也很是高,如何解决?

simhash_hammingdistance

相关文章
相关标签/搜索