赞
踩
随机取出两个词向量A和B,A的词向量表示为[A1,A2,A3…An],B的词向量表示为[B1,B2,B3…Bn]。对于词向量A和B,其欧式距离计算公式如下:

对于词向量A和B,其余弦相似度计算公式如下:

词向量可以表示单词间的内在联系,即两个单词的词向量通过计算可以得出其关联程度,词向量相似度计算公式主要有:欧氏距离、余弦相似度、Jaccard距离、编辑距离等几种,本文主要介绍欧式距离与余弦相似度。
首先我们提出两个问题:
接下来我们对此进行解析。
根据word2vec的同义可替换原则,某两个单词的上下文单词相同,训练出来的这两个单词的词向量也相近,所以可以使用欧式距离和余弦相似度表示词汇之间的关联程度。
以下面两个句子为例,采用skip-gram方法训练模型:
1.The cat stayed well out of range of the children.
2.The dog stayed well out of range of the children.
用cat和dog作为数据分别预测the、stayed、well 这三个单词,当反向传播更新参数时,因为其预测的单词相同,更新参数后其词向量也会更加相似。
某两个词汇的词向量欧式距离转换如下:

如果设它们都是单位向量,则

我们神经网络训练的过程就是最小化其欧式距离,最大化其余弦值,即我们可以使用欧式距离和余弦值表示单词相似度。
欧式距离注重两个词向量位置的差异,余弦相似度更看重两个词向量在方向上的差异。
如下图所示,欧式距离dist(A,B)表示词向量的空间距离,余弦cosθ显示词向量在方向上的差距。

举个例子:
X和Y用户对两个内容评分,按5分制,X用户对内容1和内容2的评分分别为1和4,Y用户对内容1和内容2的评分分别为2和5。令A=[1,2],B=[4,5]。此时dist(A,B)=4.24,在5分制的评级中,该距离较大,表示用户对内容1和2的总体看法区别较大。cosθ=0.99,余弦相似度较大,表示两个用户的偏好基本一致,即相对于内容1,两个用户都稍加喜欢内容2。
(因为本人电脑配置的显卡为1050ti,如此庞大的数据,我的电脑无法计算出其欧式距离,所以这里只计算其余弦相似度)
原始计算公式为:
我们的任务是找出每个词最相似的100个词,即我们的任务是计算每个词与另外40万个词(同时计算自身的相似度,作为检验)的余弦值,并取其最大的100个余弦值对应的词汇作为最终结果进行保存。计算某个词与另外40w个词的余弦相似度时,其
∑
i
=
1
n
(
A
i
)
2
\sqrt {\sum\nolimits_{i = 1}^n {{{({A_i})}^2}} }
∑i=1n(Ai)2
值是相同的,并不影响计算结果的排序,所以我们计算过程中可以将其省略。
即计算公式变更为:

对于40万 x 40万级别的运算量,本人无法一次性对其进行矩阵计算(gpu显存不足),所以多处使用分块的思想。
每个单词用长度为300的词向量进行表示,所以总数据可以表示为[40w,300]大小的矩阵。
总体运算为:[40w,300]大小的词向量矩阵与其转置矩阵相乘,生成大小为[40w,40w]的矩阵,此矩阵再除以每个词向量对应的
∑
i
=
1
n
(
B
i
)
2
\sqrt {\sum\nolimits_{i = 1}^n {{{({B_i})}^2}} }
∑i=1n(Bi)2
值,再计算并取出每一行最大的100个数及其对应词表中的单词,最后将这100个单词保存到本地。
分块计算过程如下:
import torch import pandas as pd class Similarity(): def __init__(self,filename): self.book=1#用于后期存储文件时的命名 self.device='cuda' if torch.cuda.is_available() else 'cpu'#如果电脑有gpu则使用gpu,否则使用cpu self.filename=filename#文件名 self.word=[]#保存40w词序列 self.take_word()#获得字符序列 def take_word(self):#字符序列获取函数 self.frword = open(self.filename,'r',encoding='UTF-8')#frword:读取数据文件 for lineWord in self.frword.readlines(): self.word.append(lineWord.split(" ")[0])#获取40w词序列 self.frword.close()#关闭文件 def serch_and_save(self):#存储数据 self.nearWord=[]#保存相似词 #对于任意单词,其余弦最大值为本身,所以取101个数,第一个数为其本身。 for line in range(0,self.seqResult.size()[0],50):#每次取50个单词进行排序,共取8次 #此处可考虑使用大顶堆代替argsort函数,减少时间复杂度.pytorch内核代码为c++编写,且考虑到并行机制,单纯使用python语言写堆排序求topk反而会降低求解速度(本人亲测) self.sortedResult=torch.argsort(self.seqResult[line:line+50],dim=1,descending=True)[:,0:101] for i in range(self.sortedResult.size()[0]): self.midWord=[] for j in range(self.sortedResult.size()[1]): self.midWord.append(self.word[self.sortedResult[i,j]]) self.nearWord.append(self.midWord) self.data= pd.DataFrame(self.nearWord, columns=["原始单词"]+["第%d相似"%(i) for i in range(1,len(self.nearWord[0]))])#形成列表文件 self.data.to_csv('./data%d.csv'%(self.book),columns=None)#保存数据 self.book+=1 """ 该问题可归纳为40w*300和300*40w大小的矩阵相乘,再除以||B||,然后取矩阵每一行最大的100个数字的问题 40w*40w的矩阵乘法,先将其切分为1000个400*40w的矩阵乘法, 对每一个400*40w的矩阵乘法,再将其切分为40个400*1w的矩阵乘法 记左矩阵为A,右矩阵为B """ def calculation(self): self.frA = open(self.filename,'r',encoding='UTF-8')#frA:读取A矩阵文件 self.lengthA=0#记录A矩阵的长度 self.seqA=[]#存储A矩阵的数据 for lineA in self.frA.readlines(): self.seqA.append([float(i) for i in lineA.split(" ")[1:]])#读取一列数据,即读取某一字符的全部数据 self.lengthA+=1 if self.lengthA%400==0:#左矩阵每取400词的数据,进行1次400*40w的矩阵乘法,取出该400词中最相似(即计算结果最大)的100词 self.lengthB=0#记录B矩阵的长度 self.seqB=[] self.seqResult=torch.tensor([])#存储400个字符的所有余弦值,用于后期求最相似的100个词 self.seqA=torch.tensor(self.seqA).to(self.device) self.frB = open(self.filename,'r',encoding='UTF-8')#frB:读取B矩阵文件 for lineB in self.frB.readlines(): self.seqB.append([float(i) for i in lineB.split(" ")[1:]]) self.lengthB+=1 if self.lengthB%10000==0:#右矩阵每取10000个数据进行一次400*1w的矩阵运算,总共运算40次,即最终为400*40w的矩阵 self.seqB=torch.tensor(self.seqB).to(self.device) self.tranB=torch.transpose(self.seqB,0,1)#进行转置,用于A*B #接下来几步计算:||B||(因为只需求最相似的100个词,对于同一个词,其||A||是相同的,所以不用除以||A||) self.mulB=self.tranB*self.tranB#平方 self.sumB=torch.sum(self.mulB,dim=0)#相加 self.sqrtB=torch.sqrt(self.sumB)#开方 if self.seqResult.size()[0]==0: self.seqResult=torch.mm(self.seqA,self.tranB)/self.sqrtB#矩阵相乘后除以||B|| else: self.mid=torch.mm(self.seqA,self.tranB)/self.sqrtB #通过拼接,不断存入400*1w的运算结果,最终形成400*40w的矩阵 self.seqResult=torch.cat((self.seqResult,self.mid),1) self.seqB=[]#用于取新值,运算结果已经保存到self.seqResult self.frB.close() if self.lengthA%4000==0:#每取4000个词,保存其中800个词,共将80000个词保存到100个文件上 self.serch_and_save()#保存数据 self.seqA=[]#归0,重新取400个词 torch.cuda.empty_cache()#释放显存 self.frA.close() if __name__== '__main__': s_word=Similarity('data\glove.6B.300d.txt') s_word.calculation()
运行完成后,取出部分结果进行查看。对于原始单词,计算出的相似度较大的单词与原始单词实际上关联也比较密切,甚至不少单词与原始单词只是单复数的区别。

1、https://www.zhihu.com/question/361999946/answer/1019671032(公式理解)
2、http://www.cxyzjd.com/article/qq_28851503/97616249(计算方法讲解)
3、https://www.cnblogs.com/bymo/p/8489037.html(欧式距离和余弦相似度比较)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。