闲聊机器人实例三:python实现小姜机器人(检索式chatbot_sentence_vec_by_word_词向量句向量)

        word2vec词向量构建生成句向量,再计算相似度,匹配问答库中的标准问题。

       小姜机器人、python、tensorflow、chatbot、dialog、fuzzywuzzy、检索式、生成式、聊天、闲聊、对话、问答、多轮、单轮、开放、封闭、任务、垂直等等描述,都属于自动问答领域。而且已经有了比较大的应用,常见的智能手机助手、聊天机器人、智能客服......

一.概述

        检索式chatbot,上回说到,使用字符粗帅选,然后再使用fuzzywuzzy计算问句间距离,再排序的方案,实现检索式chatbot,对于精准匹配,达成了一定的效果。

        但是你发现没有,对于长短句不一,有同义词的句子,字符匹配还是很有问题的,它解决的只是字面上的问题匹配,对于同义词,它束手无策,语义层面就比较困难了。

        或许你说用同义词表、用字典,但这是不现实的,词条辣么多,怎么可能列举出来。词条同义词列举不完,不慌,我们有词向量,有word2vec,有glove可以解决这个问题。

        没错,用词向量,我们可以轻易解决同义词问题,部分长短句问题。至于更加困难的语义问题,我们还有bert呢。在这里,推荐一下同事的github项目,计算bert文本相似度的,很棒棒的。

                                  bert计算bert句向量、相似度(github地址): https://github.com/terrifyzhao/bert-utils

        回到检索式chatbot,这次我们使用gensim的word2vec做demo,glove似乎我只找到了C++版的,训练的话,不那么方便。

二.实例

        我自己实现了一个小demo,github的地址是:

               https://github.com/yongzhuo/nlp_xiaojiang/blob/master/ChatBot/chatbot_search/chatbot_sentence_vec_by_word.py

               1.  首先要数据清洗,构建基础问答语料库;

               2.  然后jieba切词,根据预先定义好的句向量生成函数,生成语料库中问句的句向量,存起来,pkl文件等;

               3.  在使用线性搜索、kd-ball、kd-tree、annoy等索引化index,方便快速查找(demo只是简单的线性搜索);

               4.  最后问句来了直接调用就好。

        说明:该demo比较简单,我大致说一下句向量生成就好。句向量生成步骤:

                 1.  首先来一个问句, 就切词,也可以去除停用词、句子改写。例如“我喜欢你”,jieba切词为【"我", "喜欢", "你"】,

                      以及词性【"r", "v", "r"】;

                  2.  然后我们可以根据词性给词赋予权重,比如说动词名词为1.3,代词为0.7什么的。

                       例如"我","你"的词向量分别乘以0.7,喜欢的词向量乘以1.3。再把加权后得到的向量定义为句向量。

                      当然你也可以用tf-idf或者其他什么的,不赋予权重也可以。

                  3.  最后当然是存起来啦。你也可以用2中得到的句向量除以3,也就是最后还要除以句子中词的个数。

                       不除也可以,不除的话看起来泛化差些,但可以区分长短句。

    不废话了,直接上代码,当时单独使用需要下载词向量和语料,你可以下载整个github项目。

    代码:

# -*- coding: UTF-8 -*-
# !/usr/bin/python
# @time     :2019/4/4 10:00
# @author   :Mo
# @function :chatbot based search, encode sentence_vec by word


from conf.path_config import w2v_model_merge_short_path, w2v_model_wiki_word_path
from conf.path_config import projectdir, chicken_and_gossip_path
from utils.text_tools import txtRead, txtWrite, getChinese
from conf.path_config import matrix_ques_part_path
from numpy import float32 as numpy_type
from collections import Counter
import pickle, jieba, os, re
import jieba.posseg as pseg
from gensim import matutils
from math import log
import numpy as np
import gensim
import jieba
import time


def load_word2vec_model(path, bin=False, limit=None):
    print("load_word2vec_model start!")
    word2vec_model = gensim.models.KeyedVectors.load_word2vec_format(path, limit=limit, binary=bin, unicode_errors='ignore')
    print("load_word2vec_model end!")
    return word2vec_model



def get_td_idf_flag(jieba_cut_list, dictionary, tfidf_model):
    # todo
    '''获取td-idf权重,有问题,同一个词只计算一次,有的还没有,比如说停用词'''
    seg1_list = []
    vec1 = tfidf_model[dictionary.doc2bow(jieba_cut_list)]
    for vec1_one in vec1:
        seg1_list.append(vec1_one[1])
    sum_seg1_list = sum(seg1_list)

    return [x/sum_seg1_list for x in seg1_list]


def get_jieba_flag(flag):
    '''词性'''
    if flag in ['n', 'nr', 'ns', 'nt', 'nz']:
        weight = 1.3
    elif flag in ['r', 'i', 't', 'ng', 'an']:
        weight = 0.7
    else:
        weight = 1
    return weight


def word_segment_process(sentence):
    """
        jieba切词\词性
    :param sentence: 
    :return: 
    """
    sentence = sentence.replace('\n', '').replace(',', '').replace('"', '').replace(' ', '').replace('\t', '').upper().strip()
    word_list = []
    flag_list = []
    try:
        sentence_cut =  ''.join(jieba.lcut(sentence, cut_all=False, HMM=False))
        words = pseg.cut(sentence_cut)
        for word in words:
            word_list.append(word.word)
            flag_list.append(word.flag)
    except Exception as e:
        word_list = [sentence]
        flag_list = ['nt']
    return word_list, flag_list


def encoding_question(w2v_model, word_list, flag_list):
    '''    生成句子向量
    :param wordlist: 分词list
    :param is_replaced: 是否替换default true
    :param debug_mode: default false
    :return: array句子的向量 len=300
    '''
    try:
        sentence_vec = w2v_model.wv[w2v_model.index2word[1]] * 0
    except:
        sentence_vec = w2v_model.wv[w2v_model.index2word[1]] * 0

    for k in range(len(word_list)):
        word = word_list[k]
        flag = flag_list[k]
        if type(word) == str:
            try:
                sentence_vec = sentence_vec + w2v_model.wv[word] * get_jieba_flag(flag)
            except Exception as e:
                if word not in [' ', '']:
                    sentence_vec = sentence_vec + 1

    return sentence_vec


def most_similar_sentence_vec(vec_ques, matrix_org, top_vec=20):
    """
      最相似的句子,句向量与矩阵点乘
    :param vec: 
    :param matrix: 
    :param keys: 
    :param topn: 
    :return: 
    """
    # 首先对句向量矩阵标号
    matrix_org_index = list(range(len(matrix_org)))
    # Scale a vector to unit length. The only exception is the zerovector, which is returned back unchanged.
    vec_ques_mean = matutils.unitvec(np.array([vec_ques]).mean(axis=0)).astype(numpy_type)
    # matrix_org单位化
    matrix_org_norm = (matrix_org / np.sqrt((matrix_org ** 2).sum(-1))[..., np.newaxis]).astype(numpy_type)
    # 计算两个向量之间的相似度,使用numpy的dot函数,矩阵点乘
    matrix_vec_dot = np.dot(matrix_org_norm, vec_ques_mean)
    # 防止top_vec越界
    top_vec = min(len(matrix_org), top_vec)
    # 相似度排序
    most_similar_sentence_vec_sort = matutils.argsort(matrix_vec_dot, topn=top_vec, reverse=True)

    index_score = []
    for t in most_similar_sentence_vec_sort[:top_vec]:
        index_score.append([matrix_org_index[t], float(matrix_vec_dot[t])])
    return index_score


def create_matrix_org_np(sen_count, word2vec_model, qa_path, matrix_ques_path_word):
    """
      创建问题句向量,设置sen_count=10000, 防止内存不够奔溃
    :param sen_count: int, write sentence_encode num per twice
    :param word2vec_model: model
    :param qa_path: str
    :param matrix_ques_path: str
    :return: 
    """
    if os.path.exists(matrix_ques_path_word):
        file_matrix_ques = open(matrix_ques_path_word, 'rb')
        matrix_ques = pickle.load(file_matrix_ques)
        return matrix_ques
    print('create_matrix_org_pkl start!')
    qa_dail = txtRead(qa_path, encodeType='utf-8')
    # questions = []
    matrix_ques = []
    count = 0
    for qa_dail_one in qa_dail:
        ques = getChinese(qa_dail_one.split('\t')[0])
        # questions.append(ques)
        word_list, flag_list = word_segment_process(ques)
        sentence_vec = encoding_question(word2vec_model, word_list, flag_list)
        matrix_ques.append(sentence_vec)
        if len(matrix_ques)%sen_count == 0 and len(matrix_ques) != 0:
            print("count: " + str(count))
            count += 1
            np.savetxt(projectdir + "/Data/sentence_vec_encode_word/" + str(count)+".txt", matrix_ques)
            matrix_ques = []
            # break

    count += 1
    np.savetxt(projectdir + "/Data/sentence_vec_encode_word/" + str(count)+".txt", matrix_ques)
    # matrix_ques = []
    # file_matrix_ques = open(matrix_ques_path, 'wb')
    # pickle.dump(matrix_ques, file_matrix_ques)
    print('create_matrix_org_np ok!')
    # return matrix_ques


if __name__ == '__main__':
    # 读取问答语料
    syn_qa_dails = txtRead(chicken_and_gossip_path, encodeType='utf-8')

    # 读取词向量,w2v_model_wiki_word_path数据是自己训练的,w2v_model_merge_short_path只取了部分数据,你可以前往下载
    if os.path.exists(w2v_model_wiki_word_path):
        word2vec_model = load_word2vec_model(w2v_model_wiki_word_path, limit=None)
        print("load w2v_model_wiki_word_path ok!")
    else:
        word2vec_model = load_word2vec_model(w2v_model_merge_short_path, limit=None)
        print("load w2v_model_merge_short_path ok!")

    # 创建标准问答中问题的句向量,存起来,到matrix_ques_path
    if not os.path.exists(matrix_ques_part_path):
        create_matrix_org_np(sen_count=100000, word2vec_model=word2vec_model, qa_path=chicken_and_gossip_path, matrix_ques_path_word=matrix_ques_part_path)

    # 读取
    print("np.loadtxt(matrix_ques_part_path) start!")
    matrix_ques = np.loadtxt(matrix_ques_part_path)
    print("np.loadtxt(matrix_ques_part_path) end!")
    while True:
        print("你: ")
        ques_ask = input()
        ques_clean = getChinese(ques_ask)
        word_list, flag_list = word_segment_process(ques_clean)
        sentence_vic = encoding_question(word2vec_model, word_list, flag_list)
        top_20_qid = most_similar_sentence_vec(sentence_vic, matrix_ques, top_vec=20)
        try:
            print("小姜机器人: " + syn_qa_dails[top_20_qid[0][0]].strip().split("\t")[1])
            print([(syn_qa_dails[top_20_qid[i][0]].strip().split("\t")[0], syn_qa_dails[top_20_qid[i][0]].strip().split("\t")[1]) for i in range(len(top_20_qid))])
        except Exception as e:
            # 有的字符可能打不出来
            print(str(e))

 

 

 

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值