如何让你的客服问答系统从“人工智障”到“人工智能”-程序员宅基地

前段时间接手了公司遗留的一个所谓“智能客服机器人”的项目,改进了之前团队的“人工智障”设计,颇有收获。今天是 1024,程序员节。我们就来整点干货,八一八整个项目的血泪教训。

 

这个项目是给某医院做的自动客服系统,对接了微信公众号,要求能回复一些固定种类的问题。比如:

1. 住院部在几楼?(问路)

2. 帮我预约一下张医生门诊(预约)

3. 查询刘医生主治的内科病人(查询)

……

当然,实际种类有十几个,就不一一列举了。

当我拿到上任“资深自然语言处理架构师”老胡发给我的代码时,我的内心是拒绝的。这并不是因为他没有头发,而是因为这些代码太“硬”了。几乎所有逻辑都是硬编码进去的,基本流程如下:

基本流程就是正则匹配问句,根据匹配上的正则表达式判断问题种类,然后执行相应逻辑。包括查询相应的数据表或 ElasticSearch 索引,或者向业务系统请求预约等。当然,实际代码里正则表达式比这个复杂一百倍,毕竟这套系统上线了好几年,收获差评数万呢。差评原因无非以下四个方面:

1. 问句种类匹配不准,比如“查询刘医生在内科主治的病人”会同时匹配到“问路”和“查询”两个类型。旧系统又编码了很多优先级规则,一团乱麻。我猜这团乱麻是老胡呕心沥血用自己头发编织出来的。

2. 问句成分匹配不准,用户输入千奇百怪。比如“张医生”“张主任”“ 张大庆”“张大庆医生”,我看搜索日志,还有用户爱称“张神医”“大庆”“大庆医生”的,也不嫌肉麻。用户随便输入,但旧系统硬编码的名单都是真实姓名,匹配不上各种称呼。另外,每次新医生入职,要过好久才对接名单更新。

3. 不支持多轮对话。用户经常没法一次性输入全所有信息,比如“帮我查一下刘医生的病人”,但是检索系统又要求必须指定科室。这时候旧系统就只能回复“请同时指定医生和科室”。用户于是回复“就是内科啊”,可这次旧系统居然匹配上了问路的正则表达式,于是返回了去内科怎么走。这下用户就被彻底被激怒了,体验极差。特别是这种查询功能一般都是医院的大领导们用的,听说间接导致了老胡的离职。

4. 检索系统搜索不准。即使“张大庆”被正则匹配上了,但在检索系统中却被切分成“张+大庆”,导致结果第一条是“住院部挂着一张大庆油田的风景画”,完全答非所问。旧系统好像用的是什么 IK 分词器,错误百出。我虽然不是科班出身,但是一看专业教材就浑身难受。

我还是找到了一些资料,并且根据这些资料将系统重新设计了一下。这种客服系统属于自然语言处理领域,学名应该叫任务型问答系统,基本就包括“意图分类”“槽识别”和“业务逻辑”三个模块,如下图所示:

其中,意图指的就是问题的类型,槽指的是每种意图中的特定名词。比如在问路型意图中,科室名称“内科”“耳鼻喉科”就是槽。也就是说,意图分类和槽识别代替了正则表达式,提供了更精确的结果,解决了 bug① 和 ②。

具体实施的时候,我自豪地面向开源项目编程,直接用了 HanLP 中的文本分类器和命名实体识别做意图分类和槽识别,一步到位不要太爽。

HanLP 是由一系列模型与算法组成的工具包,目标是普及自然语言处理在生产环境中的应用。HanLP 具备功能完善、性能高效、架构清晰、语料时新、可自定义的特点;提供词法分析(中文分词、词性标注、命名实体识别)、句法分析、文本分类和情感分析等功能。被广泛用于 Lucene、Solr、ElasticSearch、Hadoop、Android、Resin 等平台,有大量开源作者开发各种插件与拓展,并且被包装或移植到 Python、C#、R、JavaScript 等语言上去。项目详见:https://github.com/hankcs/HanLP

所谓文本分类器,就是给定一段文本,HanLP 自动判断类别。怎么个判断法呢?HanLP 怎么知道你要判断医院的“问路”“查询”“预约”还是新闻网站的“经济”“财经”“教育”呢?这时候就需要用户提供一些例子给 HanLP 学习,这些例子在自然语言处理里面叫语料。不是随便什么爬虫爬下来的文本都是语料,必须得有具体业务所需要的答案才行。

比如,在医院的人工客服系统中,累积了很多用户的查询问句,以及客服小姐姐给查询语句打的标签(问路、查询还是预约)。这些问句+标签就是文本分类的语料,可以直接丢给 HanLP 让它学习。

学习得到一个文件,储存着如何把问句分类为“问路型”“查询型”“预约型”等等类别的知识,这个文件叫做模型。存起来丢给线上的 HanLP 分类器加载一下就可以执行分类了。这里比较推荐基于 SVM 的文本分类器,据说速度和准确率都是最高的。

所谓命名实体识别,就是识别人名、地名、机构名之类专有名词的工具。我这个医疗客服系统里面主要就是医生的姓名了。听说 HanLP 的感知机分词器是在一亿字的语料库上训练出来的,准确率的确不知道比其他同类工具高到哪里去了。

HanLP 分词结果中,人名的词性标签是 nr,根据这个就拿到了医生姓名。至于科室名,HanLP 虽然能分出来但是词性标签就是名词 n。我的做法是把老胡正则表达式中的科室做成词典,指定词性为 ndep 交给 HanLP 挂载就行了。反正科室是一个很死板的东西,不会经常变化,一本用户词典管十年基本没毛病。

新系统 2.0 上线后调查数据显示,用户满意度果然提高了不少。搞搜索引擎的小伙伴听说了也把他们 ES 的 IK 分词器也换成了 HanLP 分词器,效果拨群。这样 bug④ 也解决啦。

后来领导看我这么快就把新系统上线了,马上给我发了奖金,马上给我派了新活儿——让我把院长上次提的多轮询问实现一下。这说明交差不能太快,闷声才能发大财啊。

根据资料,多轮对话的管理必须有一个对话管理器,负责综合对话历史和当前对话决定回复什么。在这个项目中,其实就是一个缓冲区的作用。当一句话里面的槽不全的时候,应该把这句话的意图和槽缓存起来,然后向用户询问更多有关缺失槽的信息。这样问答系统才更有意思,不仅负责回答问题,还能够向用户提问,帮助他们提出更准确的问题。

对话管理器的流程如下:

当然实际的对话管理器比这个复杂,有些问题涉及到好多槽,有时候对话的话题又会变化,总之还是需要消耗一些头发的。

3.0 上线后我以为项目总该庆功了吧,不料我还是太年轻。吹毛求疵的客户又报了几个工单上来,虽然相比旧系统少了很多,但总得给人解决吧。这次的问题是有些意图的区分度非常低,比如“查询张大庆负责的科室”和“查询科室负责的张大庆”这两个问句分别是要查“科室”和“张大庆”,但分类结果却是一样的。我找了找资料,原来分类算法使用了一种叫词袋模型的方法建模文档。词袋模型最大的问题是忽略了文档单词的顺序,也就是说,这两句话在词袋向量看来,都是下图的无序集合:

我动脑筋想了想,解决办法有两个。一是在分词的时候把相邻的两个单词组合起来形成一个“新单词”,这样就在一定程度上保留了文档的语序。二是利用 HanLP 的句法分析得出问句的语法结构,把语法结构作为一种特征信息提供给分类器。

比如“查询张大庆负责的科室”的语法结构如下:

查询的宾语是“科室”。

“查询负责科室的张大庆”的语法结构如下:

查询的宾语是“张大庆”。

我通过自定义 HanLP 的分词器和重载文本分类接口分别实现了两种方法,然后将语料划分为训练集和测试集。结果在测试集上第二种方案的准确率更高,说明句法分析对这种短文本分类还是有一定疗效的。至此,这个“智能客服机器人”项目基本一致好评,圆满庆功了。

啊哈!是不是突然觉得自然语言处理还是很有趣的。如果你问我是哪个师傅带我进门的,那可绝对不是老胡,是这本《自然语言处理入门》。这大概是市面上最友好的 NLP 入门书了,都是白话讲解,没有故弄玄虚的数学公式,另外还配备产品级代码。你不需要熬夜琢磨,地铁上也可以读懂,更重要的是对你的头发有好处。

(点击封面,即可抱回家)

Java 与 Python 双实现

助你零起点上手自然语言处理

本书作者是自然语言处理类库 HanLP 作者何晗。截至 2019 年 10 月初,该项目在 GitHub 上 Star 数已达 15 K,超过了宾夕法尼亚大学的 NLTK、斯坦福大学的 CoreNLP、哈尔滨工业大学的 LTP。

作者汇集多年经验,从基本概念出发,逐步介绍中文分词、词性标注、命名实体识别、信息抽取、文本聚类、文本分类、句法分析这几个热门问题的算法原理与工程实现。书中通过对多种算法的讲解,比较了它们的优缺点和适用场景,同时详细演示生产级成熟代码,助你真正将自然语言处理应用在生产环境中。

呐!HanLP 1.7.5 发布了,最后我们就来一起回顾下它的成长的历程。希望今天 1024 程序员节,大家能无 bug 早下班。

题图来源:动漫《Chobits》

   文末畅聊

细数那些年领导/客户给你提过的需求,你都是如何应对的?精选留言选出 5 位获得赠书一本。你可以选择《自然语言处理入门》也可以选择《程序员面试金典》,只要是图灵近两个月出版的新书,任你挑选。

1024 程序员节,也可以说说今天公司都为你准备了哪些活动?你今天都在忙哪些项目?精选留言选出 5 位,送出图灵书签一支。活动截至 2019.10.26。

PS:1024 程序员节,图灵社区电子书 5 折,仅此一天。另外,当当也有图书满 100 减 50 活动,小伙伴们该出手时就出手呀!

推荐阅读:

Spring Cloud之赵国的覆灭

只要会 Java 或 Python,普通程序员也能入门自然语言处理

热度直逼TensorFlow的深度学习框架,我用它画下女朋友最美的脸

当当满 100 减 50 活动正在进行中

☟☟  购书指南

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/turingbooks/article/details/102735333

智能推荐

Python|每日一练|整数数组|非重复子集(幂集)|递归:子集 II_给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂-程序员宅基地

文章浏览阅读266次。给你一个整数数组nums,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。返回的解集中,子集可以按任意顺序排列。_给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂

ICC II 8 Routing & Optimization(布线优化)_icc2中绕线时flash的pin和net总是交错导致short-程序员宅基地

文章浏览阅读1.1w次,点赞20次,收藏162次。Object:执行 布线前的检查和设置使用 route_auto 布线signal nets报告 修复 DRC 的违例优化设计使用route_opt布线阶段的目标 (routing phase goal)布局和时钟树综合此时应该是完成的;可接受的拥塞 setup/hold time 以及逻辑DRCs时钟树网络已经被布线了;布线阶段的目标是:在最小化物理DRC违例情况下 将所有的signal nets 布线选择性的执行 布线后的 时钟树优化或者CCD;优化数据路径逻辑的 时序 DRC_icc2中绕线时flash的pin和net总是交错导致short

大疆机甲大师教育机器人Python开发:中文命名变量初尝试_机甲大师python编程-程序员宅基地

文章浏览阅读1k次。简言之, 官方例程中的Twister程序, 略加修改添加了一个中文变量, 运行成功!def start() 速度 = 120 ... chassis_ctrl.set_rotate_speed(速度) gimbal_ctrl.set_rotate_speed(速度)...略长版本如下.首先, 当然是激动人心的第一次启动. 因为头朝后, 碰到后装甲, 各种手..._机甲大师python编程

Jmeter设置分布式(ubuntu)_ubuntu jmeter分布式服务-程序员宅基地

文章浏览阅读469次。环境ubuntu16.04jmeter5.1.1(尽量使用新版本)配置原理:slaver(执行机)配置安装jmeter,官网下载并解压提取(此处不进行详细说明)修改配置文件/apache-jmeter-5.1.1/bin/jmeter.properties修改本机host文件sudo vim /etc/hosts # 修改当前IP为非回环地址,127.0.0.1即为回环..._ubuntu jmeter分布式服务

Java 判断URL是否有效_java判断url有效性-程序员宅基地

文章浏览阅读1.7k次。Java 判断URL是否有效;主要用于一些url文件下载,或者请求之类的,避免url无效报错代码 /** * 功能:检测当前URL是否可连接或是否有效, * 描述:连接网络不成功,视为该地址不可用 * @param urlStr 指定URL网络地址 * @return URL * @author dyc * date: 2020/9/22 10:54 */ public static URL isConnect(String urlStr) {._java判断url有效性

Rancher Labs获2500万美元C轮融资-程序员宅基地

文章浏览阅读360次。2019年7月29日,业界领先的容器管理软件提供商Rancher Labs(以下简称Rancher)正式宣布已完成新一轮2500万美元C轮融资。本轮投资人为原A轮及B轮投..._rancher 竞品

随便推点

加载.npz文件时,出现错误:OSError: Failed to interpret file ‘xxx.npz‘ as a pickle-程序员宅基地

文章浏览阅读9.9k次,点赞4次,收藏6次。.npz文件的内容是怎样的,怎么打开?加载.npz文件时,出现错误:OSError: Failed to interpret file ‘xxx.npz’ as a pickle首先了解pickle的定义:pickle: 用于python特有的类型和python的数据类型间进行转换pickle提供四个功能:dumps,dump,loads,loadpickle可以存储所有python支持的原生类型(bool,int,float,string,byte,none等),由任何原生类型组成的列表、元_as a pickle

关于stack around the variable “” was corrupted问题_stack around the variable 'dataitem' was corrupted-程序员宅基地

文章浏览阅读1k次。错误:stack around the variable “XX” was corrupted.,中文翻译就是“在变量XX周围的堆栈已损坏”。后面在上网看了很多技术资料,发现大多数网站都有这样的文章:Code:把 project->配置属性->c/c++->代码生成->基本运行时检查 为 默认值 就不会报本异常。具体原因正在研究中。。。 如果改为其他就有e_stack around the variable 'dataitem' was corrupted.

Python 可视化库-程序员宅基地

文章浏览阅读236次。https://www.infoq.cn/article/pSV6tZ1SbUC8qJpo_v8H在奥斯汀举行的SciPy 2018年特别会议上,大量开源 Python 可视化工具的代表分享了他们对 Python 数据可视化未来的展望。我们看到了Matplotlib、Plotly、VisPy等许多库的更新。我作为PyViz、GeoViews、Datashader、Panel、hvPl..._python的ae库

python自动化测试 | 接口自动化测试脚本如何写好?_测试自动化脚本怎么写-程序员宅基地

文章浏览阅读1.4k次,点赞9次,收藏17次。接口测试可以在没有前端界面下进行测试后端的功能校验在前端很难进行测试,因为前端已经有初步校验控制,所以接口测试可以发现很多在前端无法发现的问题提升测试效率,降低人工回归测试的人力成本与时间成本,缩短测试周期。真正的业务逻辑核心是后端。例子说明:有个登录页面,你要登上网站,就需要输入你的账号密码,把账号密码作为请求参数打登录接口,这时客户端会给服务器发个登录请求,服务器鉴权和校验通过之后,就登上去了。到这里就完成了一次接口的请求,或者说跑完了一条接口测试用例。_测试自动化脚本怎么写

cdh oozie 无法启动问题Could not load service classes, Cannot create PoolableConnectionFactory_e0103: could not load service classes, cannot crea-程序员宅基地

文章浏览阅读1.1k次。问题描述:在安装cdh元数据myslq高可用时,使用的是myslq主主复制+keepalived实现。期间发现切换时,出现如下异常信息!花了很长时间寻找问题的原因。因为切换的时候,使用本机命令行是可以连接的,但是夸服务器就无法连接,没有去这方面的尝试,后来使用navicate无法连接后,就推测是mysql高可用切换的问题导致的。问题分析:如果不重启keepalived,是无法实现切换和连接..._e0103: could not load service classes, cannot create poolableconnectionfacto

Federated Machine Learning学习笔记(一):综述:概念与模型-程序员宅基地

文章浏览阅读2.2k次,点赞4次,收藏10次。开始将mobile与AI进行靠拢了,RL,FL,DL一网打尽,先多看看survey再说。本文主要来自于Federated Machine Learning: Concept and Applications,主要介绍了使用Federated Machine Learning的原因,常用的一些概念,以及目前存在的一些应用。为了鼓励自己好好学习,决定提炼核心思想,以备不时之需。一、Why we ne..._federated machine learning