(十六)RASA最佳实践
作者简介
作者:孟繁中
原文:https://zhuanlan.zhihu.com/p/334645995
转载者:杨夕
面筋地址:https://github.com/km1994/NLP-Interview-Notes
个人笔记:https://github.com/km1994/nlp_paper_study
前面基本上吧RASA的各个方面都串了一遍,本文主要是讲如何使用RASA。使用RASA,可以只通过配置就可以完成一个对话机器人的上线。首先要合理的编写stories,合理使用规则等训练数据,其次要尽快让机器人原型上线,接收来自用户的挑战。
一、Conversation-Driven Development (CDD)
对话驱动开发(CDD)是一个倾听用户意见并利用这些见解改进AI助手的过程。它是聊天机器人开发的最佳实践方法。开发优秀的人工智能助手是一个挑战,因为用户总是会说一些你没有预料到的事情。CDD背后的原则是,在每一次对话中,用户都会用自己的话告诉你他们到底想要什么。通过在robot开发的每个阶段练习CDD,您可以使您的助手适应真正的用户语言和行为。CDD包括以下行动:
分享:尽快与用户共享您的助手
回顾:定期回顾谈话内容
标注:标注消息并将其用作NLU训练数据
测试:测试一下你的助手符合预期
跟踪:追踪助理失败的,并衡量其表现
修正:帮助你的助手如何处理不成功的谈话
CDD不是一个线性的过程;当您开发和改进bot时,是一个不断迭代的过程。
CDD在研发阶段
如果您处于bot开发的早期阶段,CDD似乎没有任何作用-毕竟,您还没有对话数据!但是,在bot开发的一开始,您可以采取一些CDD操作:
1)请参阅NLU数据和故事的最佳实践,使用CDD创建训练数据
2)让你的机器人尽早面向用户测试
注意,测试用户是不知道bot如何工作的人。bot开发团队中的人不是测试用户,因为他们知道bot可以做什么,不能做什么。不要过度指导您的测试用户;他们对bot领域的了解应该和您的最终用户一样多。
3)设置CI/CD管道。
当您从bot对话中收集见解时,CDD会导致对bot进行频繁、较小的更新。在开发的早期设置CI/CD管道使您能够对话中出现的问题快速迭代
在这个阶段,您可以在本地模式下安装Rasa X,以便更容易地与测试用户共享bot、收集对话,并基于收集的对话应用NLU和Story最佳实践。
CDD在生产阶段
一旦投入生产,将有更多的对话数据来获得用户意图。可以完全应用CDD操作。在这个阶段,可以在服务器上安装rasax来部署bot,并在生产中使用bot启用CDD。
回顾
在对话中寻找用户真正的需求。测试用户至少知道一些让robot做什么的指令,但是真正的用户通常根本不知道,或者不准备给robot指令。我们不能迎合所有意想不到的用户行为,但是你可以尝试解决你注意到的主要摩擦点。例如:
1)查看发生“超出范围”意图或回退行为的对话。这可能意味着一种潜在的新技能,或者仅仅是一种错误分类的用户话语。
2)寻找用户的挫败感,比如请求转移到人类身上。
标注
继续遵循NLU的最佳实践,将真实对话中的新用户话语添加到训练数据中。注意不要过度使用你的NLU模型来表达那些已经在你的训练数据中的话语。为了避免过度拟合,并帮助您的模型泛化为更多样化的用户话语,只添加模型先前预测错误或可信度较低的用户话语。
试验
将成功的用户对话添加到测试对话中。坚持这样做有助于确保在对bot进行其他修复时不会引入回退。
跟踪
寻找成功和失败的线索,帮助您跟踪bot的性能。
有些指标在bot外部。例如,如果您正在构建一个bot来缓解客户服务呼叫中心的需求,那么成功的一个指标可能是呼叫中心的流量减少。其他可以直接从对话中获得的信息,比如用户是否达到了代表用户目标的某个动作。
自动跟踪的指标本质上是代理指标;要真正衡量成功,唯一的方法就是对与bot的每一次对话进行单独的审查和评分。虽然这显然是不现实的,但请记住,没有一个指标可以完美地代表您的机器人的性能,所以不要只依赖指标来确定您的机器人需要改进的地方。
修复
在扩展和提高bot的技能时,继续遵循故事的最佳实践。让用户需求指导您添加哪些技能和哪些修复。经常做一些小的改变,而不是偶尔做一次大的改变。这将帮助您评估所做更改的有效性,因为您将更频繁地获得用户反馈。您的CI/CD管道应该允许您自信地这样做。
二、NLU应用CDD
收集真实数据
在构建NLU培训数据时,开发人员有时会倾向于使用文本生成工具或模板来快速增加培训示例的数量。这是个坏主意,有两个原因:
首先,你的合成数据看起来不像用户实际发送给你助手的信息,所以你的模型将表现不佳。
第二,通过对合成数据进行培训和测试,您欺骗自己,让自己认为您的模型实际上表现良好,而且您不会注意到重大问题。
为了避免这些问题,收集尽可能多的真实用户数据作为训练数据总是一个好主意。真正的用户消息可能会很混乱,包含错误,并且远远不是您意图的“理想”示例。但请记住,这些是你要求模型做出预测的信息!你的助手一开始总是会犯错误,但是对用户数据的培训和评估过程会使你的模型在现实世界中更有效地进行概括。
尽早与测试用户共享
为了收集真实的数据,你需要真实的用户信息。一个机器人开发者只能拿出有限的例子,而用户总是会用他们所说的让你大吃一惊。这意味着您应该尽早与开发团队之外的测试用户共享bot。
三、避免意图混淆
使用从训练数据中提取的字符和单词级特征对意图进行分类,这取决于您在NLU管道中添加了哪些特征。当不同的意图包含以相似方式排列的相同单词时,这可能会给意图分类器造成混乱。
实体与意图的分裂
当你想让助手的反应以用户提供的信息为条件时,常常会出现意图混乱。例如,“How do I migrate to Rasa from IBM Watson?”与“I want to migrate from Dialogflow.”
由于每一条消息都会导致不同的响应,因此您的初始方法可能是为每种迁移类型创建单独的意图,例如watson_migration和dialogflow_migration
。然而,这些意图试图达到相同的目标(迁移到Rasa),并且可能会用类似的措辞,这可能会导致模型混淆这些意图。
为了避免意图混淆,将这些训练数据分组为单个migration意图,并使响应依赖于来自实体的slot:product的值。这也使得在没有提供实体的情况下更容易处理,例如“How do I migrate to Rasa?”例如:
stories:
- story: migrate from IBM Watson
steps:
- intent: migration
entities:
- product
- slot_was_set:
- product: Watson
- action: utter_watson_migration
- story: migrate from Dialogflow
steps:
- intent: migration
entities:
- product
- slot_was_set:
- product: Dialogflow
- action: utter_dialogflow_migration
- story: migrate from unspecified
steps:
- intent: migration
- action: utter_ask_migration_product
四、改善实体识别
使用Rasa开源软件,您可以定义自定义实体,并在训练数据中对它们进行注释,以训练您的模型识别它们。Rasa开源软件还提供组件来提取预先训练过的实体,以及其他形式的训练数据,以帮助您的模型识别和处理实体。
预训练的实体提取器
名称、地址和城市等常见实体需要大量的训练数据,才能有效地对NLU模型进行泛化。Rasa开源提供了两个很好的预训练抽取选项:SpacyEntityExtractor和DucklingEntityExtractor。因为这些提取器已经在大量的数据集上进行了预先训练,所以您可以使用它们来提取它们支持的实体,而无需在训练数据中对它们进行注释。
正则表达式
正则表达式对于对结构化模式(如邮政编码)执行实体提取非常有用。正则表达式模式可用于生成供NLU模型学习的特性,或作为直接实体匹配的方法。
查找表
查找表作为regex模式处理,该模式检查训练示例中是否存在任何查找表条目。与regex类似,查找表可用于向模型提供特征以改进实体识别,或用于执行基于匹配的实体识别。查找表示例如,冰淇淋口味、瓶装水品牌,甚至袜子长度的样式(参见查找表)。
同义词
在训练数据中添加同义词对于将某些实体值映射到单个规范化实体非常有用。但是,同义词并不是为了提高模型的实体识别率,对NLU性能没有任何影响。同义词的一个很好的用例是规范化属于不同组的实体。例如,在一个助手询问用户他们感兴趣的保险单时,他们可能会回答“我的卡车”、“一辆车”或“我开蝙蝠车”。绘制卡车、汽车、汽车的Graph是个好主意,而batmobile则将标准化值auto,这样处理逻辑可以简化数据集合。
五、处理边缘案例
拼写错误
遇到拼写错误是不可避免的,所以你的机器人需要一个有效的方法来处理这个问题。请记住,目标不是纠正拼写错误,而是正确识别意图和实体。出于这个原因,虽然拼写检查似乎是一个显而易见的解决方案,调整你的特征和训练数据往往足以解释拼写错误。
添加字符级别的特征化器可以有效地防止拼写错误,因为它只考虑单词的一部分,而不是整个单词。通过使用CountVectorsFeaturizer的char_wb分析器,可以将字符级特征化添加到管道中,例如:
pipeline:
# <other components>
- name: CountVectorsFeaturizer
analyze: char_wb
min_ngram: 1
max_ngram: 4
# <other components>
除了字符级特征化之外,您还可以将常见的拼写错误添加到训练数据中。
定义超出范围的意图
在bot中定义一个“超出”作用域的意图来捕获bot域之外的任何用户消息总是一个好主意。当一个超出范围的意图被识别出来时,你可以用诸如“I'm not sure how to handle that, here are some things you can ask me...”这样的消息来优雅地引导用户使用支持的技能。
六、发布更新
像对待代码一样对待数据。如果没有评审,就永远发布代码更新,同样,对训练数据的更新也应该仔细检查,因为它会对模型的性能产生重大影响。
使用版本控制系统(如Github或Bitbucket)跟踪对数据的更改,并在必要时回滚更新。
一定要为你的NLU模型建立测试,以评估性能随着训练数据和超参数的变化。在CI管道(如Jenkins或Git工作流)中自动化这些测试,以简化开发过程,并确保只提供高质量的更新。
七、编写对话数据
对话数据包括训练Rasa模型的故事和规则。编写良好的对话数据可以让助手可靠地跟踪您安排的会话路径,并归纳出意外的路径。
设计故事
在设计故事时,有两组对话交互需要考虑:快乐和不快乐。快乐路径描述了当用户按照您的期望跟踪对话流时,总是按照机器人提示提供必要的信息。然而,用户经常会用问题、闲聊或其他问题来偏离快乐路径。我们称之为unhappy路径。对于您的bot来说,优雅地处理unhappy路径是很重要的,但是也不可能预测给定用户可能会选择哪条路径。通常,在设计unhappy路径时,开发人员会尝试考虑每一条可能的分歧路径。在状态机中规划每一个可能的状态(其中许多状态永远无法实现)需要大量的额外工作并显著增加训练时间。
相反,我们建议在设计unhappy路径时采用CDD方法。CDD要求尽可能早与用户共享机器人。从这些数据中,您可以创建故事来完成用户的请求,并开始思考如何引导他们回到一条快乐的路径上。
写规则还是故事
规则是对话管理器使用的训练数据,规则总是处理始终遵循相同路径的对话片段。在执行以下操作时,规则可能很有用:
一次性交互:有些消息不需要任何上下文来回答它们。规则是将意图映射到响应的一种简单方法,可以指定这些消息的固定答案。
回退行为:与回退分类器相结合,您可以编写规则,以使用特定的回退行为响应低可信度的用户消息。
表单:激活和提交表单通常都会遵循固定的路径。您还可以编写规则来处理窗体期间的意外输入。
因为规则不适用于看不见的对话,所以您应该为单回合会话片段保留规则,并使用故事来训练多回合对话。
一个规则的例子是,bot向用户消息返回一个固定的响应“utter_greet”,意图是“greet”如下:
rules:
- rule: Greeting Rule
steps:
- intent: greet
- action: utter_greet
而使用story编写多轮对话:
stories: - story: Greeting and ask user how they're doing steps: - intent: greet - action: utter_greet - action: utter_ask_how_doing - intent: doing_great - action: utter_happy
八、管理对话流程
何时使用slot来影响谈话
slot充当机器人的内存。定义slot时,可以定义slot是否应影响对话。属性influence_conversation设置为false的插槽只能存储信息。属性influence_conversation设置为true的插槽可以根据其中存储的信息影响对话流。
影响谈话的slot需要添加到您的故事或规则中。如果slot是由自定义操作设置的,这也适用于这种情况。例如,可以使用由自定义操作设置的布尔槽,根据对话流的值使用以下故事控制对话流:
stories:
- story: Welcome message, premium user
steps:
- intent: greet
- action: action_check_profile
- slot_was_set:
- premium_account: true
- action: utter_welcome_premium
- story: Welcome message, basic user
steps:
- intent: greet
- action: action_check_profile
- slot_was_set:
- premium_account: false
- action: utter_welcome_basic
- action: utter_ask_upgrade
实现分支逻辑
在编写故事时,有时下一个操作将取决于在某个自定义操作中返回的值。在这些情况下,重要的是要在返回插槽的值和直接使用自定义操作代码来影响bot下一步的操作之间找到正确的平衡。
如果值仅用于确定bot的响应,请考虑将决策逻辑嵌入到自定义操作中,而不是在故事中使用特征化的slot。这有助于降低总体复杂性,并使您的故事更易于管理。
使用OR语句和检查点
OR语句和检查点对于减少必须编写的故事数量非常有用。但是,应谨慎使用。过度使用OR语句或检查点会减慢培训速度,创建过多的检查点会使您的故事难以理解。
OR声明
在bot以相同方式处理不同意图的故事中,可以使用OR语句作为创建新故事的替代方法。
例如,可以合并这两个故事:
stories:
- story: newsletter signup with OR
steps:
- intent: signup_newsletter
- action: utter_ask_confirm_signup
- or:
- intent: affirm
- intent: thanks
- action: action_signup_newsletter
检查点
检查点对于将故事模块化为经常重复的单独块非常有用。例如,如果您希望bot在每个会话流结束时请求用户反馈,可以使用检查点来避免在每个故事结尾包含反馈交互:
stories:
- story: beginning of conversation
steps:
- intent: greet
- action: utter_greet
- intent: goodbye
- action: utter_goodbye
- checkpoint: ask_feedback
- story: user provides feedback
steps:
- checkpoint: ask_feedback
- action: utter_ask_feedback
- intent: inform
- action: utter_thank_you
- action: utter_anything_else
- story: user doesn't have feedback
steps:
- checkpoint: ask_feedback
- action: utter_ask_feedback
- intent: deny
- action: utter_no_problem
- action: utter_anything_else
在故事中创建逻辑中断
在设计会话流时,通常很容易创建长篇大论的示例,从开始到结束捕获完整的会话交互。在许多情况下,这将增加说明分支路径所需的培训故事的数量。相反,可以考虑将你的长篇故事分成处理子任务的较小的会话块。
处理丢失的信用卡涉及一系列的子任务,即检查欺诈性交易的消费历史记录,确认更换信用卡的邮寄地址,然后跟进用户的任何其他请求。在这个对话弧中,有几个地方bot会提示用户输入,从而创建需要考虑的分支路径。
例如,当提示用户“完全询问欺诈交易”时,如果没有适用的意图,用户可能会以“拒绝”意图进行响应。当被问到bot是否还有其他可以帮助他们的东西时,用户也可以选择用“拒绝”的意图来回答。
我们可以把这个长篇故事分成几个小故事。
处理上下文切换
通常,用户不会用你问他们的信息来回应,而是用一些无关的问题偏离快乐的道路。通过使用CDD来了解用户的不愉快路径,您可以创建用于处理上下文切换的故事。
使用规则上下文切换
在某些情况下,例如单圈感叹词,您可以使用规则而不是通过故事来处理上下文切换。
使用story上下文切换
当用户的感叹词需要多个会话回合时,您需要编写额外的故事来处理上下文切换。如果您有两个不同的会话流,并且希望用户能够在两个流之间切换,那么您将需要创建故事来指定如何进行切换以及如何维护上下文。