相关了解可以参考下面的博客:
/weixin_40771521/article/details/103893982
提出问题:如何计算中文句子的相似度
本文使用的是CBOW模型,通过负采样减少计算量
1.先给出框架
2.对数据做预处理(数据末尾有链接data)
运行pre_process.py文件
##pre_process.py###1.生成样本数据:每一句有效词w2v_words.pkl 2.词表(词:序号)w2v_vocab.pklimport jiebaimport pickle as pkldef pre_process():# 加载数据with open('../data/w2v_msr_training.utf8', 'r', encoding='utf-8') as f:#8864行含有空格 引号 回车的句子tmp = f.readlines()text_pure = []for i in tmp:#处理前:'接连 亮相 的 全 都是 票友 , 生 旦 净 丑 , 行当 俱全 , 流派 纷呈 。'#去掉引号,回车,空格t1 = i.replace('“ ', '')t2 = t1.replace('\n', '')t3 = t2.replace(' ', '')#处理后:'接连亮相的全都是票友,生旦净丑,行当俱全,流派纷呈。'text_pure.append(t3)#text_pure有8844行纯文本的句子with open('../data/w2v_stopwords.txt', 'r', encoding='utf-8') as f:stopwords = [line.strip() for line in f.readlines()]#stopwords有1893个停用词# 去除停用词 构词表words_ = []vocab = set()for sen in text_pure:#'接连亮相的全都是票友,生旦净丑,行当俱全,流派纷呈。'words = jieba.lcut(sen) #['接连', '亮相', '的', '全都', '是', '票友', ',', '生旦净', '丑', ',', '行当', '俱全', ',', '流派', '纷呈', '。']temp = []for wd in words:if wd not in stopwords:temp.append(wd)vocab.add(wd)#只要不是停用词,就加入词的集合中,{'流派', '丑', '票友', '生旦净', '俱全', '接连', '行当', '纷呈', '亮相'}words_.append(temp)#[['接连', '亮相', '票友', '生旦净', '丑', '行当', '俱全', '流派', '纷呈'],# ['没想到', '车', '碰上', '一位', '作家', '耽误', '几分钟', '进场', '一看', '座无虚席', '二楼', '东侧', '好不容易', '找到', '角度', '最次', '空座', '时', '主持人', '李世英', '报', '第三个', '登台演唱', '名字']]#words_有8844行,每一行为每一句的有效词的列表#vocab有28938个有效词,集合具有无序性vocab_dist = {}for i, wd in enumerate(list(vocab)):vocab_dist[wd] = i#生成词和序号的键值对{'菲利普': 0, '第十五届': 1, '元': 2, '但求无过': 3, '献上': 4, '坐': 5, '今春': 6, '咖啡馆': 7, '带出': 8,# '宗介华': 9, '辞去': 10, '腰椎': 11, '责令': 12, '支部': 13, '分类': 14, '泊车': 15, '动物': 16, '沈阳铁路局': 17,共28938对return words_, vocab_distif __name__ == '__main__':words_, vocab_ = pre_process()#将words_(有8844行,每一行为每一句的有效词的列表)写入/data/w2v_words.pkl文件中with open('../data/w2v_words.pkl', 'wb') as f:pkl.dump(words_, f)#将vocab_dist(由28938个有效词及其序号构成的键值对)写入/data/w2v_words.pkl文件中with open('../data/w2v_vocab.pkl', 'wb') as f:pkl.dump(vocab_, f)
得到data文件夹变化如下:
3.CBOW模型实现
# coding=utf8#model of generating CBOWimport torchimport torch.nn as nnimport torch.nn.functional as Fimport numpy as npclass CBOW(nn.Module):def __init__(self, vocab_size, embedding_dim, hidden_dim):super(CBOW, self).__init__()#将vocab_size个字,每一个字都表示为embedding_dim维张量self.embedding = nn.Embedding(vocab_size, embedding_dim)self.vocab_size = vocab_size#通过第一个隐藏层将每个字仿射为hidden_dim维张量self.layer1 = nn.Linear(embedding_dim, hidden_dim)#通过输出层将hidden_dim维张量仿射为vocab_size维张量self.layer2 = nn.Linear(hidden_dim, vocab_size)def forward(self, inputs, target):#每一个字都是64维的表示,输入的是待预测值的前两个字和后两个字,一共就是4 * 64 ,sum(0)就是列向相加,得到1*64#再/4,即计算平均值embed = self.embedding(inputs).sum(0) / 4hidden_o = F.relu(self.layer1(embed)) # 非线性变换output_ = self.layer2(hidden_o) # 仿射变换#包含标签的21个序号Nec_list = self.__random__(target, 20)#设置一个词表大小(28938)的全True张量temp = torch.ones_like(output_, dtype=torch.bool)for i in Nec_list:#将上面包含标签的21个序号设置为Falsetemp[i] = False#将词表大小的输出张量output_里面下标对应temp中的序号为True的全设置为负无穷-inf#负采样操作,减少计算压力masked_out = output_.masked_fill(temp, -np.inf)out = F.softmax(masked_out, dim=0).view(1, -1)# out = F.softmax(output_, dim=0).view(1, -1)return outdef __random__(self, target, num_random):#随机选出小于词表大小的20个序号,并且这20个序号不含目标序号rand_list = np.random.randint(self.vocab_size, size=num_random)if target in rand_list:#如果标签序号在其中,就再随机取一个,直到集齐20个非标签的序号while True:temp = np.random.randint(self.vocab_size, size=1)if temp != target:rand_list = np.append(rand_list, temp)breakelse:rand_list = np.append(rand_list, target)return rand_list
4.生成词袋模型 .bin文件,方便后面词嵌入时调用
##generate_cbow_bin.py###Through the training sample set, the continuous word bag model is generated#The role of the word bag model is to predict the key words from the contextimport torchimport torch.nn as nnimport torch.optim as optfrom tqdm import tqdmfrom models.CBOW import CBOWimport pickle as pkldef train(words, vocab):torch.manual_seed(1)vocab_size = len(vocab)train_cbow = []for line in words:#['接连', '亮相', '票友', '生旦净', '丑', '行当', '俱全', '流派', '纷呈']for i in range(2, len(line) - 2):#([['接连', '亮相'], ['生旦净', '丑']], '票友')-->([['亮相', '票友'], ['丑', '行当']], '生旦净')#先以下标为2的词为中心,窗口大小为5 中心右移直到倒数第三个词,每一个窗口都得到一个元组类型temp = ([[line[i - 2], line[i - 1]], [line[i + 1], line[i + 2]]], line[i])train_cbow.append(temp)#每一句都这样滑动,得到由79792个元组构成的的列表hidden_dim = 32embedding_dim = 64losses = []#初始化模型model = CBOW(vocab_size, embedding_dim, hidden_dim)#定义损失函数为交叉熵损失loss_fun = nn.CrossEntropyLoss()#使用梯度下降法更新参数optimizer = opt.SGD(model.parameters(), lr=3e-2)for i in tqdm(range(10)):total_loss = 0#目标是用上下文预测中心词for context, target in tqdm(train_cbow):#context——[['接连', '亮相'], ['生旦净', '丑']] target——'票友'context_indexes = []for t in context:context_indexes.extend([vocab[t[0]], vocab[t[1]]])#将列表转变为张量context_indexes = torch.tensor(context_indexes, dtype=torch.long)## 将模型的参数梯度初始化为0model.zero_grad()#prob--tensor([[0., 0., 0., ..., 0., 0., 0.]],out[0][8662] -- tensor(0.0386)prob = model(context_indexes, vocab[target])#true_label -- tensor([8662]) --> tensor([26834])true_label = torch.tensor([vocab[target]], dtype=torch.long)loss = loss_fun(prob, true_label) # 预测值 真实值#反向传播计算梯度#tensor(10.2344, grad_fn=<NllLossBackward0>) --> tensor(10.2322, grad_fn=<NllLossBackward0>)loss.backward()optimizer.step()#每一轮的总损失,可以用来评价这一轮的效果total_loss += loss.item()#最后我们保存了运行了10轮的连续词袋模型,torch.save(model, '../data/w2v_CBOW.bin')if __name__ == '__main__':with open('../data/w2v_words.pkl', 'rb') as f:_words = pkl.load(f)with open('../data/w2v_vocab.pkl', 'rb') as f:_vocab = pkl.load(f)train(_words, _vocab)
运行这个文件大概要2~3个小时:(这里给出.bin文件的链接)
运行完毕后,data文件夹变化如下:
5.计算句子的相似度
##CbowSenTest.py###Calculate the cosine similarity between sentences when the word bag model is used to represent sentencesimport torchimport pickle as pklimport jieba#输入一组词的序号张量,使得句子中的每一个词得到embedding_dim维的张量表示def get_word_embed(sentence):#先下载训练好的模型的参数model = torch.load('../data/w2v_CBOW.bin')embed = model.embedding(sentence)wd_tensors = model.layer1(embed)#输出32维的张量return wd_tensorsdef test():sentences1 = '海啸发生在当地时间17日晚8时许。'sentences2 = '这次海啸是由在该国北海岸发生的一次里氏7级海底地震引发的。'sentences3 = '韩日元贬值还使东南亚国家的出口严重受挫。'sens = [sentences1, sentences2, sentences3]word1 = '贬值'word2 = '总裁'word3 = '总统'words_sim = [word1, word2, word3]with open('../data/w2v_stopwords.txt', 'r', encoding='utf-8') as f:stopwords = [line.strip() for line in f.readlines()]with open('../data/w2v_vocab.pkl', 'rb') as f:#词表大小为28938_vocab = pkl.load(f)words_ = []for sen in sens:words = jieba.lcut(sen)temp = []for wd in words:if wd not in stopwords:temp.append(wd)words_.append(temp)#words_ = [ ['海啸', '发生', '时间', '日晚', '时许'],# ['海啸', '该国', '北海岸', '发生', '里氏', '级', '海底', '地震', '引发'],# ['韩', '日元', '贬值', '东南亚', '国家', '出口', '受挫'] ]sens_id = []for sen in words_:sen_id = []for wd in sen:sen_id.append(_vocab[wd])#sens_id记录了sens中的有效词的序号sens_id.append(sen_id)words_tensor = torch.tensor([_vocab[wd] for wd in words_sim], dtype=torch.long)sens_tensor = [torch.tensor(idx, dtype=torch.long) for idx in sens_id]#得到每一个词的张量word_embed = get_word_embed(words_tensor)sen_embed = []for s in sens_tensor:len_s = len(s)#一个句子有len_s个词,即有len_s*32的张量s_1 = get_word_embed(s)#将句子变为1*32维的张量,每一列取平均值,得到一个句子的32维张量表示s_2 = s_1.sum(0)/len_ssen_embed.append(s_2)sim_sen1 = torch.cosine_similarity(sen_embed[0].view(1, -1), sen_embed[1].view(1, -1))sim_sen2 = torch.cosine_similarity(sen_embed[1].view(1, -1), sen_embed[2].view(1, -1))sim_wd1 = torch.cosine_similarity(word_embed[0].view(1, -1), word_embed[2].view(1, -1))sim_wd2 = torch.cosine_similarity(word_embed[1].view(1, -1), word_embed[2].view(1, -1))print('句子1和句子2的相似度是:{}'.format(sim_sen1.item()))print('句子2和句子3的相似度是:{}'.format(sim_sen2.item()))print('词语1和词语2的相似度是:{}'.format(sim_wd1.item()))print('词语2和词语3的相似度是:{}'.format(sim_wd2.item()))odis = torch.nn.PairwiseDistance(p=2)odis_sen1 = odis(sen_embed[0].view(1, -1), sen_embed[1].view(1, -1))odis_sen2 = odis(sen_embed[0].view(1, -1), sen_embed[2].view(1, -1))odis_wd1 = odis(word_embed[0].view(1, -1), word_embed[2].view(1, -1))odis_wd2 = odis(word_embed[1].view(1, -1), word_embed[2].view(1, -1))print('句子1和句子2的欧式距离是:{}'.format(odis_sen1.item()))print('句子2和句子3的欧氏距离是:{}'.format(odis_sen2.item()))print('词语1和词语2的欧氏距离是:{}'.format(odis_wd1.item()))print('词语2和词语3的欧氏距离是:{}'.format(odis_wd2.item()))if __name__ == '__main__':test()
输出结果如下:
资源
w2v_CBOW.bin:
链接:/s/12hhablJ2q-34ZySjrYiPTQ
提取码:2933
data:
链接:/s/1i0H8ULcqDH4c8OyTmbJgzQ
提取码:2933
如果觉得《连续词袋模型(CBOW)计算句子相似度(余弦相似度和欧氏距离)》对你有帮助,请点赞、收藏,并留下你的观点哦!