通過源碼發現nltk.Text.similar相似度衡量標準

1. 如何用nltk來找到text中相似的word

如果我們想搜索某一篇文章(text)中相似的詞(word),可以使用nltk這個強大的NLP模塊。下面以nltk自帶的shakespeare數據集來做示例。

第一次使用nltk,需要先運行下面的代碼來下載shakespeare數據集。

import nltk
nltk.download('shakespeare')

然後,我們就可以加載shakespeare數據集來做實驗了:

import  nltk
text = nltk.Text(word.lower() for word in nltk.corpus.shakespeare.words(('hamlet.xml')))

只要使用nltk.Text.similar(word),就能找到text中跟某個word相似的其他所有word了,如下示例

text.similar('woman')

我們就能得到與woman相似的所有詞:

matter at eaten but fit this to vulcan by like servant disclose
follows twice laertes it cat such sin

可以看到,這些輸出的詞中,有的確實與woman有一點點相似,比如“vulcan”和“servant”。但還有大量的詞是與woman沒有任何關係的,比如“twice”。

那問題來了,nltk.Text.similar是根據什麼來衡量兩個詞的相似度呢?

2. nltk.Text.similar源碼

網上並沒有太多關於nltk.Text.similar的原理解釋,所以只有查閱源碼,才能看到細節。

nltk.Text.similar的源碼在https://github.com/nltk/nltk/blob/develop/nltk/text.py,其中有一個函數similar(self, word, num=20),這就是nltk.Text.similar的實現,核心代碼如下:

def similar(self, word, num=20):
    """
    Distributional similarity: find other words which appear in the
    same contexts as the specified word; list most similar words first.
    :param word: The word used to seed the similarity search
    :type word: str
    :param num: The number of words to generate (default=20)
    :type num: int
    :seealso: ContextIndex.similar_words()
    """
    word = word.lower()# 統一轉換爲小寫進行比較
    wci = self._word_context_index._word_to_contexts
    if word in wci.conditions():
        # 根據上下文context相同來查找其它詞
        contexts = set(wci[word])
        fd = Counter(
            w
            for w in wci.conditions()
            for c in wci[w]
            if c in contexts and not w == word
        )
        # most_common是根據出現次數從到到低來輸出
        words = [w for w, _ in fd.most_common(num)]
    print(tokenwrap(words))

從源碼中,我們可以看到, nltk.Text.similar是用Distributional similarity來衡量兩個word是否相似。Distributional similarity的做法,首先找到與給定詞(the specified word)具有相同上下文(same context)的所有詞,然後根據這些詞的出現次數,按出現次數從高到低依次輸出(most similar words first)。

舉個例子,假如有如下一篇文檔:

C3 W4 C4
C1 W3 C2
C1 W3 C2
C1 W3 C2
C1 W2 C2
C1 W2 C2
C1 W1 C2
C1 W C2

與詞W有相同上下文(C1 X C2)的,是詞W1,W2,W3。但W3出現了3次,W2出現了2次,所以W3先輸出,W2後輸出。

通過閱讀nltk.Text.similar源碼,我們理解了它判斷兩個word是否相似,是根據上下文來判斷,而非我們人理解的相似度。因爲nltk.Text.similar根據上下文,只能找到相似,或不相似的詞,所以也就無法做量化的相似度衡量(比如W1與W2相似度爲0.8)。

我們再來人爲構建幾個例子,通過實例深入理解nltk.Text.similar。

3. 上下文similar實例

通過如下代碼,我們可以找到語料s中與boy相似的其它詞。

import nltk

s = '''A B C boy D E F G
A B C dog D E F G
A B C cat D E F G
A A A man B B B B
'''

tokens = nltk.word_tokenize(s)
text = nltk.Text(tokens)

text.similar('boy')

可以得到結果爲“cat dog”,這個容易理解,應爲“cat”和“dog”的上下文(A B C X D E F G)與“boy”相同。預料中的“man”雖然邏輯上與“boy”應該更具有相似性,但應爲nltk.Text.similar是根據上下文來判斷相似性,而不是根據邏輯來判斷相似性,所以輸出結果中沒有“man”。

根據源碼中similar()的定義similar(self, word, num=20),我們還發現similar()的另一個參數num,它默認是20,指的是輸出20個與給定詞相似的詞,我們將本例中的代碼更改爲num=1,即text.similar('boy',1),則輸出只有1個詞“cat”。

本例中,boy的上下文爲"A B C" 與 “D E F G”,那similar()在進行相似度搜索是,是完全根據"A B C" 與 "D E F G"都相同纔算相似詞嗎?還是上下文有一個搜索長度的限制,比如只考慮boy之前的3個單詞和與之後的3個單詞?我發現源碼中並無類似的參數,所以設計瞭如下實驗來進行驗證。

4. 上下文的長度是多少?

下面是改動了各個詞相似的上下文(單詞)長度,用來測試上下文長度的例子(查找語料s中與boy相似的詞):

import nltk

s = '''A B C boy D E F G
C dog D 
A B B cat D E F G
A A c man d B B B
'''

tokens = nltk.word_tokenize(s)
text = nltk.Text(tokens)
text.similar('boy')

代碼的輸出是“dog man”,所以我們得到兩個結論

  1. 上下文,只考慮待搜索詞的前一個詞和後一個詞,即boy(上下文爲C X D)與dog(上下文爲C X D)相似

  2. 上下文搜索時,不考慮大小寫,即boy(上下文爲C X D)與man(上下文爲c X d)相似

5. 參考

  1. How to load and analysis corpus shakespeare

  2. Understand nltk.Text.similar
  3. nltk source code here
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章