simhash與Google的網頁去重
前幾天去吃葫蘆頭的路上,大飛哥給詳細的講解了他在比較文字相似度實驗時對Google的simhash方法高效的驚歎,回來特意去找了原文去拜讀。
Simhash
傳統IR領域內文字相似度比較所採用的經典方法是文字相似度的向量夾角餘弦,其主要思想是根據一個文章中出現詞的詞頻構成一個向量,然後計算兩篇文章對應向量的向量夾角。但由於有可能一個文章的特徵向量詞特別多導致整個向量維度很高,使得計算的代價太大,對於Google這種處理萬億級別的網頁的搜尋引擎而言是不可接受的,simhash演算法的主要思想是降維,將高維的特徵向量對映成一個f-bit的指紋(fingerprint),通過比較兩篇文章的f-bit指紋的Hamming Distance來確定文章是否重複或者高度近似。
simhash演算法很精巧,但卻十分容易理解和實現,具體的simhash過程如下:
1. 首先基於傳統的IR方法,將文章轉換為一組加權的特徵值構成的向量。
2.初始化一個f維的向量V,其中每一個元素初始值為0。
3.對於文章的特徵向量集中的每一個特徵,做如下計算:
利用傳統的hash演算法對映到一個f-bit的簽名。對於這個f- bit的簽名,如果簽名的第i位上為1,則對向量V中第i維加上這個特徵的權值,否則對向量的第i維減去該特徵的權值。
4.對整個特徵向量集合迭代上述運算後,根據V中每一維向量的符號來確定生成的f-bit指紋的值,如果V的第i維為正數,則生成f-bit指紋的第i維為1,否則為0。
simhash和普通hash最大的不同在於傳統的hash函式雖然也可以用於對映來比較文字的重複,但是對於可能差距只有一個位元組的文件也會對映成兩個完全不同的雜湊結果,而simhash對相似的文字的雜湊對映結果也相似。Google的論文中取了f=64,即將整個網頁的加權特徵集合對映到一個64-bit的fingerprint上。
比起simhash,整片文章中Google所採用的查詢與給定f-bit的fingerprint的海明距離(Hamming Distance)小於k的演算法相對還稍微難理解點。
fingerprint的Hamming Distance
問題:一個80億的64-bit指紋組成的集合Q,對於一個給定64-bit的指紋F,如何在a few millionseconds中找到Q中和f至多隻有k(k=3)位差別的指紋。
思想:1. 對於一個具有2^d個記錄的集合,只需要考慮d-bit hash。2. 選取一個d’使得|d’-d|十分小,因此如果兩fingerprint在d’-bits上都相同,那麼在d-bits也很可能相同。然後在這些d-bit match的結果中尋找整個f-bit的Hamming Distance小於k的fingerprint。 簡單的說,就是利用fingerprint少量特徵位數比較從而首先縮小範圍,然後再去確定是否差異小於k個bit。
演算法:
1. 首先對於集合Q構建多個表T1,T2…Tt,每一個表都是採用對應的置換函式π(i)將64-bit的fingerprint中的某p(i)位序列置換換到整個序列的最前面。即每個表儲存都是整個Q的fingerprint的複製置換。
2.對於給定的F,在每個Ti中進行匹配,尋找所有前pi位與F經過π(i)置換後的前pi位相同的fingerprint。
3.對於所有在上一步中匹配到的置換後的fingerprint,計算其是否與π(i)(F)至多有k-bit不同。
演算法的重點在於對於集合Q的分表以及每個表所對應的置換函式,假設對於64-bit的fingerprint,k=3,儲存16個table,劃分參考下圖:
將64-bit按照16位劃分為4個區間,每個區間剩餘的48-bit再按照每個12-bit劃分為4個區間,因此總共16個table並行查詢,即使三個不同的k-bit落在A、B、C、D中三個不同的區塊,此劃分方法也不會導致遺漏。
以上方法是對於online的query,即一個給定的F在集合中查詢相似的fingerprint。如果爬蟲每天爬取了100w個網頁,快速的查詢這些新抓取的網頁是否在原集合中有Near-duplication,對於這種batch-query的情況,Map-Reduce就發揮它的威力了。
不同的是,在batch-query的處理中,是對待查集合B(1M個fingerprint)進行復制置換構建Table而非8B的目標集合,而在每一個chunkserver上對Fi(F為整個8B的fingerprint)在整個Table(B)中進行探測,每一個chunkserver上的的該Map過程輸出該Fi中與整個B的near-duplicates,Reduces過程則將所有的結果收集、去重、然後輸出為一個sorted file。
Haffman編碼壓縮
上述的查詢過程,特別是針對online-version的演算法,可以看出需要對8B的fingerprint進行多表複製和構建,其佔據的容量是非常大的,不過由於構建的每一個置換Table都是sorted的,因此可以利用每一個fingerprint與其前一個的開始不同的bit-position h(h∈[0,f-1]) 來進行資料壓縮,即如果前一個編碼是11011011,而自身是11011001,則後一個可以編碼為(6)1,即h=6,其中6表示從第6位(從0開始編號)開始和上一個fingerprint不相同(上一個為1,這個必然為0),然後再儲存不相同位置右側的編碼,依次生成整個table。
Google首先計算整個排序的fingerprint表中h的分佈情況,即不同的h出現次數,依據此對[0,f-1]上出現的h建立Haffman code,再根據上述規則生成table(例如上面的6就表示成對應的Haffman code)。其中table分為多個block,每一個block中的第一個fingerprint儲存原資料,後面的依次按照編碼生成。
將每一個block中所對應的最後一個fingerprint儲存在記憶體中,因此在比對的時候就可以直接根據記憶體中的fingerprint來確定是哪一個block需要被decompress進行比較。
8B個64-bit的fingerprint原佔據空間大約為64GB,利用上述Haffman code壓縮後幾乎會減少一般,而記憶體中又只對每一個block儲存了一個fingerprint。
每次看Google的論文都會讓人眼前一亮,而且與很多(特別是國內)的論文是對未來進行設想不同,Google的東西都是已經運行了2,3年了再到WWW,OSDI這種頂級會議上灌個水。再次各種羨慕能去這個Dream Company工作的人,你們懂得。
參考: