文本相似度检测之余弦相似度

为什么文本也需要余弦相似度

文本的余弦相似度是为了计算文本的相似程度而引入的一种方法,例如我们要比较这样两句话的相似程度:

A句子:你笑起来真好看。

B句子:你笑起来不好看。

这两句话,看起来很相似了吧,但是句子的意思却完全不一样,那么我们怎么去确定文本的相似度呢?我们从数学中找到了灵感。

向量的余弦表示

假设向量空间中有两个向量a和b,我们可以通过计算两个向量之间的夹角来确定两个向量的相似程度:

image-20200831165930842

当夹角θ越接近0,两个向量越相似,当a=b时,cosθ=1,放在坐标系中的表示:

image-20200831170226989

计算余弦值cosθ:

image-20200831170257397

这样我们就将矢量转化为标量进行计算了,更加便利的是,数学家已经证明,余弦的这种计算方法对n维向量也成立。假定A和B是两个n维向量,A是 [A1, A2, …, An] ,B是 [B1, B2, …, Bn] ,则A与B的夹角θ的余弦等于:

image-20200831170532645

好了到现在我们已经知道这个余弦值能够表示两个向量的相似度了,同时我们也知道我们的文本也可以转换为向量啊,这样一结合不就可以解决我们的问题了吗,来看看。

文本的余弦相似度

假设有这样两个句子,我们要构建一个词向量来表征它们,然后计算这两个向量的余弦相似度,然后看看结果:

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

根据上面n维向量的余弦计算公式,我们来计算两个句子词频向量间夹角的余弦值:

image-20200831171043621

这就是余弦相似度的计算方法了,这里还有一份代码实现,我们可以去自己跑着看看:

文本余弦相似度计算的代码实现

import jieba
import math
s1 = '我喜欢看电视,不喜欢看电影。'
s1_cut = [i for i in jieba.cut(s1, cut_all=True) if i != '']
s2 = '我不喜欢看电视,也不喜欢看电影。'
s2_cut = [i for i in jieba.cut(s2, cut_all=True) if i != '']
print(s1_cut)
print(s2_cut)
word_set = set(s1_cut).union(set(s2_cut))
print(word_set)

word_dict = dict()
i = 0
for word in word_set:
    word_dict[word] = i
    i += 1
print(word_dict)

s1_cut_code = [word_dict[word] for word in s1_cut]
print(s1_cut_code)
s1_cut_code = [0]*len(word_dict)

for word in s1_cut:
    s1_cut_code[word_dict[word]]+=1
print(s1_cut_code)

s2_cut_code = [word_dict[word] for word in s2_cut]
print(s2_cut_code)
s2_cut_code = [0]*len(word_dict)
for word in s2_cut:
    s2_cut_code[word_dict[word]]+=1
print(s2_cut_code)

# 计算余弦相似度
sum = 0
sq1 = 0
sq2 = 0
for i in range(len(s1_cut_code)):
    sum += s1_cut_code[i] * s2_cut_code[i]
    sq1 += pow(s1_cut_code[i], 2)
    sq2 += pow(s2_cut_code[i], 2)

try:
    result = round(float(sum) / (math.sqrt(sq1) * math.sqrt(sq2)), 2)
except ZeroDivisionError:
    result = 0.0
print(result)

执行结果如下:

[‘我’, ‘喜欢’, ‘看电视’, ‘电视’, ‘,’, ‘不’, ‘喜欢’, ‘看’, ‘电影’, ‘。’]
[‘我’, ‘不’, ‘喜欢’, ‘看电视’, ‘电视’, ‘,’, ‘也’, ‘不’, ‘喜欢’, ‘看’, ‘电影’, ‘。’]
{‘喜欢’, ‘。’, ‘电视’, ‘不’, ‘电影’, ‘看电视’, ‘,’, ‘我’, ‘也’, ‘看’}
{‘喜欢’: 0, ‘。’: 1, ‘电视’: 2, ‘不’: 3, ‘电影’: 4, ‘看电视’: 5, ‘,’: 6, ‘我’: 7, ‘也’: 8, ‘看’: 9}
[7, 0, 5, 2, 6, 3, 0, 9, 4, 1]
[2, 1, 1, 1, 1, 1, 1, 1, 0, 1]
[7, 3, 0, 5, 2, 6, 8, 3, 0, 9, 4, 1]
[2, 1, 1, 2, 1, 1, 1, 1, 1, 1]
0.94

思考

思考1:两个句子意思截然不同而余弦相似度很高?

可以看到上面两个句子的夹角余弦值很大,但实际上两个句子的前半句所表达的意思是截然相反的,那为什么他们的余弦相似度还那么高呢?甚至极端一点,对于下面两个句子我们按照上面的方法计算余弦相似度:

句子A:我喜欢看电视,不喜欢看电影。

句子B:我喜欢看电影,不喜欢看电视。

这两个句子的余弦相似度是1。完全一样??

出现这样的问题的原因是我们的向量是基于词频构建的,这个本身就不具备表达位置信息和时序信息的功能,在处理这样的句子时,当然也就有很大的局限性,后面我的笔记会讨论能够解决这一痛点的方法。

思考2:长度不一样但夹角很小,也判成一样?

比如下面两个句子:

句子A:我喜欢看。

句子B:我喜欢看电影,不喜欢看电视。

这两个句子的余弦相似度是0.72,也很大了,但实际上第一句并没有表达什么确切含义,这个其实原因跟第一个思考的原因是一样的,其次,里边还有一个原因,那就是cosθ本质上还是关注的是向量之间的夹角,并不关心向量的长度,所以我们常常需要特征降维、数据规范化等前期处理。


后续继续探究其他文本相似度检测的方法,看看各个方法之间的差异和优缺点。


计算中文文本相似度有哪些好用的算法?

NLP文本相似度(TF-IDF)

TF-IDF与余弦相似性的应用(二):找出相似文章

  • 9
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值