基於hash的文檔判重——simhash

本文環境:python

  • python3.5git

  • ubuntu 16.04github

第三方庫:算法

  • jieba

文件寄於github: https://github.com/w392807287/angelo_tools.gitsql


simhash介紹

沒多久就要寫畢業論文了,聽說須要查重,對文檔重複斷定還挺好奇的因此看了下相關的東西。發現simhash比較好用,實現簡單。ubuntu

顧名思義 simhash是一種hash算法,之前在我印象中hash算法是將一個對象映射成一個hash值,通常只要求當兩個對象徹底相同時纔有相同的hash值,而兩個類似的對象的hash值並不須要有任何關係。只相差一個字符hash出來的值也可能相差十萬八千里。可是若是hash函數設計的足夠巧妙,也可讓類似的對象擁有相同或者類似的hash值,使用hash來進行類似性搜索更方便快捷。
simhash就是這麼一個神奇的算法。它知足:bash

  • 當兩個對象的距離不大於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)。

相關文章
相關標籤/搜索