python文件查重_海量文件查重SimHash和Minhash
SimHash
事实上,传统比较两个文本相似性的方法,大多是将文本分词之后,转化为特征向量距离的度量,比如常见的欧氏距离、海明距离或者余弦角度等等。两两比较固然能很好地适应,但这种方法的一个最大的缺点就是,无法将其扩展到海量数据。例如,试想像Google那种收录了数以几十亿互联网信息的大型搜索引擎,每天都会通过爬虫的方式为自己的索引库新增的数百万网页,如果待收录每一条数据都去和网页库里面的每条记录算一下余弦角度,其计算量是相当恐怖的。
我们考虑采用为每一个web文档通过hash的方式生成一个指纹(fingerprint)。传统的加密式hash,比如md5,其设计的目的是为了让整个分布尽可能地均匀,输入内容哪怕只有轻微变化,hash就会发生很大地变化。我们理想当中的哈希函数,需要对几乎相同的输入内容,产生相同或者相近的hashcode,换句话说,hashcode的相似程度要能直接反映输入内容的相似程度。很明显,前面所说的md5等传统hash无法满足我们的需求。
simhash是locality sensitive hash(局部敏感哈希)的一种,最早由Moses Charikar在《similarity estimation techniques from rounding algorithms》一文中提出。Google就是基于此算法实现网页文件查重的。
海明距离的定义,为两个二进制串中不同位的数量。上述三个文本的simhash结果,其两两之间的海明距离为(p1,p2)=4,(p1,p3)=16以及(p2,p3)=12。事实上,这正好符合文本之间的相似度,p1和p2间的相似度要远大于与p3的。
如何实现这种hash算法呢?以上述三个文本为例,整个过程可以分为以下六步:
1、选择simhash的位数,请综合考虑存储成本以及数据集的大小,比如说32位
2、将simhash的各位初始化为0
3、提取原始文本中的特征,一般采用各种分词的方式。比如对于"the cat sat on the mat",采用两两分词的方式得到如下结果:{"th", "he", "e ", " c", "ca", "at", "t ", " s", "sa", " o", "on", "n ", " t", " m", "ma"}
4、使用传统的32位hash函数计算各个word的hashcode,比如:"th".hash = -502157718
,"he".hash = -369049682,……
5、对各word的hashcode的每一位,如果该位为1,则simhash相应位的值加它的权重(通常是出现的频率);否则减它的权重
6、对最后得到的32位的simhash,如果该位大于1,则设为1;否则设为0
整个过程可以描述为:
按照Charikar在论文中阐述的,64位simhash,海明距离在3以内的文本都可以认为是近重复文本。当然,具体数值需要结合具体业务以及经验值来确定。
利用鸽舍原理在快速查找出位数不同的数目小于等于3的算法的描述如下:
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中检索出的结果
代码:
#!/usr/bin/python
#-*- coding:utf-8 -*-
from __future__ import division,unicode_literals
import sys
import re
import hashlib
import collections
import datetime
reload(sys)
sys.setdefaultencoding('utf-8')
import codecs
import itertools
lib_newsfp_file = sys.argv[1] #读入库中存储的所有新闻
result_file = sys.argv[2]
test_news_fp = {}
lib_news_fp = {}
bucket = collections.defaultdict(set)
offsets = []
def cacu_frequent(list1):
frequent = {}
for i in list1:
if i not in frequent:
frequent[i] = 0
frequent[i] += 1
return frequent
def load_lib_newsfp_file():
global lib_news_fp
fin = codecs.open(lib_newsfp_file,'r','utf-8')
for line in fin:
lines = line.strip()
if len(lines) == 0:
continue
Arr = lines.split('\t')
if len(Arr) < 3:
continue
lib_news_fp[Arr[0]] = Arr[3]
def get_near_dups(check_value):
ans = set()
for key in get_keys(int(check_value)):
dups = bucket[key]
for dup in dups:
total_value,url = dup.split(',',1)
if isSimilar(int(check_value),int(total_value)) == True:
ans.add(url)
break #与一条重复 退出查找
if ans:
break
return list(ans)
def ini_Index():
global bucket
getoffsets()
print offsets
objs = [(str(url),str(values)) for url,values in lib_news_fp.items()]
for i,q in enumerate(objs):
addindex(*q)
def addindex(url,value):
global bucket
for key in get_keys(int(value)):
v = '%d,%s' % (int(value),url)
bucket[key].add(v)
def deleteindex(url,value):
global bucket
for key in get_keys(int(value)):
v = '%d,%s' %(int(value),url)
if v in bucket[key]:
bucket[key].remove(v)
def getoffsets(f = 64 , k = 4):
global offsets
offsets = [f // (k + 1) * i for i in range(k + 1)]
def get_keys(value, f = 64):
for i, offset in enumerate(offsets):
if i == (len(offsets) - 1):
m = 2 ** (f - offset) - 1
else:
m = 2 ** (offsets[i + 1] - offset) - 1
c = value >> offset & m
yield '%x:%x' % (c , i)
def bucket_size():
return len(bucket)
def isSimilar(value1,value2,n = 4,f = 64):
ans = 0
x = (value1 ^ value2) &((1 << f) - 1)
while x and (ans <= n):
ans += 1
x &= x - 1
if ans <= n:
return True
return False
def load_test_file():
global test_news_fp
for line in sys.stdin:
features = []
result = line.strip().split('\t')
url = result[0]
content = result[2].split()
title = result[1].split()
features.extend(content)
features.extend(title)
total_features = cacu_frequent(features)
test_news_fp[url] = build_by_features(total_features)
def load_test_newsfp_file():
global test_news_fp
for line in sys.stdin:
lines = line.strip()
if len(lines) == 0:
continue
Arr = lines.split('\t')
if len(Arr) < 3:
continue
test_news_fp[Arr[0]] = Arr[3]
def build_by_features(features,f=64,hashfunc=None):
v = [0]*f
masks = [1 << i for i in range(f+f)]
if hashfunc is None:
def _hashfunc(x):
return int(hashlib.md5(x).hexdigest(),16)
hashfunc = _hashfunc
if isinstance(features,dict):
total_features = features.items()
else:
total_features = features
for fea in total_features:
if isinstance(fea,basestring):
h = hashfunc(fea.encode('utf-8'))
w = 1
else:
h = hashfunc(fea[0].encode('utf-8'))
w = fea[1]
for i in range(f):
v[i] += w if h & masks[i+32] else -w
ans = 0
for i in range(f):
if v[i] >= 0:
ans |= masks[i]
return ans
sum = 0
def process():
global test_news_fp
global sum
fout = codecs.open(result_file,'w','utf-8')
load_lib_newsfp_file()
#load_test_file()
ini_Index()
check_features = test_news_fp.items()
lib_features = lib_news_fp.items()
i = 0
for check_fp in check_features:
#print i
ans = []
ans = get_near_dups(check_fp[1])
if ans:
for url in ans:
output_str = str(check_fp[0])+'\t'+str(url)
fout.write(output_str+'\n')
#break
#print check_fp[0],'is duplicate'
sum = sum + 1#del test_news_fp[check_fp[0]]
print i
i += 1
fout.close()
if __name__ == '__main__':
# process()
begin = datetime.datetime.now()
load_test_newsfp_file()
#load_test_file()
#getoffsets()
#print offsets
#load_lib_newsfp_file()
process()
end = datetime.datetime.now()
print '耗时:',end - begin,' 重复新闻数:',sum,' 准确率: ', sum/2589
MinHash
1.概述
跟SimHash一样,MinHash也是LSH的一种,可以用来快速估算两个集合的相似度。MinHash由Andrei Broder提出,最初用于在搜索引擎中检测重复网页。它也可以应用于大规模聚类问题。
2.Jaccard index
在介绍MinHash之前,我们先介绍下Jaccard index。
Jaccard index是用来计算相似性,也就是距离的一种度量标准。假如有集合A、B,那么, 也就是说,集合A,B的Jaccard系数等于A,B中共同拥有的元素数与A,B总共拥有的元素数的比例。很显然,Jaccard系数值区间为[0,1]。
3.MinHash
先定义几个符号术语:
h(x): 把x映射成一个整数的哈希函数。
hmin(S):集合S中的元素经过h(x)哈希后,具有最小哈希值的元素。
那么对集合A、B,hmin(A) = hmin(B)成立的条件是A ∪ B 中具有最小哈希值的元素也在 ∩ B中。这里
有一个假设,h(x)是一个良好的哈希函数,它具有很好的均匀性,能够把不同元素映射成不同的整数。
所以有,Pr[hmin(A) = hmin(B)] = J(A,B),即集合A和B的相似度为集合A、B经过hash后最小哈希值相
等的概率。
有了上面的结论,我们便可以根据MinHash来计算两个集合的相似度了。一般有两种方法:
第一种:使用多个hash函数
为了计算集合A、B具有最小哈希值的概率,我们可以选择一定数量的hash函数,比如K个。然后用这K个hash函数分别对集合A、B求哈希值,对
每个集合都得到K个最小值。比如Min(A)k={a1,a2,...,ak},Min(B)k={b1,b2,...,bk}。
那么,集合A、B的相似度为|Min(A)k ∩ Min(B)k| / |Min(A)k ∪ Min(B)k|,及Min(A)k和Min(B)k中相同元素个数与总的元素个数的比例。
第二种:使用单个hash函数
第一种方法有一个很明显的缺陷,那就是计算复杂度高。使用单个hash函数是怎么解决这个问题的呢?请看:
前面我们定义过 hmin(S)为集合S中具有最小哈希值的一个元素,那么我们也可以定义hmink(S)为集合S中具有最小哈希值的K个元素。这样一来,
我们就只需要对每个集合求一次哈希,然后取最小的K个元素。计算两个集合A、B的相似度,就是集合A中最小的K个元素与集合B中最小的K个元素
的交集个数与并集个数的比例。
看完上面的,你应该大概清楚MinHash是怎么回事了。但是,MinHash的好处到底在哪里呢?计算两篇文档的相似度,就直接统计相同的词数和总的
次数,然后就Jaccard index不就可以了吗?对,如果仅仅对两篇文档计算相似度而言,MinHash没有什么优势,反而把问题复杂化了。但是如果有海量的文档需要求相似度,比如在推荐系统
中计算物品的相似度,如果两两计算相似度,计算量过于庞大。下面我们看看MinHash是怎么解决问题的。
比如 元素集合{a,b,c,d,e},其中s1={a,d},s2={c},s3={b,d,e},s4={a,c,d} 那么这四个集合的矩阵表示为:
如果要对某一个集合做MinHash,则可以从上面矩阵的任意一个行排列中选取一个,然后MinHash值是排列中第一个1的行号。
例如,对上述矩阵,我们选取排列 beadc,那么对应的矩阵为
那么, h(S1) = a,同样可以得到h(S2) = c, h(S3) = b, h(S4) = a。
如果只对其中一个行排列做MinHash,不用说,计算相似度当然是不可靠的。因此,我们要选择多个行排列来计算MinHash,最后根据Jaccard index公式 来计算相似度。但是求排列本身的复杂度比较高,特别是针对很大的矩阵来说。因此,我们可以设计一个随机哈希函数去模拟排列,能够把行号0~n随机映射到0~n上。比如H(0)=100,H(1)=3...。当然,冲突是不可避免的,冲突后可以二次散列。并且如果选取的随机哈希函数够均匀,并且当n较大时,冲突发生的概率还是比较低的。关于随机排列算法可以参考这篇文章:随机排列生成算法的一些随想
说到这里,只是讨论了用MinHash对海量文档求相似度的具体过程,但是它到底是怎么减少复杂度的呢?
比如有n个文档,每个文档的维度为m,我们可以选取其中k个排列求MinHash,由于每个对每个排列而言,MinHash把一篇文档映射成一个整数,所以对k个排列计算MinHash就得到k个整数。那么所求的MinHash矩阵为n*k维,而原矩阵为n*m维。n>>m时,计算量就降了下来。
简单粗暴的方法
统计分词后的文本中出现的各个词,直接计算两个文本的jaccard距离。(相同的词出现的越多,文本重复的概率越大。)
1 #!/usr/bin/python
2 #-* coding:utf-8 -*-
3
4 4 importsys5 5 importre6 6 importhashlib7 7 importcollections8 8 importdatetime9 9 importcodecs10 10
11 11reload(sys)12 12 sys.setdefaultencoding('utf-8')13 13
14 14 importthreading15 15 from Queue importQueue16 16 queue =Queue()17 17 thread_flag_list =[0,0,0,0,0]18 18
19 19 res_file = sys.argv[1]20 20
21 21 news_list =[]22 22 defload():23 23 globalnews_list24 24 for line insys.stdin:25 25 line =line.strip()26 26 if len(line) ==0:27 27 continue
28 28 Arr = line.split('\t')29 29
30 30 if len(Arr) < 3:31 31 continue
32 32
33 33 url =Arr[0]34 34 title = Arr[1]35 35 content = Arr[2]36 36
37 37 term_list = content.split(' ')38 38 term_set =set(term_list)39 39news_list.append([url,term_set])40 40
41 41
42 42 defcalculate(news_f,news_s):43 43 set1 = news_f[1]44 44 set2 = news_s[1]45 45
46 46 set_join = set1 &set247 47 set_union = set1 |set248 48
49 49 simi_value = float(len(set_join))/float(len(set_union))50 50 returnsimi_value51 51
52 52 defrun_thread(start_id,thread_id):53 53 globalqueue54 54 globalthread_flag_list55 55 news_first =news_list[start_id]56 56 for i in range(start_id+1,len(news_list)):57 57 news_second =news_list[i]58 58 simi_value =calculate(news_first,news_second)59 59 if simi_value > 0.8:60 60 url1 =news_first[0]61 61 url2 =news_second[0]62 62 output_str = url1+'\t'+url2+'\t'+str(simi_value)63 63queue.put(output_str)64 64 thread_flag_list[thread_id] = 0#标记线程结束
65 65
66 66 defprocess():67 67 globalqueue68 68 globalthread_flag_list69 69 fout = codecs.open(res_file,'w','utf-8')70 70 id_max =len(news_list)71 71 id_now =072 72 whileTrue:73 73 run_flag =False74 74 thread_list =[]75 75 for i inrange(0,len(thread_flag_list)):76 76 if thread_flag_list[i] ==0:77 77 if id_now ==id_max:78 continue
79 79 thread_flag_list[i] = 1
80 80 print 'now run is:',id_now81 81
82 82 thread = threading.Thread(target=run_thread,args=(id_now,i))83 83thread_list.append(thread)84 84
85 85 id_now = id_now + 1
86 86 else:87 87 run_flag =True88 88
89 89 for thread inthread_list:90 90thread.setDaemon(True)91 91thread.start()92 92
93 93 while notqueue.empty():94 94 elem =queue.get()95 95 printelem96 96 fout.write(elem+'\n')97 97
98 98 if run_flag != True and id_now ==id_max:99 99 break
100 100
101 101fout.close()102 102
103 103 if __name__ == '__main__':104 104load()105 105 print 'load done'
106 106process()107 107
python文件查重_海量文件查重SimHash和Minhash相关推荐
- python程序文件是什么_.py文件是什么?
展开全部 .py文件是python的脚本文件. Python在执行时,首先会将.py文件中的62616964757a686964616fe59b9ee7ad9431333431363039源代码编译成 ...
- python设置打印机参数_打印文件并配置打印机设置
我试图在Windows上使用Python编写打印机自动化程序,但无法完成. 我不是真的理解这个话题,我有点惊讶-一个"简单"的方法来完成这个似乎不存在..?有这么多的api允许以一 ...
- python的fopen函数_打开文件fopen函数的用法
在C语言中,对文件操作之前,首先需要打开文件,使用的函数是fopen函数,它的作用是打开文件,获取该文件的文件指针,方便后续操作.函数原型为:FILE *fopen(const char *filen ...
- rsync文件实时同步_从文件同步rsync算法谈起
之前在某个产品中使用了Gossip算法进行数据库数据的同步,但是在新的产品中有个需求,就是当文件变化时,(由于文件比较大,比较多)支持增量推送到文件服务器上.于是想到了Unix下的rsync算法,本文 ...
- mysql参数文件选项组_选项文件(Option Files)/配置文件(Configuration Files)的使用
1.选项文件的概念及作用 大多数MySQL程序能从选项文件(有时称为配置文件)中读取启动选项.运行程序时,为了不必在命令行输入常用选项,选项文件为确定这些常用选项提供了一个方便的途径. 为了决定程序是 ...
- java文件file字符集_获取文件字符集(或文件编码) 的工具类
packageorg.mozilla.intl.chardet; importjava.io.BufferedInputStream; importjava.io.File; importjava.i ...
- plsql窗口文件怎么找回_电脑文件丢失怎么找回?知道原因和方法很关键
电脑文件丢失怎么找回?对于使用电脑进行办公的大家伙都知道,很多重要或者临时文件存储在上面.在使用电脑的时候,可能因为自己的"强迫症".想要电脑运行的速度更加快捷或者因为其它的原因, ...
- java文件绝对路径_获取文件夹文件绝对路径
引用 linuxpro https://zhidao.baidu.com/question/59940919.html?fr=iks&word=DOS+%C3%FC%C1%EE&i ...
- java中拷贝文件的代码_拷贝文件夹中的所有文件到另外一个文件夹
[java]代码库/** * * 拷贝文件夹中的所有文件到另外一个文件夹 * * @param srcDirector * 源文件夹 * * @param desDirector * 目标文件夹 * ...
- xftp6设置默认打开文件的程序_修改文件默认打开方式,不改变原图标
由于经常需要看pdf文件,一直以来都是用的Microsoft Edge阅读pdf文件,但是Microsoft Edge打开pdf文件字体的清晰度不高,而且使用ctrl+f进行查找时,高亮部分不够明显, ...
最新文章
- bat遍历当前目录下的文件,批量重命名
- 光端机的技术指标及构成有哪些?
- zClock - 置顶时钟, 倒计时, 网速显示
- Opencv--CvMat声明和使用
- 摄影爱好者的照片,怎样才能变收入?
- 单表查询 多表查询 子查询
- HTML5 Video播放服务端大文件
- 基于 USB 传输的针式打印机驱动程序开发
- java 异或代码编程
- 如何用天气预警API接口进行快速开发
- insert into on duplicate key update
- Rsyslog Properties and the Property Replacer
- 清华一日游-恰逢清华大学99周年校庆
- UVA - 11604 General Sultan
- 推挽变换器漏感电压尖峰
- html5病毒营销,病毒式营销的特点及成功案例
- 国人对国产操作系统的误会
- html按钮激活状态有边框,HTML按钮边框
- 传奇3单机服务器怎么修改器,皓月传奇单机版修改器
- 15-02 身份安全
热门文章
- iphone开发每日一练【2011-10-21】
- java 医疗监护_医疗监护仪解决方案
- 什么是 博弈论?博弈论的研究解决了什么问题?
- 小型微型计算机小错误,如何查看IBM小型机系统的错误记录
- 2017.10.14晚,用迅雷下载大部分BT资源出现失败,tracker服务器被封了?FK
- 利用“微PE”自制PE可引导iso
- 解决adobe reader卸载不干净的问题
- 02-Spring的核心API
- 米思齐(Mixly)图形化系列教程(三)-变量
- 物联网-米思齐-Mixly-RFID智能门禁