NLP文本相似度(word2vec)的原理及实现

word2vec的原理及实现方式、训练优化方式前文已做详细介绍,这里不再累赘。这里主要记录NLP文本相似度(word2vec)怎样实现的及实现结果。

怎样实现:在问答系统(KBQA)中输入一个query,比如query为“贪污公款被捕后要还钱吗”?是怎么匹配到知识库中的最相近的一条问题并返回相对应的答案呢?

利用word2vec进行句子相似度计算,是先将输入query,进行分词,把目标句子的各个词的词向量进行相加取平均,从而把任意长的句子表示为固定维度的向量,然后计算两句子词嵌入之间的余弦相似度,进行相似度比较。当然,这么做虽然忽略了句子中的词顺序,但可以作为baseline简单衡量句子相似度计算以及词向量的训练效果。

一般情况下,在平均词嵌入之间求余弦相似性的word2vec方法表现得非常好,据了解比词移距离(Word Mover’s Distance)效果要好。

语料:爬取的问答语料

word2vec实现步骤:全局参数设置、文本预处理、训练词向量、计算相似度、得到最后的文本结果。

全局参数设置:

​主要设置编码方式,文件存储位置,word2vec参数设置。

数据预处理

训练之前需要对文本进行预处理:

  • 读取csv,提取问题及其答案,分词并按行保存到文件中,方便训练词向量。
  • 去停用词,这里由于要保留上下文信息,停用词大都包括标点符号及其他特殊字符。
#!/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))

这里使用默认的训练参数,几个代表性的参数:

  • 词向量的维度size=150
  • 上下文窗口window=5
  • CBOW模型、负采样

代码中首先用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欠钱不还会坐牢吗
*********************************************

小结

这里简单对语料文本进行词向量的训练,并直接通过词序列向量求和取平均作为句子的向量表示,进行句子相似度的计算。这种方式简单粗暴,对于像文章中的短句,而且歧义少的文本有不错的效果。如果目标文本是长文,并且需要考虑词顺序,句子顺序的情况,这种简单的方法很难适应。

 

你可能感兴趣的:(NLP,文本相似度)