基于检索的智能问答。目前使用了简单词汇对比、词性权重、词向量3种类似度计算模式。输入符合格式的QA文本文件便可马上使用。
python
使用 python3 运行
jieba 分词使用的库
gensim 词向量使用的库,若是使用词向量vec模式,则须要载入app
若是使用词向量vec模式,须要下载3个文件:Word60.model,Word60.model.syn0.npy,Word60.model.syn1neg.npy
下载地址:http://pan.baidu.com/s/1kURNutT 密码:1tq1测试
QA文件包含想要告知用户的问答内容。
QA文件必须是UTF-8的无bom格式的文本文件。调试
注释:注释文字由#开头。(整个一行都是注释内容)日志
问答块格式以下:
【问题】问题标题(能够有1或多个,至少有1个。必须由"【问题】"开头。)
答案内容(能够有多行,必须紧跟着上面的【问题】,多行答案中间不能有空白的行。)
多个问答块之间能够用空白行分割
code
直接运行该文件,便可进行问答。你能够载入本身的QA文件,请保证QA文件格式正确。
robot.answer(inputtxt,'simple_POS') 可得出输入问题的返回答案。
simType参数有以下模式:
simple:简单的对比相同词汇数量,获得句子类似度
simple_POS:简单的对比相同词汇数量,并对词性乘以不一样的权重,获得句子类似度
vec:用词向量计算类似度,并对词性乘以不一样的权重,获得句子类似度
all:调试模式,把以上几种模式的结果都显示出来,方便对比和调试orm
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.txtip
【问题】我己签约怎么没有放款? 【问题】已经签约何时放款 【问题】签约成功何时放款 【问题】你好,我昨天4.20签约的,款怎么一直没有到? 【问题】请问签约了要多久放款 【问题】签约后,还须要等多长时间 【问题】签约后多久下款 【问题】我想问一下,签约到放款要多久 【问题】何时放款 签约以后总部会对您的合同进行最后一个环节审核,审核都经过才会放款。签约后审核的时效为1-3个工做日左右 【问题】提早还款 【问题】我要提早还款 【问题】申请提早还款 【问题】我想了解提早还款 【问题】如何提早还款 【问题】提早还款怎么办 我想提早还款,应该怎么操做 提早还款1.还款日前三个工做日与客户经理联系2.利息截止到当期,服务费减免 您要办理提早结清,您提早三个工做日联系门店,在您还款日先后办理不了 【问题】你好初审额度已经出面签也签了还须要等多久 【问题】审批结果要多久? 【问题】提交申请了多久审核 【问题】请问审核须要多久? 【问题】审核通常要几天 【问题】撒时候放款 【问题】审核总共有几个环节 客户审批流程须要通过三个环节:第一环节材料审核(资料齐全,符合标准,且不须要实地征信)时效须要3个工做日左右;第二环节面审(签署合同),时效1个工做日左右;第三环节合同审核,时效1-3个工做日左右。
qa.pyutf-8
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) + '\na=' + str(self.a) + '\nq_word=' + str(self.q_word) + '\nq_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 += '\n' + 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:\t' + 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') + '\n')
词向量文件下载地址
连接:https://pan.baidu.com/s/1c7V91VcWbHPBFIfmtWGb2g 密码:mgps
知识库形式
q=['我己签约怎么没有放款?', '已经签约何时放款', '签约成功何时放款', '你好,我昨天4.20签约的,款怎么一直没有到?', '请问签约了要多久放款', '签约后,还须要等多长时间', '签约后多久下款', '我想问一下,签约到放款要多久', '何时放款'] a=签约以后总部会对您的合同进行最后一个环节审核,审核都经过才会放款。签约后审核的时效为1-3个工做日左右 q_word=[{'签约', '我己', '放款', '怎么', '没有', '?'}, {'签约', '已经', '什么', '放款', '时候'}, {'签约', '成功', '什么', '放款', '时候'}, {'一直', '签约', '你好', '的', '4.20', ',', '怎么', '没有', '?', '我', '到', '昨天', '款'}, {'签约', '要', '请问', '放款', '了', '多久'}, {'签约', '等', '后', '须要', ',', '多长时间', '还'}, {'签约', '下款', '多久', '后'}, {'签约', '要', '多久', ',', '放款', '问', '想', '我', '到', '一下'}, {'什么', '放款', '时候'}] q_vec=[{'签约', '我己', '放款', '怎么', '没有', '?'}, {'签约', '已经', '什么', '放款', '时候'}, {'签约', '成功', '什么', '放款', '时候'}, {'一直', '签约', '你好', '的', '4.20', ',', '怎么', '没有', '?', '我', '到', '昨天', '款'}, {'签约', '要', '请问', '放款', '了', '多久'}, {'签约', '等', '后', '须要', ',', '多长时间', '还'}, {'签约', '下款', '多久', '后'}, {'签约', '要', ',', '放款', '问', '想', '一下', '我', '到', '多久'}, {'什么', '放款', '时候'}]