simhash的背景
simhash广泛的用于搜索领域中,也许在面试时你会经常遇到这样的问题,如果对抓取的网页进行排重,如何对搜索结果进行排重等等。随着信息膨胀时代的来临,算法也在不断的精进,相似算法同样在不断的发展,接触过lucene的同学想必都会了解相似夹角的概念,那就是一种相似算法,通过计算两个向量的余弦值来判断两个向量的相似性,但这种方式需要两两进行计算向量的余弦夹角,计算量比较大,不能用于实时计算或是大数据量的运算,比如在做搜索结果的相似性排重时,需要对上千条结果进行相似性排重,如果两两比对的话,最少也需要进行上千次的向量余弦值计算,其中涉及到分词,特征提取,余弦值计算等,很难满足性能的需要。jaccard相似度也是一种相似算法,它的计算方式比较直观,就是sim(x,y)= (x∩y) / (x∪y),例如:
若 S={a, d}
,T={a, c, d}
则 Jaccard_Sim(S, T) = |{a, d}|/|{a, c, d}| = 2/3
也是一种需要实时生成特征向量并且进行计算的算法。
那么
什么是simhash呢?
Simhash 是一种用单个哈希函数得到文档最小哈希签名的方法,过程为:
- 将一个 f 维的向量 V 初始化为0;f 位的二进制数 S 初始化为0;
- 对每一个特征:用传统的 hash 算法对该特征产生一个 f 位的签名 b。对 i=1 到 f:
- 如果 b 的第 i 位为1,则 V 的第 i 个元素加上该特征的权重;
- 否则,V 的第 i 个元素减去该特征的权重。
- 如果 V 的第 i 个元素大于0,则 S 的第 i 位为1,否则为0;
- 输出 S 作为签名。
对两篇文档,它们的 Simhash 值之间不同位的个数越少(即海明距离越小),它们之间的 Jaccard 相似度越高。
什么是局部敏感性哈希呢?
局部敏感哈希(LSH)是指这样的哈希方法:对两篇文档,如果它们相似,则它们的哈希值有较高的概率是相同的。有了文档的最小哈希签名,我们就能实现这种哈希方法。直观的做法是,将包含 b×r 个值最小哈希签名分为 b 等份,每份 r 个,对两个文档,定义 P 为两个文档至少含有1个相同份的概率,显然,文档间的Jaccard 相似度越高,哈希签名具有相同值的位数就越多,概率 P 就越大。
如何计算simhash呢?
simhash从内部看就是特征向量hash叠加的效果,并且具有局部敏感性,普通的hash算法针对差别不大的字符串会产生截然不同的hash值,但是simhash计算出来的hash值的海明距离会很相近。
具体的算法是将doc分解为特征向量,具体可以通过分词,也可以通过bigram实现,并且可以赋予一定的权重,针对每个向量使用统一的hash函数进行hash,针对每个特征的hash值的每一位,如果是1就对simhash值的对应位加1,当然也可以使用权重,如果对应位是0,则simhash的对应位减1,对最后的simhash值如果该位大于0则该位置1,如果该位小于0,则该位置0,最后就可以得到一个真正的simhash值。
过程可以参考下面:
汉明距离的计算代码如下:
/*************************************************************************
> File Name: hamdistance.c
> Author: desionwang
> Mail: wdxin1322@qq.com
> Created Time: Wed 20 Nov 2013 02:02:13 PM CST
************************************************************************/
#include<stdio.h>
#include<stdint.h>
int hamdistance(uint64_t sim1, uint64_t sim2){
uint64_t xor_val = sim1 ^ sim2;
int distance = 0;
while(xor_val > 0){
xor_val = xor_val & (xor_val - 1);
distance++;
}
return distance;
}
int main(int argc, char *argv[]){
uint64_t sim1 = 851459198;
uint64_t sim2 = 847263864;
int dis = hamdistance(sim1, sim2);
printf("hamdistance of %lu and %lu is %d", sim1, sim2, dis);
}
输出为:
hamdistance of 851459198 and 847263864 is 4
simhash的好处?
simhash离线计算指纹,方便了大规模数据比较时的消耗,不需要在计算时提取特征进行计算,hash值的可比性很强,只需要比较汉明距离,
方便了从海量数据中发掘相似项的实现,试想一个新文档同百万级甚至千万级数据做相似夹角的情况。simhash大大减少了相似项排重的复杂度。
如何应用simhash呢,具体的应用场景?
simhash有两个比较典型的应用,一个是网页抓取的排重,一个是检索时相似doc
的排重
前者是在大集合中寻找是否具有相似项,后者是对检索的集合进行滤重。
检索集中发现相似项比较简单,就是针对要排重的field离线提取特征,并计算simhash值,然后将检索集中德记录进行两两比较,就是计算汉明距离,两个simhash值异或后求二进制中1的个数就是汉明距离了,这是真对较少的记录。
针对较多的记录,例如抓取时做排重,针对上千万,或数亿的数据,就需要对算法进行改进,因为求simhash的相似性,其实就是计算simhash间的汉明距离。
提到查找算法就不得不提到O(1)的hash表,但是如何让simhash和hash表联系在一起呢?
假如我们认为海明距离在3以内的具有很高的相似性,那样我们就可以用到鸽巢原理,如果将simhash分成4段的话,那么至少有一段完全相等的情况下才能满足海明距离在3以内。同理将simhash分为6段,那么至少要满足三段完全相等,以此类推。
这样我们就可以使用相等的部分做为hash的key,然后将具体的simhash值依次链接到value中,方便计算具体汉明距离。
1、将64位的二进制串等分成四块
2、调整上述64位二进制,将任意一块作为前16位,总共有四种组合,生成四份table
3、采用精确匹配的方式查找前16位
4、如果样本库中存有2^34(差不多10亿)的哈希指纹,则每个table返回2^(34-16)=262144个候选结果,大大减少了海明距离的计算成本
我们可以将这种方法拓展成多种配置,不过,请记住,table的数量与每个table返回的结果呈此消彼长的关系,也就是说,时间效率与空间效率不可兼得,参看下图:
2、调整上述64位二进制,将任意一块作为前16位,总共有四种组合,生成四份table
3、采用精确匹配的方式查找前16位
4、如果样本库中存有2^34(差不多10亿)的哈希指纹,则每个table返回2^(34-16)=262144个候选结果,大大减少了海明距离的计算成本
我们可以将这种方法拓展成多种配置,不过,请记住,table的数量与每个table返回的结果呈此消彼长的关系,也就是说,时间效率与空间效率不可兼得,参看下图:
现在的以图找图功能也是simhash的一种展现,同样适用指纹值分桶查找的原理实现。