日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[转]文本相似性算法:simhash/minhash/余弦算法

發布時間:2023/12/20 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [转]文本相似性算法:simhash/minhash/余弦算法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.




數據挖掘之lsh(局部敏感hash) minhash、simhash



在項目中碰到這樣的問題:

互聯網用戶每天會訪問很多的網頁,假設兩個用戶訪問過相同的網頁,說明兩個用戶相似,相同的網頁越多,用戶相似度越高,這就是典型的CF中的user-based推薦算法。

算法的原理很簡單,只要兩兩計算用戶的相似性,針對每個用戶,獲取最相似的K個用戶即可。

但是在實際的工程上,假定用戶規模在億的規模N,計算復雜度為N*N,即使是分布式,也是非常可怕的復雜度。

考慮一下,我們是不是真的需要計算所有用戶之間的相似性?其實我們只需要計算和用戶A最相似的K個用戶即可,如果已知B和A一定不相似,那么就沒有必要計算,這就是LSH的思想。

?

LSH:local sensitive hash,局部敏感哈希,關注可能相似的pair,而非所有的pair,這是lsh的基本思想。

?

舉個例子:

用戶user1 訪問過 url1,url2

用戶user2訪問過 url2,url3

用戶user3訪問過url3

很明顯,user1和user2相似,而 user1和user3是不相似的,換句話,user1和user3是不需要比較的。

如何做到呢?最簡單的思路,把url作為hash的key,user作為value,計算同一個key下面user的相似度。

url1:user1

url2:user1 user2

url3:user2 user3

這樣分別計算user1 user2 以及user2和user3的相似性即可,不用計算user1和user3,也就是不相似的user不需要計算其相似性,基本上就是LSH的思想

?

但是,很明顯,上面的作法過于簡單和粗暴:

1.如果每個user有上w維的特征,針對每個特征做一個hash,會導致計算復雜度大大增加,兩個特征相同的用戶,需要計算w次相似性

2.無法刻畫lsh中,只關注相似的paire 中的"相似”程度,比如如果相似性<0.5,則認為不相似,盡量不出現在一個桶等等

?

第一個問題談到是降維,第二個是如何進行刻畫相似性以及進行hash。

?

?

minhash以及simhash就是來解決上面的兩個問題的,這兩個都是來刻畫jaccard距離的。

回到剛開始的例子,及時就是計算user1與user2的jaccard距離,假設url進行了編號,有唯一的id,最大編號為N,每個用戶訪問過的url數目為N(u)。

這樣我們可以理解每個用戶有N個特征,其中訪問過的對應位置為1,沒有訪問的為0,維數很高,幾十B的規模。

?

minhash就是來解決降維的問題,具體的minhash原理網上有很多介紹,就不在詳細說了。

minhash最后的產出是每個用戶有K維的特征{id1,id2....idk},不同用戶第k特特征相同的概率和直接利用用戶原始的N維特征計算jaccard距離的相似性相同, K<<N,達到降維的目的。

如果利用K維特征,計算2-2相似性,復雜度還是很高。

?

利用LSH思想,我們只需要計算可能形似用戶的相似度,保證相似的用戶對應的hash值一樣,而不相似的對應的hash值不同。

兩個用戶度為p,則用戶對應相同位置特征值相同的概率為p,有證明。

將K個特征劃分為band,b1,b2...bm,每個band里面的元素個數為r個,r*m=K

用戶每個band里面r個特征全部相同的概率為p^r,也就是基于這個band作為hash值,兩個用戶hash值相同的概率為p^r,那么hash值不同的概率為1-s^r,m個band hash值都不一樣的概率為(1-p^r)^m,也就是兩個用戶不在任何一個桶里面的概率。

而1-(1-p^r)^m 則為兩個用戶落在至少一個桶里面的概率,很容易理解,如果r越小,最后值越大,很不相似(p很小)的元素落在一個桶里面的概率很大,計算的復雜度高。如果r很大,則最后的值很小,也就是很相似(p比較到)落在一個桶里面的概率很小.

比如 r=1, m=16,p=0.2,計算后為99.8%,也就是相似性為0.2的兩個元素,99.2%的概率會落在一個桶里面,進行計算,事實上是沒有必要的。

r=20,m=1,p=0.8,計算后0.02%,也就是說相似性為0.8的兩個元素,0.02%的概率是落著一個桶里面,概率很低,影響召回率。

這時候根據實際需要來確定r的大小,比如 r=2,m=8,

p=0.3時為53%概率落在一個桶

p=0.5時為90%概率落在一個桶

p=0.7時為99.6%概率裸著一個桶。

通過這個方法平衡計算復雜度和項目需求

?

整體來看,minhash主要是用來降維,且為LSH提供的條件。

?

simhash和minhash有很大的相似性,都是lsh的一個方法,但是其牛逼的地方在于,simhash值之間的海明距離可以刻畫其相似程度。

simhash本身也是用來降維以及很方便的利用LSH思想。

?

具體的simhash的介紹很多,不做介紹。

假設simhash的結果是16bit的0-1串作為特征 ,假設有最多k個bit不同,我們認為其相似,那么需要將其劃分成k+1個band

比如 k=3,我們需要劃分成4個band,這個比較容易理解,也有很完整的證明。

?

整體來看,lsh是來解相似性計算的規模問題,避免計算所有pair,只計算可能相似的pair。

基本的思路就是劃分band,band的大小和計算復雜度以及召回率有很大的關系。

針對基于jaccard距離的問題,直接基于在原始特征上,無法劃分band,因為維度過高以及數據很稀疏,效果不好。

這時候,就需要將數據降維,便于高效處理。

minhash、simhash從不同的角度解決這個問題。

來源:http://blog.csdn.net/hxxiaopei/article/details/7977248?




minHash(最小哈希)和LSH(局部敏感哈希)


? ? ? ? ?在數據挖掘中,有一個比較基本的問題,就是比較兩個集合的相似度。關于這個問題,最笨的方法就是用一個兩重循環來遍歷這兩個集合中的所有元素,進而統計這兩個集合中相同元素的個數。但是,當這兩個集合里的元素數量非常龐大時,同時又有很多個集合需要判斷兩兩之間的相似度時,這種方法就呵呵了,對內存和時間的消耗都非常大。因此,為了解決這個問題,數據挖掘中有另一個方法。

Jaccard相似度

?????????在介紹具體算法之前,我們首先來了解一個概念:Jaccard相似度。

???????? Jaccard相似度是用來描述兩個集合間的相似度的,其計算方法如下(假設有兩個集合A,B):,也就是A與B交集的元素個數除以A與B并集的元素個數;為了書寫方便,下面的討論中我們將集合A和B的Jaccard相似度記為SIM(A,B);


例如:上圖中有兩個集合A,B;A中有4個元素,B中有5個元素;A,B的交集元素個數為2,并集元素個數為7,所以SIM(A,B) = 2 / 7;


k-Shingle

? ? ???假如我們把一整篇文章看成一個長的字符串,那么k-shingle就是這篇文檔中長度為k的任意字符子串。所以,一篇文章就是很多個不同的k-shingle的集合。

? ? ? ? 例如:現在我們有一篇很短的文章,文章內容為abcdabd,令k=2,那么這篇文章中所有的2-shingle組成的集合為{ab,bc,cd,da,bd},需要注意的是,ab在文章中出現了兩次,但是在集合中只出現一次,這是因為集合中不能有相同的元素。

???????? 盡管用k-shingle的方式來表示每篇文章,然后再通過判斷每篇文章中shingle集合的相同元素的數量,就可以得出文章的相似度;但是,一篇文章得到的shingle集合的元素個數是很多的。假定k=4,那么每個shingle中就會有4個字符,存在內存中就至少需要4個字節;那么要以這種方式存下一篇文章的所有shingle,需要的內存空間大概是原文檔大小的4倍(假設原文檔大小為100K,那么存下這篇文檔的所有shingle則需要400K),這是因為原文檔中的每個字符串都會出現在4個shingle中(除了開頭和結尾那幾個字符)。因此,以shingle的方式來存文章會消耗大量的內存。

???????? 接下來,我們需要把上面的shingle集合替換成規模小很多的“簽名”集合。這樣,就可以通過比較兩篇文章的簽名集合的相似度,就能夠估計shingle的相似度了。這樣得到的相似度只是原來相似度的近似值。

???????? 在介紹簽名集合之前,我們先來看下如何將集合表示成其特征矩陣。


特征矩陣

特征矩陣的一列就對應一個集合,所有的行加起來就是所有集合元素的全集,如果集合中有那個元素,則矩陣中的對應位置為1,否則為0(好吧,講的不是很清楚,還是直接上例子吧):

假設現在有4個集合,分別為S1,S2,S3,S4;其中,S1={a,d}, S2={c}, S3={b,d,e}, S4={a,c,d},所以全集U={a,b,c,d,e}

那么這4個集合的特征矩陣為:


其中第一行和第一列不是特征矩陣的一部分,只是為了便于理解而寫上的。


最小哈希

構建集合的特征矩陣是為了計算集合的最小哈希。

???????? 為了計算最小哈希,首先對特征矩陣的行進行打亂(也即隨機調換行與行之間的位置),這個打亂是隨機的。然后某一列的最小哈希值就等于打亂后的這一列第一個值為1的行所在的行號(不明白的直接看例子),行號從0開始。

例如,定義一個最小哈希函數h,然后對上面的特征矩陣進行行打亂,原來第一列的順序為abcde,打亂后為beadc,則新的特征矩陣為:


對于列S1,從這一列的第一行往下走,直到遇到第一個1,所在的行號則為這一列的最小哈希值。所以這4列的最小哈希值依次為h(S1) = 2, h(S2) = 4, h(S3) = 0, h(S4) = 2.


最小哈希與Jaccard相似度

???????? 在經過行打亂后的兩個集合計算得到的最小哈希值相等的概率等于這兩個集合的Jaccard相似度。簡單推導如下:

???????? 假設只考慮集合S1和S2,那么這兩列所在的行有下面三種類型:

1.??????這一行的S1和S2的值都為1(即兩列值都為1),記為X類;

2.??????這一行只有一個值為1,另一個值為0,記為Y類;

3.??????這一行兩列的值都為0,記為Z類。

假設屬于X類的行有x個,屬于Y類的行有y個,所以S1和S2交集的元素個數為x,并集的元素個數為x+y,所以SIM(S1,S2) = x / (x+y)。注:SIM(S1,S2)就是集合S1和S2的Jaccard相似度。

接下來計算最小哈希h(S1) = h(S2)的概率。經過行打亂之后,對特征矩陣從上往下掃描,在碰到Y類行之前碰到X類行的概率是x / (x+y);又因為X類行中h(S1)=h(S2),所以h(S1)=h(S2)的概率為x/(x+y),也就是這兩個集合Jaccard相似度。


最小哈希簽名

???????? 上面是用一個行打亂來處理特征矩陣,然后就可以得到每個集合最小哈希值,這樣多個集合就會有多個最小哈希值,這些值就可以組成一列。當我們用多個隨機行打亂(假設為n個,分別為h1,h2…hn)來處理特征矩陣時,然后分別計算打亂后的這n個矩陣的最小哈希值;這樣,對于集合S,就會有n個最小哈希值,這n個哈希值就可以組成一個列向量,為[h1(S), h2(S)…hn(S)];因此對于一個集合,經過上面的處理后,就能得到一個列向量;如果有m個集合,就會有m個列向量,每個列向量中有n個元素。把這m個列向量組成一個矩陣,這個矩陣就是特征矩陣的簽名矩陣;這個簽名矩陣的列數與特征矩陣相同,但行數為n,也即哈希函數的個數。通常來說,n都會比特征矩陣的行數要小很多,所以簽名矩陣就會比特征矩陣小很多。


最小簽名的計算

?????????? 其實得到上面的簽名矩陣之后,我們就可以用簽名矩陣中列與列之間的相似度來計算集合間的Jaccard相似度了;但是這樣會帶來一個問題,就是當一個特征矩陣很大時(假設有上億行),那么對其進行行打亂是非常耗時,更要命的是還要進行多次行打亂。?????????????? 為了解決這個問題,可以通過一些隨機哈希函數來模擬行打亂的效果。具體做法如下:

?????????? 假設我們要進行n次行打亂,則為了模擬這個效果,我們選用n個隨機哈希函數h1,h2,h3…hn(注意,這里的h跟上面的h不是同一個哈希函數,只是為了方便,就不用其他字母了)。處理過程如下:

令SIG(i,c)表示簽名矩陣中第i個哈希函數在第c列上的元素。開始時,將所有的SIG(i,c)初始化為Inf(無窮大),然后對第r行進行如下處理:

1.??????計算h1(r), h2(r)…hn(r);

2.??????對于每一列c:

a)????????如果c所在的第r行為0,則什么都不做;

b)????????如果c所在的第r行為1,則對于每個i=1,2…n,將SIG(i,c)置為原來的SIG(i,c)和hi(r)之間的最小值。

(看不懂的直接看例子吧,這里講的比較晦澀)

例如,考慮上面的特征矩陣,將abcde換成對應的行號,在后面加上兩個哈希函數,其中h1(x)=(x+1) mod 5,h2(x) = (3*x+1) mod 5,注意這里x指的是行號:


接下來計算簽名矩陣。一開始時,全部初始化為Inf:


接著看特征矩陣中的第0行;這時S2和S3的值為0,所以無需改動;S1和S4的值為1,需改動。h1= 1,h2= 1。1比Inf小,所以需把S1和S4這兩個位置對應的值替換掉,替換后效果如下:


接著看第1行;只有S3的值為1;此時h1= 2,h2= 4;對S3那一列進行替換,得到:


接著看第2行;S2和S4的值為1;h1=3,h2=2;因為簽名矩陣S4那一列的兩個值都為1,比3和2小,所以只需替換S2那一列:


接著看第3行;S1,S3和S4的值都為1,h1=4, h2= 0;替換后效果如下:


接著看第4行;S3值為1,h1=0, h2= 3,最終效果如下:


這樣,所有的行都被遍歷一次了,最終得到的簽名矩陣如下:


基于這個簽名矩陣,我們就可以估計原始集合之間的Jaccard相似度了。由于S2和S4對應的列向量完全一樣,所以可以估計SIM(S1,S4)=1;同理可得SIM(S1,S3) = 0.5;


局部敏感哈希算法(LSH)

???????? 通過上面的方法處理過后,一篇文檔可以用一個很小的簽名矩陣來表示,節省下很多內存空間;但是,還有一個問題沒有解決,那就是如果有很多篇文檔,那么如果要找出相似度很高的文檔,其中一種辦法就是先計算出所有文檔的簽名矩陣,然后依次兩兩比較簽名矩陣的相似度;這樣做的缺點是當文檔數量很多時,要比較的次數會非常大。那么我們可不可以只比較那些相似度可能會很高的文檔,而直接忽略過那些相似度很低的文檔。接下來我們就討論這個問題的解決方法。

???????? 首先,我們可以通過上面的方法得到一個簽名矩陣,然后把這個矩陣劃分成b個行條(band),每個行條由r行組成。對于每個行條,存在一個哈希函數能夠將行條中的每r個整數組成的列向量(行條中的每一列)映射到某個桶中。可以對所有行條使用相同的哈希函數,但是對于每個行條我們都使用一個獨立的桶數組,因此即便是不同行條中的相同列向量,也不會被哈希到同一個桶中。這樣,只要兩個集合在某個行條中有落在相同桶的兩列,這兩個集合就被認為可能相似度比較高,作為后續計算的候選對;而那些在所有行條中都不落在同一個桶中的兩列,就會被認為相似度不會很高,而被直接忽略。下面直接看一個例子:

例如,現在有一個12行簽名矩陣,把這個矩陣分為4個行條,每個行條有3行;為了方便,這里只寫出行條1的內容。


可以看出,行條1中第2列和第4列的內容都為[0,2,1],所以這兩列會落在行條1下的相同桶中,因此無論在剩下的3個行條中這兩列是否有落在相同桶中,這兩個集合都會成為候選對。在行條1中不相等的兩列還有另外的3次機會成為候選對,因為他們只需在剩下的3個行條中有一次相等即可。

???????? 經過上面的處理后,我們就找出了相似度可能會很高的一些候選對,接下來我們只需對這些候選隊進行比較就可以了,而直接忽略那些不是候選對的集合。這個方法適合用來計算相似度超過某個值的文檔的相似度,而不適用于計算所有文檔的相似度,因為那些相似度可能很低的文檔已經被直接忽略了。



文章來源:http://blog.csdn.net/liujan511536/article/details/47729721




聚類之minhash


最小哈希法

最小哈希原理介紹

  • MinHash是基于Jaccard Index相似度(海量數據不可行)的算法,一種降維的方法A,B 兩個集合:A = {s1, s3, s6, s8, s9}? B = {s3, s4, s7, s8, s10}
  • MinHash的基本原理:在A∪B這個大的隨機域里,選中的元素落在A∩B這個區域的概率,這個概率就等于Jaccard的相似度
  • 最小哈希:

    ?

    S1

    S2

    S3

    A

    1

    0

    0

    B

    0

    1

    0

    C

    0

    0

    0

    D

    1

    0

    1

    行的隨機排列轉換(也稱置換運算)

    ?

    S1

    S2

    S3

    B

    0

    1

    0

    D

    1

    0

    1

    A

    1

    0

    0

    C

    0

    0

    0

    哈希值:排列轉換后的行排列次序下第一個列值為1的行的行號,例如h(S1)=D,h(S2)=B

    兩個集合經隨機排列之后得到的兩個最小哈希值相等的概率等于這兩個集合的Jaccard相似度。

    問題:

    對于上百萬甚至數十億的行選擇一個隨機排列轉換極其消耗時間,怎么辦?

  • 隨機選擇n個哈希函數h1,h2…
  • 對每列C進行如下操作:
  •     a) ?如果c在第r行為0,則什么也不做;

        b)?? 否則,如果c在第r行為1,那么對于每個i=1,2,…,n,將SIG(i,c)置為原來的SIG(i,c)和h(r)中的較小值

    ?

    S1

    S2

    S3

    S4

    x+1 mod 5

    3x+1 mod 5

    0

    1

    0

    0

    1

    1

    1

    1

    0

    0

    1

    0

    2

    4

    2

    0

    1

    0

    1

    3

    2

    3

    1

    0

    1

    1

    4

    0

    4

    0

    0

    1

    0

    0

    3

    計算最小哈希簽名矩陣:

    ?

    S1

    S2

    S3

    S4

    h1

    1

    3

    0

    1

    h2

    0

    2

    0

    0

    計算Jaccard相似度:

    SIM(S1,S4)=1.0;SIM(S1,S3)=1/3;SIM(S1,S2)=0

    真實SIM(S1,S4)=2/3;SIM(S1,S3)=1/4;SIM(S1,S2)=0

    ?

    相關論文:

    1.《Google News Personalization: Scalable Online Collaborative Filtering》

    根據用戶的歷史點擊數據,進行新聞推薦;采用最小哈希聚類的協同過濾算法

    2.《The Link-Prediction Problem for Social Networks》

    比較社交網絡鏈接預測問題的各種算法

    計算好友相似度的流程:

    • 找到N個哈希函數,對每個用戶好友集合生成一組Minhash(N個)
    • 對于一個用戶,按Minhash相同個數做排序,給出推薦候選集
    • 計算用戶跟被備選集的Jaccard Index
    • 按Jaccard結果排序,給出TopN進行推薦

    哈希函數生成和哈希值計算:

  • 輸入向量Vector轉換為bytes
    • numHashFunctions--預設生成hash函數的個數(假定為10個)
    • for (int i = 0; i < numHashFunctions; i++) {for (Vector.Element ele : featureVector) {int value = (int) ele.get();bytesToHash[0] = (byte) (value >> 24);bytesToHash[1] = (byte) (value >> 16);bytesToHash[2] = (byte) (value >> 8);bytesToHash[3] = (byte) value;int hashIndex = hashFunction[i].hash(bytesToHash); //計算哈希函數值//只保留最小哈希值if (minHashValues[i] > hashIndex) {minHashValues[i] = hashIndex;}}}

      ?

  • 采用Mersenne Twister算法構造偽隨機生成器
    • Random random = new MersenneTwisterRNG(new FastRandomSeedGenerator());
      • org.uncommons.maths.random.MersenneTwisterRNG.MersenneTwisterRNG(SeedGeneratorseedGenerator)
    • Mersenne Twister(馬特賽特旋轉演算法),是偽隨機數發生器之一,其主要作用是生成偽隨機數。
      Mersenne Twister算法的原理:Mersenne Twister算法是利用線性反饋移位寄存器(LFSR)產生隨機數的,LFSR的反饋函數是寄存器中某些位的簡單異或,這些位也稱之為抽頭序列。一個n位的LFSR能夠在重復之前產生2^n-1位長的偽隨機序列。只有具有一定抽頭序列的LFSR才能通過所有2^n-1個內部狀態,產生2^n - 1位長的偽隨機序列,這個輸出的序列就稱之為m序列。為了使LFSR成為最大周期的LFSR,由抽頭序列加上常數1形成的多項式必須是本原多項式。一個n階本原多項式是不可約多項式,它能整除x^(2*n-1)+1而不能整除x^d+1,其中d能整除2^n-1。例如(32,7,5,3,2,1,0)是指本原多項式x^32+x^7+x^5+x^3+x^2+x+1,把它轉化為最大周期LFSR就是在LFSR第32,7,5,2,1位抽頭。利用上述兩種方法產生周期為m的偽隨機序列后,只需要將產生的偽隨機序列除以序列的周期,就可以得到(0,1)上均勻分布的偽隨機序列了。Mersenne Twister有以下優點:隨機性好,在計算機上容易實現,占用內存較少(mt19937的C程式碼執行僅需624個字的工作區域),產生隨機數的速度快、周期長,可達到2^19937-1,且具有623維均勻分布的性質,對于一般的應用來說,足夠大了,序列關聯比較小,能通過很多隨機性測試。
  • 四種哈希函數生成器
    • MAX_INT_SMALLER_TWIN_PRIME = 2147482949;為什么選這個值?
      • 它是整型范圍內最大孿生素數(相差為2的兩個數都是質數的情況)的較小值;哈希用素數取模沖突小
    • seedA、seedB、seedC是采用MersenneTwisterRNG隨機生成器生成的0~11均勻分布的隨機數
    • 第一種:LinearHash
      @Overridepublic int hash(byte[] bytes) {long hashValue = 31;for (long byteVal : bytes) {hashValue *= seedA * byteVal;hashValue += seedB;}return Math.abs((int) (hashValue % RandomUtils.MAX_INT_SMALLER_TWIN_PRIME));}
    • 第二種:PolynomialHash @Overridepublic int hash(byte[] bytes) {long hashValue = 31;for (long byteVal : bytes) {hashValue *= seedA * (byteVal >> 4);hashValue += seedB * byteVal + seedC;}return Math.abs((int) (hashValue % RandomUtils.MAX_INT_SMALLER_TWIN_PRIME));}
    • 第三種:MurmurHashWrapper@Overridepublic int hash(byte[] bytes) {long hashValue = MurmurHash.hash64A(bytes, seed);return Math.abs((int) (hashValue % RandomUtils.MAX_INT_SMALLER_TWIN_PRIME));}
    • 第四種:MurmurHash3Wrapper@Overridepublic int hash(byte[] bytes) {long hashValue = MurmurHash3.murmurhash3_x86_32(bytes, 0, bytes.length, seed);return Math.abs((int) (hashValue % RandomUtils.MAX_INT_SMALLER_TWIN_PRIME));}?



  • 來源:http://www.cnblogs.com/shipengzhi/articles/2826209.html




    simhash算法原理及實現



    simhash是google用來處理海量文本去重的算法。 google出品,你懂的。 simhash最牛逼的一點就是將一個文檔,最后轉換成一個64位的字節,暫且稱之為特征字,然后判斷重復只需要判斷他們的特征字的距離是不是<n(根據經驗這個n一般取值為3),就可以判斷兩個文檔是否相似。

    原理

    simhash值的生成圖解如下

    大概花三分鐘看懂這個圖就差不多怎么實現這個simhash算法了。特別簡單。谷歌出品嘛,簡單實用。

    算法過程大概如下:

  • 將Doc進行關鍵詞抽取(其中包括分詞和計算權重),抽取出n個(關鍵詞,權重)對, 即圖中的(feature, weight)們。 記為?feature_weight_pairs?= [fw1, fw2 … fwn],其中 fwn = (feature_n,weight_n`)。
  • hash_weight_pairs?= [ (hash(feature), weight) for feature, weight infeature_weight_pairs?] 生成圖中的(hash,weight)們, 此時假設hash生成的位數bits_count = 6(如圖);
  • 然后對?hash_weight_pairs?進行位的縱向累加,如果該位是1,則+weight,如果是0,則-weight,最后生成bits_count個數字,如圖所示是[13, 108, -22, -5, -32, 55], 這里產生的值和hash函數所用的算法相關。
  • [13,108,-22,-5,-32,55] -> 110001這個就很簡單啦,正1負0。
  • 到此,如何從一個doc到一個simhash值的過程已經講明白了。 但是還有一個重要的部分沒講,

    『simhash值的海明距離計算』

    二進制串A 和 二進制串B 的海明距離 就是?A xor B?后二進制中1的個數。

    舉例如下:

    A = 100111; B = 101010; hamming_distance(A, B) = count_1(A xor B) = count_1(001101) = 3;

    當我們算出所有doc的simhash值之后,需要計算doc A和doc B之間是否相似的條件是:

    A和B的海明距離是否小于等于n,這個n值根據經驗一般取值為3,

    simhash本質上是局部敏感性的hash,和md5之類的不一樣。 正因為它的局部敏感性,所以我們可以使用海明距離來衡量simhash值的相似度。

    『高效計算二進制序列中1的個數』

    /* src/Simhasher.hpp */ bool isEqual(uint64_t lhs, uint64_t rhs, unsigned short n = 3) {unsigned short cnt = 0;lhs ^= rhs;while(lhs && cnt <= n){lhs &= lhs - 1;cnt++;}if(cnt <= n){return true;}return false; }

    由上式這個函數來計算的話,時間復雜度是 O(n); 這里的n默認取值為3。由此可見還是蠻高效的。

    『計算二進制序列中1的個數之O(1)算法實現』

    感謝?@SCatWang?的評論分享:

    感謝您做的simhash庫,感覺會很方便。 有關求二進制中1的個數,其實有各種O(1)的實現。可以參考這個地方:http://stackoverflow.com/a/14682688

    simhash 實現的工程項目

    • C++ 版本?simhash
    • Golang 版本?gosimhash

    主要是針對中文文檔,也就是此項目進行simhash之前同時還進行了分詞和關鍵詞的抽取。

    對比其他算法

    『百度的去重算法』

    百度的去重算法最簡單,就是直接找出此文章的最長的n句話,做一遍hash簽名。n一般取3。 工程實現巨簡單,據說準確率和召回率都能到達80%以上。

    『shingle算法』

    shingle原理略復雜,不細說。 shingle算法我認為過于學院派,對于工程實現不夠友好,速度太慢,基本上無法處理海量數據。

    『其他算法』

    具體看微博上的討論

    參考

    • Similarity estimation techniques from rounding algorithms
    • simhash與Google的網頁去重
    • 海量數據相似度計算之simhash和海明距離


    來源:http://yanyiwu.com/work/2014/01/30/simhash-shi-xian-xiang-jie.html




    實現文本相似度算法(余弦定理)


    ? ? ? ?最近由于工作項目,需要判斷兩個txt文本是否相似,于是開始在網上找資料研究,因為在程序中會把文本轉換成String再做比較,所以最開始找到了這篇關于?距離編輯算法?Blog寫的非常好,受益匪淺。

    ????? ?于是我決定把它用到項目中,來判斷兩個文本的相似度。但后來實際操作發現有一些問題:直接說就是查詢一本書中的相似章節花了我7、8分鐘;這是我不能接受……

    ????? ?于是停下來仔細分析發現,這種算法在此項目中不是特別適用,由于要判斷一本書中是否有相同章節,所以每兩個章節之間都要比較,若一本書書有x章的話,這里需對比x(x-1)/2次;而此算法采用矩陣的方式,計算兩個字符串之間的變化步驟,會遍歷兩個文本中的每一個字符兩兩比較,可以推斷出時間復雜度至少為?document1.length × document2.length,我所比較的章節字數平均在幾千~一萬字;這樣計算實在要了老命。

    ????? ?想到Lucene中的評分機制,也是算一個相似度的問題,不過它采用的是計算向量間的夾角(余弦公式),在google黑板報中的:數學之美(余弦定理和新聞分類)?也有說明,可以通過余弦定理來判斷相似度;于是決定自己動手試試。

    ????? ?首相選擇向量的模型:在以字為向量還是以詞為向量的問題上,糾結了一會;后來還是覺得用字,雖然詞更為準確,但分詞卻需要增加額外的復雜度,并且此項目要求速度,準確率可以放低,于是還是選擇字為向量。

    ????? ?然后每個字在章節中出現的次數,便是以此字向量的值。現在我們假設:

    ????? ?章節1中出現的字為:Z1c1,Z1c2,Z1c3,Z1c4……Z1cn;它們在章節中的個數為:Z1n1,Z1n2,Z1n3……Z1nm

    ???????章節2中出現的字為:Z2c1,Z2c2,Z2c3,Z2c4……Z2cn;它們在章節中的個數為:Z2n1,Z2n2,Z2n3……Z2nm

    ????? ?其中,Z1c1和Z2c1表示兩個文本中同一個字,Z1n1和Z2n1是它們分別對應的個數,

    ???????最后我們的相似度可以這么計算:

    ????? ?程序實現如下:(若有可優化或更好的實現請不吝賜教)

    public class CosineSimilarAlgorithm {public static double getSimilarity(String doc1, String doc2) {if (doc1 != null && doc1.trim().length() > 0 && doc2 != null&& doc2.trim().length() > 0) {Map<Integer, int[]> AlgorithmMap = new HashMap<Integer, int[]>();//將兩個字符串中的中文字符以及出現的總數封裝到,AlgorithmMap中for (int i = 0; i < doc1.length(); i++) {char d1 = doc1.charAt(i);if(isHanZi(d1)){int charIndex = getGB2312Id(d1);if(charIndex != -1){int[] fq = AlgorithmMap.get(charIndex);if(fq != null && fq.length == 2){fq[0]++;}else {fq = new int[2];fq[0] = 1;fq[1] = 0;AlgorithmMap.put(charIndex, fq);}}}}for (int i = 0; i < doc2.length(); i++) {char d2 = doc2.charAt(i);if(isHanZi(d2)){int charIndex = getGB2312Id(d2);if(charIndex != -1){int[] fq = AlgorithmMap.get(charIndex);if(fq != null && fq.length == 2){fq[1]++;}else {fq = new int[2];fq[0] = 0;fq[1] = 1;AlgorithmMap.put(charIndex, fq);}}}}Iterator<Integer> iterator = AlgorithmMap.keySet().iterator();double sqdoc1 = 0;double sqdoc2 = 0;double denominator = 0; while(iterator.hasNext()){int[] c = AlgorithmMap.get(iterator.next());denominator += c[0]*c[1];sqdoc1 += c[0]*c[0];sqdoc2 += c[1]*c[1];}return denominator / Math.sqrt(sqdoc1*sqdoc2);} else {throw new NullPointerException(" the Document is null or have not cahrs!!");}}public static boolean isHanZi(char ch) {// 判斷是否漢字return (ch >= 0x4E00 && ch <= 0x9FA5);}/*** 根據輸入的Unicode字符,獲取它的GB2312編碼或者ascii編碼,* * @param ch* 輸入的GB2312中文字符或者ASCII字符(128個)* @return ch在GB2312中的位置,-1表示該字符不認識*/public static short getGB2312Id(char ch) {try {byte[] buffer = Character.toString(ch).getBytes("GB2312");if (buffer.length != 2) {// 正常情況下buffer應該是兩個字節,否則說明ch不屬于GB2312編碼,故返回'?',此時說明不認識該字符return -1;}int b0 = (int) (buffer[0] & 0x0FF) - 161; // 編碼從A1開始,因此減去0xA1=161int b1 = (int) (buffer[1] & 0x0FF) - 161; // 第一個字符和最后一個字符沒有漢字,因此每個區只收16*6-2=94個漢字return (short) (b0 * 94 + b1);} catch (UnsupportedEncodingException e) {e.printStackTrace();}return -1;} }

    ????? ?程序中做了兩小的改進,以加快效率:

    ????? ?1. 只將漢字作為向量,其他的如標點,數字等符號不處理;2. 在HashMap中存放漢字和其在文本中對于的個數時,先將單個漢字通過GB2312編碼轉換成數字,再存放。

    ????? ?最后寫了個測試,根據兩種不同的算法對比下時間,下面是測試結果:

    ????? ?余弦定理算法:doc1 與 doc2 相似度為:0.9954971, 耗時:22mm

    ????? ?距離編輯算法:doc1 與 doc2 相似度為:0.99425095, 耗時:322mm

    ????? ?可見效率有明顯提高,算法復雜度大致為:document1.length + document2.length

    ? ? ? ?

    文章來源:?http://my.oschina.net/BreathL/blog/42477



    PHP實現余弦相似度算法


    上一次,我用TF-IDF算法自動提取關鍵詞。

    今天,我們再來研究另一個相關的問題。有些時候,除了找到關鍵詞,我們還希望找到與原文章相似的其他文章。比如,"Google新聞"在主新聞下方,還提供多條相似的新聞。

    為了找出相似的文章,需要用到"余弦相似性"(cosine similiarity)。下面,我舉一個例子來說明,什么是"余弦相似性"。

    為了簡單起見,我們先從句子著手。

      句子A:我喜歡看電視,不喜歡看電影。

      句子B:我不喜歡看電視,也不喜歡看電影。

    請問怎樣才能計算上面兩句話的相似程度?

    基本思路是:如果這兩句話的用詞越相似,它們的內容就應該越相似。因此,可以從詞頻入手,計算它們的相似程度。

    第一步,分詞。

      句子A:我/喜歡/看/電視,不/喜歡/看/電影。

      句子B:我/不/喜歡/看/電視,也/不/喜歡/看/電影。

    第二步,列出所有的詞。

      我,喜歡,看,電視,電影,不,也。

    第三步,計算詞頻。

      句子A:我 1,喜歡 2,看 2,電視 1,電影 1,不 1,也 0。

      句子B:我 1,喜歡 2,看 2,電視 1,電影 1,不 2,也 1。

    第四步,寫出詞頻向量。

      句子A:[1, 2, 2, 1, 1, 1, 0]

      句子B:[1, 2, 2, 1, 1, 2, 1]

    到這里,問題就變成了如何計算這兩個向量的相似程度。

    我們可以把它們想象成空間中的兩條線段,都是從原點([0, 0, ...])出發,指向不同的方向。兩條線段之間形成一個夾角,如果夾角為0度,意味著方向相同、線段重合;如果夾角為90度,意味著形成直角,方向完全不相似;如果夾角為180度,意味著方向正好相反。因此,我們可以通過夾角的大小,來判斷向量的相似程度。夾角越小,就代表越相似。

    以二維空間為例,上圖的a和b是兩個向量,我們要計算它們的夾角θ。余弦定理告訴我們,可以用下面的公式求得:

    假定a向量是[x1, y1],b向量是[x2, y2],那么可以將余弦定理改寫成下面的形式:

    數學家已經證明,余弦的這種計算方法對n維向量也成立。假定A和B是兩個n維向量,A是 [A1, A2, ..., An] ,B是 [B1, B2, ..., Bn] ,則A與B的夾角θ的余弦等于:

    使用這個公式,我們就可以得到,句子A與句子B的夾角的余弦。

    余弦值越接近1,就表明夾角越接近0度,也就是兩個向量越相似,這就叫"余弦相似性"。所以,上面的句子A和句子B是很相似的,事實上它們的夾角大約為20.3度。

    由此,我們就得到了"找出相似文章"的一種算法:

      (1)使用TF-IDF算法,找出兩篇文章的關鍵詞;

      (2)每篇文章各取出若干個關鍵詞(比如20個),合并成一個集合,計算每篇文章對于這個集合中的詞的詞頻(為了避免文章長度的差異,可以使用相對詞頻);

      (3)生成兩篇文章各自的詞頻向量;

      (4)計算兩個向量的余弦相似度,值越大就表示越相似。

    "余弦相似度"是一種非常有用的算法,只要是計算兩個向量的相似程度,都可以采用它。


    下面是PHP實現余弦相似度計算的算法

    <?php /*** 數據分析引擎* 分析向量的元素 必須和基準向量的元素一致,取最大個數,分析向量不足元素以0填補。* 求出分析向量與基準向量的余弦值*/ /*** 獲得向量的模* @param unknown_type $array 傳入分析數據的基準點的N維向量。|eg:array(1,1,1,1,1);*/ function getMarkMod($arrParam){$strModDouble = 0;foreach($arrParam as $val){$strModDouble += $val * $val;}$strMod = sqrt($strModDouble);//是否需要保留小數點后幾位return $strMod; }/*** 獲取標桿的元素個數* @param unknown_type $arrParam* @return number*/ function getMarkLenth($arrParam){$intLenth = count($arrParam);return $intLenth; }/*** 對傳入數組進行索引分配,基準點的索引必須為k,求夾角的向量索引必須為 'j'.* @param unknown_type $arrParam* @param unknown_type $index* @ruturn $arrBack*/ function handIndex($arrParam, $index = 'k'){foreach($arrParam as $key => $val){$in = $index.$key;$arrBack[$in] = $val;}return $arrBack; }/**** @param unknown_type $arrMark 標桿向量數組(索引被處理過)|array('k0'=>1,'k1'=>2....)* @param unknown_type $arrAnaly 分析向量數組(索引被處理過)|array('j0'=>1,'j1'=>2....)* @param unknown_type $strMarkMod 標桿向量的模* @param unknown_type $intLenth 向量的長度*/ function getCosine($arrMark, $arrAnaly, $strMarkMod ,$intLenth){$strVector = 0;$strCosine = 0;for($i = 0; $i < $intLenth; $i++){$strMarkVal = $arrMark['k'.$i];$strAnalyVal = $arrAnaly['j'.$i];$strVector += $strMarkVal * $strAnalyVal;}$arrAnalyMod = getMarkMod($arrAnaly); //求分析向量的模$strFenzi = $strVector;$strFenMu = $arrAnalyMod * $strMarkMod;$strCosine = $strFenzi / $strFenMu;if(0 !== (int)$strFenMu){$strCosine = $strFenzi / $strFenMu;}return $strCosine; } //基準點的N維向量 $arrMark = array(1,1,1,1,1); //分析點的N維向量 $arrAnaly = array(1,2,3,4,5); //向量的模 $MarkMod = getMarkMod($arrMark); //向量的長度 $MarkLenth = getMarkLenth($arrMark); //標桿向量數組 $Index1 = handIndex($arrMark,"k"); //分析向量數組 $Index2 = handIndex($arrAnaly,"j"); //分析向量與基準向量的余弦值 $Cosine = getCosine($Index1,$Index2,$MarkMod,$MarkLenth); echo "向量的模:".$MarkMod; echo "<br>"; echo "向量的長度:".$MarkLenth; echo "<br>"; echo "標桿向量數組:"; print_r($Index1); echo "<br>"; echo "分析向量數組:"; print_r($Index2); echo "<br>"; echo "分析向量與基準向量的余弦值:".$Cosine; ?>





    小潤測試的demo如上圖


    來源:http://www.tuicool.com/articles/YB3uy2N



    簡單結論:

    標題相似性的判斷,一般使用 minhash,內容相似性判斷一般使simhash,簡單業務可以使用余弦算法。




    總結

    以上是生活随笔為你收集整理的[转]文本相似性算法:simhash/minhash/余弦算法的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。