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”,所以我們得到兩個結論
-
上下文,只考慮待搜索詞的前一個詞和後一個詞,即boy(上下文爲C X D)與dog(上下文爲C X D)相似
-
上下文搜索時,不考慮大小寫,即boy(上下文爲C X D)與man(上下文爲c X d)相似