• 基于检索的智能问答


    基于检索的智能问答。目前使用了简单词汇对比、词性权重、词向量3种相似度计算模式。输入符合格式的QA文本文件即可立刻使用。

    程序版本和依赖库

    使用 python3 运行
    jieba 分词使用的库
    gensim  词向量使用的库,如果使用词向量vec模式,则需要载入

    依赖的文件

    如果使用词向量vec模式,需要下载3个文件:Word60.model,Word60.model.syn0.npy,Word60.model.syn1neg.npy
    下载地址:http://pan.baidu.com/s/1kURNutT 密码:1tq1

    QA文件

    QA文件包含想要告知用户的问答内容。
    QA文件必须是UTF-8的无bom格式的文本文件。

    注释:注释文字由#开头。(整个一行都是注释内容)

    问答块格式如下:
    【问题】问题标题(可以有1或多个,至少有1个。必须由"【问题】"开头。)
    答案内容(可以有多行,必须紧跟着上面的【问题】,多行答案中间不能有空白的行。)
    多个问答块之间可以用空白行分割

    主程序qa.py

    直接运行该文件,即可进行问答。你可以载入自己的QA文件,请保证QA文件格式正确。
    robot.answer(inputtxt,'simple_POS') 可得出输入问题的返回答案。
    simType参数有如下模式:
    simple:简单的对比相同词汇数量,得到句子相似度
    simple_POS:简单的对比相同词汇数量,并对词性乘以不同的权重,得到句子相似度
    vec:用词向量计算相似度,并对词性乘以不同的权重,得到句子相似度
    all:调试模式,把以上几种模式的结果都显示出来,方便对比和调试

    utils.py

    import logging
    from os.path import join, dirname
    
    
    POS_WEIGHT = {
        "Ag": 1,  # 形语素
        "a": 0.5,  # 形容词
        "ad": 0.5,  # 副形词
        "an": 1,  # 名形词
        "b": 1,  # 区别词
        "c": 0.2,  # 连词
        "dg": 0.5,  # 副语素
        "d": 0.5,  # 副词
        "e": 0.5,  # 叹词
        "f": 0.5,  # 方位词
        "g": 0.5,  # 语素
        "h": 0.5,  # 前接成分
        "i": 0.5,  # 成语
        "j": 0.5,  # 简称略语
        "k": 0.5,  # 后接成分
        "l": 0.5,  # 习用语
        "m": 0.5,  # 数词
        "Ng": 1,  # 名语素
        "n": 1,  # 名词
        "nr": 1,  # 人名
        "ns": 1,  # 地名
        "nt": 1,  # 机构团体
        "nz": 1,  # 其他专名
        "o": 0.5,  # 拟声词
        "p": 0.3,  # 介词
        "q": 0.5,  # 量词
        "r": 0.2,  # 代词
        "s": 1,  # 处所词
        "tg": 0.5,  # 时语素
        "t": 0.5,  # 时间词
        "u": 0.5,  # 助词
        "vg": 0.5,  # 动语素
        "v": 1,  # 动词
        "vd": 1,  # 副动词
        "vn": 1,  # 名动词
        "w": 0.01,  # 标点符号
        "x": 0.5,  # 非语素字
        "y": 0.5,  # 语气词
        "z": 0.5,  # 状态词
        "un": 0.3  # 未知词
    }
    
    
    def get_logger(name, logfile=None):
        """
        name: logger 的名称,建议使用模块名称
        logfile: 日志记录文件,如无则输出到标准输出
        """
        formatter = logging.Formatter(
            '[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d] %(message)s',
            datefmt='%m/%d/%Y %I:%M:%S'
        )
    
        if not logfile:
            handler = logging.StreamHandler()
        else:
            handler = logging.FileHandler(logfile)
    
        handler.setFormatter(formatter)
    
        logger = logging.getLogger(name)
        logger.addHandler(handler)
        logger.setLevel(logging.DEBUG)
    
        return logger
    
    
    def similarity(a, b, method='simple', pos_weight=None, embedding=None):
        """a 和 b 是同类型的可迭代对象,比如都是词的 list"""
        if not a or not b:
            return 0
    
        pos_weight = pos_weight or POS_WEIGHT
        if method == 'simple':
            # 词重叠率
            return len(set(a) & set(a)) / len(set(a))
    
        elif method == 'simple_pos':
            sim_weight = 0
            for word, pos in set(a):
                sim_weight += pos_weight.get(pos, 1) if word in b else 0
    
            total_weight = sum(pos_weight.get(pos, 1) for _, pos in set(a))
            return sim_weight / total_weight if total_weight > 0 else 0
    
        elif method == 'vec' and embedding:
            # 词向量+词性权重
            sim_weight = 0
            total_weight = 0
            for word, pos in a:
                if word not in embedding.wv.index2word:
                    continue
    
                # 词性权重
                cur_weight = pos_weight.get(pos, 1)
                # 最大的词向量相似度
                max_word_sim = max(embedding.similarity(bword, word) for bword in b)
                # 词性权重*最大的词向量相似度
                sim_weight += cur_weight * max_word_sim
                # 词性权重之和
                total_weight += cur_weight
    
            # 返回 词性权重*最大的词向量相似度/词性权重之和
            return sim_weight / total_weight if total_weight > 0 else 0
    
    

    qs_a.txt

    【问题】我己签约怎么没有放款?
    【问题】已经签约什么时候放款
    【问题】签约成功什么时候放款
    【问题】你好,我昨天4.20签约的,款怎么一直没有到?
    【问题】请问签约了要多久放款
    【问题】签约后,还需要等多长时间
    【问题】签约后多久下款
    【问题】我想问一下,签约到放款要多久
    【问题】什么时候放款
    签约之后总部会对您的合同进行最后一个环节审核,审核都通过才会放款。签约后审核的时效为1-3个工作日左右
    
    
    【问题】提前还款
    【问题】我要提前还款
    【问题】申请提前还款
    【问题】我想了解提前还款
    【问题】如何提前还款
    【问题】提前还款怎么办 我想提前还款,应该怎么操作
    提前还款1.还款日前三个工作日与客户经理联系2.利息截止到当期,服务费减免 您要办理提前结清,您提前三个工作日联系门店,在您还款日前后办理不了
    
    
    【问题】你好初审额度已经出面签也签了还需要等多久
    【问题】审批结果要多久?
    【问题】提交申请了多久审核
    【问题】请问审核需要多久?
    【问题】审核一般要几天
    【问题】撒时候放款
    【问题】审核总共有几个环节
    客户审批流程需要经过三个环节:第一环节材料审核(资料齐全,符合标准,且不需要实地征信)时效需要3个工作日左右;第二环节面审(签署合同),时效1个工作日左右;第三环节合同审核,时效1-3个工作日左右。
    
    

    qa.py

    import os
    import time
    import logging
    from collections import deque
    
    import jieba
    import jieba.posseg as pseg
    
    from utils import get_logger
    from utils import similarity
    
    jieba.dt.tmp_dir = "./"
    jieba.default_logger.setLevel(logging.ERROR)
    logger = get_logger('qa', logfile="qa.log")
    
    
    class Repository(object):
        """
        知识库类
        a是答案(必须是1给), q是问题(1个或多个)
        用以存放处理之后的知识库形式
        """
        def __init__(self, q):
            self.q = [q]
            self.a = ""
            self.sim = 0
            self.q_vec = []
            self.q_word = []
    
        def __str__(self):
            return 'q=' + str(self.q) + '
    a=' + str(self.a) + '
    q_word=' + str(self.q_word) + '
    q_vec=' + str(self.q_vec)
    
    
    class QA(object):
        def __init__(self, zhishitxt, lastTxtLen=10, usedVec=False):
            # usedVec 如果是True 在初始化时会解析词向量,加快计算句子相似度的速度
            self.lastTxt = deque([], lastTxtLen)
            self.zhishitxt = zhishitxt
            self.usedVec = usedVec
            self.reload()
    
        def load_qa(self):
            print('问答知识库开始载入')
            self.zhishiku = []
            with open(self.zhishitxt, encoding='utf-8') as f:
                txt = f.readlines()
                abovetxt = 0  # 上一行的种类: 0空白/注释  1答案   2问题
                for t in txt:  # 读取FAQ文本文件
                    t = t.strip()
                    if not t or t.startswith('#'):
                        abovetxt = 0
                    elif abovetxt != 2:
                        if t.startswith('【问题】'):  # 输入第一个问题
                            self.zhishiku.append(Repository(t[4:]))
                            abovetxt = 2
                        else:  # 输入答案文本(非第一行的)
                            self.zhishiku[-1].a += '
    ' + t
                            abovetxt = 1
                    else:
                        if t.startswith('【问题】'):  # 输入问题(非第一行的)
                            self.zhishiku[-1].q.append(t[4:])
                            abovetxt = 2
                        else:  # 输入答案文本
                            self.zhishiku[-1].a += t
                            abovetxt = 1
    
            for t in self.zhishiku:
                for question in t.q:
                    t.q_word.append(set(jieba.cut(question)))
    
        def load_embedding(self):
            from gensim.models import Word2Vec
            # 如果不存在词向量文件,则不使用词向量
            if not os.path.exists('Word60.model'):
                self.vecModel = None
                return
    
            # 载入60维的词向量(Word60.model,Word60.model.syn0.npy,Word60.model.syn1neg.npy)
            self.vecModel = Word2Vec.load('Word60.model')
            for t in self.zhishiku:
                t.q_vec = []
                for question in t.q_word:
                    t.q_vec.append({t for t in question if t in self.vecModel.wv.index2word})
    
        def reload(self):
            self.load_qa()
            self.load_embedding()
    
            print('问答知识库载入完毕')
    
        def maxSimTxt(self, intxt, simCondision=0.1, simType='simple'):
            """
            找出知识库里的和输入句子相似度最高的句子
            simType=simple, simple_POS, vec
            """
            self.lastTxt.append(intxt)
            if simType not in ('simple', 'simple_pos', 'vec'):
                return 'error:  maxSimTxt的simType类型不存在: {}'.format(simType)
    
            # 如果没有加载词向量,那么降级成 simple_pos 方法
            embedding = self.vecModel
            if simType == 'vec' and not embedding:
                simType = 'vec'
    
            for t in self.zhishiku:
                questions = t.q_vec if simType == 'vec' else t.q_word
                in_vec = jieba.lcut(intxt) if simType == 'simple' else pseg.lcut(intxt)
    
                t.sim = max(similarity(in_vec, question, method=simType, embedding=embedding) for question in questions)
            maxSim = max(self.zhishiku, key=lambda x: x.sim)
            logger.info('maxSim=' + format(maxSim.sim, '.0%'))
    
            if maxSim.sim < simCondision:
                return '抱歉,我没有理解您的意思。请您询问有关业务的话题。'
    
            return maxSim.a
    
        def answer(self, intxt, simType='simple'):
            """simType=simple, simple_POS, vec, all"""
            if not intxt:
                return ''
    
            if simType == 'all':  # 用于测试不同类型方法的准确度,返回空文本
                for method in ('simple', 'simple_pos', 'vec'):
                    outtext = 'method:	' + self.maxSim(intxt, simType=method)
                    print(outtext)
    
                return ''
            else:
                outtxt = self.maxSimTxt(intxt, simType=simType)
                # 输出回复内容,并计入日志
            return outtxt
    
    
    if __name__ == '__main__':
        robot = QA('qs_a.txt', usedVec=True)
        while True:
            # simType=simple, simple_pos, vec, all
            print('回复:' + robot.answer(input('输入:'), 'vec') + '
    ')
    
    

    词向量文件下载地址

    链接:https://pan.baidu.com/s/1c7V91VcWbHPBFIfmtWGb2g 密码:mgps
    

    知识库形式

    q=['我己签约怎么没有放款?', '已经签约什么时候放款', '签约成功什么时候放款', '你好,我昨天4.20签约的,款怎么一直没有到?', '请问签约了要多久放款', '签约后,还需要等多长时间', '签约后多久下款', '我想问一下,签约到放款要多久', '什么时候放款']
    a=签约之后总部会对您的合同进行最后一个环节审核,审核都通过才会放款。签约后审核的时效为1-3个工作日左右
    q_word=[{'签约', '我己', '放款', '怎么', '没有', '?'}, {'签约', '已经', '什么', '放款', '时候'}, {'签约', '成功', '什么', '放款', '时候'}, {'一直', '签约', '你好', '的', '4.20', ',', '怎么', '没有', '?', '我', '到', '昨天', '款'}, {'签约', '要', '请问', '放款', '了', '多久'}, {'签约', '等', '后', '需要', ',', '多长时间', '还'}, {'签约', '下款', '多久', '后'}, {'签约', '要', '多久', ',', '放款', '问', '想', '我', '到', '一下'}, {'什么', '放款', '时候'}]
    q_vec=[{'签约', '我己', '放款', '怎么', '没有', '?'}, {'签约', '已经', '什么', '放款', '时候'}, {'签约', '成功', '什么', '放款', '时候'}, {'一直', '签约', '你好', '的', '4.20', ',', '怎么', '没有', '?', '我', '到', '昨天', '款'}, {'签约', '要', '请问', '放款', '了', '多久'}, {'签约', '等', '后', '需要', ',', '多长时间', '还'}, {'签约', '下款', '多久', '后'}, {'签约', '要', ',', '放款', '问', '想', '一下', '我', '到', '多久'}, {'什么', '放款', '时候'}]
    
    • 形成知识库
    • 将问题分词
    • pseg.lcut 分词带词性
    • 带词性权重的词重叠率
    • 词性权重
    • 词向量相似度
    • 词性权重*最大的词向量相似度/词性权重之和
  • 相关阅读:
    .net core 3.1 使用autofac注入
    基于.NetCore3.1系列 —— 日志记录之初识Serilog
    antd vue select可选可清空
    ant-design-vue纯前端分页
    mysql查询逗号分隔的id,连表查询出name,同样用逗号分隔
    oracle字符串里面有通过逗号分隔的各个id,直接通过字符串获取id对应的name的字符串
    人脉、交往、会说话和做人、专业素质
    Ubuntu 一些执行命令
    CentOS 7 express nodejs
    IdWorker
  • 原文地址:https://www.cnblogs.com/chenxiangzhen/p/10711866.html
Copyright © 2020-2023  润新知