simhash长文本查重算法原理与实战

simhash

simhash 是一种长文本的查重算法。

SimHash本身属于一种局部敏感hash,其主要思想是降维,将高维的特征向量转化成一个f位的指纹(fingerprint),通过算出两个指纹的海明距离(hamming distince)来确定两篇文章的相似度,海明距离越小,相似度越低(根据 Detecting Near-Duplicates for Web Crawling 论文中所说),一般海明距离为3就代表两篇文章相同

simhash也有其局限性,在处理小于500字的短文本时,simhash的表现并不是很好,所以在使用simhash前一定要注意这个细节。

pip install jieba
pip install simhash

参考文献

https://blog.csdn.net/midae/article/details/120418269

https://blog.itpub.net/69901774/viewspace-2675029/

https://zhuanlan.zhihu.com/p/71488127

https://www.cnblogs.com/maybe2030/p/5203186.html#_label4

https://blog.csdn.net/silmeweed/article/details/107850865

https://blog.csdn.net/qq_45193988/article/details/127280731

https://www.cnblogs.com/lihongda/p/8626277.html

https://www.jianshu.com/p/711933f83ab3

requirements.txt

scikit-learn~=1.4.1.post1
simhash~=2.1.2
jieba~=0.42.1
numpy~=1.24.3

算法流程

simhash的算法具体分为5个步骤:分词hash加权合并降维,具体过程如下:

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

括号里面的权重可以是单词在句子中出现的次数, 也可以是TF-IDF加权, 推荐使用后者

2:hash。通过hash算法把每个词变成hash值,比如“美国”通过hash算法计算为 100101,“51区”通过hash算法计算为 101011。这样,我们的字符串就变成了一串串数字,还记得文章开头说过的吗?要把文章变为数字计算,才能提高相似度计算性能,现在是降维过程进行时。

hash 一般是一个 64 位的二进制数

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”。

img

TF-IDF加权

原文链接:https://blog.csdn.net/silmeweed/article/details/107850865

TF-IDF有两层意思,一层是"词频"(Term Frequency,缩写为TF),另一层是"逆文档频率"(Inverse Document Frequency,缩写为IDF)是一种用于信息检索与数据挖掘的常用加权技术,常用于挖掘文章中的关键词。

TF=(某词在文档中出现的次数/文档的总词量)

IDF= log(文档总数/包含该词的文档数+1)

算法过程:先计算出文档中每个词的TF-IDF值,然后按降序排列,取排在最前面的几个词作为关键词进行输出。

1. 计算词频:

img

2. 计算逆文档频率

(包含某词语的文档越少,IDF值越大,说明该词语具有很强的区分能力)

img

3.TF-IDF值(权重)

img

img

img

实战中, 我们一般使用 jieba 分词自带的 TF-IDF值 方式

# pip install jieba
import jieba
import jieba.analyse
# pip install simhash
from simhash import Simhash
import math

content1 = '关于查重系统很多人并不陌生,...'
content2 = '关于查重系统很多人并不陌生,...'

# 最多取出 topK 两百个单词
keywords1 = jieba.analyse.extract_tags(content1, topK=200, withWeight=True, allowPOS=())
keywords2 = jieba.analyse.extract_tags(content2, topK=200, withWeight=True, allowPOS=())

print(keywords1) # [('simhash', 0.2386802539641493), ('hash', 0.21792544927161459), ('特征向量', 0.14715699180364583),... ]
temp = []
for item in keywords1:
    t = (item[0],math.floor(item[1]* 200))
    temp.append(t)
keywords1 = temp

python上的实现

首先,python是有现成的simhash的包的,包名,就是这个名字;

直接执行pip install simhash即可

刚开始看,这是针对英文的,所以,想去搜搜有没有中文方面现成的,找了找没有,于是就去看看simhash的源码,看看对中文的支持

结果:simhash“表面”上对中文的支持不好,是因为它的中文分词是完全一个个字的分解;

但是,这完全不影响对simhash包的使用,simhash支持分词完的列表作为输入数据,所以,完全可以使用jieba分词之后,在使用simhash进行计算;

示例

# pip install jieba
import jieba
import jieba.analyse
# pip install simhash
from simhash import Simhash
import math

content1 = '关于查重系统很多人并不陌生,无论本科还是硕博毕业都不可避免涉及论文查重问题,这也对学术不正之风起到一定纠正作用。单位主要针对科技项目申报审核,传统的方式人力物力比较大,且伴随季度性的繁重工作,效率不高。基于此,单位觉得开发一款可以达到实用的智能查重系统。遍及网络文献,终未得到有价值的参考资料,这个也是自然。首先类似知网,paperpass这样的商业公司其毕业申报专利并进行保密,其他科研单位因发展需要也不会开源。笔者就结合NLP相关知识进行设计一款自主的查重系统,首先采用自然语言处理方法主要提出两个模型:科技项目查重的训练模型和科技项目查重的测试模型。其中训练模型主要对数据的清洗预处理及其规约化处理,测试系统也是主查重系统,对其查重原理和性能进行设计实现。最后将其封装成包,PHP或者Java等语言调用即可。'
content2 = '关于查重系统很多人并不陌生,无论是本科或者硕博毕业都不可避免涉及论文查重问题,这也对学术不正之风起到一定纠正。单位主要针对科技项目申报审核,传统的方式人力物力比较大,且伴随季度性的繁重工作,效率不高。基于此,单位觉得开发一款可以达到实用的智能查重系统。遍及网络文献,终未得到有价值的参考资料,这个也是自然。首先类似知网,paperpass这样的商业公司其毕业申报专利并进行保密,其他科研单位因发展需要也不会开源。笔者就结合NLP相关知识进行设计一款自主的查重系统,首先采用自然语言处理方法主要提出两个模型:科技项目查重的训练模型和科技项目查重的测试模型。其中训练模型主要对数据的清洗预处理及其规约化处理,测试系统也是主查重系统,对其查重原理和性能进行设计实现。最后将其封装成包,PHP或者JavaScript等语言调用即可。'

# 1. 可以使用普通的分词, 这样权重就全是1
words1 = jieba.lcut(content1, cut_all=True)
words2 = jieba.lcut(content2, cut_all=True)

with open('contect.txt', 'r', encoding='utf-8') as f1:
    content1 = f1.read()

with open('contect2.txt', 'r', encoding='utf-8') as f1:
    content2 = f1.read()
keywords1 = jieba.analyse.extract_tags(content1, topK=200, withWeight=True, allowPOS=())
keywords2 = jieba.analyse.extract_tags(content2, topK=200, withWeight=True, allowPOS=())

temp = []
for item in keywords1:
    t = (item[0], math.floor(item[1] * len(keywords1)))
    temp.append(t)
keywords1 = temp

temp = []
for item in keywords2:
    t = (item[0], math.floor(item[1] * len(keywords2)))
    temp.append(t)
keywords2 = temp

print(Simhash(keywords1).distance(Simhash(keywords2))) # 0
print(Simhash(words1).distance(Simhash(words2))) # 3  # 计算海明距离 0~63 , 两个64位的二进制数异或然后看1的个数即可

海明距离

两个码字的对应比特取值不同的比特数称为这两个码字的海明距离。在一个有效编码集中,任意两个码字的海明距离的最小值称为该编码集的海明距离。举例如下:10101和00110从第一位开始依次有第一位、第四、第五位不同,则海明距离为3。

在 google 的论文给出的数据中,64位的签名,在海明距离为3的情况下,可认为两篇文档是相似的或者是重复的,当然这个值只是参考值。

存储和查找

到这里相似度问题基本解决,但是按这个思路,在海量数据几百亿的数量下,效率问题还是没有解决的,因为数据是不断添加进来的,不可能每来一条数据,都要和全库的数据做一次比较,按照这种思路,处理速度会越来越慢,线性增长。

这里,我们要引入一个新的概念:抽屉原理,也称鸽巢原理。下面我们简单举例说一下:

桌子上有四个苹果,但只有三个抽屉,如果要将四个苹果放入三个抽屉里,那么必然有一个抽屉中放入了两个苹果。如果每个抽屉代表一个集合,每一个苹果就可以代表一个元素,假如有n+1个元素放到n个集合中去,其中必定有一个集合里至少有两个元素。

抽屉原理就是这么简单,那如果用它来解决我们海量数据的遍历问题呢?

针对海量数据的去重效率,我们可以将64位指纹,切分为4份16位的数据块,根据抽屉原理在海明距离为3的情况,如果两个文档相似,那么它必有一个块的数据是相等的。

那也就是说,我们可以以某文本的 SimHash 的每个16位截断指纹为 Key,Value 为 Key 相等时文本的 SimHash 集合存入 K-V 数据库即可,查询时候,精确匹配这个指纹的4个16位截断指纹所对应的4个 SimHash 集合即可。

如此,假设样本库,有2^37 条数据(1375亿数据),假设数据均匀分布,则每个16位(16个01数字随机组成的组合为2^16 个)倒排返回的最大数量为 (2^37)*4/(2^16)=8388608个候选结果,4个16位截断索引,总的结果为:4*8388608=33554432约为3356万,通过这样一来的降维处理,原来需要比较1375亿次,现在只需要比较3356万次即可得到结果,这样以来大大提升了计算效率。

根据网上测试数据显示,普通 PC 比较1000万次海明距离大约需要 300ms,也就是说3356万次(1375亿数据)只需花费3356/1000*0.3=1.0068s。那也就是说对于千亿级文本数据(如果每个文本1kb,约100TB数据)的相似度计算与去重工作我们最多只需要一秒的时间即可得出结果。

如果我们需要得到key对应的value,需要经过这些计算,传入key,计算key的hashcode,得到7的位置;发现7位置对应的value还有好几个,就通过链表查找,直到找到v72。其实通过这么分析,如果我们的hashcode设置的不够好,hashmap的效率也不见得高。借鉴这个算法,来设计我们的simhash查找。通过顺序查找肯定是不行的,能否像hashmap一样先通过键值对的方式减少顺序比较的次数。看下图:

在这里插入图片描述

存储
1、将一个64位的simhash签名拆分成4个16位的二进制码。(图上红色的16位)
2、分别拿着4个16位二进制码查找当前对应位置上是否有元素。(放大后的16位)
3、对应位置没有元素,直接追加到链表上;对应位置有则直接追加到链表尾端。(图上的 S1 — SN)

可以使用 mongodb , hive , mysql 都行, 只要读取出来可以组成这样的数据结构即可

查找
1、将需要比较的simhash签名拆分成4个16位的二进制码。
2、分别拿着4个16位二进制码每一个去查找simhash集合对应位置上是否有元素。
3、如果有元素,则把链表拿出来顺序查找比较,直到simhash小于一定大小的值,整个过程完成。

原理
借鉴hashmap算法找出可以hash的key值,因为我们使用的simhash是局部敏感哈希,这个算法的特点是只要相似的字符串只有个别的位数是有差别变化。那这样我们可以推断两个相似的文本,至少有16位的simhash是一样的。具体选择16位、8位、4位,大家根据自己的数据测试选择,虽然比较的位数越小越精准,但是空间会变大。分为4个16位段的存储空间是单独simhash存储空间的4倍。之前算出5000w数据是 382 Mb,扩大4倍1.5G左右,还可以接受

举例:

设现在有两个文本 p 1 p_1 p1 p 2 p_2 p2 , 每个文本可以划分为 4 个 key, 我们假设他们相似, 且 K p 1 1 K_{p_1}^1 Kp11 K p 2 1 K_{p_2}^1 K

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值