训练数据是每个机器学习模型的重要组成部分,Rasa 的目的是使你的助手无需编写规则,而是可以观察真正的对话,从中学习并使用这些知识来管理对话。由于人类自然语言的复杂性,建模对话并非易事。这就需要设计仔细的对话数据用于助手的学习模式。在 Rasa 中把这对话数据称之为:Rasa stories。
但是,如何实际设计呢?在本文中主要介绍设计 Rasa stories 的最佳实践和构建最佳对话式 AI 时应注意的事项。本文中的 stories 示例来自于 Sara,它是 Rasa 文档中使用 Rasa Stack 构建的开源入门助手,你可以在此处找到 Sara 的开源代码。
本文的目录结构:
- Rasa stories 简介
- 如何开始创建训练 stories?
- 使用 Slots 设计 stories
- Rasa Forms stories
- 处理闲聊
- 需要多少个训练 stories?
- 总结
1. Rasa stories 简介
Rasa stories 是一种用于训练 Rasa Core 对话管理模型的训练数据。一个 story 是用户和 AI 助手之间实际对话的一个表示,它被转换为特定格式,其中用户输入表示为相应的意图(必要时还包括实体),而助手的响应表示为相应的动作名称。
下面是 Rasa stories 由哪些要素组成的示例:
- Story 以前缀
##
开头,建议你为 stories 指定名称,易于阅读和调试。在命名 stories 时,最好取有意义且唯一的名称,以便清楚地表示 stories 代表的是什么意思。 - 以前缀
*
开头来表示用户输入相应的意图。 - 如果在对话的特定状态下,提取了重要实体并影响了对话的方向,则还必须在大括号内指定实体的名称和值,将它们也包含在 stories 中。
- 以前缀
-
开头表示助手的响应动作名称。 - 所有 stories 都应以空行结尾,空行标志着训练 stories 的结束。
请注意,在你的 stories 中,一个用户的意图之后可能话会有多个话语。例如,下面的故事显示,一旦用户询问什么是Rasa NLU,则 AI 助手首先会响应一条消息,确认它可以处理该请求,然后提供对所提出问题的实际答复:
* greet
- utter_greet
* ask_product{"product": "Rasa NLU"}
- utter_on_it
- utter_explain_rasa_nlu
* thanks
通过观察训练 stories,对话管理模型可以了解当前用户输入和对话的先前状态如何影响下一个响应的预测。但是,如何获得这些会话训练数据呢?
2. 如何开始创建训练 stories?
获取训练数据是 Rasa 开发人员需应对的常见挑战,之所以具有挑战性,是因为没有很多公开可用的对话数据集。此外,大多数时候,需要对你的特定领域和场景来定制 AI 助手的训练数据。因此,很有可能需要从头开始生成训练数据。
2.1 生成训练数据
为了使此过程更轻松、更有效,首先应该设计对话流程,以了解用户与助手之间最常见的对话是什么样的。为此,您可以使用 Botsociety 之类的工具来设计对话,在平台上可以直观的查看对话消息是怎么传递的,最重要的是,可以将其对话消息以 Rasa 格式导出并导入到开发中。设计完对话流程后,应该要生成更多简单 stories 的示例,这些示例将涵盖与对话流程的一些偏差。你可以使用 Rasa Core 的交互式学习、检查点、OR 语句功能来生成更多的训练数据。
2.2 交互式学习
交互式学习是一种训练 AI 助手并在与机器人对话时生成训练 stories 的好方法。在交互式学习过程中,机器人会要求你对其做出的每个预测(意图分类和响应预测)提供反馈。你在交互式学习过程中与 AI 助手进行的所有对话都可以稍后导出为 NLU 和对话训练示例,并附加到原始训练数据样本中。使用交互式学习,你还可以可视化训练数据,观察与助手交谈时对话流程的变化。
要在交互式学习中训练助手,你至少需要事先准备一些训练 stories。在这里,您可以从前面提到的 Rasa 和 Botsociety 集成中获得最大收益,通过 Botsociety 设计愉快的流程,以 Rasa 格式导出它,并使用导出的 stories 在交互式学习中训练你的助手。
2.3 检查点
检查点可以帮助你将 stories 模块化。检查点的主要思想是创建 stories 的蓝图,以后可以使用检查点名称将其添加到任何 stories 中,而无需手工编写整个 stories。要创建检查点,必须在要重用的对话部分的末尾添加 > checkpoint_name
。例如:
## first story
* hello
- action_ask_user_question
> check_asked_question
这意味着现在你可以通过引用检查点的名称将此对话部分添加到训练数据文件中的任何其他 stories 中。下面是两个 stories 示例来说明这一点。这两个 stories 都以先前的 ## first story
开始,然后以用户的 affirm
或 deny
继续:
## user affirms question
> check_asked_question
* affirm
- action_handle_affirmation
## user denies question
> check_asked_question
* deny
- utter_goodbye
虽然检查点可以为你节省一些时间来生成训练 stories,但它们会很快导致严重的内存问题,并使你的训练 stories 很难阅读,因此你需要在何时何地使用检查点,要谨记在心。
2.4 OR 语句
另一种方法是使用 OR 语句对特定会话状态下的可能意图编写更多 stories,这样你可以用同一个 story 来涵盖更多不同的对话轮次。例如,下面的 story 涵盖了两个不同的对话轮次,提供了用户在该特定对话状态下可能使用的两种可能意图:
## story
...
- utter_ask_confirm
* affirm OR thankyou
- action_handle_affirmation
最好的部分是,在 OR 语句中可以包含多少个意图,没有具体数目。但是,就像检查点一样,过度使用 OR 语句可能会在训练助手时导致内存问题,这也可能提示 NLU 训练数据中的某些意图应该合并。
一旦有了助手工作的基础知识,就应该让真实的用户帮助你改进助手。为此,让你的助手与真实用户聊天,收集对话数据并观察他们与 AI 助手之间的对话情况。它使你可以更好地了解你的聊天机器人应该能够进行哪些对话,你的 AI 助手应该学习哪些新技能,最重要的是,你拥有强大的训练数据集来不断改进你的机器人。
现在,当你知道如何生成训练数据后,让我们看一下设计训练示例时的一些最常见示例和最佳实践。
3. 使用 Slots 设计 stories
除了意图分类、实体提取和和先前对话状态外,对话管理模型的预测还可能受 slots 影响。Slots 是键值存储,在整个对话期间或重置之前,通过它来存储重要的信息来保持对话的上下文。使用 slots,您可以让助手何时应询问必要的详细信息,以及如果已提供详细信息,则何时跳过这些问题。
例如:Sara 的技能之一是为 Rasa 新闻订阅新的 Rasa 用户。为此,助手必须知道用户的电子邮件,因为没有它,助手将无法执行操作。因此,目标是教助手当用户未提供电子邮件,该助手应询问,当提供过,则助手应跳过该问题并转到下一个会话状态。可以使用 slots 对此类行为进行建模:如果未填充电子邮件 slot,则助手将要求此信息,但是如果该 slot 已被填充,则助手应很好地执行操作。方法 slot{}
在 Rasa stories 中,在对话的特定状态下指定特定的设置时段。以下是两个示例 stories,它们说明了对话不同阶段的 slot{}
事件如何影响对话。
用户未提供电子邮件的初始请求的 story:
## story_email_not provided
* greet
- utter_greet
* subscribe_newsletter
- utter_ask_email
* inform{email: '[email protected]'}
- slot{email: '[email protected]'}
- action_subscribe_newsletter
用户已提供电子邮件的初始请求的 story:
## story_email provided
* greet
- utter_greet
* subscribe_newsletter{email: '[email protected]'}
- slot{email: '[email protected]'}
- action_subscribe_newsletter
如果你有一个名为 email 的 slot,并且你的 NLU 模型提取了一个名为 email 的实体,那么一旦该实体被提取,该 slot 就会自动设置。因此,在这种情况下,你无需在 stories 中包含 slot {}
方法。如果你使用交互式学习来生成 stories,则 slot {}
方法会自动添加到你的 stories 中,因此你完全不必担心它们。
根据提取的详细信息,在对不同的对话回合进行建模时,slots 是关键。你可以使用不同的 slots 类型来决定它们应如何影响下一个动作的预测。你可以在定义助手的 domain 时指定 slots 类型。例如:对于文本类型 slot,文本存在或不存在都会影响 AI 助手接下来要做什么。但在其他情况下,你可能希望使用分类或布尔类型 slots,这些 slots 将考虑它的实际值,或者你可能希望使用未配置的 slots 来简单地存储信息而不影响预测。你可以阅读更多有关 Rasa 中不同类型的 slots。
在某些情况下,自定义动作返回的详细信息也会影响对话。自定义动作返回的 slots 可提供额外的信息,并将对话推向特定的方向。例如:在以上使用的时事通讯订阅示例中,助手的行为根据用户是否已经是时事通讯的订阅者而有所不同。在这种情况下,自定义动作可以检查用户是否已经订阅了新闻通讯,并使用 SlotSet()
方法将布尔值槽设置为 True
或 False
:
class ActionSubscribeNewsletter(Action):
""" This action calls our newsletter API and subscribes the user with
their email address"""
def name(self):
return "action_subscribe_newsletter"
def run(self, dispatcher, tracker, domain):
email = tracker.get_slot('email')
if email:
# if the email is already subscribed, this returns False
subscribed = check_if_subscribed(email)
return [SlotSet('subscribed', subscribed)]
return []
这种行为也必须反映在训练 stories 中。为此,你应该在设置 slots 的自定义动作之后,在 stories 中添加 slot {}
事件(如果使用交互式学习,则会自动为你完成)。以下是自定义动作填充的 slots 的值如何影响会话流程的示例。如果用户还不是订阅者,则助手进行订阅并向用户发送确认电子邮件,但是如果用户已经是订阅者,则助手会发送一条消息,告知用户该用户已经订阅:
## newsletter + not subscribed
* greet
- action_greet_user
* signup_newsletter
- utter_ask_email
* inform{"email": "[email protected]"}
- slot{"email": "[email protected]"}
- action_subscribe_newsletter
- slot{"subscribed": true}
- utter_awesome
- utter_confirmationemail
- utter_ask_feedback
* deny
- utter_thumbsup
## newsletter + already subscribed
* greet
- action_greet_user
* signup_newsletter
- utter_ask_email
* inform{"email": "[email protected]"}
- slot{"email": "[email protected]"}
- action_subscribe_newsletter
- slot{"subscribed": false}
- utter_already_subscribed
- utter_ask_feedback
* deny
- utter_thumbsup
如果你的 AI 助手仅需要收集一两个信息来执行特定动作,则应该使用 slots,但是通常,您的助手需要收集更多的详细信息。如果助手需要填写的信息不止几个,那么 stories 的复杂性会随着所需的每条额外信息的增加而增加,设计只有 slots的训练 stories 会变得很复杂。除此之外,您可能还需要大量的训练 stories 来涵盖一条愉快的路径,更不用说偏离它了。要解决此问题并避免 stories 太长,您应该使用 Rasa Forms 进行 slots 填充。
4. Rasa Forms stories
Rasa Forms 是 Rasa 的一项重要功能,可让你轻松设计 AI 助手的快乐路径,并使 slots 填充更加可靠。例如:使用Sara,Rasa 用户可以预订销售电话以了解有关 Rasa 企业功能的更多信息。在安排呼叫之前,Sara必须了解有关用户的许多重要详细信息:用户的职位,用例,姓名,公司等。要仅用 slots 来建模此类对话,你就必须考虑至少一些用户如何提供这些详细信息的不同方式(向他们提供所有初始请求,向他们提供一些原始请求,仅在明确要求时提供等)。取而代之的是,使用 Rasa Forms 可以将此类情况建模为一个单一的 story,并确保聊天机器人在继续前进之前已收集了所有详细信息。
## happy path
* greet
- utter_greet
* contact_sales # user request
- sales_form # activate sales form
- form{"name": "sales_form"} # run form action
- form{"name": null} # deactivate form
- utter_slots_values
* thankyou
- utter_noworries
Rasa Forms 的主要思想是:一旦激活了表单,FormPolicy
将接管对话管理并使用表单动作来检查哪些必需的 slots 仍然丢失。设置所有 slots 后,将停用该表单,并且 FormPolicy
将对话管理移交给配置文件中指定的其他策略。你可以在此处找到有关如何配置表单动作的更多详细信息。
显然,用户并不会总是遵循愉快的路径,他们经常会拒绝提供所需的信息或说出与助手目标无关的内容。要对此类情况进行建模,你的 stories 应在表单动作会话中包括中间动作。例如:下面的 stories 表示一种情况,用户决定在表单动作会话的中间停止提供信息,但后面又提供了所需的详细信息:
## stop but continue path
* contact_sales
- sales_form
- form{"name": "sales_form"}
* stop
- utter_ask_continue
* affirm
- sales_form
- form{"name": null}
- utter_slots_values
* thankyou
- utter_noworries
由于使用 Rasa Forms 的 stories 可能会变得复杂,尤其是在对偏离快乐路径的偏离进行建模时,因此我们建议使用交互式学习来生成训练数据。
在开发情境 AI 助手时,很可能必须处理与快乐路径更为严重的偏差。例如:很有可能你的用户在某个时候完全偏离了最初的目标,开始进行所谓的“聊天”,说一些与助手领域完全无关的事情。下面我们来探讨解决该问题的最佳方法。
5. 处理闲聊
Chitchat 是一种所谓的不合作行为,用户在其中谈论与机器人打算做什么完全不相关的事情。这样的行为的一个例子可能是用户问餐厅搜索助手建议看什么电影,或者谁是美国总统。虽然很难让你的 AI 助手对每种情况都做出不同而自然的反应,但至少要优雅地识别和处理它是很重要的。那么如何识别闲聊呢?最好的方法是创建一个单独的意图,例如:chitchat
并在各种输入上对其进行训练,与助手的实际操作无关。然后,一旦你的 NLU 模型能够将闲聊与其他意图区分开,你便可以将其包含在实际发生闲聊的 stories 中。
助手如何响应闲聊取决于你,一种简单的方法可能只是用诸如:“对不起,我无法帮助您!”之类的普遍话语做出回应。有关这种情况的示例 storey 如下所示:
## form stop + come back
* greet
- utter_greet
* contact_sales
- sales_form
- form{"name": "sales_form"}
* stop
- utter_ask_continue
* affirm
- sales_form
- form{"name": null}
- utter_slots_values
* thankyou
- utter_noworries
由于闲谈可以发生在会话的任何状态,因此需要大量的训练示例,让常规训练策略正确地进行处理。为解决此问题,我们创建了一个名为 MappingPolicy
的新策略,该策略使得处理聊天和类似 FAQ 的交互变得更加容易。MappingPolicy
的主要思想是:它允许你指定是否将某些意图映射到特定的动作,从而确保一旦识别出特定的意图(例如:chitchat
),则 AI 助手始终以映射的动作(例如:utter_chitchat
)进行响应并忽略之前在对话中发生的所有事情。要指定哪些意图应映射到哪些动作,应在 domain 文件中向特定意图添加 triggers
方法。
intents:
- chitchat: {triggers: utter_chitchat}
识别和响应闲聊只是面向目标助手工作的一部分。同样非常重要的是使你的助手能够掌控对话并使用户回到对话的初始路径。为实现这一目标,你的 stories 不仅应包括对聊天的回应,还应包括让用户想起之前对话的回应。下面的 story 就是一个很好的例子:当用户开始聊天,助手就会响应并提醒在聊天发生之前发送给用户的最新请求:
## form + chitchat
* contact_sales
- utter_ask_jobfunction
* chitchat
- utter_chitchat
- utter_ask_jobfunction
* enter_data{"jobfunction": "product manager"}
- utter_ask_usecase
* chitchat
- utter_chitchat
- utter_ask_usecase
要为此类对话建模,您可以尝试 Rasa EmbeddingPolicy,该政策是专门为处理不合作的用户行为而设计的,可以更好地泛化看不见的对话。
6. 需要多少个训练 stories?
这是一个非常普遍和重要的问题。建立一个可以正常工作的助手需要多少 stories 是没有经验法则,但是几十个训练 stories 应该足以启动开发。通过更多的训练示例,你的 AI 助手将学会更好地推广并处理更复杂的偏离快乐路径的偏差。重要的是要记住,要实现这一点,训练数据文件中的 stories 必须动态且涵盖不同的对话轮次。要构建一个生产就绪的助手,您可能至少需要数百个训练 stories(具体取决于你希望助手处理的领域和对话的复杂性)。随着训练数据量的增加,你可以考虑将训练数据分为不同的文件,以便于调试。如果你这样做,然后你可以将包含训练数据文件的文件夹传递给Rasa Core 训练功能,并且该文件夹内的所有文件将被视为一个大训练数据文件的一部分。以下是一个文件夹结构与多个训练数据文件的外观示例:
然后,你可以将数据文件夹传递给 Rasa 训练函数,如下所示:
rasa traiin
7. 总结
训练数据对于构建成功的 AI 助手至关重要。所需的 stories 数量和种类取决于你的用户场景,因此请务必记住你要为其开发助手的用户。
作者:关于我
备注:转载请注明出处。
如发现错误,欢迎留言指正。