在本系列的第一部分中,我们介绍了会话 AI 的不同成熟度级别,并开始使用 Rasa 构建旅行助理。在本文中,我们将介绍构建快乐和不快乐的对话路径、各种机器学习策略和配置以改进您的对话模型,并使用基于传输学习的语言模型生成自然对话。
你能做什么,库普?
由于助理的主要目的是,让我们命名Coop,是预订真棒假期,Coop要求关键的信息从用户,以便做到这一点。为了本文的目的,让我们假设 Coop 只需要人数、度假目的地以及所述假期的开始和结束日期。
在下一次迭代中,我们希望从用户那里提取有关他们的兴趣、预算、年龄、行程限制以及我们为用户创建精心策划的旅行体验所需的任何其他信息。有了这套初始知识,让我们来看看如何使 Coop 通过与用户的自然对话收集这些信息。
使用插槽保留信息
Rasa 使用插槽来保存用户提供的信息,等等。插槽本质上是键值对,可用于影响与用户的对话。插槽的值可以通过多种方式设置 – 通过 NLU、交互式卡和操作。Rasa 将其定义为槽填充。插槽在”域.yml”文件中定义。每个插槽都指定一个名称、类型和一个可选的初始值。下面是 Coop 的域.yml 文件中的片段:
...
slots:
enddate:
type: unfeaturized
location:
type: unfeaturized
num_people:
type: unfeaturized
startdate:
type: unfeaturized
...
请注意,我们为每个插槽将类型设置为”取消活动”,因为我们不希望槽值影响我们的会话流。
使用表单进行槽填充
为了代表用户执行操作(就像我们试图使用 Coop 一样),我们需要填充多个连续的插槽或关键信息。我们可以使用 FormAction。FormAction 本质上是 python 类。它需要需要填写的插槽列表或需要回答的问题,并询问用户上述信息以填充每个插槽。请注意,FormAction 仅要求用户提供信息以填充尚未设置的插槽。
让我们来看看一条快乐的道路。快乐路径是上下文助手能够不间断地从用户那里收集所需信息。换句话说,用户回答问题时不会偏离其路径,如下所示。
为了启用 FormAction 机制,您需要将”窗体策略”添加到配置文件中,通常名为”config.yml”:
...
policies:
- name: FormPolicy
...
接下来,让我们定义自定义表单类:
class BookingForm(FormAction):
def name(self):
# type: () -> Text
"""Unique identifier of the form"""
return "booking_form"
@staticmethod
def required_slots(tracker: Tracker) -> List[Text]:
"""A list of required slots that the form has to fill"""
return ["num_people", "location", "startdate", "enddate"]
def submit(self,
dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: Dict[Text, Any]) -> List[Dict]:
"""Define what the form has to do
after all required slots are filled"""
dispatcher
这些方法不言自明。
现在,让我们告诉我们的模型调用预订表单操作。打开stories.md文件并添加一个或多个快乐路径故事:
...
* request_vacation
- booking_form
- form{"name": "booking_form"}
- form{"name": null}
...
插槽和实体:最佳关系类型
我们在 Coop 中定义了几个有趣的位置。”位置”槽用于保存有关用户度假目的地的信息。我们还定义了同名实体。Rasa NLU 将在识别用户消息中的”位置”实体时填充”位置”槽。
为了能够这样做,我们需要训练 NLU 来提取位置实体。虽然这非常令人兴奋,但将 NLU 模型训练为标识通用实体(如位置)是一个耗时的过程,需要大量的数据。
这是拉萨 NLU 管道的开箱即用的”SpacyEntityExtractor”组件来拯救我们的地方。此预先训练的组件是一个命名实体识别器,用于标识各种常见实体(称为维度),如人员、组织、城市和状态。
让我们来看看如何挂钩到这个组件,以填补我们的位置插槽。我们首先将”SpacyEntityExtractor”组件添加到我们的 NLU 管道中。编辑”config.yml”文件。
language: en
pipeline:
...
- name: "SpacyEntityExtractor"
dimensions: ["GPE", "LOC"]
Rasa 在 FormAction 类中提供了一种称为”槽映射”的方法,可用于进一步配置插槽的填充方式。在我们的案例中,我们可以使用此方法来确保按以下顺序填充”位置”槽:
-
使用我们的 NLU 模型标识的”位置”实体
-
如果步骤 1 失败,请使用由”SpacyEntityExtractor”标识的”GPE”维度
-
如果步骤 2 失败,请使用由”SpacyEntityExtractor”标识的”LOC”维度
def slot_mappings(self):
# type: () -> Dict[Text: Union[Dict, List[Dict]]]
"""A dictionary to map required slots to
- an extracted entity
- intent: value pairs
- a whole message
or a list of them, where a first match will be picked"""
return {"location": [self.from_entity(entity="location"),
self.from_entity(entity="GPE"),
self.from_entity(entity="LOC")]}
您可以在此处阅读有关预定义函数的更多内容。
Coop 域中其他有趣的插槽是”开始日期”和”结束日期”。如名称所暗示的,这些插槽表示用户对其假期开始和结束日期的选择。我们可以使用”鸭群提取器”组件,而不是训练我们的 NLU 模型来识别和提取此数据,并可能解决实体歧义问题。此预先训练的组件是一个命名实体识别器,用于标识各种常见实体,如时间、距离和数字。与配置”斯帕西实体提取器”的方式类似,”鸭头提取器”组件应添加到我们的 NLU 管道中。编辑”config.yml”文件。
language: en
pipeline:
...
- name: "DucklingHTTPExtractor"
url: http://localhost:8000
dimensions: ["time", "number", "amount-of-money", "distance"]
从上面的配置中可以看出,”DucklingHTTPExtractor”预计将在指定的主机和端口上运行
请注意,FormAction 类允许我们定义可用于验证用户提供的信息的自定义验证。例如,我们希望确保开始日期早于结束日期。这些验证方法应基于约定命名。如果您有一个名为”结束日期”的槽,则希望定义名为”验证_enddate”的方法,以便 Rasa 调用该方法。
def validate_enddate(self,
value: Text,
dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: Dict[Text, Any]) -> Optional[Text]:
"""Ensure that the start date is before the end date."""
try:
startdate = tracker.get_slot("startdate")
startdate_obj = dateutil.parser.parse(startdate)
enddate_obj = dateutil.parser.parse(value)
if startdate_obj < enddate_obj:
return value
else:
dispatcher.utter_template('utter_invalid_date', tracker)
# validation failed, set slot to None
return None
except:
print("log error")
return None
不幸的路径是规则,而不是例外
FormActions 对于从用户收集信息并代表用户执行操作非常有用。但是,如你所知,用户行为可能是不可预测的,并且对话是混乱的。有30,000种不同的方式,你可以问天气;我最喜欢的方法之一是当人们说,”今天会下雨吗?用户很少提供所需的信息,而不偏离、参与闲聊、改变主意、纠正答案、提出后续问题等等 – 所有这些都是有效的、预期的,并且需要处理,如果您正在构建强大的对话AI。
这些偏差称为不快乐路径。我强烈建议使用通过 CLI 进行交互式学习来训练您的模型来处理不愉快的路径。
下面是在交互式模式下运行 Rasa 的命令:
rasa interactive --endpoints endpoints.yml
完成后,您可以保存训练数据并重新训练模型。下面是一个不幸的路径故事示例。
* request_vacation
- booking_form
- form{"name": "booking_form"}
- slot{"requested_slot": "num_people"}
...
* form: inform{"location": "paris", "GPE": "Paris"}
- slot{"location": "paris"}
- form: booking_form
- slot{"location": "paris"}
- slot{"requested_slot": "startdate"}
* correct{"num_people": "2"}
- slot{"num_people": "2"}
- action_correct
- booking_form
- slot{"requested_slot": "startdate"}
* form: inform{"time": "2019-07-04T00:00:00.000-07:00"}
...
Rasa 提供了多个策略,可用于配置和培训其核心对话管理系统。”嵌入”策略(也称为循环嵌入对话策略 (REDP)可用于有效处理不快乐的路径。此外,它还提供可用于微调模型的超参数。您可以在此处阅读有关 REDP 的更多内容。
我使用库普的嵌入策略。
policies:
- name: EmbeddingPolicy
epochs: 2000
attn_shift_range: 5
...
现在,让我们来看看一条不幸的路径,它涉及到纠正和解释。更正是当用户对其以前的答案或语句发出修订时。例如,如果他们的电话号码弄错了,并希望纠正它
在上面的示例中,请注意 Coop 如何引导对话回到手头的主题。对话模型学习处理更正,并解释为什么它需要某些信息,实质上是使用户回到快乐的道路上来。
NLG 是一个超级强国
自然语言处理 (NLP) 有两个重要领域,它们与会话 AI 一起发挥作用。首先,有一个方面,试图了解用户说什么;用户的意图是什么?其次,有生成和响应用户的方式,自然和对话的方面。自然语言生成 (NLG) 的最终目标是教授模型将结构化数据转换为自然语言,然后我们可以使用这些模型在对话中响应用户。
无可否认,您可以创建角色和撰写出色的对话,使您的助手自然地发出对话的声音。但是,这可能需要写很多故事和规则。虽然规则是伟大的、稳定的和可预测的,但它们需要大量的工程,并且很难扩展和维护。他们也缺乏你人类对话中的自发性和创造力。
通过训练一个大规模的无监督语言模型,该模型包含它需要生成的语言和示例,最终形成了它自己应该做什么的规则,并且有更多的自由意志来发挥创造力。我调整了现有的基于传输学习的语言模型,以产生闲聊和闲聊。有了更多的示例和数据,此模型可以生成自然语言来总结文本和回答问题,而无需任何具体的任务培训。
在上面的示例中,请注意 Coop 如何持续引导用户在进行闲聊时回到快乐的道路上。对话模型学会处理狭窄和广泛的背景,而忽略多余的信息。
但 C 表示对话
写好的谈话对会话 AI 至关重要。在向外界发布您的上下文助手之前,您希望投资编写清晰简洁的副本,该副本具有正确的语音音、相关和上下文语言,以及与受众产生共鸣的人物形象,此外还具有很强的性能NLG。良好的对话可以取悦用户,建立品牌忠诚度,并确保高用户保留率。
您还需要考虑提供菜单按钮和快速回复,以便用户点击并触发某些事件,以最大程度地减少用户输入。它们是建议选项和推动用户走上快乐道路的好方法。
为会话 AI 编写副本是值得关注的内容,超出了本文的范围。在此处阅读有关编写机器人副本的更多内容。Coop目前没有很多对话或 UI 选项,但随着我们收集真实数据,我们将尝试了解用户行为,进一步调整我们的自定义 NLG 模型,因为它与真实用户交互,专注于编写良好的副本,并持续改进在接下来的几个迭代中。
下一步是什么?
在本系列的最后一部分,我们将讨论可用于测试和评估模型的各种测试策略。我们还将讨论将 Coop 部署到生产中,之后我们将监视并持续改进