学科分类
目录
数据分析

文本相似度

在自然语言处理中,经常会涉及到度量两个文本的相似性的问题,在诸如信息检索、数据挖掘、机器翻译、文档复制检测等领域中,如何度量句子或短语之间的相似度显得尤为重要。

文本相似度的衡量计算主要包括如下三种方法:

(1) 基于关键字匹配的传统方法,比如N-gram相似度。

(2) 将文本映射到向量空间,再利用余弦相似度等方法进行计算。

(3) 基于深度学习的方法,比如卷积神经网络的ConvNet、用户点击数据的深度学习语义匹配模型DSSM等。

随着深度学习的发展,文本相似度的方法已经逐渐不再是基于关键词匹配的传统方法,而是转向了深度学习,目前结合向量的深度学习使用较多,因此,这里我们采用第二种方式来计算文本的相似度,一般实现的步骤如下。

(1) 通过特征提取的模型或手动实现,找出这两篇文章的关键词。

(2) 从每篇文章中各取出若干个关键词(比如10个),把这些关键词合并成一个集合,然后计算每篇文章中各个词对于这个集合中的关键词的词频。为了避免文章长度的差异,可以使用相对词频。

(3) 生成两篇文章中各自的词频向量。

(4) 计算两个向量的余弦相似度,值越大则表示越相似。

我们都知道,文本是一种高维的语义空间,要想计算两个文本的相似度,可以先将它们转化为向量,站在数学角度上去量化其相似性,这样就比较简单了。那么,如何把文本转化成向量呢?一般,我们会使用词频(某一个给定词语在文档中出现的次数)来表示文本特征,若某个词在这些文本中出现的次数最多,则表示这个单词比较具有代表性。

例如,现在有如下两个英文句子:

John likes to watch movies.

John also likes to watch football games.

要想找出上述两个句子中的关键词,则需要先统计一下每个单词出现的次数。为此,NLTK库中提供了一个FreqDist类,主要负责记录每个词出现的次数。FreqDist类的结构比较简单,可以用一个有序词典实现,所以dict的方法在此类中也是适用的。例如,使用FreqDist类统计上述英文句子中每个单词的词频,具体代码如下。

In [29]: import nltk
         from nltk import FreqDist
         text1 = 'John likes to watch movies'
         text2 = 'John also likes to watch football games'
         all_text = text1 +" " + text2
         # 分词
         words = nltk.word_tokenize(all_text)
         # 创建FreqDist对象,记录每个单词出现的频率
         freq_dist = FreqDist(words)
         freq_dist
Out[29]: FreqDist({'John': 2, 'likes': 2, 'to': 2, 'watch': 2, 
                   'movies': 1, 'also': 1, 'football': 1, 'games': 1})

上述输出了创建的FreqDist对象,它的里面是一个字典结构,其中字典的键为分词后的单词,字典的值为该词出现的频率。例如,获取字典里面单词“John”出现的频率,代码如下。

In [30]: freq_dist['John']
Out[30]: 2

这里将从这些单词中选出出现频率最高的若干个单词,构造成关键词列表。如果希望达到这个目的,则需要调用FreqDist类的most_common()方法,返回出现次数比较频繁的词与频率。

例如,从freq_dist中取出出现频率最高的五个单词,代码如下。

In [31]: # 取出n个常用的单词
         n = 5
         # 返回常用单词列表
         most_common_words = freq_dist.most_common(n)
         most_common_words
Out[31]: [('John', 2), ('likes', 2), ('to', 2), ('watch', 2), ('movies', 1)]

选出关键词之后,我们需要记录一下这些单词的位置,为了简单起见,这里采用的是从字典中遍历取出每个单词,第一个单词的位置赋值为1,第二个单词的位置赋值为2,依此类推。

例如,查找常用单词的位置的示例如下。

# 查找常用单词的位置
In [32]: def find_position(common_words):  
             result = {}
             pos = 0
             for word in common_words:
                 result[word[0]] = pos
                 pos += 1
             return result
         # 记录常用单词的位置
         pos_dict = find_position(most_common_words)
         pos_dict
Out[32]: {'John': 0, 'likes': 1, 'to': 2, 'watch': 3, 'movies': 4}

上述示例输出了关键词与位置的字典,其中,“John”对应着位置0,“likes”对应着位置1……由此表明,根据这些位置可以确定一个关键词列表,也就是得到一个向量为['John', 'likes', 'to', 'watch', 'movies']。

如果句子中的某个单词存在于关键词列表中,就在关键词所在的位置上标记一次,结果为记录总次数,对于没有出现的单词则记为0即可,从而构成了一个词频列表。定义一个函数参照着关键词列表统计单词的词频,并以列表的形式进行返回,具体代码如下。

In [33]: def text_to_vector(words):
             '''
             将文本转换为词频向量
             '''
             # 初始化向量
             freq_vec = [0] * n
             # 在“常用单词列表”上计算词频
             for word in words:
                 if word in list(pos_dict.keys()):
                     freq_vec[pos_dict[word]] += 1
             return freq_vec

将文本text1和text2转化为词频向量,示例代码如下。

In [34]: # 词频向量
         vector1 = text_to_vector(nltk.word_tokenize(text1))
         vector1

Out[34]: [1, 1, 1, 1, 1]
In [35]: vector2 = text_to_vector(nltk.word_tokenize(text2))
         vector2
Out[35]: [1, 1, 1, 1, 0]

现在只剩下最后一步,利用余弦相似度来比较两个向量的相似度。余弦相似度,又称为余弦相似性,通过计算两个向量的夹角余弦值来评估它们的相似度。计算两个向量的余弦相似度公式如下:

img

求得两个向量的夹角,并得出夹角对应的余弦值,此余弦值就可以用来表征这两个向量的相似性,如图1所示。

http://7xnc4x.com1.z0.glb.clouddn.com/%E4%BD%99%E5%BC%A6%E7%9B%B8%E4%BC%BC%E5%BA%A6.png

图1 求余弦值图例

余弦取值范围为[-1,1]。夹角越小,趋近于0度,余弦值越接近于1,它们的方向更加吻合,则越相似。当两个向量的方向完全相反,夹角余弦取最小值-1。当余弦值为0时,两向量正交,夹角为90度。由此可以看出,余弦相似度与向量的幅值无关,只与向量的方向相关。

NLTK库中提供了余弦相似度的实现函数cosine_distance(),位于cluster.util模块中,所以这里需要使用import引入该模块,之后在调用cosine_distance()函数时只要传入两个向量值就行。

例如,求两个词频向量的夹角余弦值,代码如下。

In [36]: from nltk.cluster.util import cosine_distance
         cosine_distance(vector1, vector2)
Out[36]: 0.10557280900008414

从输出的余弦值可以看出,它接近于0,表明向量vector1和vector2正交,夹角接近于90度。由此表明,text1和text2文本的相似度并不高。

点击此处
隐藏目录