區域性敏感雜湊演算法的實現
近來由於工作需要,需要將字串的相似度的計算速度進行提升。之前曾採用最長公共子序列、編輯距離等演算法實現過,但總滿足不了實時比較的效能及速度需求。前些天由同事推薦區域性敏感雜湊演算法,便嘗試了一把,結果發現速度還不錯,本著記錄與分享的精神,簡單總結下實現的過程及思路。
【Shingle】
將待查詢的字串集進行對映,對映到一個集合裡,如字串“abcdeeeefg", 對映到集合”(a,b,c,d,e,f,g)", 注意集合中元素是無重複的。這一步驟其實叫Shingling, 意即構建文件中的短字串集合,即shingle集合。
這是最簡單的對映,直接以一個字元進行切分了,也可以對映到更復雜的集合,如(ab, bc, de, ef ,fg),(abc, bcd, def, efg)等
字串集對映的集合,可以進行一步雜湊,如a hash到1, b hash 到2,c hash到1 等等, 對映到桶有個很大的好處,是可以減少資料量, 對映到桶之後,我們便可以將字串用桶編號來進行表示了
【特徵矩陣】
字串1 | 字串2 | 字串3 | 字串4 | |
桶1 | 0 | 1 | 1 | 1 |
桶2 | 1 | 0 | 0 | 1 |
桶3 | 0 | 0 | 0 | 0 |
桶4 | 1 | 0 | 1 | 0 |
桶5 | 0 | 1 | 0 | 1 |
矩陣中為1指對應列字串的shingle集合有元素可以對映到對應桶, 為0代表沒有無素對映到對應桶
由《大資料網際網路大規模資料探勘與分散式處理》一書可以得知字串之間本身的相似度可以用它們對映到的桶相間的相似度來度量,如字串1和字串2的相似度可以用(0 1 0 1 0)及(1 0 0 0 1)的相似度來進行表示
【排列轉換】
但shingle集合一般都非常大,即使將每個shingle都雜湊到4個位元組(即上表中的桶),很可能也不能把字串的shingle集合全部放入記憶體中,那我們有什麼 辦法可以避免大資料量呢?試想我們將該特徵矩陣對映到更小維度、規模更小的簽名,用簽名來代替特徵矩陣,豈不是更好?
為了對特徵矩陣每列所表示的集合進行最小雜湊計算,首先選擇行的一個排列轉換。任意一列的最小雜湊值是在排列轉換後的行排列次序下第一個列值為1的行的行號,仍然舉例來說,我們把以上表中的桶順序打亂
字串1 | 字串2 | 字串3 | 字串4 | |
桶2 | 1 | 0 | 0 | 1 |
桶3 | 0 | 0 | 0 | 0 |
桶1 | 0 | 1 | 1 | 1 |
桶4 | 1 | 0 | 1 | 0 |
桶 5 |
0 | 1 | 0 | 1 |
對於字串1,其在第一行就已經可以遇到到桶2,那就把字串1的雜湊簽名h設為桶2對應的hash值,即h(字串1)=桶2 hash; 同理,h(字串2)=桶1 hash; h(字串3)=桶1 hash; h(字串4)=桶2 hash;
【最小雜湊簽名矩陣的計算】
假如我們選擇n個排列轉換用於以上特徵矩陣的處理,對於特徵矩陣的列,分別呼叫這些排列轉換所決定的最小雜湊函式h1, h2...., hn, 就可以構建特徵矩陣的最小雜湊簽名矩陣
假設SIG為最終得到的最小雜湊簽名矩陣,SIG(i, c)為簽名矩陣中第i個雜湊函式在第c列上的元素,剛開始時,矩陣元素設為無窮大。然後對於每一行r, 進行如下處理
(1). 計算h1(r), h2(r), .... hn(r)
(2). 對每一列c進行如下處理
(a) 如果特徵矩陣中第c列第r行為0, 什麼也不做
(b) 如果特徵矩陣中第c列第r行為1,將SIG(i, c)設為原來的SIG(i, c)和hi(r)的最小值
為了簡便計算,我們可以這樣做,對於特徵矩陣中每一列,我們集中該列中所有為1的行,對這些行應用雜湊函式hi,取最小的雜湊值,即可得到雜湊函式hi對應的該列的簽名,應用所有雜湊函式,即可得到該列的簽名向量。
最後,得到n行的簽名矩陣,這比特徵矩陣,遠小了很多。
到這時,兩個字串的相似度的計算,又可轉換成為對應的簽名向量的相似度計算,如何度量向量的相似度呢,我們採用jaccard相似度
【jaccard相似度】
jaccard相似度用於計算兩個集合之間的相似情況,也就是兩個集合的交集與並集大小之間的比率
利用jaccard相似度,我們可以計算出字串對應的簽名向量的相似度,但如果字串的數量太多,兩兩比較簽名向量,也是很耗時的工作,實際中我們往往只需要得到那些最相似或者相似度大於某一閥值的字串對,如果我們能首先將這些候選的字串對找出來,再運用jaccard相似度,豈不是可以極大地減少計算量?
【行條化策略】
很顯然,如果兩個字串相似,那麼它們對應的簽名向量應該也相似,在區域性某個範圍內極有可能相同, 相同,如果採用雜湊的話,就可以對映到同一個桶。
設想我們對簽名矩陣劃分若干個行條,每一個行條裡有數列,如果兩個字串相似,那猜測肯定在某個行條裡,這兩個字串對應的簽名向量應該相等,它們會對映到同一個桶裡。
對每一個行條,我們設定一個大桶,對行條中的每一列計算其hash值,相同hash值的列會對映到同一個hash桶。在hash桶裡的列,就組成了候選對。
對最後得到的候選對再計算jaccard相似度
【需要注意的問題】
1. 最小雜湊函式族是效能的關鍵,雜湊函式之間一定要獨立,不要出現一個雜湊函式在同樣的引數條件下,都比另一個雜湊函式大或者小的情況,最好是亂七八糟,毫不相干
2. 最小雜湊函式對映到的value範圍不宜過小,太小了容易產生衝突
3.行條化採用的雜湊函式對映到的Value範圍也不宜過小,太小了同樣容易衝突,後期要查詢的候選對太多會影響速度
4. 注意大素數的應用,構造hash函式,大素數相當有用
注:原理及部分表述取自《大資料網際網路大規模資料探勘與分散式處理》第三章