word2vec的原理及实现方式、训练优化方式前文已做详细介绍,这里不再累赘。这里主要记录NLP文本相似度(word2vec)怎样实现的及实现结果。
怎样实现:在问答系统(KBQA)中输入一个query,比如query为“贪污公款被捕后要还钱吗”?是怎么匹配到知识库中的最相近的一条问题并返回相对应的答案呢?
利用word2vec进行句子相似度计算,是先将输入query,进行分词,把目标句子的各个词的词向量进行相加取平均,从而把任意长的句子表示为固定维度的向量,然后计算两句子词嵌入之间的余弦相似度,进行相似度比较。当然,这么做虽然忽略了句子中的词顺序,但可以作为baseline简单衡量句子相似度计算以及词向量的训练效果。
一般情况下,在平均词嵌入之间求余弦相似性的word2vec方法表现得非常好,据了解比词移距离(Word Mover’s Distance)效果要好。
语料:爬取的问答语料
word2vec实现步骤:全局参数设置、文本预处理、训练词向量、计算相似度、得到最后的文本结果。
全局参数设置:
主要设置编码方式,文件存储位置,word2vec参数设置。
数据预处理
训练之前需要对文本进行预处理:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
#停用词+分词+数据预处理
import GrobalParament
import jieba
import pandas as pd
#去掉回车行
def delete_r_n(line):
return line.replace('\r','').replace('\n','').strip()
#读取停用词
def get_stop_words(stop_words_dir):
stop_words = []
with open(stop_words_dir,'r',encoding=GrobalParament.encoding) as f_reader:
for line in f_reader:
line = delete_r_n(line)
stop_words.append(line)
stop_words = set(stop_words)
return stop_words
#jieba精准分词
def jieba_cut(content,stop_words):
word_list = []
if content != '' and content is not None:
seg_list = jieba.cut(content)
for word in seg_list:
if word not in stop_words:
word_list.append(word)
return word_list
#结巴搜索引擎分词
def jieba_cut_for_search(content,stop_words):
word_list = []
if content != '' and content is not None:
seg_list = jieba.cut_for_search(content)
for word in seg_list:
if word not in stop_words:
word_list.append(word)
return word_list
#清理不在词汇表中的词语
def clear_word_from_vocab(word_list,vocab):
new_word_list = []
for word in word_list:
if word in vocab:
new_word_list.append(word)
return new_word_list
#文本预处理第一种方法Pandas
def preprocessing_text_pd(text_dir,after_process_text_dir,stop_word_dir):
stop_words = get_stop_words(stop_word_dir)
sentences = []
df = pd.read_csv(text_dir)
for index,row in df.iterrows():
print(index)
title = delete_r_n(row['title'])
word_list = jieba_cut(title,stop_words)
df.loc[index,'title'] = ' '.join(word_list)
sentences.append(word_list)
df.to_csv(after_process_text_dir,encoding=GrobalParament.encoding,index=False)
return sentences
#文本预处理第二种方法
def preprocessing_text(text_dir,after_process_text_dir,stop_word_dir):
stop_words = get_stop_words(stop_word_dir)
sentences = []
f_writer = open(after_process_text_dir,'w',encoding=GrobalParament.encoding)
# count = 0
with open(text_dir,'r',encoding=GrobalParament.encoding) as f_reader:
for line in f_reader:
line_list = line.split(",")
if len(line_list) == 2:
line_list2 = delete_r_n(line_list[1])
word_list = jieba_cut(line_list2,stop_words)
sentences.append(word_list)
f_writer.write(line_list[0] + "," + " ".join(word_list) + '\n')
f_writer.flush()
# count =count + 1
# print(count)
f_writer.close()
return sentences
if __name__ == "__main__":
stop_words =get_stop_words(GrobalParament.stop_word_dir)
# print(len(stop_words))
# print(stop_words)
# sentences = preprocessing_text(GrobalParament.test_set_dir, GrobalParament.test_after_process_text_dir,
# GrobalParament.stop_word_dir)
# print(sentences[:10])
sentences = preprocessing_text(GrobalParament.test_set_dir,GrobalParament.test_after_process_text_dir,GrobalParament.stop_word_dir)
print(sentences[:10])
代码中首先读取语料库文件data/train.csv和data/test.csv,提取目标文本,预处理后保存到data/train_after_process.csv和data/test_after_process.csv,
训练模型:
在理解词向量的训练原理和相关参数设置后,实现部分利用开源工具,过程相对简单。该部分使用gensim对上面预处理后的文本进行词向量的训练:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
#训练模型
import GrobalParament
import utils
from gensim.models import word2vec
def train(sentences,model_out_put_path):
print('开始训练')
model = word2vec.Word2Vec(sentences=sentences,size=GrobalParament.train_size,window=GrobalParament.train_window,min_count=1)
model.save(model_out_put_path)
print('训练完成')
if __name__ == "__main__":
# sentences = utils.preprocessing_text(GrobalParament.train_set_dir,GrobalParament.train_after_process_text_dir,GrobalParament.stop_word_dir)
# train(sentences,GrobalParament.model_output_path)
model = word2vec.Word2Vec.load(GrobalParament.model_output_path)
vocab = list(model.wv.vocab.keys())
for e in model.most_similar(positive=['借贷'],topn=5):
print(e[0],e[1])
print(len(vocab))
这里使用默认的训练参数,几个代表性的参数:
代码中首先用LineSentence将语料文本中的内容按行读入为词序列作为训练数据。接着对语料库进行5次迭代,并取词的min_count为1,即所有词汇都参与训练。最后保存训练好的模型。
相似度计算:
这里进行简单测试,从所有问题集作为目标句子,然后从读取测试集中的问题,从目标句子中匹配相似度前五的5个问题。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
#计算相似度
import GrobalParament
import utils
from gensim.models.word2vec import Word2Vec
def calc_sim(model_path,train_path,test_path,out_result_path):
model = Word2Vec.load(model_path)
# vocab = list(model.wv.vocab.keys())
#改进
vocab = set(model.wv.vocab.keys())
# print(len(vocab))
# print(vocab[:20])
#计算相似度
f_write = open(out_result_path,'w',encoding=GrobalParament.encoding)
with open(test_path,'r',encoding=GrobalParament.encoding) as f_test_reader:
f_test_reader.readline()
for test_line in f_test_reader:
# print(test_line)
test_line = utils.delete_r_n(test_line)
test_line_list = test_line.split(',')
print('测试集合:' + test_line_list[0])
test_word_list = test_line_list[1].split()
test_word_list = utils.clear_word_from_vocab(test_word_list,vocab)
sim_score = dict()
with open(train_path,'r',encoding=GrobalParament.encoding) as f_train_reader:
f_train_reader.readline()
for train_line in f_train_reader:
train_line = utils.delete_r_n(train_line)
train_line_list = train_line.split(',')
if len(train_line_list) == 2:
print('训练集合:' + train_line_list[0])
train_word_list = train_line_list[1].split()
train_word_list = utils.clear_word_from_vocab(train_word_list,vocab)
if len(train_word_list) > 0:
sim_score[train_line_list[0]] = model.n_similarity(test_word_list,train_word_list)
sim_score = sorted(sim_score.items(),key=lambda d:d[1],reverse=True)
#print(sim_score[:5])
print('开始计算前5个最相似的')
train_doc_num = ''
for items in sim_score[:5]:
train_doc_num = train_doc_num + items[0] + ' '
f_write.write(test_line_list[0] + ','+train_doc_num.strip() + '\n')
f_write.flush()
f_write.close()
if __name__ =="__main__":
calc_sim(GrobalParament.model_output_path,GrobalParament.train_after_process_text_dir,GrobalParament.test_after_process_text_dir,GrobalParament.result_out_path)
load训练好的模型,利用n_similarity进行两个词序列的相似度计算,方法的输入参数是测试集csv文件中 句子分词后的词序列。
得到最后的文本结果:
将前一步骤得到相似结果的id转化为真正所对应的文本结果。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
#最终结果
import GrobalParament
import utils
#构建文档字典
def build_dict(file_path):
doc_dict = dict()
with open(file_path,'r',encoding=GrobalParament.encoding) as f_reader:
for line in f_reader:
line = utils.delete_r_n(line)
line_list = line.split(',')
if len(line_list) == 2:
doc_dict[line_list[0]] = line_list[1]
return doc_dict
def sim_result_out(sim_out_path,test_dict,train_dict,result_path):
f_writer = open(result_path,'w',encoding=GrobalParament.encoding)
with open(sim_out_path,'r',encoding=GrobalParament.encoding) as f_reader:
for line in f_reader:
line = utils.delete_r_n(line)
line_list = line.split(',')
if len(line_list) == 2:
test_docid = line_list[0]
sim_result = test_docid + ',' + test_dict[test_docid] + '\n'+'***最相似的前20个***\n'
train_docid_list = line_list[1].split()
for id in train_docid_list:
sim_result = sim_result+ id + ',' +train_dict[id]+ '\n'
f_writer.write(sim_result)
f_writer.write('**********************************************\n')
f_writer.close()
if __name__ =="__main__":
train_dict =build_dict(GrobalParament.train_set_dir)
test_dict = build_dict(GrobalParament.test_set_dir)
sim_result_out(GrobalParament.result_out_path,test_dict,train_dict,GrobalParament.sim_result_path)
随机测试几个问题所得到的结果:
1,贪公款被捕后要还钱吗
***最相似的前5个***
1883,贪公款被捕后要还钱吗
3988,借了别人的钱还不起会坐牢吗
1847,贪污公款坐牢还需要还钱吗
1383,借债人突然去世钱还能要回来吗?
3263,碰到无赖亲戚不还钱怎么办
*********************************************
2,贪污公款坐牢还需要还钱吗
***最相似的前5个***
1847,贪污公款坐牢还需要还钱吗
1883,贪公款被捕后要还钱吗
24257,催人还钱,可以用这四大招
6853,捡到的东西多少钱以上构成犯罪
7197,遇离谱“好心人” 捡钱包只还证件不还钱
*********************************************
3,2019欠钱不还能告他吗
***最相似的前5个***
3301,2019欠钱不还能告他吗
44459,2015欠钱不还能告他吗
44728,2015别人欠钱不还怎么办
44643,2015欠钱不还可以报警吗
44658,2015欠钱不还会坐牢吗
*********************************************
小结
这里简单对语料文本进行词向量的训练,并直接通过词序列向量求和取平均作为句子的向量表示,进行句子相似度的计算。这种方式简单粗暴,对于像文章中的短句,而且歧义少的文本有不错的效果。如果目标文本是长文,并且需要考虑词顺序,句子顺序的情况,这种简单的方法很难适应。