基于FAQ的智能问答(一):Elasticsearch的调教

背景

对话领域是当前最热门的一个NLP的方向之一,无论在学术界还是在工业界。由此衍生出来的产品包括通用形态的苹果siri,微软小冰,小米的小爱同学等,以及各个行业领域的智能助手,智能客服等。 这些产品基本可以看成下一代人机自然语言交互的雏形。

具体而言人机对话又可以拆分为以下几种形式:

(1)FAQ-Bot: 基于常见问答对的问答,也是运用最为广泛的智能问答技术,可以认为是最朴素的一种对话。抽象出来是一个信息检索的问题,给定用户的问题,在由{问答:答案}组成的知识库中检索相似的问题,最后将与用户相似问法问题的答案作为结果返回给用户。

(2)MRC-Bot: 基于机器阅读的智能问答,一般运用在开放域的问答中。给定用户的问题,具体分成召回和机器阅读两个阶段,先从知识库中检索出可能存在答案的文档,再针对文档做机器阅读确定答案。在实际落地中也很有前景,相比FAQ-Bot用户不需要耗费很大力气构建知识库,只需要上传产品文档即可。但是目前机器阅读的准确性还不够,效果不稳定,还不能直接将机器阅读的结果作为答案返回给用户。

(3)KG-Bot: 基于知识图谱的问答,一般用于解答属性型的问题,比如“北京的市长是谁”。给定用户的问题,需要先解析成知识图谱查询语句,再到知识图谱中检索答案。这种问答一般回答的准确率非常高,但是能回答的问题也非常局限,同时构建知识图谱非常耗费人力。

(4)Task-Bot: 任务型对话,是面向特定场景的多轮对话,比如“查天气”,“订机票”。”Task oriented dialogue”在学术和工业界都已经有了很深入的研究,分成pipeline和end-to-end两种思路。在实地落地过程中,难得是如何让用户自主的灵活配置一个任务型对话场景,训练语料可能只有一两条,如何训练出一个NER的槽位?

(5)Chat-Bot: 闲聊对话,一般用于提高机器人的趣味性,比如“你是谁?”,“你是机器人吗?”等。在学术上一般基于end-to-end的方案,可以支持多轮,但是回复结果不可控。所以在实际落地中还是会转换成FAQ-Bot,预先构建一个寒暄库,转换成检索的任务。

机器人类型 知识库结构 核心技术 落地难度
FAQ-Bot {问题:答案} 信息检索
MRC-Bot 文档 信息检索+机器阅读
KG-Bot 知识三元组 知识图谱构建/检索
Task-Bot 槽位/对话策略 对话状态跟踪/管理
Chat-Bot {寒暄语:回复} 信息检索

总结:目前最简单最切合实际的落地方式还是基于FAQ-Bot,而目前“智能客服”等产品采用的技术也大都基于此。

所以本系列文章将系统分享FAQ-Bot是如何进行产品化落地,在实际落地过程中踩过的坑以及我们自己的一些微创新。

这是本系列的第一篇文章,Elasticsearch的调教~

Elasticsearch的搭建

基于FAQ的智能问答,本质是一个信息检索任务,而Elasticsearch真是这个领域的必备的工具!

贴一段官网上的介绍:

速度:Elasticsearch 很快,快到不可思议
可扩展性:可以在笔记本电脑上运行。也可以在承载了 PB 级数据的上千台服务器上运行。
相关度:搜索所有内容,找到所需的具体信息

简单的说,将问答对{q:a}存入ES中以后,给定一个用户的问题(query),ES可以快速返回有序的相似问题。

具体在搭建ES的过程中,还有几点需要注意:

(1)版本:推荐使用7.x版本!!

ES的7.x版本的核心安全功能免费提供了,意味不用去破解x-pack获取密码登陆的功能了…

同时7.x版本配套的kibana整体风格也更明快,同时也多了“机器学习”等新的模块。

ES 7.8版本的Kibana

(2)安装: docker
推荐使用docker快速安装elasticsearch和kibana

需要注意的是基于docker安装需要在宿主机器上额外设置virtual memory

1
sysctl -w vm.max_map_count=262144

如果基于k8s启动则需要配置一个初始化容器

1
2
3
4
5
6
initContainers:
- name: increase-vm-max-map
image: busybox
command: [ "sysctl", "-w", "vm.max_map_count=262144" ]
securityContext:
privileged: true

(3) 分词:
中文场景下需要使用分词的插件,一般常用的就是 IK 分词器;安装时候需要选择对应的ES版本。

如果基于docker启动的es,则可以自己构建一个包含了IK分词器的docker镜像。

与MySQL的数据同步

一般业务数据(知识库中的问答对)都存储在MySQL中,所以ES的数据来源是MySQL,并且如果数据发生了变化(增删改查)需要及时的从MySQL同步到ES中。

调研了一圈ES与MySQL的同步方案,最终选择阿里的canal ,运行至今,也一直非常稳定。

canal [kə’næl],译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费

canal具体分成server与client两端,server端监控MySQL,并将接收到的binlog数据投递到MQ,client端负责从MQ中消费binlog数据,并处理后写入到ES中.

如果通过容器启动,需要启动server和client两个服务。

需要注意的是该同步服务是增量同步,也就是一旦client处理错误,或者server不稳定丢了binlog,binlog就不会再次同步了。

所以会进行定期的MySQL和ES全量数据同步,一般在每天深夜进行。

支持首字母与拼音检索

经过上面两个步骤,启动了ES,从MySQL中同步了数据,就已经具备了检索的功能了!

1
2
3
query: "公务员考试"

search_result: "公务员考试省考与国考题型区别大吗?"

这时候希望额外支持对首字母,拼音,以及混合的检索,即支持:

1
2
3
query: "gongwuyuan考试" , "gwyks", "公务员ks" , "公务员考试"

search_result: "公务员考试省考与国考题型区别大吗?"

这时候就需要引入另外一个分词器: medcl/elasticsearch-analysis-pinyin

安装以后,具体还需要额外配置

(1) 配置默认分词器为ik分词器,并引入pinyin分词器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"analysis": {
"analyzer": {
"default":{
"tokenizer":"ik_max_word"
},
"pinyin_analyzer": {
"type": "custom",
"tokenizer": "custom_pinyin",
"filter": ["word_delimiter"]
}
},
"tokenizer": {
"custom_pinyin" : {
"type" : "pinyin",
"keep_first_letter":true,
"keep_separate_first_letter" : false,
"keep_full_pinyin" : true,
"keep_original" : false,
"limit_first_letter_length" : 16,
"lowercase" : true
}
}
}

(2) 对于需要支持拼音检索的字段,配置拼音分词

1
2
3
4
5
6
7
8
9
10
11
12
13
"question" : {
"type" : "text",
"analyzer" : "ik_max_word",
"boost" : 20
"fields" : {
"pinyin" : {
"type" : "text",
"term_vector" : "with_positions_offsets",
"analyzer" : "pinyin_analyzer",
"boost" : 10
}
}
}

(3) 同时支持拼音和汉字检索

通过multi_match的检索,可以同时对quesiton和quesiton.pinyin两个字段进行检索

1
2
3
4
5
6
7
8
9
{
"query": {
"multi_match": {
"type":"most_fields",
"query":"公务员ks",
"fields":["quesiton", "quesiton.pinyin"]
}
}
}

当然还可以配置更精细的检索规则,例如:”如果query中包含中文,则必须包含在返回的结果中”等。

自定义词典

如果引入了IK分词器,会自动引入一个中文的词典:elasticsearch-analysis-ik/config/main.dic

但是,这个词表还是有局限的。针对例子: “美甲上门服务”, 以下是ik的分词结果

可以看到切出了一个很奇怪的词语: “甲上”, 而最新的词的”美甲”是没有被正确切分的。

所以检索“美甲”检索到的结果会很靠后,只有“美” 命中。同时“美甲”不能高亮显示。经查证:“甲上”确实是IK中自带的一个词

所以需要根据自己的业务场景,配置领域词典,对于该例,配置词典“美甲”即可。

IK分词器支持配置远程扩展字典,产品上可以支持由用户自己在前端配置领域词典。

停用词

K分词器也自带了停用词,如下所示

这个默认的词典只有33个英文,自然的想法是我们一般都是中文的场景,所以这33个英文停用词是远远不够。而Github上刚好也由一份相对完备的中文停用词表,2K+ star!

所以会尝试在自己的项目中直接用仓库中整理的停用词表,例如“baidu_stopwords”,停用词达到了1300多个!

但是,实际场景中会造成很大的误伤!!

例如配置了baidu_stopwords:

检索 :“附近有哪些药店?” 因为: “附近”是停用词,”有”是停用词,“哪些”是停用词,“?”是停用词. 这样经过停用词处理以后只有“药店”,相比原本的query丢失了很多的信息!

所以我们最后自己维护了一份停用词表,只有标点符号和最基本的语气词,包括”吗”,”呢”等

甚至有的场景下没有配置停用词,实际效果也没有下降。

最后

ES在IR领域是非常“宝藏”的工具,在信息检索中绝大多数业务功能:“输入联想”,“分组查询”,“分页查询”,“基于GIS附近的人”等都有了很好的支持,同时ES的社区非常活跃,版本也在持续更新维护。所以了解学习ES可以做到遇事不慌,快速搭建出一个能用的baseline~