跳转至

使用余弦相似度算法计算文本相似度

在工作中一直使用余弦相似度算法计算两段文本的相似度和两个用户的相似度, 一直弄不明白多维的余弦相似度公式是怎么推导来的。今天终于花费时间把公式推导出来,其实很简单,都是高中学过的知识,只是很多年没用了,都还给老师了。本文还通过一个例子演示如果使用余弦相似度计算两段文本的相似度。

余弦函数在三角形中的计算公式为:

cosine-similarity-algorithm2ca_1.png

在直角坐标系中,向量表示的三角形的余弦函数是怎么样的呢?下图中向量a用坐标(x1,y1)表示,向量b用坐标(x2,y2)表示。

cosine-similarity-algorithm2ca_1.jpeg

向量a和向量b在直角坐标中的长度为

cosine-similarity-algorithm2ca_2.png

向量a和向量b之间的距离我们用向量c表示,就是上图中的黄色直线,那么向量c在直角坐标系中的长度为

cosine-similarity-algorithm2ca_3.png

将a,b,c带入三角函数的公式中得到如下的公式:

cosine-similarity-algorithm2ca_2.jpeg

这是2维空间中余弦函数的公式,那么多维空间余弦函数的公式就是:

cosine-similarity-algorithm2ca_3.jpeg

余弦相似度

余弦相似度量:计算个体间的相似度。相似度越小,距离越大。相似度越大,距离越小。

假设有3个物品,item1,item2和item3,用向量表示分别为:

item1[1,1,0,0,1],

item2[0,0,1,2,1],

item3[0,0,1,2,0],

即五维空间中的3个点。用欧式距离公式计算item1、itme2之间的距离,以及item2和item3之间的距离,分别是:

item1-item2=cosine-similarity-algorithm2ca_4.png

item2-item3=cosine-similarity-algorithm2ca_5.png

用余弦函数计算item1和item2夹角间的余弦值为:

cosine-similarity-algorithm2ca_4.jpeg

用余弦函数计算item2和item3夹角间的余弦值为:

cosine-similarity-algorithm2ca_5.jpeg

由此可得出item1和item2相似度小,两个之间的距离大(距离为7),item2和itme3相似度大,两者之间的距离小(距离为1)。

余弦相似度算法:一个向量空间中两个向量夹角间的余弦值作为衡量两个个体之间差异的大小,余弦值接近1,夹角趋于0,表明两个向量越相似,余弦值接近于0,夹角趋于90度,表明两个向量越不相似。

余弦相似度

余弦相似度量:计算个体间的相似度。

相似度越小,距离越大。相似度越大,距离越小。

余弦相似度算法:一个向量空间中两个向量夹角间的余弦值作为衡量两个个体之间差异的大小,余弦值接近1,夹角趋于0,表明两个向量越相似,余弦值接近于0,夹角趋于90度,表明两个向量越不相似。

下面我们介绍使用余弦相似度计算两段文本的相似度。思路:1、分词;2、列出所有词;3、分词编码;4、词频向量化;5、套用余弦函数计量两个句子的相似度。

句子A:这只皮靴号码大了。那只号码合适。

句子B:这只皮靴号码不小,那只更合适。

1、分词:

使用结巴分词对上面两个句子分词后,分别得到两个列表:

listA=[‘这‘, ‘只‘, ‘皮靴‘, ‘号码‘, ‘大‘, ‘了‘, ‘那‘, ‘只‘, ‘号码‘, ‘合适‘]

listB=[‘这‘, ‘只‘, ‘皮靴‘, ‘号码‘, ‘不小‘, ‘那‘, ‘只‘, ‘更合‘, ‘合适‘]

2、列出所有词。 将listA和listB放在一个set中,得到:

set={'不小', '了', '合适', '那', '只', '皮靴', '更合', '号码', '这', '大'}

将上述set转换为dict,key为set中的词,value为set中词出现的位置,即‘这’:1这样的形式。

dict1={'不小': 0, '了': 1, '合适': 2, '那': 3, '只': 4, '皮靴': 5, '更合': 6, '号码': 7, '这': 8, '大': 9},可以看出“不小”这个词在set中排第1,下标为0。

3、将listA和listB进行编码。 将每个字转换为出现在set中的位置,转换后为:

listAcode=[8, 4, 5, 7, 9, 1, 3, 4, 7, 2]

listBcode=[8, 4, 5, 7, 0, 3, 4, 6, 2]

我们来分析listAcode,结合dict1,可以看到8对应的字是“这”,4对应的字是“只”,9对应的字是“大”,就是句子A和句子B转换为用数字来表示。

4、对listAcode和listBcode进行oneHot编码。 就是计算每个分词出现的次数。oneHot编号后得到的结果如下:

listAcodeOneHot = [0, 1, 1, 1, 2, 1, 0, 2, 1, 1]

listBcodeOneHot = [1, 0, 1, 1, 2, 1, 1, 1, 1, 0]

下图总结了句子从分词,列出所有词,对分词进行编码,计算词频的过程

cosine-similarity-algorithm2ca_6.jpeg

5、得出两个句子的词频向量之后,就变成了计算两个向量之间夹角的余弦值,值越大相似度越高。

listAcodeOneHot = [0, 1, 1, 1, 2, 1, 0, 2, 1, 1]

listBcodeOneHot = [1, 0, 1, 1, 2, 1, 1, 1, 1, 0]

cosine-similarity-algorithm2ca_6.png

根据余弦相似度,句子A和句子B相似度很高。

代码如下:

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)

总结,例子很简单,只是比较短短两句话的相似度,算是机器学习入门的一个小案例。可以看到这是使用机器学习算法处理业务的通用步骤,先是取特征,然后特征向量化,最后代入数学公式中。

凡本网注明"来源:XXX "的文/图/视频等稿件,本网转载出于传递更多信息之目的,并不意味着赞同其观点或证实其内容的真实性。如涉及作品内容、版权和其它问题,请与本网联系,我们将在第一时间删除内容!
作者: 一二三冲鸭
来源: https://zhuanlan.zhihu.com/p/43396514