Python簡單實現基於VSM的余弦相似度計算


        在知識圖譜構建階段的實體對齊和屬性值決策、判斷一篇文章是否是你喜歡的文章、比較兩篇文章的相似性等實例中,都涉及到了向量空間模型(Vector Space Model,簡稱VSM)和余弦相似度計算相關知識。
        這篇文章主要是先敘述VSM和余弦相似度相關理論知識,然后引用阮一峰大神的例子進行解釋,最后通過Python簡單實現百度百科和互動百科Infobox的余弦相似度計算。

一. 基礎知識

        第一部分參考我的文章: 基於VSM的命名實體識別、歧義消解和指代消解

        第一步,向量空間模型VSM
        向量空間模型(Vector Space Model,簡稱VSM)表示通過向量的方式來表征文本。一個文檔(Document)被描述為一系列關鍵詞(Term)的向量。
        簡言之,判斷一篇文章是否是你喜歡的文章,即將文章抽象成一個向量,該向量由n個詞Term組成,每個詞都有一個權重(Term Weight),不同的詞根據自己在文檔中的權重來影響文檔相關性的重要程度。
        Document = { term1, term2, …… , termN }
        Document Vector = { weight1, weight2, …… , weightN }

 
        其中ti(i=1,2,...n)是一列相互之間不同的詞,wi(d)是ti在d中對應的權值。
        選取特征詞時,需要降維處理選出有代表性的特征詞,包括人工選擇或自動選擇。

        第二步,TF-IDF
        特征抽取完后,因為每個詞語對實體的貢獻度不同,所以需要對這些詞語賦予不同的權重。計算詞項在向量中的權重方法——TF-IDF。
        它表示TF(詞頻)和IDF(倒文檔頻率)的乘積:
        詞頻(Term Frequency,簡稱TF)表示特征詞出現的次數除以文章總詞數:
        其中TF表示某個關鍵詞出現的頻率,IDF為所有文檔的數目除以包含該詞語的文檔數目的對數值。
        |D|表示所有文檔的數目,|w∈d|表示包含詞語w的文檔數目。
        由於“是”“的”“這”等詞經常會出現,故需要IDF值來降低其權值。所謂降維,就是降低維度。具體到文檔相似度計算,就是減少詞語的數量。常見的可用於降維的詞以功能詞和停用詞為主(如:"的","這"等),事實上,采取降維的策略在很多情況下不僅可以提高效率,還可以提高精度。
        最后TF-IDF計算權重越大表示該詞條對這個文本的重要性越大。

        第三步,余弦相似度計算
        這樣,就需要一群你喜歡的文章,才可以計算IDF值。依次計算得到你喜歡的文章D=(w1, w2, ..., wn)共n個關鍵詞的權重。當你給出一篇文章E時,采用相同的方法計算出E=(q1, q2, ..., qn),然后計算D和E的相似度。
        計算兩篇文章間的相似度就通過兩個向量的余弦夾角cos來描述。文本D1和D2的相似性公式如下:
        其中分子表示兩個向量的點乘積,分母表示兩個向量的模的積。
        計算過后,就可以得到相似度了。我們也可以人工的選擇兩個相似度高的文檔,計算其相似度,然后定義其閾值。同樣,一篇文章和你喜歡的一類文章,可以取平均值或尋找一類文章向量的中心來計算。主要是將語言問題轉換為數學問題進行解決。
        缺點:計算量太大、添加新文本需要重新訓練詞的權值、詞之間的關聯性沒考慮等。其中余弦定理為什么能表示文章相似度間參考資料。

 

二. 實例解釋

        第二部分主要參考阮一峰大神的個人博客,舉例解釋VSM實現余弦相似度計算,強烈推薦大家去閱讀阮神的博客:TF-IDF與余弦相似性的應用
        此部分為轉載,阮神舉了一個簡單的例子(后面第三部分是相對復雜的例子):

  句子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]

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

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

    余弦值越接近1,就表明夾角越接近0度,也就是兩個向量越相似,這就叫"余弦相似性"。所以,上面的句子A和句子B是很相似的,事實上它們的夾角大約為20.3度。
    由此,我們就得到了"找出相似文章"的一種算法:

(1)使用TF-IDF算法,找出兩篇文章的關鍵詞;
(2)每篇文章各取出若干個關鍵詞(比如20個),合並成一個集合,計算每篇文章對於這個集合中的詞的詞頻(為了避免文章長度的差異,可以使用相對詞頻);
(3)生成兩篇文章各自的詞頻向量;
(4)計算兩個向量的余弦相似度,值越大就表示越相似。

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

        PS:這部分內容完全照搬阮神的博客,因為真的講得通俗易懂,我都有點愛不釋手了。如果覺得版權不妥之處,我可以刪除,同時推薦大家閱讀他的更多文章。
        阮一峰個人博客鏈接:http://www.ruanyifeng.com/home.html

 

三. 代碼實現

        最后就簡單講解我的Python實現百度百科和互動百科關於消息盒InfoBox的相似度計算。其中爬蟲部分參考我的博客:
        [Python爬蟲] Selenium獲取百度百科旅游景點的InfoBox消息盒



        我已經通過Selenium爬取了所有“國家5A級景區”的InfoBox消息盒,並使用開源分詞工具進行了分詞處理,“故宮”數據如下所示:

        計算“百度百科-故宮”和“互動百科-故宮”的消息盒相似度代碼如下。基本步驟:
        1.分別統計兩個文檔的關鍵詞,讀取txt文件,CountKey()函數統計
        2.兩篇文章的關鍵詞合並成一個集合MergeKey()函數,相同的合並,不同的添加
        3.計算每篇文章對於這個集合的詞的詞頻 TF-IDF算法計算權重,此處僅詞頻
        4.生成兩篇文章各自的詞頻向量
        5.計算兩個向量的余弦相似度,值越大表示越相似 
  1 # -*- coding: utf-8 -*-
  2 import time          
  3 import re          
  4 import os
  5 import string
  6 import sys
  7 import math
  8 
  9 
 10 ''' ------------------------------------------------------- '''
 11 #統計關鍵詞及個數
 12 def CountKey(fileName, resultName):
 13     try:
 14         #計算文件行數
 15         lineNums = len(open(fileName,'rU').readlines())
 16         print u'文件行數: ' + str(lineNums)
 17 
 18         #統計格式 格式<Key:Value> <屬性:出現個數>
 19         i = 0
 20         table = {}
 21         source = open(fileName,"r")
 22         result = open(resultName,"w")
 23         
 24         while i < lineNums:
 25             line = source.readline()
 26             line = line.rstrip('\n')
 27             print line
 28             
 29             words = line.split(" ")  #空格分隔
 30             print str(words).decode('string_escape') #list顯示中文
 31             
 32             #字典插入與賦值
 33             for word in words:
 34                 if word!="" and table.has_key(word):      #如果存在次數加1
 35                     num = table[word]
 36                     table[word] = num + 1
 37                 elif word!="":                            #否則初值為1
 38                     table[word] = 1
 39             i = i + 1
 40 
 41         #鍵值從大到小排序 函數原型:sorted(dic,value,reverse)
 42         dic = sorted(table.iteritems(), key = lambda asd:asd[1], reverse = True)
 43         for i in range(len(dic)):
 44             #print 'key=%s, value=%s' % (dic[i][0],dic[i][1])
 45             result.write("<"+dic[i][0]+":"+str(dic[i][1])+">\n")
 46         return dic
 47         
 48     except Exception,e:    
 49         print 'Error:',e
 50     finally:
 51         source.close()
 52         result.close()
 53         print 'END\n\n'
 54 
 55 
 56 ''' ------------------------------------------------------- '''
 57 #統計關鍵詞及個數 並計算相似度
 58 def MergeKeys(dic1,dic2):
 59     #合並關鍵詞 采用三個數組實現
 60     arrayKey = []
 61     for i in range(len(dic1)):
 62         arrayKey.append(dic1[i][0])       #向數組中添加元素
 63     for i in range(len(dic2)):       
 64         if dic2[i][0] in arrayKey:
 65             print 'has_key',dic2[i][0]
 66         else:                             #合並
 67             arrayKey.append(dic2[i][0])
 68     else:
 69         print '\n\n'
 70     
 71     test = str(arrayKey).decode('string_escape')  #字符轉換
 72     print test
 73 
 74     #計算詞頻 infobox可忽略TF-IDF
 75     arrayNum1 = [0]*len(arrayKey)
 76     arrayNum2 = [0]*len(arrayKey)
 77     
 78     #賦值arrayNum1
 79     for i in range(len(dic1)):     
 80         key = dic1[i][0]
 81         value = dic1[i][1]
 82         j = 0
 83         while j < len(arrayKey):
 84             if key == arrayKey[j]:
 85                 arrayNum1[j] = value
 86                 break
 87             else:
 88                 j = j + 1
 89 
 90     #賦值arrayNum2
 91     for i in range(len(dic2)):     
 92         key = dic2[i][0]
 93         value = dic2[i][1]
 94         j = 0
 95         while j < len(arrayKey):
 96             if key == arrayKey[j]:
 97                 arrayNum2[j] = value
 98                 break
 99             else:
100                 j = j + 1
101     
102     print arrayNum1
103     print arrayNum2
104     print len(arrayNum1),len(arrayNum2),len(arrayKey)
105 
106     #計算兩個向量的點積
107     x = 0
108     i = 0
109     while i < len(arrayKey):
110         x = x + arrayNum1[i] * arrayNum2[i]
111         i = i + 1
112     print x
113 
114     #計算兩個向量的模
115     i = 0
116     sq1 = 0
117     while i < len(arrayKey):
118         sq1 = sq1 + arrayNum1[i] * arrayNum1[i]   #pow(a,2)
119         i = i + 1
120     print sq1
121     
122     i = 0
123     sq2 = 0
124     while i < len(arrayKey):
125         sq2 = sq2 + arrayNum2[i] * arrayNum2[i]
126         i = i + 1
127     print sq2
128     
129     result = float(x) / ( math.sqrt(sq1) * math.sqrt(sq2) )
130     return result
131     
132 
133 ''' ------------------------------------------------------- 
134     基本步驟:
135         1.分別統計兩個文檔的關鍵詞
136         2.兩篇文章的關鍵詞合並成一個集合,相同的合並,不同的添加
137         3.計算每篇文章對於這個集合的詞的詞頻 TF-IDF算法計算權重
138         4.生成兩篇文章各自的詞頻向量
139         5.計算兩個向量的余弦相似度,值越大表示越相似                             
140     ------------------------------------------------------- '''
141 #主函數
142 def main():
143     #計算文檔1-百度的關鍵詞及個數
144     fileName1 = "BaiduSpider.txt"
145     resultName1 = "Result_Key_BD.txt"
146     dic1 = CountKey(fileName1, resultName1)
147     
148     #計算文檔2-互動的關鍵詞及個數
149     fileName2 = "HudongSpider\\001.txt"
150     resultName2 = "HudongSpider\\Result_Key_001.txt"
151     dic2 = CountKey(fileName2, resultName2)
152 
153     #合並兩篇文章的關鍵詞及相似度計算
154     result = MergeKeys(dic1, dic2)
155     print result
156 
157 
158 if __name__ == '__main__':
159     main()

 

        其中由於只需要計算InfoBox消息盒的相似度,不會存在一些故不需要計算TF-IDF值,通過詞頻就可以表示權重,在代碼中簡單添加循環后,可以計算百度百科的“故宮”與互動百科不同實體的相似度,運行結果如下所示,可以發現“北京故宮”和“故宮”相似度最高。這也是簡單的實體對齊。
 
        希望文章對你有所幫助,尤其是代碼部分。如果文章中有錯誤或不足之處,還請海涵~畢竟作者自己也還在學習當中,如果有關於實體對齊和屬性對齊的好方法和實現代碼,也可以推薦給我,3Q。
        最后是參考和推薦一些相關的文章關於VSM和余弦相似度計算:
        TF-IDF與余弦相似性的應用(一):自動提取關鍵詞 - 阮一峰
        TF-IDF與余弦相似性的應用(二):找出相似文章 - 阮一峰
        Lucene學習之計算相似度模型VSM(Vector Space Model)
        VSM向量空間模型對文本的分類以及簡單實現 - java
        話說正確率、召回率和F值 - silence1214
        向量空間模型(VSM) - wyy_820211網易博客
        向量空間模型(VSM)的余弦定理公式(cos) - live41
        向量空間模型文檔相似度計算實現(C#)- felomeng
        向量空間模型(VSM)在文檔相似度計算上的簡單介紹 - felomeng
        隱馬爾科夫模型學習總結.pdf - a123456ei
        向量空間模型VSM - ljiabin
(By:Eastmount 2015-11-18 深夜5點   http://blog.csdn.net/eastmount/)  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM