通常而言,通用聊天机器人(比如小冰等)底层技术是采用类似Seq2Seq等“生成”技术的。但是这种机器人属于探索性质,无法
提供特定的服务。而Siri则是兼具闲聊以及垂直领域功能的,比如可以预约提醒,打电话,定餐厅等特定功能。相信Siri在实现特定预约提醒,打电话功能等,则是使用了“语言模板”匹配技术以及结合分类器来实现精度的控制和定向动作的执行。
对于聊天机器人我个人是相当感兴趣的,奈何现在的已经公开的文章都“相对初级和入门”,或者说过于专注里面的某个算法,比如问答匹配算法。所以萌生了写一篇文章的想法。本文基于自己开发相应系统的经验,理论上会给大家带来一些帮助。但是因为是内部系统,只能谈及一些较为公开的思想。
现在我们的目标是探讨是如何设计和实现一个,只要通过简单配置就完成一个特定主题对话的机器人。有需要的话,可以经过插件(组件)开发,为其增加新垂直领域对话功能。这些插件,就如我前面所言,可能需要集成大量的针对特定领域问题的算法。
语言模板可以保证意图识别的准确性,机器学习除了能否增加覆盖度以外,同时也是对话配置系统的核心所在。在今天这篇文章里我们会重点探讨1,2两点,3有需要的时候会提及。
语言模板很好理解,就是一句话匹配上了某个模板,这个模板会指向一个动作,从而能够给出一个响应。一个大致的配置如下:
{ "action": "how to deal with <xdisease>", "template": [ "<疾病>*怎么办", "<疾病>*怎么*治疗" ], "reg": "", "id": "24" }
比如 糖尿病怎么办
,就会匹配上这个模板,并且执行action动作。如果怕模板定制的过于宽泛,可以再通过reg (正则)来进行限制。id 则是这个模板的唯一编号。 * 表示中间可以匹配任何字符。<疾病> 则表示特定实体,这需要有海量的实体词典支持。
通常语言匹配模板引擎会有比较明显的性能问题。当你有几千个上万个模板,每个模板里面又有几十个甚至上百个子模板,那么一次匹配的成本会相当高,对CPU压力也会非常大。所以通常我们会采用倒排索引技术,比如 <xdisease>*怎么*治疗
会进行如下编码:
怎么 -> [24,100...... ] 治疗 -> [24,36...... ]
比如 糖尿病应该怎么办治疗
,我们会提取出”怎么“,”治疗“,两个词汇,然后获得他们的倒排列表,求交集,就能得到24,并且再做一次实体检查,就能实现快速查找了。当然具体如何做倒排列表,如何做抽词,包括做实体识别,实体积累我们在本文不做详解。
语言模板引擎是聊天机器人里较为核心的组件,通常算法在这种场景里是补充。
对话配置系统,其实就是chatbot framework, 据说有一些开源实现,不过我没具体了解过。我这里说说我的设计。
通常对于一次性对话(一问一答)这个比较好处理,依托于上面的语言模板引擎基本就能实现了。对于有一个”对话引导流程“的会话,这种多伦对话则需要一个较为完善的对话配置系统。
对话配置系统会涉及到几个概念:
上面都是一些要点,我这里会举一个最简单的配置例子:
{ "对话名称": { "id": 1, "intercept": [ { "name": "......ConversationChangeInterceptor", "params": { "match": "template:6,8,9,10", "target_step": { "match_one": "3:1", "no_match": "2:0" } } } ], "conversation": [ { "chain_type": "condition", "match": [ "template:6,7,8" ], "desc": "", "msg": "", "step": 0, "target_step": { "question_finish": 11, "no_match": 10 } }, { "chain_type": "conversation", "match": [ ], "desc": "", "format_class": { "name": ".....ClassifyFormatter", "params": { "url": "..../prediction" } }, "msg": "您好 ${name}先生", "step": 6, "target_step": -1 }
intercept表示会话拦截,在对话流程里任何一个环节,都需要检查下是不是发现了主题变更,如果符合,则会根据target_step实现对应的跳转。在interceptor的target_step 被表示为 A:B 这种形式,意思是跳转到A对话里的B节点上。
match 表示匹配了哪些模板,当然,也可以是一个算法模型,比如"model:com.org.QuestionClassify",比如我需要判定用户是不是在描述自己的身体状况,这个时候用模板显然是不行的,可能需要继承一个算法分类器。显然上面的配置是支持这种集成的。
如果匹配上了则跳转到对应的step 11,如果没有匹配则跳转到step 10。根据类型(chain_type),这是一个条件节点,所以他不会对用户做任何输出,而是默默的根据条件往其他节点条。
msg 表示应答的语句,如果你想动态调整这个输出,可以配置format_class。format_class主要实现复杂动态的应答逻辑。另外还有一个类似配置是query_class,会拦截进来用户的问题,并且改写用户的问题。
step 表示当前处于会话的那个节点,这个节点处理完后的下一个节点会是target_step。 通过适当形态,可以实现会话之间的跳转。我举的例子只是展示了内部跳转。
有了这套配置引擎后,比如做一个客服机器人,就变得很简单了,把用户的常见问题罗列下,之后执行特定的动作。当然,很多传统的客服机器人为了简单期间,主要是通过依托于QA算法做匹配,并不会采用这种我说的方案。
我们根据这套配置引擎,可以实现一个很复杂的对话。而且可以配置很多有趣的功能,比如 功能导航对话,比如吃饭请按1,睡觉轻按2 这种。只要配置一个新对话即可,然后这个对话作为作为起始对话,通过会话间跳转来完成导航功能。
因为千人千面,所以在实际的引擎实现过程中,我们需要记录每个用户当前所处的会话以及所在的节点,还包括会话期间的一些信息搜集,这也是一个较为复杂的话题了。我们底层采用redis做这个存储。
一般而言,我们无法使用“某个”算法就实现一个复杂的系统,当然,“某个”算法可能很重要,甚至是系统能否成功的关键。一个复杂的系统通常都是根据每个环节的需求不同,综合利用了方方面面的算法,而每个算法使用的数据又是其他算法处理而来的。ChatBot framework 本身能够通过配置,复用一些已有的组件完成一些基础的对话功能,但是如果要实现更复杂的对话,则需要更多算法和组件的支持。