在工作學習中,我往往感嘆數學奇蹟般的解決一些貌似不可能完成的任務,並且十分希望將這種喜悅分享給大家,就好比說:「老婆,出來看上帝」……
隨著資訊**時代的來臨,網際網路上充斥著著大量的近重複資訊,有效地識別它們是乙個很有意義的課題。例如,對於搜尋引擎的爬蟲系統來說,收錄重複的網頁是毫無意義的,只會造成儲存和計算資源的浪費;同時,展示重複的資訊對於使用者來說也並不是最好的體驗。造成網頁近重複的可能原因主要包括:
乙個簡化的爬蟲系統架構如下圖所示:
事實上,傳統比較兩個文字相似性的方法,大多是將文字分詞之後,轉化為特徵向量距離的度量,比如常見的歐氏距離、海明距離或者余弦角度等等。兩兩比較固然能很好地適應,但這種方法的乙個最大的缺點就是,無法將其擴充套件到海量資料。例如,試想像google那種收錄了數以幾十億網際網路資訊的大型搜尋引擎,每天都會通過爬蟲的方式為自己的索引庫新增的數百萬網頁,如果待收錄每一條資料都去和網頁庫裡面的每條記錄算一下余弦角度,其計算量是相當恐怖的。
我們考慮採用為每乙個web文件通過hash的方式生成乙個指紋(fingerprint)。傳統的加密式hash,比如md5,其設計的目的是為了讓整個分布盡可能地均勻,輸入內容哪怕只有輕微變化,hash就會發生很大地變化。我們理想當中的雜湊函式,需要對幾乎相同的輸入內容,產生相同或者相近的hashcode,換句話說,hashcode的相似程度要能直接反映輸入內容的相似程度。很明顯,前面所說的md5等傳統hash無法滿足我們的需求。
simhash是locality sensitive hash(區域性敏感雜湊)的一種,最早由moses charikar在《similarity estimation techniques from rounding algorithms》一文中提出。google就是基於此演算法實現網頁檔案查重的。我們假設有以下三段文字:
使用傳統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演算法呢?以上述三個文字為例,整個過程可以分為以下六步:
1、選擇simhash的位數,請綜合考慮儲存成本以及資料集的大小,比如說32位
2、將simhash的各位初始化為0
3、提取原始文字中的特徵,一般採用各種分詞的方式。比如對於"the cat sat on the mat",採用兩兩分詞的方式得到如下結果:
4、使用傳統的32位hash函式計算各個word的hashcode,比如:"th".hash = -502157718
,"he".hash = -369049682,……
5、對各word的hashcode的每一位,如果該位為1,則simhash相應位的值加1;否則減1
6、對最後得到的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位相同的記錄作為候選記錄,如下圖所示:
讓我們來總結一下上述演算法的實質:
1、將64位的二進位制串等分成四塊
2、調整上述64位二進位制,將任意一塊作為前16位,總共有四種組合,生成四份table
3、採用精確匹配的方式查詢前16位
4、如果樣本庫中存有2^34(差不多10億)的雜湊指紋,則每個table返回2^(34-16)=262144個候選結果,大大減少了海明距離的計算成本
我們可以將這種方法拓展成多種配置,不過,請記住,table的數量與每個table返回的結果呈此消彼長的關係,也就是說,時間效率與空間效率不可兼得,參看下圖:
注:最後**右下方為2^9和4....
事實上,這就是google每天所做的,用來識別獲取的網頁是否與它龐大的、數以十億計的網頁庫是否重複。另外,simhash還可以用於資訊聚類、檔案壓縮等。
也許,讀到這裡,你已經感受到數學的魅力了。
附上算1個數的**:
/* src/simhasher.hpp */
bool
isequal
(uint64_t
lhs,
uint64_t
rhs,
unsigned
shortn=
3)if(
cnt<=n)
return
false
;}
由上式這個函式來計算的話,時間複雜度是 o(n); 這裡的n預設取值為3。由此可見還是蠻高效的。
List列表拒絕新增重複資訊
例項說明 利用程式輸入資訊時,經常由於馬虎輸入了重複的資訊。為避免輸入重複資訊,可以在新增重複資訊時進行警告。程式設計思路 additem方法,將專案新增到listbox列表裡。語法 object.additem item,index 引數 item必需的。字串表示式,用來指定新增到列表中的專案。i...
設定年級課程資訊 臨時表刪除重複資訊
雖然我後來才反應過來乙個編號對應著乙個課程看,但是當我們查詢的資訊內容重複的時候,怎麼辦?首先,我先對 進行修改 紅線這行 的意思便是查詢的臨時表資訊是不重複的。distinct關鍵字可從 select 語句的結果中除去重複的行。如果沒有指定 distinct,那麼將返回所有行,包括重複的行。但是當...
將資料整理成不含重複資訊的資料
set 函式建立乙個無序不重複元素集,可進行關係測試,刪除重複資料,還可以計算交集 差集 並集等。x set runoob y set google x,y set b r u o n set e o g l 重複的被刪除 x y 交集 set o x y 並集 set b e g l o n r ...