“想不想开发一款自己的聊天机器人?”
“我也可以吗?神马AI,机器学习,DNN……我都不懂啊”
“没关系,其实真的没有那么复杂——掌握方法和工具的话,一天时间就够了……”
举个例子:淘宝小助手
小明业余时间开了一家淘宝店,他是店里唯一的工作人员。白天要上班,晚上不敢熬夜,总是因为错过回复用户消息而丢单。
要是有个客服机器人就好了——小明向好友程序员小刚提出了自己的想法。
小刚问:一般用户都问你什么问题?小明总结了一下,大概有以下4类问题:1. 包邮吗?2. 打折吗?3. 是专柜正品吗?4. 其他。
小刚写了如下一段逻辑,然后告诉小明:你已经有一个客服机器人了。
GET_INPUT IF "包邮" in User_Input SAY "江浙沪满29包邮,其他地区一律不包邮。" ELIF "打折" in User_Input SAY "买2件打9折,3件打8折,4件及以上打7折。" ELIF "正品" in User_Input SAY "请放心,本店所有商品均为专柜正品。" ELSE SAY "感谢提问,客服人员会尽快很您联系。"
引用-1
虽然很简单,但是小明的店确实已经有了一个可以随时回答用户提问的小机器人。
过了几天,小明找到小刚:同一件事有很多种说法,比如:“包邮”也可以说“免邮”;“打折”和“优惠”可以互换……
小刚:这还不好办嘛,条件判断的时候多加几个OR不就得了。通过扩大关键词白名单的方法,小明的自动客服又工作了一段时间。
小明再次找到小刚,提出了一些新的问题:
只是这样匹配关键词不行啊,比如有人问“邮费能给免了吗”,有人问“这个商品邮费多少钱?”,这根本是两个意思,如果都有“邮费”匹配,给同一个答案,就答非所问了。
再说,所有同类问题都给一个回答也太傻了,如果明明知道人家在上海,就直接说包邮不就得了。
还有啊,很多人都是问具体某一个商品怎么怎么样的,现在这个自动客服,根本不回答不了啊……
小刚:不要说了,看来你是真的需要一个聊天机器人(Chatbot)了。我给你讲讲原理,你自己学着开发吧。
Chitchat Bot vs Task Completion Bot
聊天机器人,根据其具体陪人聊天的目的,至少能分为两个大类:Chitchat Bot (闲聊机器人),和Task Completion Bot(任务完成机器人)。
微软的小冰是前者的代表,这类机器人存在的目的就是陪用户闲聊。
大多数闲聊机器人,会希望用户消耗在聊天上的时间尽量长,但也不排除有些希望用户最好不要多花时间(比如贤二机器僧)。
各种客服机器人,个人助手/语音助手(包括微软小娜/Cortana)则属于后者。
它们存在的目的是为了帮用户解决确实的问题,例如查询某一种信息,或者完成某一个动作(例如:订机票、酒店、餐厅座位等)。
这两种机器人背后的实现机制差异巨大。
聊天机器人的三个基本部分
一个聊天机器人,有三个基本部分:
图-1
NOTE:如上的三个部分是从功能角度的分析,并不是说一个Chatbot必须要有与之一一对应的模块或分层。
聊天机器人的实现技术
从学术研究的角度讲,聊天机器人所需技术涉及到自然语言处理、文本挖掘、知识图谱等众多领域。
在当前的研究中,大量机器学习、深度学习技术被引入。各种炫酷的算法模型跑在Google、微软等IT寡头的高质量数据上,得到了颇多激动人心的研究成果。
但具体到实践当中,在没有那么巨量的人工标注数据和大规模计算资源的情况下,于有限范围(scope)内,开发一款真正有用的机器人,更多需要关注的往往不是高深的算法和强健的模型,而是工程细节和用户体验。
此处,我们只是简单介绍几种当前实践中最常用,且相对简单的方法:
Solution-1: 用户问题->标准问题->答案
知识库中存储的是一对对的“问题-答案”对(QA Pair)。这些Pair可以是人工构建的,源于专家系统或者旧有知识库的,也可以是从互联网上爬取下来的。
现在互联网资源这么丰富,各种网页上到处都是FAQ,Q&A,直接爬下来就可以导入知识库。以很小的代价就能让机器人上知天文下晓地理。
当用户输入问题后,将其和知识库现有的标准问题进行一一比对,寻找与用户问题最相近的标准问题,然后将该问题组对的答案返回给用户。
其中,用户问题->标准问题的匹配方法可以是关键词匹配(包括正则表达式匹配);也可以是先将用户问题和标准问题都转化为向量,再计算两者之间的距离(余弦距离、欧氏距离、交叉熵、Jaccard距离等),找到距离最近且距离值低于预设阈值的那个标准问题,作为查找结果。
但关键字匹配覆盖面太小。距离计算的话,在实践中比对出来的最近距离的两句话,可能在语义上毫无关联,甚至满拧(比如一个比另一个多了一个否定词)。另外,确认相似度的阈值也很难有一个通用的有效方法,很多时候都是开发者自己拍脑袋定的。
因此,这种方案,很难达到高质高效。
Solution-2. 用户问题->答案
知识库中存储的不是问题-答案对,而仅存储答案(文档)。
当接收到用户问题后,直接拿问题去和知识库中的一篇篇文档比对,找到在内容上关联最紧密的那篇,作为答案返回给用户。
这种方法维护知识库的成本更小,但相对于Solution-1,准确度更低。
Solution-3. 用户问题->语义理解->知识库查询->查询结果生成答案
从用户的问题当中识别出用户的意图,并抽取这个意图针对的实体。
相应的,知识库内存储的知识,可以按照二维表的方式组织(类比关系数据库中的table)。
Chatbot在提取了意图和实体后,构造出对知识库的查询(Query),实施查询,得出结果后生成回答,回复给用户。
我们下面将的One-day Solution,就是基于本方案的。
Chatbot One-Day Solution
在此,给出一个极简版Chatbot实现方案,帮助你在一天的时间内,开发一款问题解决型聊天机器人。
下图是这款机器人的架构:
图-2
最左侧的语言理解(Language Understanding)模块,和右上的回复生成器(Response Generator)加起来对应的图-1中的是I/O部分。
正中的对话管理器(Dialog Manager)和上下文存储(Context Store)属于图-1里的中控部分。
最下方是知识库。
语言理解
语义理解的过程包括两个子任务:
具体某个Chatbot的意图类型和实体类型,是其开发者自己定义的。例如小明的客服机器人可以如此定义:
Case1:发到北京的货包邮吗?—— 意图:查询包邮;目的地实体:北京 Case2:00183号商品快递到伊犁邮费多少?—— 意图:查询邮费;目的地实体:伊犁,商品Id实体:00183 Case3:02465号商品有保修吗?——意图:保修查询;商品Id实体:02465
引用-2-1
或者,换个定义意图和实体的方式,也没有问题:
Case2’:00183号商品快递到伊犁邮费多少?—— 意图:商品查询;目的地实体:伊犁,商品Id实体:00183,商品属性实体:邮费 Case3’:02465号商品有保修吗?——意图:商品查询;商品Id实体:02465,商品属性实体:保修
引用-2-2
语义理解模块当然可以自己开发。一般来说,意图识别是一个典型的分类模型(e.g. Logistic Regression,Decision Tree等),而实体抽取则是一个Sequence-to-Sequence判别模型(一般选用Conditional Random Field)。
不过,这需要长期积累的自然语言处理(NLP)的专业知识和经验,高效的运算框架,以及标注工具的支持。作为一个轻量级Bot的开发者,单独开发一个语言理解模块耗时耗力,效果还未必好。
微软语言理解智能服务 LUIS
为了帮助普通开发者解决自然语言理解这一开发瓶颈,微软推出了自己的语言理解智能服务 - LUIS(https://www.luis.ai)。
图-3
LUIS (Language Understanding Intelligent Service) 的使命是让非NLP专业的开发者,能够轻松地创建和维护高质量的自然语言理解模型,并无缝对接到相关应用中去。
使用LUIS,一个Bot需要创建一个(或多个)LUIS App,然后标注所期望的输入(用户的自然语言提问)和输出(意图和实体),再经过在线训练来获得自己的语言理解模型。
整个开发过程中,开发者只需要清晰地定义自己需要让机器理解的用户意图和实体即可,并不需要了解背后算法的细节。
LUIS的开发流程包括三大步骤:
步骤1:数据输入和标注
步骤2:在线模型训练
步骤3:模型发布和服务
图-4
LUIS开发者可以在界面上轻松地进行在线数据标注。
首先,在对应的用户意图中输入自然语言语句,例如:在“商品查询”意图中输入一句“00183号商品快递到伊犁邮费多少?” ;然后,通过鼠标选取实体并指定类型,例如:选择“邮费”标注为“商品属性”。
图-5
LUIS平台会自动从用户输入并标注的数据中提取文本特征。这些特征,包括LUIS预设的常用文本特征(从大数据语料中提取),也包括用户自定的新特征。
LUIS允许用户通过两种方式来定义新特征:
i)短语列表特征(Phrase List Features)
需用户自己定义若干短语列表,这些被定义在同一列表中的短语,都会被当作同一个实体类型中的实体处理。
图-6
在定义过程中,LUIS还会通过其语义词典(semantic dictionary)挖掘技术,根据用户输入的短语,自动从海量的网络数据中发现相似的短语,并推荐给用户。从而有效地提升了效率。
ii)模式特征(Pattern Features)
也称为正则表达式特征。主要用于定义若干正则表达式。
LUIS根据这些表达式从用户输入数据中抽取符合其模式的实体。
LUIS的模型训练过程极其简单,开发者只需点击一下 “Train” 按钮,后台就会基于输入数据进行自动训练。
训练的时间与标注数据量相关,标注数据越多,训练所需的时间越长。同时,训练时间还与LUIS App所支持的意图和实体个数相关,意图和实体越多,训练时间也越长。
训练完模型,开发者可以对其进行性能测试。方法有两种:
i)交互式测试:开发者可以在页面上直接输入自然语言语句,然后目测模型输出结果。
ii)批量测试:开发者需要上传一份测试数据,LUIS完成全部测试后给出精准率和召回率等统计数据,并针对每一项意图和实体的绘制出Confusion Matrix。
测试过的模型,只需点击“Publish”按钮,就可以发布到微软的Azure云平台上,成为一个立即可用的API Service。
开发者可以通过Http的Get方法,调用模型,对新的语句进行意图识别和实体抽取。
上述三个步骤是可以不断重复迭代的。
模型训练完发布上线后,可以继续输入、标注新的数据,重新训练,再次发布。如此循环往复,逐步改进质量。
知识库查询和结果返回
我们选择SQL Server作为图-2中的知识库。知识存储在table中。
用户的问题经过语言理解,被提取成了意图和若干实体。下面要做的就是:将解析出来的意图和实体构造成一个SQL Query,用于在知识库table中进行查询。
例如,我们来看引用-2-2中的Case2’和Case3’。
知识库里有一个Table,名字叫Product,其中每一个row对应一种产品,每个column对应一个属性。
那么从Case2’中解析出来的意图和实体(意图:商品查询;实体:[目的地:伊犁,商品Id:00183,商品属性:邮费]),则可以被构造成一个SQL Query:
SELECT ‘邮费’ FROM Product WHERE Product_name = '00183' AND Destination = ‘伊犁’
引用-3
Query在SQL Server中运行的结果(比如是26元),被放到一个预置的针对商品查询的答案模板里,生成答案。
预置模板:“您查询的${商品Id}号商品的${商品属性}是${Query_Result}。” 生成答案:“您查询的00183号商品的邮费是26元。”
引用-4
上下文存储
客户和客服对话的时候,经常会问多个问题。而不同的问题之间,可能有一些信息是共享的。例如:
客户:02366这款产品可以退换吗?(问题1) 客服:7天之内无理由退换。 客户:寄到南昌邮费多少啊?(问题2) 客服:10块亲。 客户:武汉呢?(问题3)
引用-5
上例中,问题1询问可否退换,并提到了一个产品的Id;问题2询问到南昌的邮费,但是没有提具体产品;问题3干脆只有一个地名。
但是作为人工客服很明白:问题2询问的产品是问题1中出现的02366,而问题3则是询问这款产品寄到武汉的邮费。
这些同一个对话中不同语句之间共享的信息,就是上下文(Context)。
想要机器人具备上下文的记忆、理解功能,而不是把用户的每一个单独语句当作本轮问题的全部信息来源,就需要有一个ContextStore来专门负责上下文信息的记录、查询、更新和删除(CRUD)。参见图-2中最右侧下方的模块。
每次用户新输入的信息都要先进行语言理解,再结合现有ContextStore中存储的内容,或更新Context,或读取之前的Context作为补充信息。
以引用-5为例,可以将意图,和几种实体类型对应的实体值(例如Id,目标属性,目的地等)存储在Context中。
当新的用户语句输入后,假设从中能够提取出新的意图或实体值,则用新值更新Context,否则,读入现有的对应实体值,作为本次语言理解的补充。
在引用-5中,问题1中读取到了商品查询的意图,商品Id,和“退换“这一商品属性,将它们存入Context。
问题2中读取到了”邮费“这一商品属性,和之前存储的不同,则更新Context的商品属性值,并新存入“目的地”这一实体。
问题3则更新了目的地,并读取其他的包括意图、商品Id和商品属性的值,与目的地一起用来构造查询。
Context的场景针对性非常强,很多时候需要针对不同的意图,记录不同类型的实体值。在不同意图之间切换的时候,也有可能会保留部分原有实体。这些都要针对具体情况case by case分析。
具体ContextStore中存储什么样的内容,CRUD策略是什么,都是开发者需要自己决定。
一天开发一款机器人
按照我们刚才说的:
(1)创建一个LUIS App,添加意图、实体类型,定义特征,并输入相应数据,进行标注、训练和发布。
(2)创建一个SQL Server Database及若干表格,用来存储问答知识。
(3)写一个程序负责:
i)通过收发Http Request/Response来调用LUIS的online model进行语言理解;
ii)根据LUIS解析结果构建SQL Query
iii)进行数据库查询;
iv)根据数据库查询结果构造答案;
v)创建并维护Context。
在完成了上述工作后,一个可以理解人类语言的聊天机器人就可以上线为顾客服务了。
在实践当中,还有一些问题需要注意:
Tip-1:知识库采用SQL Server只是选择之一,知识库可以是任何形式。
如果有API可以调用,直接用API作为知识库也是一个不错的选择。这样,意图+实体=>SQL Query就变成了意图+实体=>API Request。
https://github.com/juliali/WeatherBot 就是这样一个以API为知识库的例子。
Tip-2:当前的LUIS是一个纯粹model-based的语言理解工具。
虽然可扩展性强,但也会有精准度不高,不容易即时修改等问题(虽然可以在线训练和发布,但作为model-based分类器和判别工具,如须改变某句话的分类,或许并不能靠添加单一的训练数据来完成)。
在这种情况下,可以考虑LUIS和rule-based的意图、实体识别相结合。可以通过添加一系列正则表达式来匹配意图,抽取实体。
Tip-3:每一个LUIS App都有一个内置的意图,叫做None,非常有用。一般用它来收集那些用户经常会问,但是Chat Bot并不打算回答的问题。
若不在None中指定它们,这些问题很可能会被错误的预测为其他用户自定义的意图,造成Chat Bot的over trigger(e.g.用户问:你是女孩吗?Bot回答:江浙沪包邮),从而严重影响用户体验。
Tip-4:某些情况下,Chatbot可能需要反问用户若干问题,或者和用户确认之前的某个回答,在这种情况下,就需要有内部流程控制。
例如:在商品查询的目标属性为邮费时,目的地缺失,这时候就需要主动要求用户输入对应的值。
不同场景的需求不同,这样的控制流程很难统一规划,因此需要在具体实践中根据具体需求,完成细节。
Tip-5:有些时候,在无法明确用户意图时,也可以主动提出几个备选问题,请用户选择他们想问的。
总之,在实践中由于具体的场景和需求,会遇到各种各样的问题。到时候,就兵来将挡,水来土掩吧!