自然语言理解(Natural Language Understanding, NLU) 是 Rasa 中非常重要的部分,用于执行意图分类、实体提取和响应检索。Rasa NLU 接收到 “I am looking for a French restaurant in the center of town”这样的句子,将会返回以下数据:
{
"intent": "search_restaurant",
"entities": {
"cuisine": "French",
"location": "center"
}
}
构建 NLU 模型很难,构建一个可用于生产的模型更难。本篇文章将会来介绍一些小技巧,通过如何设计 NLU 训练数据和管道来充分利用机器人。
1. NLU的会话驱动开发
会话驱动开发(Conversation-Driven Development,CDD)意味着让真实的用户对话指导我们开发。要建立 NLU 模型有两件事情很重要:
- 收集真实数据
-
1.1 收集真实数据
当提到构建 NLU 训练数据时,开发者有时会尝试用文本生成工具或模板来快速增加训练样本的数量,这种错误的想法有 2 个不建议的理由:
首先,人造的数据并不会像用户实际发送给机器人的消息一样,所以模型将会表现不好。
- 其次,在人造数据上进行训练和测试,其实是在欺骗自己模型表现得很好,并且你也不会注意到主要问题。
如果你使用脚本来生成数据,那么请记住模型能学到的知识如何对脚本进行反向工程。为了避免这些问题,收集尽可能多的真实用户数据作为训练数据始终是一个好主意。真实的用户消息可能会杂乱无章,包含打字错误,并且永远不是您意图的理想示例。但是请记住,这些消息才是模型将要给出预测的实际内容。
1.2 尽早与测试用户共享
为了收集真实数据,你需要真实的用户消息。开发人员只能提供有限的示例,然而用户通常会说一些让你大吃一惊的话。这意味着你应该尽早地与开发团队之外的测试用户共享机器人。
2. 避免意图混淆
根据管道中的特征化器,Rasa 会从训练样本提取出字符和单词级的特征来进行意图分类。当不同的意图包含相同单词以类似方式排序对应不同的意图时,将会可能造成意图分类器的混淆。
2.1 实体分割 VS. 意图
当我们希望机器人的回答取决于用户所提供的消息时,往往会出现意图混淆。例如,“”How do I migrate to Rasa from IBM Watson?”和“I want to migrate from Dialogflow.”。
因为这些消息中的每一条都会导致不同的响应,所以我们的初始方法可能会为每种迁移类型创建单独的意图,例如,watson_migration
和 dialogflow_migration
。但是,这些意图都是试图实现同样的目标(迁移到 Rasa),它们很可能使用类似的措辞,从而会导致模型会混淆这些意图。
为了避免意图混淆,将这些训练样本分组为单个 migration
意图,并根据来自实体的分类 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
3. 提升意图识别
在 Rasa 中,我们可以自定义实体并在训练数据中进行标注,来教会模型如何识别他们。Rasa 同样提供了用于提取预训练实体的组件,以及其他形式的训练数据来帮助模型识别和处理实体。
3.1 预训练的实体提取器
常规实体(例如姓名、地址、城市等实体)都需要大量的训练数据喂给 NLU 模型来提高泛化。Rasa 提供了两个非常好的选项来进行预训练提取:SpacyEntityExtractor
和 DucklingEntityExtractor
。由于这些提取器已经在大量数据上进行了预训练,因此我们可以使用它们进行实体提取,而无需在训练数据中进行标注。
3.2 正则表达式
正则表达式在结构化模式上进行实体提取非常有效,例如 5 位美国邮政编码。正则表达式模式可用于生成 NLU 模型学习的特征,或者作为直接实体匹配的方法。
3.3 查找表
查找表被处理为正则表达式,用于检查训练样本中是否存在查找表中的实体。与正则表达式类似,查找表可用于为模型提供特征以改进实体识别,或用于执行基于匹配的实体识别。一个有用的查找表应用为冰淇淋口味、瓶装水品牌,针织袜子长度的样式。
3.4 同义词
向训练数据中添加同义词,在某些实体值映射到单个规范化实体上非常有用。然而,同义词并不意味着挺高模型的实体识别能力,并且它对 NLU 性能也没有影响。
基于同义词的一个很好的用例是将不同组的实体进行规范化化。例如,在机器人询问用户有哪些保险单感兴趣时,他们可能回答“卡车”、“汽车”或者“蝙蝠车”。那么,将 truck
、car
和 batmobile
规范化为 auto
是一个非常好的主意,这样的处理逻辑只需要考虑范围很小的可能性。
4. 处理边缘案例
4.1 拼写错误
拼写错误的情况往往是不可避免的,所以机器人需要一个有效的方法来处理这个问题。请记住,目标不是纠正拼写错误,而是正确识别意图和实体。处于这个原因,虽然语法检查器是一个显而易见的解决方案,但通过调整特征器和训练数据足以解决拼写错误。
字符级特征化器通过考虑部分单词(而非整个单词)能够有效地防止拼写错误,我们可以在管道中使用基于 CountVectorsFeaturizer
的 char_wb
分析器来添加字符级特征化器,例如:
pipeline:
- name: CountVectorsFeaturizer
analyze: char_wb
min_ngram: 1
max_ngram: 4
# <other components>s
除了添加字符级特征化器之外,我们还可以向训练数据中添加常见的拼写错误。
4.2 定义超出范围的意图
在机器人中定义 out_of_scope
意图以捕获域之外的任何用户消息是个非常好的注意,当 out_of_scope
意图定义后,机器人就可以回复类似 “I’m not sure how to handle that, here are some things you can ask me…”这样的消息,从而优雅地引导用户获得支持。
5. 发布更新
像对待代码一样对待数据,就像你永远不会再没有审查的情况下发布代码更新一样,应该仔细查看训练数据的更新,因为它们会对模型的性能产生重大影响。建议使用版本控制系统(例如 Github 或者 Bitbucket)来跟踪数据的改变,并在有需要时进行版本的回滚。
确保为 NLU 模型构建测试,以评估训练数据和超参数变化时的性能。在 CI 管道(例如 Jenkins 或者 Git Workflow)中进行自动化测试,来简化开发过程以及确保只交互高质量的更新。