FAQ问答机器人
0.Abstract
本文实现了一个解决用户信息获取类的问答机器人, 通过问题匹配来寻找可能的最佳答案. 评估方法使用Mean Reciprocal Rank.
项目代码github地址: https://github.com/neesetifa/FAQbot
1.任务介绍
FAQ问答机器人通常有两种(闲聊类的机器人,比如微软小冰,n年前小黄鸡这种暂不讨论)
第一种, 任务驱动型. 此类问答机器人通常用于完成一些指定任务,比如订餐,订票,订单处理等(比如Macy’s的客服电话,打过的朋友会发现拨通后都是该类型的问答机器人帮你处理一些简单的订单问题)
第二种, 解决用户信息获取类的问题. 该类型机器人通过用户提出的问题/关键字, 寻找潜在的最佳答案返回给用户. 此类型是本文实现的问答机器人.
本文的基本思路是, 根据用户提出的问题,在已有的问题库里寻找和当前问题可能最相关的问题,将该问题的答案作为当前问题的答案提供给用户.
本项目中, 首先实验两个基线模型查看效果,分别是ELMo和BERT. 然后对BERT进行各种finetune(微调)尝试提高模型效果.
数据集
项目的数据集使用ChineseNLPCorpus提供的"法律知道"
https://github.com/SophonPlus/ChineseNlpCorpus
评估方法
本项目使用的评估方法是Mean Reciprocal Rank.
它的计算方式是根据当前结果在所有结果中的排名的倒数求和做平均.
它的最大值是1. 即每个结果都在排名中被排在首位. 所以MRR值越大代表效果越好.
测试集
我自己造了一个数量为50条的测试数据集.
question为测试用问题(我提出的问题), title为对应匹配的问题(我认为应该匹配的问题).
2.使用ELMo预训练模型
尝试使用ELMo作为基线模型(base model)
(1)分词
使用ELMo模型,首先需要进行分词操作, 这里分词使用北大的分词工具pkuseg
import pkuseg
seg=pkuseg.pkuseg()
sents = ["今天天气真好啊", "潮水退了就知道谁没穿裤子"]
sents = [seg.cut(sent) for sent in sents]
print(sents)
# [['今天', '天气', '真', '好', '啊'], ['潮水', '退', '了', '就', '知道', '谁', '没', '穿', '裤子']]
(2) ELMo环境
使用ELMo需要安装allennlp环境. 不过因为allennlp提供的ELMo只支持英文,所以…
使用中文的话,需要额外安装这个库Pre-trained ELMo Representations for Many Languages
https://github.com/HIT-SCIR/ELMoForManyLangs
然后我们就可以使用了
from elmoformanylangs import Embedder #只需要用到Embedder
这个repository里还提供了预训练好的EMLo简体中文模型,可以直接下载使用.
如何加载预训练好的模型
e=Embedder('./zhs.model') #加载模型
# sents=[['今天', '天气', '真', '好', '啊'], ['潮水', '退', '了', '就', '知道', '谁', '没', '穿', '裤子']]
embeddings=e.sents2elmo(sents) #将句子embedding成向量,变量类型为numpy.ndarray
print(len(embeddings)) # 2 两个句子
print(embeddings[0].shape) # (5,1024) 句子1里有5个词,每个词是1024维的向量
(3) 将数据集里的每个问题全部都做embedding
然后将 “问题,问题的embedding,问题的答案” 存入一个文件
这里为了节省空间,对每句话的embedding做了平均
未做平均: 文件大小约800MB, 做完平均: 约89MB
实际效果: 两者区别不大
(4) 将输入的问题做embedding,然后和所有问题的embedding作对比, 对比方式使用cosine similarity, 取出相似度最高的5条问答,打印出来. 可以看到,在5条候选答案中,较为相关的回答还是很多的.
(5) 评估
ELMo模型的MRR约为 0.198
3.使用BERT预训练模型
尝试使用BERT作为基线模型(base model)
(1) BERT的中文预训练模型使用Cui Yimin提供的<BERT-wwm-ext, Chinese>
https://github.com/ymcui/Chinese-BERT-wwm
(2)为了使用BERT对句子进行编码,这里借用并且修改了hugging face提供的代码.
https://github.com/huggingface/transformers/blob/master/examples/run_glue.py
我只使用了一个句子作为输入,即 [CLS]问题[SEP]None[SEP] 然后提取pooled_output来代表句子的向量, 使用cosine similarity作为评测分数
(3) 评估
BERT模型的MRR约为 0.183
BERT分数略低于ELMo
之后又测试了另一组数据,ELMo约为 0.203, BERT约为0.265.
4.针对基线模型的分析思考以及可能的提升方向
- 我发现构造测试数据集时, 问题的问法很有讲究,换一种问法模型可能就无法找到正确的答案. 模型在相同的句式以及较高的关键字匹配的情况下可以获得较高的分数, 而变化一种句型,比如原问题是"民事纠纷有哪些类型", 我提出的是"民事纠纷如何分类”, 模型便有可能无法匹配到正确答案.
- 同时我还发现, 在向模型提出毫不相关的问题时,模型给出的答案也会有非常高的分数:
这样也引出了一个提升方向: 增大和正例(同义句)的分数,减小和负例(非同义句)的分数.
下面我将从这个方向对BERT模型结构进行微调(fine-tune),从而使得它能够更加准确的判断两个句子的相似度.
5.BERT训练模型
这里需要先提一下,在思考的时候,看到的一篇对我有很大启发的论文: ParaNMT-50M
这篇论文是2018年4月写的,那时BERT尚未提出.下面是我对这篇论文的概述:
该论文提出了一个数据集, 由5000万条(50 million)英语-英语句子释义对(sentential paraphrase pairs)或者说同义句子对组成. 作者生成这个数据集的方法是,将英语翻译成捷克语,再把捷克语翻译回英语.
作者希望这个成为一个比较好的释义生成资源(释义生成,paraphrase generation, 是文本生成text generation的一个子任务). 并认为这么训练可以获得更好的句子表示(sentence embedding).
原论文里作者使用了WORD AVERAGING,TRIGRAM,LSTM三种模型. 我这里使用BERT代替.
1) 损失函数
下面这个式子是原论文里用的损失函数hinge loss/margin loss, cos()是计算cosine similarity
l o s s = m a x ( 0 , δ − c o s ( g s , g s ′ ) + c o s ( g s , g t ) ) loss=max(0, \delta-cos(g_s,g_{s'})+cos(g_s,g_{t})) loss=