simhash海量文字去重的工程化

2021-09-25 08:27:16 字數 3134 閱讀 3247

simhash演算法是google發明的,專門用於海量文字去重的需求,所以在這裡記錄一下simhash工程化落地問題。

下面我說的都是工程化落地步驟,不僅僅是理論。

被抄襲的文章一般不改,或者少量改動就發表了,所以判重並不是等於的關係,而是相似判斷,這個判別的演算法就是simhash。

給定一篇文章內容,利用simhash演算法可以計算出乙個雜湊值(64位整形)。

判別兩篇文章是相似的方法,就是兩個simhash值的距離<=3,這裡距離計算採用漢明距離,也就是2個simhash做一下異或運算,數一下位元位=1的有n位,那麼距離就是n。

現在問題就是,如何計算文字的simhash?

首先需要將文章作分詞,得到若干個(片語,權重)。

分詞我們知道很多庫都可以實現,最常見的就是結巴分詞。權重是怎麼得來的呢?

idf是片語在所有文章中的出現比例,出現越多說明片語對文章的區分度越低越不重要,但是idf因為需要基於所有文章統計,所以一般是離線去批量計算出乙個idf字典。

結巴分詞支援載入idf詞典並且提供了乙個預設的詞典,它包含了大量的片語以及基於海量文字統計出來的idf詞頻,基本可以拿來即用,除非你想自己去挖掘這樣乙個字典。如果文章產生的分詞在idf字典裡不存在,那麼會採用idf字典的中位數作為預設idf,所謂中庸之道。

所以呢,我建議用結巴分詞做乙個分詞服務,產生文章的(分詞,權重),作為simhash計算的輸入。

對於每個片語,套用乙個雜湊演算法(比如times33,murmurhash…)把片語轉換成乙個64位的整形,對應的二進位制就是01010101000…這樣的。

接下來,遍歷片語雜湊值的64位元,若對應位置是0則記為-權重,是1就是+權重,可以得到乙個寬度64的向量:[-權重,+權重,-權重,+權重…]。

然後將top n片語的向量做加法,得到乙個最終的寬64的向量。

接下來,遍歷這個寬度64的向量,若對應位置<0則記錄為0,>0則記錄為1,從而又變成了乙個64位元的整形,這個整形就是文章的simhash。

我們只能遍歷100億個simhash,分別做異或運算,看看漢明距離是否<=3,這個效能是沒法接受的。

優化的方法就是」抽屜原理」,因為2個simhash相似的標準是<=3位元的差異,所以如果我們把64位元的simhash切成4段,每一段16位元,那麼不同的3位元最多散落在3段中,至少有1段是完全相同的。

同理,如果我們把simhash切成5段,分別長度 13bit、13bit、13bit、13bit、12bit,因為2個simhash最多有3位元的差異,那麼2個simhash至少有2段是完全相同的。

對於乙個simhash,我們暫時決定將其切成4段,稱為a.b.c.d,每一段16位元,分別是:a=0000000000000000,b=0000000011111111,c=1111111100000000,d=111111111111111。

因為抽屜原理的存在,所以我們可以將simhash的每一段作為key,將simhash自身作為value追加索引到key下。

假設用redis做為儲存,那麼上述simhash在redis裡會存成這樣:

key:a=0000000000000000 value(set結構):

key:b=0000000011111111 value(set結構):

key:c=1111111100000000 value(set結構):

key:d=111111111111111 value(set結構):

也就是乙個simhash會按不同的段分別索引4次。

假設有乙個新的simhash希望判重,它的simhash值是:

a=0000000000000000,b=000000001111110,c=1111111100000001,d=111111111111110

它和此前索引的simhash在3段中一共有3位元的差異,符合重複的條件。

那麼在查詢時,我們對上述simhash做4段切割,然後做先後4次查詢:

用a=0000000000000000 找到了set集合,遍歷集合裡的每個simhash做異或運算,發現了漢明距離<=3的重複simhash。

用b=000000001111110 沒找到set集合。

用c=1111111100000001 沒找到set集合。

用d=d=111111111111110 沒找到set集合。

經過上述索引與查詢方式,其實可以估算出優化後的查詢計算量。

假設索引庫中有100億個simhash(也就是2^34個simhash),並且simhash本身是均勻離散的。

一次判重需要遍歷4個redis集合,每個集合大概有 2^32 / 2^16個元素,也就是26萬個simhash,比遍歷100億次要高效多了。

左側表示了乙個simhash索引了4份,右側表示查詢時的分段4次查詢。

假設分成5段索引,分別命名為:a.b.c.d.e。

根據抽屜原理,至多3位元的差異會導致至少有2段是相同的,所以一共有這些組合需要索引:

乙個simhash需要索引10份,乙個集合的大小是2^34 / 2^(26)=256個。

一次查詢需要訪問10次集合,每個集合256個元素,一共只需要異或計算2560次,基本上查詢效能已不再是瓶頸。

但是也可以知道,因為冗餘的索引份數從4份變成了10份,所以其實是在犧牲空間換取時間。

對應的,這麼大量的儲存空間,再繼續使用redis也是不可能的事情,需要換乙個依靠廉價磁碟的分布式儲存。

毫無疑問選擇hbase,特別適合scan遍歷集合。

rowkey設計:4位元組的segment+1位元組的段標識flag+8位元組的simhash。

切4段,索引一段需要16位元;切5段,索引2段需要13+13位元;所以用4位元組的segments來存段落。

1位元組的抽屜標識,比如是切4段則標識是1,2,3,4;切5段則可以是1,2,3,4,5,6,7,8,9,10,分別代表(a,b),(a,c),(a,d),(a,e),(b,c) …

然後最後追加上simhash自身作為區分值,這樣在查詢時只需要指定segment+flag做4/10次scan操作,進行異或運算即可。

使用SimHash進行海量文字去重

閱讀目錄 在之前的兩篇博文分別介紹了常用的hash方法 data structure algorithm hash那點事兒 以及區域性敏感hash演算法 algorithm 區域性敏感雜湊演算法 locality sensitive hashing 本文介紹的simhash是一種區域性敏感hash,...

使用SimHash進行海量文字去重

在之前的兩篇博文分別介紹了常用的hash方法 data structure algorithm hash那點事兒 以及區域性敏感hash演算法 algorithm 區域性敏感雜湊演算法 locality sensitive hashing 本文介紹的simhash是一種區域性敏感hash,它也是go...

使用SimHash進行海量文字去重

閱讀目錄 在之前的兩篇博文分別介紹了常用的hash方法 data structure algorithm hash那點事兒 以及區域性敏感hash演算法 algorithm 區域性敏感雜湊演算法 locality sensitive hashing 本文介紹的simhash是一種區域性敏感hash,...