在工做學習中,我每每感嘆數學奇蹟般的解決一些貌似不可能完成的任務,而且十分但願將這種喜悅分享給你們,就比如說:「老婆,出來看上帝」…… 隨着信息爆炸時代的來臨,互聯網上充斥着着大量的近重複信息,有效地識別它們是一個頗有意義的課題。例如,對於搜索引擎的爬蟲系統來講,收錄重複的網頁是毫無心義的,只會形成存儲和計算資源的浪費;同時,展現重複的信息對於用戶來講也並非最好的體驗。形成網頁近重複的可能緣由主要包括:
一個簡化的爬蟲系統架構以下圖所示:
事實上,傳統比較兩個文本類似性的方法,大可能是將文本分詞以後,轉化爲特徵向量距離的度量,好比常見的歐氏距離、海明距離或者餘弦角度等等。兩兩比較當然能很好地適應,但這種方法的一個最大的缺點就是,沒法將其擴展到海量數據。例如,試想像Google那種收錄了數以幾十億互聯網信息的大型搜索引擎,天天都會經過爬蟲的方式爲本身的索引庫新增的數百萬網頁,若是待收錄每一條數據都去和網頁庫裏面的每條記錄算一下餘弦角度,其計算量是至關恐怖的。 咱們考慮採用爲每個web文檔經過hash的方式生成一個指紋(fingerprint)。傳統的加密式hash,好比md5,其設計的目的是爲了讓整個分佈儘量地均勻,輸入內容哪怕只有輕微變化,hash就會發生很大地變化。咱們理想當中的哈希函數,須要對幾乎相同的輸入內容,產生相同或者相近的hashcode,換句話說,hashcode的類似程度要能直接反映輸入內容的類似程度。很明顯,前面所說的md5等傳統hash沒法知足咱們的需求。 simhash是locality sensitive hash(局部敏感哈希)的一種,最先由Moses Charikar在《similarity estimation techniques from rounding algorithms》一文中提出。Google就是基於此算法實現網頁文件查重的。咱們假設有如下三段文本:
- the cat sat on the mat
- the cat sat on a mat
- we all scream for ice cream
使用傳統hash可能會產生以下的結果:
引用
irb(main):006:0> p1 = 'the cat sat on the mat' irb(main):005:0> p2 = 'the cat sat on a mat' irb(main):007:0> p3 = 'we all scream for ice cream' irb(main):007:0> p1.hash => 415542861 irb(main):007:0> p2.hash => 668720516 irb(main):007:0> p3.hash => 767429688
使用simhash會應該產生相似以下的結果:
引用
irb(main):003:0> p1.simhash => 851459198 00110010110000000011110001111110 irb(main):004:0> p2.simhash => 847263864 00110010100000000011100001111000 irb(main):002:0> p3.simhash => 984968088 00111010101101010110101110011000
海明距離的定義,爲兩個二進制串中不一樣位的數量。上述三個文本的simhash結果,其兩兩之間的海明距離爲(p1,p2)=4,(p1,p3)=16以及(p2,p3)=12。事實上,這正好符合文本之間的類似度,p1和p2間的類似度要遠大於與p3的。 如何實現這種hash算法呢?以上述三個文本爲例,整個過程能夠分爲如下六步: 一、選擇simhash的位數,請綜合考慮存儲成本以及數據集的大小,好比說32位 二、將simhash的各位初始化爲0 三、提取原始文本中的特徵,通常採用各類分詞的方式。好比對於"the cat sat on the mat",採用兩兩分詞的方式獲得以下結果:{"th", "he", "e ", " c", "ca", "at", "t ", " s", "sa", " o", "on", "n ", " t", " m", "ma"} 四、使用傳統的32位hash函數計算各個word的hashcode,好比:"th".hash = -502157718 ,"he".hash = -369049682,…… 五、對各word的hashcode的每一位,若是該位爲1,則simhash相應位的值加1;不然減1 六、對最後獲得的32位的simhash,若是該位大於1,則設爲1;不然設爲0 整個過程能夠參考下圖:

按照Charikar在論文中闡述的,64位simhash,海明距離在3之內的文本均可以認爲是近重複文本。固然,具體數值須要結合具體業務以及經驗值來肯定。 使用上述方法產生的simhash能夠用來比較兩個文本之間的類似度。問題是,如何將其擴展到海量數據的近重複檢測中去呢?譬如說對於64位的待查詢文本的simhash code來講,如何在海量的樣本庫(>1M)中查詢與其海明距離在3之內的記錄呢?下面在引入simhash的索引結構以前,先提供兩種常規的思路。第一種是方案是查找待查詢文本的64位simhash code的全部3位之內變化的組合,大約須要四萬屢次的查詢,參考下圖:

另外一種方案是預生成庫中全部樣本simhash code的3位變化之內的組合,大約須要佔據4萬多倍的原始空間,參考下圖:

顯然,上述兩種方法,或者時間複雜度,或者空間複雜度,其一沒法知足實際的需求。咱們須要一種方法,其時間複雜度優於前者,空間複雜度優於後者。 假設咱們要尋找海明距離3之內的數值,根據抽屜原理,只要咱們將整個64位的二進制串劃分爲4塊,不管如何,匹配的兩個simhash code之間至少有一塊區域是徹底相同的,以下圖所示:

因爲咱們沒法事先得知徹底相同的是哪一塊區域,所以咱們必須採用存儲多份table的方式。在本例的狀況下,咱們須要存儲4份table,並將64位的simhash code等分紅4份;對於每個輸入的code,咱們經過精確匹配的方式,查找前16位相同的記錄做爲候選記錄,以下圖所示:

讓咱們來總結一下上述算法的實質: 一、將64位的二進制串等分紅四塊 二、調整上述64位二進制,將任意一塊做爲前16位,總共有四種組合,生成四份table 三、採用精確匹配的方式查找前16位 四、若是樣本庫中存有2^34(差很少10億)的哈希指紋,則每一個table返回2^(34-16)=262144個候選結果,大大減小了海明距離的計算成本 咱們能夠將這種方法拓展成多種配置,不過,請記住,table的數量與每一個table返回的結果呈此消彼長的關係,也就是說,時間效率與空間效率不可兼得,參看下圖:

事實上,這就是Google天天所作的,用來識別獲取的網頁是否與它龐大的、數以十億計的網頁庫是否重複。另外,simhash還能夠用於信息聚類、文件壓縮等。 也許,讀到這裏,你已經感覺到數學的魅力了。