NLP智能问答系统

Posted ZSYL

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NLP智能问答系统相关的知识,希望对你有一定的参考价值。

1. 问答系统简介

问答系统通常分为

  1. 任务型机器人
  2. 闲聊机器人
  3. 解决型机器人(客服机器人)

三者的设计分别针对不同的应用场景:

  1. 任务型机器人主要用于完成用户的某些特定任务,比如:买机票、话费充值或者天气咨询。
  2. 闲聊机器人主要用于深入的和用户进行无目的交流;
  3. 解决型机器人(客服机器人)用于解决用户的问题,比如:商品购买咨询、商品退货咨询等。

任务型问题:

1):“成都今天天气怎么样”;
2):“明天呢";
3):“后天呢”。


Slot Filling

首先,“成都今天天气怎么样”属于天气类问题(其中包含实体“地点”、“时间”),已经能够完成应答;

然后,“明天呢”该句话仅包含实体信息(”时间“),并未包含地点信息,如果直接采用意图分类,不能完成此次应答;

最后,”后天呢“同样是只包含实体信息(”时间“)。

针对此类的多轮对话场景,可采用slot filling的方式进行应答(slot filling是多个槽值组成,例如:天气场景需要实体槽值“地点”和“时间”)。”明天呢“和”后天呢“只包含”时间“实体,但是上文”成都今天天气怎么样“则包含了”地点“实体,只需要将下文的实体(“时间”)替换上文的实体(“时间“)即可。

解决型问题

1):”iphone X多少钱“;
2):”邮费是多少呢”;
3):“可以无理由退货么?”

特征拼接上下文模型

针对此处的多轮对话,涉及到商品的购买、售前运费和退换货政策三个意图,并且后面的意图分析需要前文的会话意图,就是一个典型的多轮对话过程。

首先,“iphone X多少钱”可以通过单句的意图分类即可完成应答;

而“运费是多少呢”则需要判断用户咨询的属于售前运费还是售后运费,此时可通过结合上文问题的方式进行意图分析(1:抽取上文的意图特征加入当前问题可解决部分上下文场景问题;2:结合上文和当前问题采用深度学习的算法进行上下文的意图分析)。

最后,“可以无理由退货么”需要知道商品的信息才可以回答用户的问题,因此需要上文商品“iphone X”(可以将对话中实体、商品信息保存用于下文应答)。

层级上下文模型(H-CNN-GRU)

此时多轮对话过程中,涉及到用户输入过程中单句输入不完整。slot filling和简单抽取上文特征的方式并不适合,而组合多句输入则可以完成此处的应答

闲聊型问题

聊天记录

针对闲聊型问题,由于用户并无明确的意图,因此不适合做意图分类,这里我们可以采用生成式模型,根据大量用户历史的闲聊语料生成相应的答案(生成式模型得到的答案可能存在语法、连贯性问题,但是闲聊场景的对话对语句语法和连贯性要求不高,相对随意)。

seq2seq模型

总结:我们在分析一个人的时候通常涉及IQ和EQ两个方面,IQ在于解决问题的能力,而EQ在于解决问题的方式。在实现一个机器人问答系统的时候,我们也应该考虑IQ和EQ两个方面。这里只是针对问答系统中的一些特殊案例进行分析,一个完整的问答系统仍需要大量其他方面的工作,比如:让问答系统的回答更加拟人化(用户情感分析)

基于Attention机制的上下文分类算法在问答系统中的应用

文本分类是自然语言处理中的基础算法,在对话系统的应用中,可利用文本分类算法来判断用户的咨询意图。然而单个问题并不能很好捕获用户的意图,通常需要结合用户上文的咨询结合当句才能更好的判断用户的意图。

这里就需要我们建立一个基于上下文的分类模型来结合上文信息判断用户的最终意图。

这里常用的方式大概分为两个方式:

1)非end-2-end建模:首先抽取上文的主题、意图或者关键词,然后结合当前问题建模。
2)end-2-end建模:通过时序模型,直接抽取上文信息结合当前问题建立模型。

本文主要参考《Hierarchical Attention Networks for Document Classification》,该论文介绍了Attention机制在英文文本分类中的应用。文章采用document级分类,即document由sentence组成,而sentence由word组成,因此天然的具有层级关系。以word为粒度输入网络抽取word级特征得到表示sentence的特征向量;然后将sentence级向量输入网络抽取sentence级特征得到最终的document级特征,然后将document特征通过一个线性表示和softmax。为了给与不同的word和不同的sentence分配不同的权重,论文设计一个层级架构的attention机制用于提升模型的性能。

本文设计上下文模型的时候稍作修改,将一个会话中的单句作为第一层输入,然后将第一层各个单句抽取的特征作为第二层会话级的输入,获得最终的会话级特征输出。Attention机制最近火的不要不要,被广泛应用在nlp和图像处理中。这里同样在第一层特征抽取之后,设计了一个Attention层,用于更加精确的抽取句子的特征。但是在会话层特征抽取之后,并没有再设计同样的Attention了,因为在对话系统中,和当前句子越接近句子的重要性越高,因此这里我们之后抽取时序模型的最后输出作为最终的特征。

整体算法的设计图如下:


LSTM网络在处理中文时,其准确率和速度都要差于CNN网络,因此这里对论文的网络结构进行调整,将第一层的LSTM网络替换为CNN网络。论文中在一层和第二层都加入了Attention机制,但是实验时发现第一层加入Attention效果没有提升,因此Attention只在第二层中使用。整个网络在融合CNN和LSTM之后,整个网络结构已经非常复杂了,而GRU是LSTM的简化版,GRU在语料较少时效果更加,因此这里采用GRU替换第二层网络的LSTM,即有最终的模型H-CNN-GRU网络:

实验步骤:

1:本次实验采用单句问题和对应的标签作为输入。实验之前首先对问题按字切词,然后采用word2vec对问题进行预训练(这里采用按字切词的方式避免的切词的麻烦,并且同样能获得较高的准确率)。

2:由于本次实验采用固定长度的GRU/LSTM,因此需要对问题和答案进行截断(过长)或补充(过短)。


3:实验建模Input。本次实验单句问题和标签进行建模(q,l),q代表问题,l代表标签。

4:将问题进行Embedding(batch_size*session_len, sentence_len, embedding_size)表示。

5:对一个batch中所有会话包含的问题采用GRU/LSTM模型计算特征。

1):这里第一层针对单句问题的特征抽取采用固定长度的句子,尝试过采用双向动态rnn,并没有很好的效果提升,并且相对计算速度慢点。


6:GRU/LSTM模型输出向量为(batch_size, seq_len,rnn_size),因此需要对输出特征向量进行特征抽取。常用的特征抽取方式为取模型最后一步的输出为下一层的特征,但是该特征抽取方式只取了最后一步的特征,丢弃了其他的特征信息,所以本次实验采用Attention机制计算每一步特征的权值,然后进行加权平均。


7:将第一层单句级的特征输出作为第二层会话级encoder的输入。

1):这里采用动态rnn进行特征抽取,因为每一个session所包含的问题不是固定的。

2):这里没有用双向的rnn而是采用单向的rnn,并且没有外加attention、avg_pool或者max_pool,因为越靠近当前句子理应越为重要。


7:对模型输出的特征进行线性变换。

8:针对多类文本分类,需要将线性变换的输出通过softmax。

参数设置:

1:、这里优化函数采用论文中使用的Adam(尝试过SGD,学习速率0.1,效果不佳)。

2、学习速率为2e-4。

3:、训练60轮。

4、embedding_size为150维。

5、attention为100维。

6、batch_size这里采用128。

7、问题长度限制为60字。

8、rnn_size为201。

9、本次算法每一级特征抽取都只采用了单层,由于计算耗时,没有尝试不同级多级的方式。

10、dropout为0.5(在输入和输出时均执行dropout,单次dropout实验时效果不佳)

注:从线上数据的分析结果来看,采用H-CNN-GRU算法在处理上下文场景时能取得不错的效果。

2. 搭建基于检索的问答系统

基于检索的问答系统,其主要实现是将用户的输入问题与数据库中的问题进行相似度匹配,将相似度最高的问题的答案返回给用户(也可以返回topn个最相似的问题,待用户选择最相似度的问题后,返回给用户答案)

数据介绍:

dev-v2.0.json: 这个数据包含了问题和答案的pair, 以JSON格式存在。

打印如下

'paragraphs': ['context': 'Beyoncé Giselle Knowles-Carter (/biːˈjɒnseɪ/ bee-YON-say) (born September 4, 1981) is an American singer, songwriter, record producer and actress. Born and raised in Houston, Texas, she performed in various singing and dancing competitions as a child, and rose to fame in the late 1990s as lead singer of R&B girl-group Destiny\\'s Child. Managed by her father, Mathew Knowles, the group became one of the world\\'s best-selling girl groups of all time. Their hiatus saw the release of Beyoncé\\'s debut album, Dangerously in Love (2003), which established her as a solo artist worldwide, earned five Grammy Awards and featured the Billboard Hot 100 number-one singles "Crazy in Love" and "Baby Boy".',
   'qas': ['answers': ['answer_start': 269, 'text': 'in the late 1990s'],
     'id': '56be85543aeaaa14008c9063',
     'is_impossible': False,
     'question': 'When did Beyonce start becoming popular?',
    'answers': ['answer_start': 207, 'text': 'singing and dancing'],
     'id': '56be85543aeaaa14008c9065',
     'is_impossible': False,
     'question': 'What areas did Beyonce compete in when she was growing up?',
    'answers': ['answer_start': 526, 'text': '2003'],
     'id': '56be85543aeaaa14008c9066',
     'is_impossible': False,
     'question': "When did Beyonce leave Destiny's Child and become a solo singer?",
    'answers': ['answer_start': 166, 'text': 'Houston, Texas'],
     'id': '56bf6b0f3aeaaa14008c9601',
     'is_impossible': False,
     'question': 'In what city and state did Beyonce  grow up? ',
    'answers': ['answer_start': 276, 'text': 'late 1990s'],
     'id': '56bf6b0f3aeaaa14008c9602',
     'is_impossible': False,
     'question': 'In which decade did Beyonce become famous?',
    'answers': ['answer_start': 320, 'text': "Destiny's Child"],
     'id': '56bf6b0f3aeaaa14008c9603',
     'is_impossible': False,
     'question': 'In what R&B group was she the lead singer?',
    'answers': ['answer_start': 505, 'text': 'Dangerously in Love'],
     'id': '56bf6b0f3aeaaa14008c9604',
     'is_impossible': False,
     'question': 'What album made her a worldwide known artist?',
    'answers': ['answer_start': 360, 'text': 'Mathew Knowles'],
     'id': '56bf6b0f3aeaaa14008c9605',
     'is_impossible': False,
     'question': "Who managed the Destiny's Child group?",
    'answers': ['answer_start': 276, 'text': 'late 1990s'],
     'id': '56d43c5f2ccc5a1400d830a9',
     'is_impossible': False,
     'question': 'When did Beyoncé rise to fame?',
    'answers': ['answer_start': 290, 'text': 'lead singer'],
     'id': '56d43c5f2ccc5a1400d830aa',
     'is_impossible': False,
     'question': "What role did Beyoncé have in Destiny's Child?",
    'answers': ['answer_start': 505, 'text': 'Dangerously in Love'],
     'id': '56d43c5f2ccc5a1400d830ab',
     'is_impossible': False,
     'question': 'What was the first album Beyoncé released as a solo artist?',
    'answers': ['answer_start': 526, 'text': '2003'],
     'id': '56d43c5f2ccc5a1400d830ac',
     'is_impossible': False,
     'question': 'When did Beyoncé release Dangerously in Love?',
    'answers': ['answer_start': 590, 'text': 'five'],
     'id': '56d43c5f2ccc5a1400d830ad',
     'is_impossible': False,
     'question': 'How many Grammy awards did Beyoncé win for her first solo album?',
    'answers': ['answer_start': 290, 'text': 'lead singer'],
     'id': '56d43ce42ccc5a1400d830b4',
     'is_impossible': False,
     'question': "What was Beyoncé's role in Destiny's Child?",
    'answers': ['answer_start': 505, 'text': 'Dangerously in Love'],
     'id': '56d43ce42ccc5a1400d830b5',
     'is_impossible': False,
     'question': "What was the name of Beyoncé's first solo album?"],

2.1 导入所需的库

import json
from matplotlib import pyplot as plt
import re
import string
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.stem.porter import PorterStemmer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from queue import PriorityQueue as PQueue
from functools import reduce

2.2 读取文件

读取训练文件,把内容分别写到两个list里(一个list对应问题集,另一个list对应答案集)

def read_corpus():
    """
    读取给定的语料库,并把问题列表和答案列表分别写入到 qlist, alist 里面。 在此过程中,不用对字符换做任何的处理
    qlist = ["问题1", “问题2”, “问题3” ....]
    alist = ["答案1", "答案2", "答案3" ....]
    务必要让每一个问题和答案对应起来(下标位置一致)
    """
    qlist = []
    alist = []
    with open("data/train-v2.0.json", 'r') as path:
        fileJson = json.load(path)
    json_list=fileJson['data']
    for data_dict in json_list:
        for data_key in data_dict:
            if data_key=="paragraphs":
                paragraphs_list=data_dict[data_key]
                for content_dict in paragraphs_list:
                    for qas_key in content_dict:
                        if "qas" == qas_key:
                            qas_list = content_dict[qas_key]
                            for q_a_dict in qas_list:
                                if len(q_a_dict["answers"]) > 0:
                                    qlist.append(q_a_dict["question"])
                                    alist.append(q_a_dict["answers"][0]["text"])
 
    print("qlist len:" + str(len(qlist)))
    print("alist len:" + str(len(alist)))
 
    assert len(qlist) == len(alist)  # 确保长度一样
 
    return qlist, alist

2.3 理解数据(可视化分析/统计信息)

对数据的理解是任何AI工作的第一步,需要充分对手上的数据有个更直观的理解。

def data_analysis(data):
    # TODO: 统计一下在qlist 总共出现了多少个单词? 总共出现了多少个不同的单词?
    # TODO: 统计一下qlist中每个单词出现的频率,并把这些频率排一下序,然后画成plot.
    qlist_word = []
    word_dic = 
    for sentences in data:
        cur_word = sentences[:len(sentences) - 1].strip().split(" ")
        qlist_word += cur_word
        for word in cur_word:
 
            if word in word_dic.keys():
                word_dic[word] = word_dic[word] + 1
            else:
                word_dic[word] = 1
 
    #统计一下在qlist总共出现了多少个不同单词
    word_total = len(set(qlist_word))  # 53306
 
    word_dic=sorted(word_dic.items(), key = lambda x:x[1], reverse = True)
 
    # 出现频率前100的单词进行可视化
    x = range(100)
    y = [c[1] for c in word_dic[:100]]
    plt.figure()
    plt.plot(x, y)
    plt.show()
 
qlist, alist = read_corpus()
data_analysis(qlist)

打印如下:

2.4 文本预处理

对于qlist, alist做文本预处理操作。 可以考虑以下几种操作:

  1. 停用词过滤 (去网上搜一下 “english stop words list”,会出现很多包含停用词库的网页,或者直接使用NLTK自带的)
  2. 转换成lower_case: 这是一个基本的操作
  3. 去掉一些无用的符号: 比如连续的感叹号!!!, 或者一些奇怪的单词。
  4. 去掉出现频率很低的词:比如出现次数少于10,20…
  5. 对于数字的处理: 分词完只有有些单词可能就是数字比如44,415,把所有这些数字都看成是一个单词,这个新的单词我们可以定义为 “#number”
  6. stemming(利用porter stemming): 因为是英文,所以stemming也是可以做的工作

请注意,不一定要按照上面的顺序来处理,具体处理的顺序思考一下,然后选择一个合理的顺序

def data_pre(temp_list):
    stop_words = set(stopwords.words('english'))
    stemmer = PorterStemmer()
    pattern = re.compile('[]'.format(re.escape(string.punctuation)))#正则匹配特殊符号
    word_list_list = []
    word_dict = 
    for line in temp_list:
        temp_word_list = []
        sentence = pattern.sub("", line) # 1.去掉一些无用的符号
        sentence = sentence.lower()      # 2.转换成lower_case
        word_list = sentence.split()
        for word in word_list:
            if word not in stop_words:  # 3.过滤停用词
                word = "#number" if word.isdigit() else word  # 4.数字特殊处理
                word = stemmer.stem(word)  # 5.词干提取(包括词形还原)
                word_dict[word] = word_dict.get(word, 0) + 1
                temp_word_list.append(word)
        word_list_list.append(temp_word_list)
    return word_dict, word_list_list
 
#6. 去掉出现频率很低的词
def filter_words(in_list=[], in_dict=, lower=0, upper=0):
    word_list = []
    for key, val in in_dict.items():
        if val >= lower and val <= upper:
            word_list.append(key)
 
    new_list = []
    for line in in_list:
        words = [w for w in line if w in word_list]
        new_list.append(' '.join(words))
    return new_list

2.5 文本表示

这里使用Tf-idf的表示方法,这里使用sklearn里面提供的包。

from sklearn.feature_extraction.text import TfidfVectorizer
 
vectorizer =  TfidfVectorizer()          # 定一个tf-idf的vectorizer
X = vectorizer.fit_transform(qlist)  # 结果存放在X矩阵

2.6 对于用户的输入问题,找到相似度TOP5高的问题,并把5个潜在的答案做返回

def top5results(input_q):
    """
    给定用户输入的问题 input_q, 返回最有可能的TOP 5问题。这里面需要做到以下几点:
    1. 对于用户的输入 input_q 首先做一系列的预处理,然后再转换成tf-idf向量(利用上面的vectorizer)
    2. 计算跟每个库里的问题之间的相似度
    3. 找出相似度最高的top5问题的答案
    """
    stop_words = set(stopwords.words('english'))
    stemmer = PorterStemmer()
    pattern = re.compile('[]'.format(re.escape(string.punctuation)))  # 正则匹配特殊符号
 
    input_q = pattern.sub("", input_q) # 1.去掉一些无用的符号
    input_q = input_q.lower()      # 2.转换成lower_case
    word_list = input_q.split()
    temp_word_list=[]
    for word in word_list:
        if word not in stop_words:  # 3.过滤停用词
            word = "#number" if word.isdigit() else word  # 4.数字特殊处理
            word = stemmer.stem(word)  # 5.词干提取(包括词形还原)
            temp_word_list.append(word)
    new_input=' '.join(temp_word_list)
    vectorizer = TfidfVectorizer(smooth_idf=False)  # 定义一个tf-idf的vectorizer
    X = vectorizer.fit_transform(new_qlist)  # 结果存放在X矩阵
    #注意fit_transform是训练,transform是加入新数据
    input_vec = vectorizer.transform([new_input])# 结果存放在X矩阵
    res = cosine_similarity(input_vec, X)[0]
 
    #即输出前k个高频词使用优先队列,优化速度
    pq = PQueue()
    for i, v in enumerate(res):
        pq.put((1.0 - v, i))
 
    top_idxs = []  # top_idxs存放相似度最高的(存在qlist里的)问题的下表
    for i in range(5):
        top_idxs.append(pq.get()[1])
 
    print(top_idxs)  # top_idxs存放相似度最高的(存在qlist里的)问题的下表
    # hint: 利用priority queue来找出top results. 思考为什么可以这么做?
    # 因为优先级队列的第一个值可

以上是关于NLP智能问答系统的主要内容,如果未能解决你的问题,请参考以下文章

百度上线NLP智能问答大赛,10万元奖金邀你来战

自然语言处理NLP之文本蕴涵智能问答语音识别对话系统文本分类情感计算

自然语言处理NLP之BERTBERT是什么智能问答阅读理解分词词性标注数据增强文本分类BERT的知识表示本质

自然语言处理最终篇之许嵩音乐智能问答系统微信小程序

NLP精彩结赛!获奖选手解题思路说给你听

Elasticsearch:使用 NLP 问答模型与你喜欢的圣诞歌曲交谈