淺談simhash及其python實現
作者原創,轉載請註明出處。
一直想寫個總結來回顧simhash,一直沒抽出時間,現在還是好好寫寫總結一下。作者隨筆,廢話有點多,不喜勿噴,歡迎指教。
谷歌每天從網上抓取海量的資訊,怎麼樣區分重複的呢,據說就採用了simhash演算法,當然肯定也不僅僅就只採用它,不過至少可以說明其效能。
預備知識:
我們知道,在文字去重的時候,有很多方式,在文字與文字之間對比,如果是整篇對比,費時費力,有人就想到用什麼東西代表每篇文章,如摘要,當然,對計算機來說,摘要和整篇的區別只是縮小了篇幅,所以又有人想到了採用關鍵字來對比。這樣確實可以大大縮減我們對比的複雜性。那我們怎麼得到一篇文章的關鍵字呢?一般採用詞頻(TF),但是隻用詞頻,如中文出現類似“的”、“我們”之類的詞語很多,應該怎麼去掉這些詞語呢,手動去掉實在是麻煩,於是可以結合逆向詞頻(IDF),這就是著名的TD-IDF,一種提取一個文章的關鍵詞的演算法。詞頻我們很好理解,一個詞語在整篇文章中出現的次數與詞語總個數之比。IDF又怎麼算呢,假如一個詞語,在我們所有文章中出現的頻率都非常高(例如“的”在我們多個文字中出現的次數很多),我們就認為,這個詞語不具有代表性,就可以降低其作用,也就是賦予其較小的權值。
那這個權重,我們怎麼計算呢,(這裡敲公式比較麻煩,直接找來圖片),如下圖,分子代表文章總數,分母表示該詞語在這些文章(|D|)出現的篇數。一般我們還會採取分母加一的方法,防止分母為0的情況出現,在這個比值之後取對數,就是IDF了。
好了,在得到idf之後,最終用tf*idf得到一個詞語的權重。這裡我知道了TD-IDF可以計算一篇文章的關鍵詞。在我們取得一篇的文章的關鍵詞,之後,我們可以採取每篇文章對比其關鍵詞的方法來去重。
這裡又有一個權衡,假如我們取的關鍵詞過少,就不能很好代表一篇文章,假如我們取很多,又會降低效率。有沒有一種方法,既可以很少的對比,又能有好的代表性呢。答案肯定是有的,於是simhash產生了。
(汗,終於講到正題來了)
原理:
simhash是一種區域性敏感hash。我們都知道什麼是hash。那什麼叫區域性敏感呢,假定A、B具有一定的相似性,在hash之後,仍然能保持這種相似性,就稱之為區域性敏感hash。
在上文中,我們得到一個文件的關鍵詞,取得一篇文章關鍵詞集合,又會降低對比效率,我們可以通過hash的方法,把上述得到的關鍵詞集合hash成一串二進位制,這樣我們直接對比二進位制數,看其相似性就可以得到兩篇文件的相似性,在檢視相似性的時候我們採用海明距離,即在對比二進位制的時候,我們看其有多少位不同,就稱海明距離為多少。在這裡,我是將文章simhash得到一串64位的二進位制,一般取海明距離為3作為閾值,即在64位二進位制中,只有三位不同,我們就認為兩個文件是相似的。當然了,這裡可以根據自己的需求來設定閾值。
就這樣,我們把一篇文件用一個二進位制代表了,也就是把一個文件hash之後得到一串二進位制數的演算法,稱這個hash為simhash。
具體simhash步驟如下:
(1)將文件分詞,取一個文章的TF-IDF權重最高的前20個詞(feature)和權重(weight)。即一篇文件得到一個長度為20的(feature:weight)的集合。
(2)對其中的詞(feature),進行普通的雜湊之後得到一個64為的二進位制,得到長度為20的(hash : weight)的集合。
(3)根據(2)中得到一串二進位制數(hash)中相應位置是1是0,對相應位置取正值weight和負值weight。例如一個詞進過(2)得到(010111:5)進過步驟(3)之後可以得到列表[-5,5,-5,5,5,5],即對一個文件,我們可以得到20個長度為64的列表[weight,-weight...weight]。
(4)對(3)中20個列表進行列向累加得到一個列表。如[-5,5,-5,5,5,5]、[-3,-3,-3,3,-3,3]、[1,-1,-1,1,1,1]進行列向累加得到[-7,1,-9,9,3,9],這樣,我們對一個文件得到,一個長度為64的列表。
(5)對(4)中得到的列表中每個值進行判斷,當為負值的時候去0,正值取1。例如,[-7,1,-9,9,3,9]得到010111,這樣,我們就得到一個文件的simhash值了。
(6)計算相似性。連個simhash取異或,看其中1的個數是否超過3。超過3則判定為不相似,小於等於3則判定為相似。
呼呼呼,終於寫完大致的步驟,可參考下圖理解步驟。
在下面python實現中,用的結巴分詞,得到tf-idf的權值。
- # -*- coding: utf-8 -*-
- import jieba
- import jieba.analyse
- import numpy as np
- import json
- class simhash:
- def __init__(self,content):
- self.simhash=self.simhash(content)
- def __str__(self):
- return str(self.simhash)
- def simhash(self,content):
- seg = jieba.cut(content)
- jieba.analyse.set_stop_words('stopword.txt')
- keyWord = jieba.analyse.extract_tags(
- '|'.join(seg), topK=20, withWeight=True, allowPOS=())#在這裡對jieba的tfidf.py進行了修改
- #將tags = sorted(freq.items(), key=itemgetter(1), reverse=True)修改成tags = sorted(freq.items(), key=itemgetter(1,0), reverse=True)
- #即先按照權重排序,再按照詞排序
- keyList = []
- # print(keyWord)
- for feature, weight in keyWord:
- weight = int(weight * 20)
- feature = self.string_hash(feature)
- temp = []
- for i in feature:
- if(i == '1'):
- temp.append(weight)
- else:
- temp.append(-weight)
- # print(temp)
- keyList.append(temp)
- list1 = np.sum(np.array(keyList), axis=0)
- print(list1)
- if(keyList==[]): #編碼讀不出來
- return'00'
- simhash = ''
- for i in list1:
- if(i > 0):
- simhash = simhash + '1'
- else:
- simhash = simhash + '0'
- return simhash
- def string_hash(self,source):
- if source == "":
- return0
- else:
- x = ord(source[0]) << 7
- m = 1000003
- mask = 2 ** 128 - 1
- for c in source:
- x = ((x * m) ^ ord(c)) & mask
- x ^= len(source)
- if x == -1:
- x = -2
- x = bin(x).replace('0b', '').zfill(64)[-64:]
- print(source,x)
- return str(x)
- '''''
- 以下是使用系統自帶hash生成,雖然每次相同的會生成的一樣,
- 不過,對於不同的漢子產生的二進位制,在計算海明碼的距離會不一樣,
- 即每次產生的海明距離不一致
- 所以不建議使用。
- '''
- # x=str(bin(hash(source)).replace('0b','').replace('-','').zfill(64)[-64:])
- # print(source,x,len(x))
- # return x
- def hammingDis(self,com):
- t1 = '0b' + self.simhash
- t2 = '0b' + com.simhash
- n=int(t1, 2) ^ int(t2, 2)
- i=0
- while n:
- n &= (n-1)
- i+=1
- return i
- <p><span style="white-space:pre"></span></p>
- 頂
- 1
- 踩