本篇的参考文献主要有以下两篇
卞世博,阎志鹏.“答非所问”与IPO市场表现——来自网上路演期间的经验证据[J].财经研究,2020,46(01):49-63.
Charlet D, Damnati G. Simbow at semeval-2017 task 3: Soft-cosine semantic similarity between questions for community question answering[A]. Proceedings of the 11th international workshop on semantic evaluation ( SemEval-2017)[C]. Vancouver,Canada:Association for Computational Linguistics,2017.
此外,也参考了这篇博客对于软余弦相似度的描述,便于理解:Soft Cosine Measure
这是文献里对软余弦相似度的描述,说明软余弦相似度在问答数据的相关性表示中优于传统的余弦相似度
本篇依据卞世博和阎志鹏(2020)的文献逐步复现。
准备工作:下载语料库,将繁体字改为简体字,训练模型
基本完全按照下方链接的前两部分进行。
https://zhuanlan.zhihu.com/p/577512827
为尊重原作者,这里就不把步骤粘贴过来了。总计可能用时4个小时。
注意,这里我对第二部分的模型训练代码做了调整,把向量维度改成了300(是因为文献里这么做),其他参数不做更改保持默认。
model = Word2Vec(LineSentence(wiki_news),vector_size=300)#设置词向量维度为300,其他参数保持默认,训练Word2Vec模型
最终得到基于维基百科语料库训练的Word2Vec模型
计算软余弦相似度
忽略上述链接里的第三步,直接复制下方代码,更改文件路径和sheet名称即可
对应的代码解释以注释方式标注,方便理解。
需要说明,不知道是作者疏忽还是我的理解有误,在我的代码里,下图中这里词语频率的范围应该是[0,n],因为Q和A矩阵里需要包含Q和A里所有不重复词语,在问句或回答里不可能出现所有不重复词语,所以频率是可能为0的。上边的博客里写的基向量也是含有0的。
如果是我理解有误,请不要使用下方代码,并希望各位大佬提出,我必定改正。
import numpy as np
from gensim.models import KeyedVectors
import jieba
from sklearn.metrics.pairwise import cosine_similarity
import math
import pandas as pd
def cal_softcos(Q,A,model):
# 进行jieba分词,运用精确模式
Q_list = jieba.lcut(Q, cut_all=False)
A_list = jieba.lcut(A, cut_all=False)
# 去除停用词和全数字词语
# 由于技术问题,模型中可能不包含某些低频词语的向量,因此需要把这些删去,否则就会报错
stopwords = {}.fromkeys([ line.rstrip() for line in open('stopwords.txt',encoding='utf-8') ]) # 加载停用词表
Qwords = []
for word in Q_list:
if word not in stopwords and word.isdigit()==False :
try:
model.wv.most_similar(word) # 调用模型,检验是否会报错,不具备实际意义
Qwords.append(word) # 如果不报错,就可以运行这一行
except:
pass
Awords = []
for word in A_list:
if word not in stopwords and word.isdigit()==False :
try:
model.wv.most_similar(word) # 调用模型,检验是否会报错,不具备实际意义
Awords.append(word) # 如果不报错,就可以运行这一行
except:
pass
# 构建不重复词语的列表,列表长度即为文献中的n
QAwords=Qwords+Awords
QAwords_set=set(QAwords)
QAwords_set_list=list(QAwords_set)
N=len(QAwords_set_list)
# 构建Q矩阵
Qnums = [] #存放Qwords中词语在QAwords中出现的频率
for Qword in Qwords:
num=0
for QAword in QAwords:
if Qword==QAword:
num+=1
Qnums.append(num)
Q=[0]*N #将出现频率,对应到1*N矩阵中,保存为Q
for Qword,Qnum in zip(Qwords,Qnums):
index=QAwords_set_list.index(Qword)
Q[index]=Qnum
Q_array = np.array(Q) #转化为numpy形式
Q_arrayT = Q_array.T #转置
#构建A矩阵
Anums = [] #存放Awords中词语在QAwords中出现的频率
for Aword in Awords:
num=0
for QAword in QAwords:
if Aword==QAword:
num+=1
Anums.append(num)
A=[0]*N #将出现频率,对应到1*N矩阵中,保存为A
for Aword,Anum in zip(Awords,Anums):
index=QAwords_set_list.index(Aword)
A[index]=Anum
A_array = np.array(A) #转化为numpy形式
A_arrayT = A_array.T #转置
# 构建M矩阵
simFrame=[] # 存放每个simList
for word1 in QAwords_set_list:
simList=[] # 创建一个新列表,存放每个词语结果
for word2 in QAwords_set_list:
sim=model.wv.similarity(word1,word2) # 直接调用similarity方法,计算两个词语之间的相关程度
# 按照文献说法,小于零则为0,大于0则取平方
if sim<=0:
sim=0
else:
sim=sim*sim
simList.append(sim)
simFrame.append(simList)
M_array = np.array(simFrame) #转化为矩阵
#根据计算公式,进行矩阵运算
fenzi=Q_arrayT@M_array@A_array
fenmu1=math.sqrt(Q_arrayT@M_array@Q_array)
fenmu2=math.sqrt(A_arrayT@M_array@A_array)
return fenzi/(fenmu1*fenmu2)
if __name__ == '__main__':
model = KeyedVectors.load('vectors.bin') # 加载训练好的Word2Vec模型
df=pd.read_excel('用户与公司问答-2020_2.xlsx',sheet_name='用户与公司问答-2020_2') # 自行输入文件地址和sheet名称
row=0
for Q,A in zip(df['Qsubj'],df['Reply']):
softcos=cal_softcos(Q,A,model)
df['softcos'][row]=softcos
row+=1
print(softcos)
df.to_excel('softcosine_result.xlsx')