1. 程式人生 > >淺談simhash及其python實現

淺談simhash及其python實現

轉自http://blog.csdn.net/madujin/article/details/53152619  1479人閱讀 評論(0) 收藏 舉報

作者原創,轉載請註明出處。

一直想寫個總結來回顧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的權值。

  1. # -*- coding: utf-8 -*-
  2. import jieba  
  3. import jieba.analyse  
  4. import numpy as np  
  5. import json  
  6. class simhash:  
  7.     def __init__(self,content):  
  8.         self.simhash=self.simhash(content)  
  9.     def __str__(self):  
  10.         return str(self.simhash)  
  11.     def simhash(self,content):  
  12.         seg = jieba.cut(content)  
  13.         jieba.analyse.set_stop_words('stopword.txt')  
  14.         keyWord = jieba.analyse.extract_tags(  
  15.             '|'.join(seg), topK=20, withWeight=True, allowPOS=())#在這裡對jieba的tfidf.py進行了修改
  16.         #將tags = sorted(freq.items(), key=itemgetter(1), reverse=True)修改成tags = sorted(freq.items(), key=itemgetter(1,0), reverse=True)
  17.         #即先按照權重排序,再按照詞排序
  18.         keyList = []  
  19.         # print(keyWord)
  20.         for feature, weight in keyWord:  
  21.             weight = int(weight * 20)  
  22.             feature = self.string_hash(feature)  
  23.             temp = []  
  24.             for i in feature:  
  25.                 if(i == '1'):  
  26.                     temp.append(weight)  
  27.                 else:  
  28.                     temp.append(-weight)  
  29.             # print(temp)
  30.             keyList.append(temp)  
  31.         list1 = np.sum(np.array(keyList), axis=0)  
  32.         print(list1)  
  33.         if(keyList==[]): #編碼讀不出來
  34.             return'00'
  35.         simhash = ''
  36.         for i in list1:  
  37.             if(i > 0):  
  38.                 simhash = simhash + '1'
  39.             else:  
  40.                 simhash = simhash + '0'
  41.         return simhash  
  42.     def string_hash(self,source):  
  43.         if source == "":  
  44.             return0
  45.         else:  
  46.             x = ord(source[0]) << 7
  47.             m = 1000003
  48.             mask = 2 ** 128 - 1
  49.             for c in source:  
  50.                 x = ((x * m) ^ ord(c)) & mask  
  51.             x ^= len(source)  
  52.             if x == -1:  
  53.                 x = -2
  54.             x = bin(x).replace('0b''').zfill(64)[-64:]  
  55.             print(source,x)  
  56.             return str(x)  
  57.         ''''' 
  58.         以下是使用系統自帶hash生成,雖然每次相同的會生成的一樣, 
  59.         不過,對於不同的漢子產生的二進位制,在計算海明碼的距離會不一樣, 
  60.         即每次產生的海明距離不一致 
  61.         所以不建議使用。 
  62.         '''
  63.         # x=str(bin(hash(source)).replace('0b','').replace('-','').zfill(64)[-64:])
  64.         # print(source,x,len(x))
  65.         # return x
  66.     def hammingDis(self,com):  
  67.         t1 = '0b' + self.simhash  
  68.         t2 = '0b' + com.simhash  
  69.         n=int(t1, 2) ^ int(t2, 2)  
  70.         i=0
  71.         while n:  
  72.             n &= (n-1)  
  73.             i+=1
  74.         return i  


  1. <p><span style="white-space:pre"></span></p>  
1