通过源码发现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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章