1. 程式人生 > >從0到1,了解NLP中的文本相似度

從0到1,了解NLP中的文本相似度

答案 更新 hive 貸款 sem += 大宗商品 判斷 坐標

本文由雲+社區發表

作者:netkiddy

導語

AI在2018年應該是互聯網界最火的名詞,沒有之一。時間來到了9102年,也是項目相關,涉及到了一些AI寫作相關的功能,為客戶生成一些素材文章。但是,AI並不一定最懂你,客戶對於AI寫出來的文章,多少是會做些修改的。為了更好的衡量出AI文章的可用度,在這兒就會需要存有一個反饋的環節,來看看用戶潤色後的文章與原始AI文章之間的區別是多大,AI寫出來的文章可用性是否足夠。由於目前還沒精力細究AI寫作其中的細節,為了更好地計算每次成文與原文的區分,便花了點小時間看了看文本相似度的知識點,記錄於此。

本文將從預備知識的概念開始介紹,從距離名詞,到文本分詞,相似度算法,並將這些概念融合、統一的介紹NLP中文本相似度的知識,期望通過本文,大家可以與我一樣,對這些知識有個基本的了解。

幾個距離

在介紹更多的內容之前,我們需要了解文本距離的概念,這些距離是我們在後文比較文本相似度的基礎,所以下面將首先形象的為大家介紹幾個重要且基礎的距離含義。

歐幾裏德距離

Euclidean Distance,是最直白的、最容易直觀理解的距離度量方法,在二維空間來看,用一句幾乎耳熟能詳的話來解釋就是:兩點之間直線最短。這句話中說到的「直線距離」就是歐幾裏德距離。我們來看下相關數學公式定義。

二維的公式:

? p = sqrt( (x1-y1)^2+(x2-y2)^2 )

三維的公式:

? p = sqrt( (x1-y1)^2+(x2-y2)^2+(x3-y3)^2 )

當然,畢竟不是存活於劉慈溪的三體世界之下,我們在小學或者日常所能感知到的多是,二維或者三維空間的距離,當大於3維,從數學理論上的n維空間的公式,在歐幾裏德空間中,點x

=(x1,...,xn)和 y =(y1,...,yn)之間的歐氏距離為:

? p = sqrt( (x1-y1)^2+(x2-y2)^2+(x3-y3)^2+ ... +(xn-yn)^2 )

曼哈頓距離

Manhattan Distance的命名原因,是從規劃為方型建築區塊的城市(如曼哈頓)間,最短的出租車從一個點A到另一個點B的行車路徑距離,任何往東三區塊、往北六區塊的的路徑一定最少要走九區塊(出租車當然不能穿插過街區),沒有其他捷徑。抽象到數學角度,從點A(x1, y1)到點B(x2, y2)的曼哈頓距離為兩個點上在標準坐標系上的絕對軸距之總和:

? p = |x1-x2| + |y1-y2|

那麽,曼哈頓距離和歐幾裏得距離的區別是什麽呢?我們從維基百科拉過來一張圖,就可以很直白的看到這二者的區別,假設在下方棋盤一樣的圖示中,白色方塊表示為建築物,灰色線條表示為道路,那麽其中綠色線路表示為黑色兩點之間的歐幾裏德距離(兩點之間直線最短),而剩下的紅藍黃三色線路表示的均為為曼哈頓距離:

技術分享圖片歐幾裏德距離和曼哈頓距離

切比雪夫距離

Chebyshev distance得名自俄羅斯數學家切比雪夫。大家對切比雪夫應該多少是覺得有印象的,沒錯,切比雪夫更被我們熟悉的是中學時候學過的關於他的切比雪夫多項式吧。所謂切比雪夫距離,是將兩點之間的距離定義為其各座標數值差的最大值。如果我們以二維空間中兩點A(x1,y1)和B(x2,y2)二點為例,其切比雪夫距離:

? p = max(|x2-x1|, |y2-y1|)

更形象的來介紹,切比雪夫距離在二維空間有著一個應用場景:國際象棋中「國王」的行走距離。由於王可以往斜前或斜後方向移動一格,因此可以較有效率的到達目的的格子,我們將國際象棋的棋盤映射到二維直角座標系中,格子的邊長定義為1,座標的x軸及y軸和棋盤方格平行,原點恰落在某一格的中心點,則「國王」從一個位置走到其他位置需要的步數恰為這兩個位置的切比雪夫距離。下圖是棋盤上所有位置距f6位置的切比雪夫距離。

技術分享圖片國際象棋的國王

余弦距離

Cosine distance使用兩個向量夾角的余弦值作為衡量兩個個體間差異的大小。相比於歐幾裏德距離,余弦距離更加註重的是兩個向量在方向上的差異。首先我們來看一下數學中余弦定理的定義:

技術分享圖片余弦定理公式

關於余弦定理,這兒我們簡單來復習下這個初中概念。前幾年曾經有過一個地方的高考題出過余弦定理的證明,當時也有人通過向量的方法來證明,兩行就得出了答案(其實這兒有點疑問,因為課本中對向量內積是通過余弦定理來證明的,所以從個人來看通過向量內積來證明余弦定理是有些邏輯問題的),那麽具體應該如何證明呢?其實很簡單,通過一張圖就可以證明:

技術分享圖片余弦定理的證明

結合這張圖,花2分鐘應該就可以得到余弦定理的結論了,其中需要了解的一點事對於bsin(θ*)的定義,是直接使用了畢氏定理。

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

技術分享圖片余弦定理的向量計算公式

使用這個公式,我們會可以更方便的計算余弦距離。

回到余弦距離上來,它與我們上面說的歐幾裏得距離的區別是什麽呢?我們引用一張網上的圖片來形象的了解下余弦距離的含義:

技術分享圖片余弦距離和歐幾裏得距離

在上圖中,歐幾裏德距離dist(A, B)衡量的是空間中兩點的絕對距離,跟各個點所在的位置坐標是直接相關的;而余弦距離衡量的是空間向量的夾角,更加體現在方向上的差異,而不是位置。如果我們保持A點位置不變,B點朝原方向遠離坐標軸原點,那麽這個時候余弦距離是保持不變的(因為夾角沒有發生變化),而A、B兩點的歐幾裏德距離顯然在發生改變,這就是兩者之間的不同之處。

關於余弦距離和歐幾裏得距離在現實場景中的區別,我們可以通過下面這個例子來形象的了解。現在有用戶A和用戶B分別對外賣騎手員工X和員工Y進行了評分。用戶A對員工X的評分為2,對員工Y的評分為3,表示到坐標系中為坐標點AB(2, 3);同樣用戶B對員工X、Y的評分表示為坐標點B(4, 6),因此他們之間的歐幾裏德距離為:

? p = sqrt((2 - 4)^2 + (3- 6)^2) = 3.6

而他們的余弦距離為:

? p = (2 * 4 + 3 * 6) / ( sqrt( 2^2 + 3^2 ) * sqrt( 4^2 + 6^2 ) ) = 1

結合圖示如下,其中,點A與點B之間的之間距離為紅色線段所示,也就是上述的歐幾裏得距離。同時,線段0A和線段0B由於斜度相等,也就是夾角為0度,反映出的余弦距離就是cos(0) = 1,說明二者完全相似。

技術分享圖片用戶評分距離圖示

歐幾裏得距離和余弦距離各自有不同的計算方式和衡量特征,因此它們適用於不同的數據分析模型:前者能夠體現個體數值特征的絕對差異,所以更多的用於需要從維度的數值大小中體現差異的分析,如使用用戶行為指標分析用戶價值的相似度或差異。後者則傾向於是從方向上區分差異,而對絕對的數值不敏感,更多的用於使用用戶對內容評分來區分興趣的相似度和差異,同時修正了用戶間可能存在的度量標準不統一的問題(因為余弦距離對絕對數值不敏感)。

漢明距離

Hamming distance在信息論中,表示為兩個「等長」字符串之間對應位置的不同字符的個數。換句話說,漢明距離就是將一個字符串變換成另外一個字符串所需要「替換」的字符個數。如下圖所示:

  • 0110與1110之間的漢明距離是1;
  • 0100與1001之間的漢明距離是3;

技術分享圖片漢明距離圖示

分詞

在了解了上述一系列的距離含義之後,我們已經基本了解了衡量相似度的一個判定方法,但是對於一段文本內容來說,我們對什麽來計算距離呢?這就涉及到了第二個基礎知識:分詞。

分詞方法

為了實現對文本相似度的比較,我們需要分析文本的內容,也就必然會涉及到對文本進行分詞處理。而說到分詞,其中涉及的內容不比任何一個其他知識點要少,考慮到不是本文重點講述,此處僅僅簡單的列舉了下當前分詞算法的幾種方向,有興趣的同學可以就此列表再去細細琢磨

  • 基於詞表的分詞方法
    • 正向最大匹配法(forward maximum matching method, FMM)
    • 逆向最大匹配法(backward maximum matching method, BMM)
    • N-最短路徑方法
  • 基於統計模型的分詞方法
    • 基於N-gram語言模型的分詞方法
  • 基於序列標註的分詞方法
    • 基於HMM的分詞方法
    • 基於CRF的分詞方法
    • 基於詞感知機的分詞方法
    • 基於深度學習的端到端的分詞方法

工程方案

從工程角度來看,目前分詞已經有了十分成熟工程實現了,如IK,ansj等,列出一些比較常用的中文分詞方案,以供大家學習使用:

  • 結巴分詞
  • ansj分詞器
  • 中科院計算所NLPIR
  • 哈工大的LTP
  • 清華大學THULAC
  • 斯坦福分詞器 (Github)
  • Hanlp分詞器
  • KCWS分詞器

文本相似度

在介紹完距離和分詞之後,接下來,我們就需要來關註計算文本相似度的算法了。總的來說,計算文本相似度的算法共分為4類:

  • 基於詞向量
  • 基於具體字符
  • 基於概率統計
  • 基於詞嵌入的

結合我們上文的幾種距離,其中歐幾裏德距離、曼哈頓距離和余弦距離等適合應用於詞向量,漢明距離應屬於基於字符的文本相似度的度量方法。本文接下來將重點介紹基於余弦復雜度的文本相似度比較算法,和適用於海量數據的simhash文本相似度算法,並給予一定的工程實現方案。

余弦復雜度

對於多個不同的文本或者短文本對話消息要來計算他們之間的相似度如何,一個好的做法就是將這些文本中詞語,映射到向量空間,形成文本中文字和向量數據的映射關系,再通過計算幾個或者多個不同的向量的差異的大小,來計算文本的相似度。下面介紹一個詳細成熟的向量空間余弦相似度方法計算相似度算法。

原理

枯燥的原理不如示例來的簡單明了,我們將以一個簡單的示例來介紹余弦復雜度的原理。現在有下面這樣的兩句話,從我們直覺感官來看,說的是一模一樣的內容,那麽我們通過計算其余弦距離來看看其相似度究竟為多少。

S1: "為什麽我的眼裏常含淚水,因為我對這片土地愛得深沈"
S2: "我深沈的愛著這片土地,所以我的眼裏常含淚水"

第一步,分詞:

我們對上述兩段話分詞分詞並得到下面的詞向量:

S1: [為什麽 我 的 眼裏 常含 淚水 因為 我 對 這片 土地 愛得 深沈 ,]
S2: [我 深沈 的 愛 著 這片 土地 所以 我 的 眼裏 常含 淚水 ,]

第二步,統計所有詞組:

將S1和S2中出現的所有不同詞組融合起來,並得到一個詞向量超集,如下:

[眼裏 這片 為什麽 我 的 常含 因為 對 所以 愛得 深沈 愛 著 , 淚水 土地]

第三步,獲取詞頻:

對應上述的超級詞向量,我們分別就S1的分詞和S2的分詞計算其出現頻次,並記錄:

S1: [1 1 1 2 1 1 1 1 0 1 1 0 0 1 1 1]
S2: [1 1 0 2 2 1 0 0 1 0 1 1 1 1 1 1]

第四步,復雜度計算:

通過上述的準備工作,現在我們可以想象在空間中存在著兩條線段:SA和SB,二者均從原點([0, 0, ...])出發,指向不同的方向,並分別終結於點A [1 1 1 2 1 1 1 1 0 1 1 0 0 1 1 1]和點B[1 1 0 2 2 1 0 0 1 0 1 1 1 1 1 1],其中點A和點B的坐標與我們上述的詞頻一致。到了這一步,我們可以發現,對於句子S1和S2的相似度問題,已經被我們抽象到如何計算上述兩個向量的相似問題了。

通過上文介紹的余弦定理,我們知道當兩條線段之間形成一個夾角,如果夾角為0度,意味著方向相同、線段重合,我們就認定這是表示兩個向量代表的文本完全相等;如果夾角為90度,意味著形成直角,方向完全不相似。因此,我們可以通過夾角的大小,來判斷向量的相似程度。夾角越小,就代表越相似。

那麽對於上述給定的兩個屬性向量AB,其余弦相似性θ由點積和向量長度給出,其余弦相似度的計算如下所示:

技術分享圖片余弦相似度計算公式

實現

下面我們將通過golang來實現一個簡單的余弦相似度算法。

func CosineSimilar(srcWords, dstWords []string) float64 {
   // get all words
   allWordsMap := make(map[string]int, 0)
   for _, word := range srcWords {
      if _, found := allWordsMap[word]; !found {
         allWordsMap[word] = 1
      } else {
         allWordsMap[word] += 1
      }
   }
   for _, word := range dstWords {
      if _, found := allWordsMap[word]; !found {
         allWordsMap[word] = 1
      } else {
         allWordsMap[word] += 1
      }
   }

   // stable the sort
   allWordsSlice := make([]string, 0)
   for word, _ := range allWordsMap {
      allWordsSlice = append(allWordsSlice, word)
   }

   // assemble vector
   srcVector := make([]int, len(allWordsSlice))
   dstVector := make([]int, len(allWordsSlice))
   for _, word := range srcWords {
      if index := indexOfSclie(allWordsSlice, word); index != -1 {
         srcVector[index] += 1
      }
   }
   for _, word := range dstWords {
      if index := indexOfSclie(allWordsSlice, word); index != -1 {
         dstVector[index] += 1
      }
   }

   // calc cos
   numerator := float64(0)
   srcSq := 0
   dstSq := 0
   for i, srcCount := range srcVector {
      dstCount := dstVector[i]
      numerator += float64(srcCount * dstCount)
      srcSq += srcCount * srcCount
      dstSq += dstCount * dstCount
   }
   denominator := math.Sqrt(float64(srcSq * dstSq))

   return numerator / denominator
}

結果

--- PASS: TestCosineSimilar (0.84s)
    similarity_test.go:23: CosineSimilar score: 0.7660323462854266

我們看到,對於上述兩句話,在經過余弦計算後,得到的相似度為0.766(其夾角大概是40度),還是比較接近於1,所以,上面的句子S1和句子S2是基本相似的。由此,我們就得到了文本相似度計算的處理流程是:

  1. 找出兩篇文章的關鍵詞;
  2. 每篇文章各取出若幹個關鍵詞,合並成一個集合,計算每篇文章對於這個集合中的詞的詞頻;
  3. 生成兩篇文章各自的詞頻向量;
  4. 計算兩個向量的余弦相似度,值越接近於1就表示越相似;

simhash

基於余弦復雜度,通過兩兩比較文本向量來得到兩個文本的相似程度是一個非常簡單的算法。然而兩兩比較也就說明了時間復雜度是O(n2),那麽在面對互聯網海量信息時,考慮到一個文章的特征向量詞可能特別多導致整個向量維度很高,使得計算的代價太大,就有些力不從心了。因此,為了在爬取網頁時用於快速去重,Google發明了一種快速衡量兩個文本集相似度的算法:simhash。

簡單來說,simhash中使用了一種局部敏感型的hash算法。所謂局部敏感性hash,與傳統hash算法不同的是(如MD5,當原始文本越是相似,其hash數值差異越大),simhash中的hash對於越是相似的內容產生的簽名越相近。

原理

simhash的主要思想是降維,將文本分詞結果從一個高維向量映射成一個0和1組成的bit指紋(fingerprint),然後通過比較這個二進制數字串的差異進而來表示原始文本內容的差異。下面我們通過圖文方式來解釋下這個降維和差異計算的過程。

技術分享圖片simhash算法流程實例

在simhash中處理一個文本的步驟如下:

第一步,分詞:

對文本進行分詞操作,同時需要我們同時返回當前詞組在文本內容中的權重(這基本上是目前所有分詞工具都支持的功能)。

第二步,計算hash:

對於每一個得到的詞組做hash,將詞語表示為到01表示的bit位,需要保證每個hash結果的位數相同,如圖中所示,使用的是8bit。

第三步,加權

根據每個詞組對應的權重,對hash值做加權計算(bit為1則取為1做乘積,bit為0則取為-1做乘積),如上圖中,

  • 10011111與權重2加權得到[2 -2 -2 2 2 2 2 2];
  • 01001011與權重1加權得到[-1 1 -1 -1 1 -1 1 1];
  • 01001011與權重4加權後得到[-4 4 -4 -4 4 -4 4 4];

第三步,縱向相加:

將上述得到的加權向量結果,進行縱向相加實現降維,如上述所示,得到[-3 3 -7 -3 7 -3 7 7]。

第四步,歸一化:

將最終降維向量,對於每一位大於0則取為1,否則取為0,這樣就能得到最終的simhash的指紋簽名[0 1 0 0 1 0 1 1]

第五步,相似度比較:

通過上面的步驟,我們可以利用SimHash算法為每一個網頁生成一個向量指紋,在simhash中,判斷2篇文本的相似性使用的是海明距離。什麽是漢明距離?前文已經介紹過了。在在經驗數據上,我們多認為兩個文本的漢明距離<=3的話則認定是相似的。

實現

完整代碼見Github

func SimHashSimilar(srcWordWeighs, dstWordWeights []WordWeight) (distance int, err error) {

   srcFingerPrint, err := simhashFingerPrint(srcWordWeighs)
   if err != nil {
      return
   }
   fmt.Println("srcFingerPrint: ", srcFingerPrint)
   dstFingerPrint, err := simhashFingerPrint(dstWordWeights)
   if err != nil {
      return
   }
   fmt.Println("dstFingerPrint: ", dstFingerPrint)

   distance = hammingDistance(srcFingerPrint, dstFingerPrint)

   return
}

func simhashFingerPrint(wordWeights []WordWeight) (fingerPrint []string, err error) {
   binaryWeights := make([]float64, 32)
   for _, ww := range wordWeights {
      bitHash := strHashBitCode(ww.Word)
      weights := calcWithWeight(bitHash, ww.Weight) //binary每個元素與weight的乘積結果數組
      binaryWeights, err = sliceInnerPlus(binaryWeights, weights)
      //fmt.Printf("ww.Word:%v, bitHash:%v, ww.Weight:%v, binaryWeights: %v\n", ww.Word,bitHash, ww.Weight, binaryWeights)
      if err != nil {
         return
      }
   }
   fingerPrint = make([]string, 0)
   for _, b := range binaryWeights {
      if b > 0 { // bit 1
         fingerPrint = append(fingerPrint, "1")
      } else { // bit 0
         fingerPrint = append(fingerPrint, "0")
      }
   }

   return
}

func calcWithWeight(bitHash string, weight float64) []float64 {
   bitHashs := strings.Split(bitHash, "")
   binarys := make([]float64, 0)

   for _, bit := range bitHashs {
      if bit == "0" {
         binarys = append(binarys, float64(-1)*weight)
      } else {
         binarys = append(binarys, float64(weight))
      }
   }

   return binarys
}

結果

我們使用了調換一段長本文的語序來測試simhash的效果:

文本1:
"沈默螺旋模式中呈現出民意動力的來源在於人類有害怕孤立的弱點,但光害怕孤立不至於影響民意的形成," +
"主要是當個人覺察到自己對某論題的意見與環境中的強勢意見一致(或不一致時),害怕孤立這個變項才會產生作用。   " +
"從心理學的範疇來看,社會中的強勢意見越來越強,甚至比實際情形還強,弱勢意見越來越弱,甚至比實際情形還弱,這種動力運作的過程成–螺旋狀"

文本2:
"從心理學的範疇來看,害怕孤立這個變項才會產生作用。社會中的強勢意見越來越強,甚至比實際情形還強,弱勢意見越來越弱," +
"主要是當個人覺察到自己對某論題的意見與環境中的強勢意見一致(或不一致時),甚至比實際情形還弱,這種動力運作的過程成–螺旋狀  " +
"但光害怕孤立不至於影響民意的形成,沈默螺旋模式中呈現出民意動力的來源在於人類有害怕孤立的弱點"

通過計算,結果得到二者的指紋是一模一樣,其漢明距離為0.

srcFingerPrint:  [1 0 0 0 0 0 1 0 1 0 1 0 1 0 0 0 1 0 0 1 0 0 0 1 1 1 0 0 0 0 0 0]
dstFingerPrint:  [1 0 0 0 0 0 1 0 1 0 1 0 1 0 0 0 1 0 0 1 0 0 0 1 1 1 0 0 0 0 0 0]
--- PASS: TestSimHashSimilar (0.85s)
    similarity_test.go:57: SimHashSimilar distance: 0
PASS

適用性

註意一:

我們再來看一個文章主旨類似,但是內容相關性較低的文本比較示例:

文本1:
"關於區塊鏈和數字貨幣的關系,很多人或多或少都存在疑惑。簡單來說,區塊鏈是比特幣的底層運用,而比特幣只是區塊鏈的一個小應用而已。" +
"數字貨幣即虛擬貨幣,最早的數字貨幣誕生於2009年,其發明者中本聰為了應對經濟危機對於實體貨幣經濟的沖擊。比特幣是最早的數字貨幣,後來出現了以太幣、火幣以及萊特幣等虛擬貨幣,這些虛擬貨幣是不能用來交易的。" +
"狹義來講,區塊鏈是一種按照時間順序將數據區塊以順序相連的方式組合成的一種鏈式數據結構, 並以密碼學方式保證的不可篡改和不可偽造的分布式賬本。" +
"廣義來講,區塊鏈技術是利用塊鏈式數據結構來驗證與存儲數據、利用分布式節點共識算法來生成和更新數據、利用密碼學的方式保證數據傳輸和訪問的安全、利用由自動化腳本代碼組成的智能合約來編程和操作數據的一種全新的分布式基礎架構與計算方式。"

文本2:
"區塊鏈技術為我們的信息防偽與數據追蹤提供了革新手段。區塊鏈中的數據區塊順序相連構成了一個不可篡改的數據鏈條,時間戳為所有的交易行為貼上了一套不講課偽造的真是標簽,這對於人們在現實生活中打擊假冒偽劣產品大有裨益; " +
"市場分析指出,整體而言,區塊鏈技術目前在十大金融領域顯示出應用前景,分別是資產證券化、保險、供應鏈金融、場外市場、資產托管、大宗商品交易、風險信息共享機制、貿易融資、銀團貸款、股權交易交割。" +
"這些金融場景有三大共性:參與節點多、驗真成本高、交易流程長,而區塊鏈的分布式記賬、不可篡改、內置合約等特性可以為這些金融業務中的痛點提供解決方案。" +
"傳統的工業互聯網模式是由一個中心化的機構收集和管理所有的數據信息,容易產生因設備生命周期和安全等方面的缺陷引起的數據丟失、篡改等問題。區塊鏈技術可以在無需任何信任單個節點的同時構建整個網絡的信任共識,從而很好的解決目前工業互聯網技術領域的一些缺陷,讓物與物之間能夠實現更好的連接。"

通過計算,當我們選擇前top10高頻詞作為衡量時,結果得到二者的指紋是如下,其漢明距離為4:

srcFingerPrint:  [1 0 1 1 0 1 0 0 0 1 1 1 1 1 1 0 1 0 0 1 0 1 1 1 0 0 0 1 0 0 0 1]
dstFingerPrint:  [1 0 1 1 0 1 0 0 0 1 1 1 1 1 1 0 1 1 0 1 1 1 1 0 0 0 0 1 0 1 0 1]
--- PASS: TestSimHashSimilar (0.84s)
    similarity_test.go:58: SimHashSimilar distance: 4
PASS

當我們選擇前top50高頻詞作為衡量時,結果得到二者的指紋是如下,其漢明距離為9:

srcFingerPrint:  [1 0 1 1 0 1 0 0 0 1 1 1 1 0 0 0 1 0 0 1 0 1 1 0 1 0 0 0 0 1 0 1]
dstFingerPrint:  [1 0 1 1 0 1 0 1 0 0 1 0 1 1 0 0 1 1 0 1 1 1 1 0 0 0 0 1 0 0 0 1]
--- PASS: TestSimHashSimilar (0.83s)
    similarity_test.go:58: SimHashSimilar distance: 9
PASS

所以我們發現了一個對結果判定很重要的參數:分詞數量。在上面的示例中,當我們選擇10個分詞時,其漢明距離僅為4,幾乎符合了我們對文本相似(漢明距離3)的判斷。而隨著topN數量的增加,引入了更多的詞組,其漢明距離越來越大,這也說明了,當大文本內容出現時,選擇合適的topN分詞數量進行比較對結果的影響是十分大的。

註意二:

另外一點需要需要註意的是,simhash的優點是適用於高維度的海量數據處理,當維度降低,如短文本的相似度比較,simhash並不合適,以我們計算余弦相似度的文本為例,

S1: "為什麽我的眼裏常含淚水,因為我對這片土地愛得深沈"
S2: "我深沈的愛著這片土地,所以我的眼裏常含淚水"

得到的結果如下:

srcFingerPrint:  [1 1 1 0 1 0 0 1 1 1 0 1 0 0 0 0 1 0 1 1 1 0 1 0 1 0 1 1 0 0 1 0]
dstFingerPrint:  [1 0 1 0 0 0 1 1 0 1 1 0 1 0 0 1 0 0 0 1 1 0 1 1 1 1 1 1 0 0 1 0]
--- PASS: TestSimHashSimilar (0.86s)
    similarity_test.go:53: SimHashSimilar distance: 12
PASS

也就是結果的漢明距離為12,遠遠大於我們預定的漢明距離3,這樣的結果跟我們通過預先相似度計算出來的0.76分(相比於1分)相差很遠,可見simhash對於短文本的相似度比較還是存在一些偏差的。

參考文獻

http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/33026.pdf

https://lujiaying.github.io/posts/2018/01/Chinese-word-segmentation/

https://www.zhihu.com/question/19578687

此文已由騰訊雲+社區在各渠道發布

獲取更多新鮮技術幹貨,可以關註我們騰訊雲技術社區-雲加社區官方號及知乎機構號

從0到1,了解NLP中的文本相似度