本文环境:

  • python3.5

  • ubuntu 16.04

第三方库:

  • jieba

文件寄于github: https://github.com/w392807287/angelo_tools.git


simhash介绍

没多久就要写毕业论文了,据说需要查重,对文档重复判定还挺好奇的所以看了下相关的东西。发现simhash比较好用,实现简单。

顾名思义 simhash是一种hash算法,以前在我印象中hash算法是将一个对象映射成一个hash值,一般只要求当两个对象完全相同时才有相同的hash值,而两个相似的对象的hash值并不需要有任何关系。只相差一个字符hash出来的值也可能相差十万八千里。但是如果hash函数设计的足够巧妙,也可以让相似的对象拥有相同或者相似的hash值,使用hash来进行相似性搜索更方便快捷。
simhash就是这么一个神奇的算法。它满足:

  • 当两个对象的距离不大于d1时,它们的hash值相同的概率不小于p1,即如d(x, y) ≤ d1,则P(hash(x) = hash(y)) ≥ p1.
  • 当两个对象的距离不小于d2时,它们的hash值相同的概率不大于p2,即如d(x, y) ≥ d2,则P(hash(x) = hash(y)) ≥ p2.

simhash可以将文档hash到一个64位二进制数,使得相似的文档具有相似的二进制数。对于一个文档,我们可以把文中的每个词或者词组作为一个特征,统计各个特征出现的频率(当然也可以加入词性的权重,怎么去设置、统计特征可以视情况而定)。下面的例子中我们使用 jieba 做分词。

目标文档 “葫芦娃葫芦娃,一根藤上七朵花”,得到的特征与相应的频率:(葫芦娃,0.33),(一根,0.17,(藤上,0.17),(七朵,0.17),(花,0.17)。然后对特征值进行hash,方便演示这里映射到6位:

  • 葫芦娃:100100
  • 一根:010101
  • 藤上:101010
  • 七朵:111010
  • 花:001010
    然后根据二进制数的各个二进制位,我们队每个特征构造一个向量。如果一个特征映射到的二进制数的某一位是1,则其向量对应位置上的分量为该特征的频率,否则为频率的相反数。如:
    葫芦娃:(0.33,-0.33,-0.33,0.33,-0.33,-0.33)
    ……
    将向量相加,得到(0.33,-0.33,0,0,0,-0.66)
    对于每个分量,如果大于0就取1,否则取0,这样就能得到二进制数的simhash,即100000。

在文本中,出现频率高的特征,其对应的向量分量的绝对值更大,对最终向量相加的结果影响也更大。因此,如果两个文档相似,那么它们出现频率高的特征也应该比较接近,最终得到的hash值也就越接近。在google网页的检索中,64位hash中至多有3个二进制位不同可判定为相似文档。

算法实现

def simhash(cls, s, RE=None, cut_func=None):
if RE:
REX = RE
else:
REX = re.compile(u'[\u4e00-\u9fa5]+')
if not cut_func:
cut_func = cls.cut_func #jieba.cut
cut = [x for x in cut_func(s) if re.match(REX, x)]
ver = [[v * (int(x) if int(x) > 0 else -1) for x in k] for k, v in cls.hist(cut).items()]
ver = np.array(ver)
ver_sum = ver.sum(axis=0)
sim = ''.join(['1' if x > 0 else '0' for x in ver_sum])
return sim

首先我们用正则定义了感兴趣的区域,这里我们只取我们感兴趣的中文。然后我们定义了分词所用的函数,这里使用的是jieba分词。
然后我们得到分词的结果:
cut = [x for x in cut_func(s) if re.match(REX, x)]
得到向量矩阵:
ver = [[v * (int(x) if int(x) > 0 else -1) for x in k] for k, v in cls.hist(cut).items()]
为了方便计算我们引入numpy帮我们做矩阵计算:

ver = np.array(ver)
ver_sum = ver.sum(axis=0)

最后将计算结果转换为二级制hash。因为我们这里使用的32位md5给分词结果做的hash所以最后得到的hash值也是32位的:

11111101011001101110111100101101

其中我们用到了几个工具函数:

@classmethoddef
hist(cls, cut):
_cut = {x: 0 for x in set(cut)}
for i in cut:
_cut[i] += 1
return {cls.hash_bin(k): v/len(cut) for k, v in _cut.items()}

hist函数是将分词列表转换为特征频率向量的。

@classmethoddef
hash2bin(cls, hash):
d = ''
for i in hash:
try:
if int(i) > 7:
d = d + '1'
else:
d = d + '0'
except ValueError:
d = d + '1'
return d
@classmethoddef
hash_bin(cls, s):
h = hashlib.md5(s.encode()).hexdigest()
return cls.hash2bin(h)

其中hash_bin函数用来将字符Hash成二级制hash值,基础hash算法为32位md5。
hash2bin函数是将16进制hash值映射成二进制hash。
为了方便比较我们使用海明距离来判定两个hash值的相似度:

@staticmethoddef
haiming(s1, s2):
x = 0
for i in zip(s1, s2):
if i[0] != i[1]:
x += 1
return x

效果

1993年,南京大学有这样一个男生寝室,四个男生都没有女朋友,于是搞了个组合叫“名草无主四大天王”。这四大天王坚持每晚举行“卧谈会”,从各种学术上讨论如何摆脱光棍状态。这一年的11月,校园的梧桐树落叶凋零,令他们分外伤情。他们在11日这一天晚上卧谈时,符号学的灵感突然登门造访。11月11日,四个1字排开,不正是好像四根光秃秃的棍子吗?这四根光棍不正是在巧妙地诉说着“名草无名四大天王”的凄凉吗?

*

知乎上有个提问,小时候缺爱的女孩子,长大后该怎么办?或许在我这里,只是希望一直有人陪。喜宝说,我想要很多很多的爱,要不就是很多很多的钱,实在不行,有健康也是好的。我有个坏毛病,经常会半夜饿到不行,爬起来找吃的。是真的饿到胃疼,有时候直接饿醒了,每次看到电影里的台词,睡着了就不饿了,我是压根不相信。为什么会半夜饿?究其原因,是大学的时候没人陪我吃饭,每次都是一直等到有人陪我的时候,我才会去吃饭,最后把自己饿到胃疼,久而久之,就渐渐习惯了熬到很晚才吃饭。我不喜欢一个人吃饭,也不喜欢一个人逛街,更不喜欢一个人呆着,可是成长啊,往往是越不喜欢的便越要学会接受它。(二)讲讲上一段恋爱吧。我和他认识的时候,是因为贴吧聚餐,他主动找我要的微信,附带一个如沐春风般的笑容。我一直以为他是被我的美色打动,后来问他原因。他说,他第一次看见那么能吃的女孩子,他惊呆了,可是有觉得看我吃饭很意思,仿佛食物都有了灵魂,让人的心情莫名的好了起来。我们初相识,是因为他看见了我饿死鬼投胎的吃相。我们在一起,是因为他厨艺很好,好到什么程度呢?就是那种你吃过一顿,就能惦记一辈子的感觉。即便是现在回忆起他来,我的味蕾都会有反应。他总是给我做很多很多好吃的,午后阳光从窗子洒进来,窗帘是淡绿色的小碎花,空气里弥漫着饭香味,我们两个人坐在桌前,一边吃饭,一边聊天。我喜欢和他一起手挽着手去菜市场买菜,西红柿土豆黄瓜小白菜,手里拎着的这些果蔬食物,就好像我拥有的全世界。有一次,我们从菜市场回去的路上,明明是艳阳高照的天气,却突然间下起了冰雹,那是他第一次看见冰雹,被砸了一下之后,便立马丢了手里的菜,双手护住我,我傻了吧唧的去捡菜,被砸了一身。他立马臭骂了我一顿,说我是他见过,最好吃的女孩子了。

以上是简书一片文章中的节选。
两个的simhash是
11111101011001101110111100101101
00101101001010110001100000101110
海明距离为16。

知乎上有个提问,小时候缺爱的女孩子,长大后该怎么办?或许在我这里,只是希望一直有人陪。喜宝说,我想要很多很多的爱,要不就是很多很多的钱,实在不行,我有个坏毛病,经常会半夜饿到不行,爬起来找吃的。是真的饿到胃疼,有时候直接饿醒了,每次看到电影里的台词,睡着了就不饿了,我是压根不相信。究其原因,是大学的时候没人陪我吃饭,每次都是一直等到有人陪我的时候,我才会去吃饭,最后把自己饿到胃疼,久而久之我不喜欢一个人吃饭,也不喜欢一个人逛街,更不喜欢一个人呆着,可是成长啊,往往是越不喜欢的便越要学会接受它。我和他认识的时候,是因为贴吧聚餐,他主动找我要的微信,附带一个如沐春风般的笑容。我一直以为他是被我的美色打动,后来问他原因。他说,他第一次看见那么能吃的女孩子,他惊呆了,可是有觉得看我吃饭很意思,仿佛食物都有了灵魂,让人的心情莫名的好了起来。我们初相识,是因为他看见了我饿死鬼投胎的吃相。我们在一起,是因为他厨艺很好,好到什么程度呢?就是那种你吃过一顿,就能惦记一辈子的感觉。即便是现在回忆起他来,我的味蕾都会有反应。他总是给我做很多很多好吃的,午后阳光从窗子洒进来,窗帘是淡绿色的小碎花,空气里弥漫着饭香味,我们两个人坐在桌前,一边吃饭,一边聊天。我喜欢和他一起手挽着手去菜市场买菜,西红柿土豆黄瓜小白菜,手里拎着的这些果蔬食物,有一次,我们从菜市场回去的路上,明明是艳阳高照的天气,却突然间下起了冰雹,那是他第一次看见冰雹,被砸了一下之后,便立马丢了手里的菜,双手护住我,我傻了吧唧的去捡菜,被砸了一身。他立马臭骂了我一顿,说我是他见过,最好吃的女孩子了。

这段是第二段稍加修改,simhash为:
00100101001010110000100000101110

与第二段的海明距离为2
可以看出效果还是很明显的。


能序列化的东西都能hash,也就都能比较相似度。simhash属于局部敏感哈希(Local-Sensitive Hashing, LSH),下次讲讲如何比较图片的相似度,使用感知哈希(Perceptual Hashing)。

 

基于hash的文档判重——simhash的更多相关文章

  1. 基于word2vec的文档向量模型的应用

    基于word2vec的文档向量模型的应用 word2vec的原理以及训练过程具体细节就不介绍了,推荐两篇文档:<word2vec parameter learning explained> ...

  2. 基于slate构建文档编辑器

    基于slate构建文档编辑器 slate.js是一个完全可定制的框架,用于构建富文本编辑器,在这里我们使用slate.js构建专注于文档编辑的富文本编辑器. 描述 Github | Editor DE ...

  3. 基于Zabbix API文档二次开发与java接口封装

    (继续贴一篇之前工作期间写的经验案例) 一.           案例背景 我负责开发过一个平台的监控报警模块,基于zabbix实现,需要对zabbix进行二次开发. Zabbix官方提供了Rest ...

  4. 多线程串口编程工具CserialPort类(附VC基于MFC单文档协议通讯源程序及详细编程步骤)

    老有人觉得MSComm通讯控件很土,更有人大声疾呼:忘了它吧.确实当我们对串口编程有了一定的了解后,应该用API函数写一个属于自己的串口程序,由于编程者对程序了解,对程序修改自如.但我一直没有停止过用 ...

  5. MFC中 创建基于CFormView的文档视图程序

    在MFC中可以创建多种类型的窗口程序,如对话框程序.单文档结构程序(非文档/视图结构).单文档(文档/视图结构)以及多文档视图结构程序等. 在编写一般的小工具时,我们的首选显然是对话框程序,不过基于对 ...

  6. 如何优雅的写UI——(2)MFC下基于CFormView的文档视图程序

    在MFC中可以创建多种类型的窗口程序,如对话框程序.单文档结构程序(非文档/视图结构).单文档(文档/视图结构)以及多文档视图结构程序等. 在编写一般的小工具时,我们的首选显然是对话框程序,不过基于对 ...

  7. 基于FlashPaper的文档播放器

    本文主要讨论.描述了使用Adobe公司的Flex与FlashPaper产品完成对发布到网上的文档资料进行只读控制,也就是说只允许浏览操作.对下载.打印进行控制. FlashPaper FlashPap ...

  8. rabbitmq 3.7.8基于centos7部署文档

    rabbitmq 3.7.8部署文档 安装erlang 安装依赖环境 yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel ope ...

  9. 基于DOMContentLoaded实现文档加载完成后执行的方法

    我们有时可能需要一些在页面加载完成之后执行的方法,其实js原生就提供了onload方法,所以我们最简单的办法就是直接给onload赋值一个函数,在页面加载完成之后就会自动执行 widnow.onloa ...

随机推荐

  1. 四轴飞行器1.6 emwin与ucgui的移植,汉字外挂字库移植和DEMO效果对比

    飞控的遥控器打算自己做,这样全局都能掌握,可以通过遥控器对飞控的参数和飞行模式进行修改,而买遥控器是做不到这样的哈..以后做图传的时候,屏幕还可以实时现实摄像头拍回来的画面,挺好的哈.. 做遥控我们选 ...

  2. opengl 正方体+模拟视角旋转

    // first_3D.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <GL/glut.h> #includ ...

  3. querySelectorAll 和 jQuery选择器

    参考 http://xahlee.info/js/jquery_diff_querySelectorAll.html http://stackoverflow.com/questions/115035 ...

  4. QTableView的表格项中加入图标的方法(重载View::mouseMoveEvent,并使用View::setIconSize函数设置图标的大小)

    当在使用表格视图的时候,需要在表格每一行前面加入图标,应该怎么做呢?Qt中通过使用MVC的处理方式,很容易做到这一点,具体实现如下: 先贴出图,让大家一睹为快 下面我就来介绍一下,上图的灯泡是怎么实现 ...

  5. Jsoup代码解读之一-概述

    Jsoup代码解读之一-概述 今天看到一个用python写的抽取正文的东东,美滋滋的用Java实现了一番,放到了webmagic里,然后发现Jsoup里已经有了…觉得自己各种不靠谱啊!算了,静下心来学 ...

  6. list根据所存对象属性排序

    比如有个list,里面存的是一个个对象,对象有个list属性,其值可以是字符串和数字. private void getSortList(List<AclResource> newList ...

  7. C/C++修改常量的值

    C/C++中常量修饰const可以用来保证一些确定的量不会被一不小心改变,比如PI,一直是3.14159...... 但是不排除有时候也会需要修改常量的值,通过直接修改是不能达到目的. 比如: #in ...

  8. C/C++存储区划分

    一. 在c中分为这几个存储区1.栈 - 由编译器自动分配释放2.堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收3.全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化 ...

  9. Java 网络编程(二) 两类传输协议:TCP UDP

    链接地址:http://www.cnblogs.com/mengdd/archive/2013/03/09/2951841.html 两类传输协议:TCP,UDP TCP TCP是Transfer C ...

  10. UVa10082 WERTYU

    #include <stdio.h>#include <string.h> int main(){ // 用C++提交AC    char s[] = "`12345 ...