🚀 原文地址:https://rasa.com/docs/rasa/training-data-format

在第一个 Rasa 项目介绍中,我们知道项目生成的 data 目录,为存储训练数据的文件夹,包含了 3 个文件:nlu.ymlrules.yml 以及 stories.yml。可以看到,Rasa 的训练数据统一使用 YAML 格式来进行管理。

我们可以将训练数据拆分成任意多个 YAML 文件,且每个文件包含任意组合的 NLU 数据、Stories 和 Rules。Rasa 数据解析器将使用最上层的 Keys 决定训练数据的类型。

除了训练数据外,Rasa 中的领域也使用相同的 YAML 格式,并且也可以拆分成多个文件,或者合并在一个文件中。领域中包含了响应和表单的定义。

:::warning 💡 在 Rasa 1.x 版本中使用的时 Markdown 格式来定义 NLU 数据和故事,目前已经被废弃掉了。 :::

1. 顶层结构

简单看一下 data 文件夹的 3 个文件,每个文件都包含了相关数据的一个或多个键。同时,一个文件可以包含多个键,但是每个键在单个文件中仅能够出现一次,这显然是 YAML 文件统一格式要求。

  • nlu.yml:version 和 nlu
  • rules.yml:version 和 rules
  • stories.yml:version 和 stories

我们看到所有 YAML 格式的训练数据文件,都包含了 version 这个键,实际项目中我们也应该给所有训练数据文件指定 version 的值。如果我们不这样做的话,Rasa 会基于当前所安装的版本,认为你使用的是最新训练数据格式规范。如果训练数据文件的版本高于你目前所安装的 Rasa 版本,则将会被忽略。目前,Rasa 2.x 最新的训练数据格式为 2.0

下面我们看一个简短的示例:在单个文件中定义所有的训练数据,包含了我们上面提到的 NLU、故事和规则。

  1. version: "2.0"
  2. nlu:
  3. - intent: greet
  4. examples: |
  5. - Hey
  6. - Hi
  7. - hey there [Sara](name)
  8. - intent: faq/language
  9. examples: |
  10. - What language do you speak?
  11. - Do you only handle english?
  12. stories:
  13. - story: greet and faq
  14. steps:
  15. - intent: greet
  16. - action: utter_greet
  17. - intent: faq
  18. - action: utter_faq
  19. rules:
  20. - rule: Greet user
  21. steps:
  22. - intent: greet
  23. - action: utter_greet

如果你想定义一个测试故事,那就需要将他们放在一个单独文件中。

  1. # tests/test_stories.yml
  2. stories:
  3. - story: greet and ask language
  4. - steps:
  5. - user: |
  6. hey
  7. intent: greet
  8. - action: utter_greet
  9. - user: |
  10. what language do you speak
  11. intent: faq/language
  12. - action: utter_faq

测试故事使用与故事训练数据相同的格式,应该单独放在一个文件中,并以 test_ 作为前缀开头。

:::info 💡 | 符号
在上面的示例中,userexamples 后面都跟了 | 管道符。在 YAML 格式中,| 用来定义多行字符串,这样我们可以在训练示例中保留一些特殊符号,例如 "' 等 :::

2. NLU训练数据

NLU 训练数据是由按照意图人分类的用户示例话术组成,训练示例中也可以包含实体。实体是从用户消息中提取的结构化信息,你可以将实体理解为类似正则的一种模式。同时,你还可以添加额外信息来帮助模型正确识别意图和实体,例如正则表达式、查询表等。

NLU 训练数据是定义在 nlu 键下面的,可被定义的项目有:

  • 训练样本:按照用户意图分组,例如带有标注的实体 ```yaml nlu:
  • intent: check_balance examples: |

    • What’s my credit balance?
    • What’s the balance on my [credit card account]{“entity”:”account”,”value”:”credit”} ```
  • 同义词:可在训练数据中定义同义词 ```yaml nlu:

  • synonym: credit examples: |

    • credit card account
    • credit account ```
  • 正则表达式:可定义多个正则表达式,进行文本匹配 ```yaml nlu:

  • regex: account_number examples: |

    • \d{10,12} ```
  • 查表:可定义表用于查找,功能类似于下拉菜单 ```yaml nlu:

  • lookup: banks examples: |
    • JPMC
    • Comerica
    • Bank of America ```

      2.1 训练样本

      训练样本按照意图分组后定义在 examples 键下。通常,以下为具体样本的定义,其中每一行为一个样本: ```yaml nlu:
  • intent: greet examples: |
    • hey
    • hi
    • whats up ```

如果你需要将自定义 NLU 组件,并且附带样本的 metadata,Rasa 也支持使用扩展格式来完成相关功能:

  1. nlu:
  2. - intent: greet
  3. examples:
  4. - text: |
  5. hi
  6. metadata:
  7. sentiment: neutral
  8. - text: |
  9. hey there!

上述 metadata 的定义可以包含任意键值对的数据,并且它可以被关联到一个样本,以及被 NLU 管道中的组件所访问。在上述示例中,我们在 pipline 中使用自定义组件中进行情感分析时,sentiment 元数据就可以被很好的利用起来。

当然,你也可以指定这个 metadataintent 处在同一个级别,这 样定义的好处是 metadata 将会被传递给每一个意图样本。

  1. nlu:
  2. - intent: greet
  3. metadata:
  4. sentiment: neutral
  5. examples:
  6. - text: |
  7. hi
  8. - text: |
  9. hey there!

按照上面示例所定义的训练,最终被解析后数据格式如下所示:

  1. [{"intent": 'greet', "metadata": {'intent': {'sentiment': 'neutral'}}}]

那么我们如何来获取训练样本的metadata呢,可参考如下示例:

  1. from rasa.shared.nlu.training_data.training_data import TrainingData
  2. intents = { e.get("intent"): e.get("metadata")['intent'] for e in training_data.intent_examples }

如果想定义一个检索意图,那么你可以按照如下示例来定义 NLU 样本:

  1. nlu:
  2. - intent: chitchat/ask_name
  3. examples: |
  4. - What is your name?
  5. - May I know your name?
  6. - What do people call you?
  7. - Do you have a name for yourself?
  8. - intent: chitchat/ask_weather
  9. examples: |
  10. - What's the weather like today?
  11. - Does it look sunny outside today?
  12. - Oh, do you mind checking the weather for me please?
  13. - I like sunny days in Berlin.

上个这个示例,“询问姓名”和“询问天气”都是闲聊意图中的一种,将 chitchat/ask_name 称之为检索意图。所有检索意图都添加了后缀(例如上面 ask_nameask_weather 就是响应键),用于给机器人标识特定的响应键。Rasa 通过 / 分隔符将后缀响应键与检索意图分开,注意,请不要以意图的名称使用 / 分隔符。

2.2 实体

前面已经提到过,实体是从用户消息中提取的结构化信息。在训练样本中,实体使用 entity 名称标注出来的信息。我们可以使用同义词、角色或者组别来标注一个实体。

下面我们来看一个带有实体标注的训练样本:

  1. nlu:
  2. - intent: check_balance
  3. examples: |
  4. - how much do I have on my [savings]("account") account
  5. - how much money is in my [checking]{"entity": "account"} account
  6. - What's the balance on my [credit card account]{"entity":"account","value":"credit"}

标注实体的完整语法为:

  1. [<entity-text>]{"entity": "<entity name>", "role": "<role name>", "group": "<group name>", "value": "<entity synonym>"}

其中关键字 rolegroupvalue 都是可选的参数:

  • value:定义同义词
  • rolehttps://rasa.com/docs/rasa/nlu-training-data#entities-roles-and-groups
  • group

    2.3 同义词

    同义词的作用是将提取出来的实体与文字提取出来的其他值进行映射,从而规范你的训练数据。你可以通过以下格式来定义同义词: ```yaml nlu:
  • synonym: credit examples: |
    • credit card account
    • credit account ```

我们也可以使用之前提到的 value 直接在训练样本中定义同义词:

  1. nlu:
  2. - intent: check_balance
  3. examples: |
  4. - how much do I have on my [credit card account]{"entity": "account", "value": "credit"}
  5. - how much do I owe on my [credit account]{"entity": "account", "value": "credit"}

2.4 正则表达式

同义词可以帮我们解决一部分的问题,但在 NLP 领域中,正则才是最常用的技术,它可以帮我们匹配各种复杂的逻辑。Rasa 中支持你使用 RegexFeaturizerRegexEntityExactor 正则表达式组件,从来提高意图分类和实体提取。我们可以采用以下方式来定一个正则表达式:

  1. nlu:
  2. - regex: account_number
  3. examples: |
  4. - \d{10,12}

上述示例中,account_number 是正则表达式的名称,当使用 RegexFeaturizer 组件时,那么正则表达式的名称就无关紧要。而当我们使用 RegexEntityExactor 组件时,正则表达式的名称应该与你想要的提取出来的实体名称匹配。

2.5 查找表

查找表是使用所列的单词来生成不区分大小写正则表达式,可以采用如下方式进行定义:

  1. nlu:
  2. - lookup: banks
  3. examples: |
  4. - JPMC
  5. - Bank of America

当我们在训练数据中提供查找表,该表的内容将被组合成一个大的正则表达式。通过该正则表达式用来检查每一个训练样本中是否包含了查找表中的实体。

查找表的正则与直接在训练样本中直接定义常规的正则表达式的处理方式是相同的,并且我们还可以将之与 RegexFeaturizerRegexEntityExactor 组件一起进行使用。查找表的名称与正则特征的名称,两者的约束是一样的。

3. 会话训练数据

Rasa 采用故事规则来表示用户和机器人之间的会话,并基于此训练对话管理模型。

  • 故事:Rasa 将从故事中训练一个机器学习模型来识别会话中的模式,并将其应用到未知的会话
  • 规则:通常描述了小段会话中应该采用相同的处理方式,同时 Rasa 将基于此训练 RulePolicy

    3.1 故事

    故事由以下几个部分组成:

  • story:故事名称的定义是随意的,且不会用来训练。通常会采用可读性高的来定义故事名。

  • metadata:元数据的定义是可选的和任意的,且不会被用来训练,它可以存储与故事相关的信息,例如作者。
  • steps:构成故事的用户消息和操作

示例:

  1. stories:
  2. - story: Greet the user
  3. metadata:
  4. author: Somebody
  5. key: value
  6. steps:
  7. - intent: greet
  8. - action: utter_greet

每个 step 可以是以下之一:

  • 用户消息:使用意图和实体表示
  • 语句:包含两个及以上的用户消息
  • 机器人动作
  • 表单
  • 事件的槽值
  • 检查点:将两个故事连接起来

    1)用户消息

    所有的用户消息都必须包含 intent: 键,还有一个可选的 entities: 键。

在编写故事时,我们不必处理用户发送的消息中的特定内容。相反地,Rasa 会参考用户可能发出具有相同意义的所有消息,将意图和实体进行组合,并通过 NLU 管道进行输出。

用户消息遵循以下格式:

  1. stories:
  2. - story: user message structure
  3. steps:
  4. - intent: intent_name # Required
  5. entities: # Optional
  6. - entity_name: entity_value
  7. - action: action_name

例如,在句子“I want to check my credit balance”表示中,credit 就是一个实体:

  1. stories:
  2. - story: story with entities
  3. steps:
  4. - intent: account_balance
  5. entities:
  6. - account_type: credit
  7. - action: action_credit_account_balance

这里包含实体非常重要,原因在于策略是基于意图和实体的组合去学习预测下一个动作(但是,你可以通过使用 use_entities 来改变此行为)。

2)动作

机器人所执行的动作都通过 action: 键后的动作名称来确定。在编写故事时,我们将遇到两种类型的动作:

  • 响应:以 utter_ 开头的动作,将会向用户发送特定的消息 ```yaml stories:
  • story: story with a response steps:

    • intent: greet
    • action: utter_greet ```
  • 自定义动作:以 action_ 开头的动作,执行任意的代码,并会发送任意数量的下消息(或者不发送)。 ```yaml stories:

  • story: story with a custom action steps:
    • intent: feedback
    • action: action_store_feedback ```

      3)表单

      表单是一种特殊类型自定义操作,它包含了在一组所需槽值进行循环以及询问用户的逻辑。我们可以在领域中 forms 部分来定义一个表单,在定义表单后,你还应该给表单指定 happy path 作为规则。为了模型可以在未知的对话序列中有更好的效果,你应该在故事中包含表单的中断或者其他 unhapppy paths。作为故事中的一个步骤,表单采用如下形式: ```yaml stories:
  • story: story with a form steps:
    • intent: find_restaurant
    • action: restaurant_form # Activate the form
    • active_loop: restaurant_form # This form is currently active
    • active_loop: null # Form complete, no form is active
    • action: utter_restaurant_found ```

Rasa 将在 action 步骤激活表单,并开始在所需的插槽上循环,active_loop: restaurant_form 这一步骤表示 Rasa 有处于当前活动的表单。与 slot_was_set 步骤非常相似,form 步骤并不会将表单激活,但表示它应该被激活。同样地,active_loop: null 步骤则表示在执行后续步骤之前,没有表单应该处于活跃。

表单是可以被中断和保持活跃的,在下面的示例中,中断应该放在 action: <form to activate> 步骤后,紧跟在 action_loop: <activate form>,具体示例如下:

  1. stories:
  2. - story: interrupted food
  3. steps:
  4. - intent: request_restaurant
  5. - action: restaurant_form
  6. - intent: chitchat
  7. - action: utter_chitchat
  8. - active_loop: restaurant_form
  9. - active_loop: null
  10. - action: utter_slots_values

4)插槽

插槽事件是通过槽名在 slot_was_set 键定义,我们也可以选择添加上槽值。

插槽充当机器人内存记忆的角色,它通过实体或者自定义动作来设置,并在故事中 slot_was_set 步骤中进行引用,示例如下:

  1. stories:
  2. - story: story with a slot
  3. steps:
  4. - intent: celebrate_bot
  5. - slot_was_set:
  6. - feedback_value: positive
  7. - action: utter_yay

上述示例表示故事要求 feedback_value 的当前槽值为 postive 时,对话才能按照指定方式继续。

是否需要包含槽值取决于插槽的类型,以及该槽值是否会对对话产生影响,如果该槽值无关紧要,则仅仅列出插槽的名称即可:

  1. stories:
  2. - story: story with a slot
  3. steps:
  4. - intent: greet
  5. - slot_was_set:
  6. - name
  7. - action: utter_greet_user_by_name

💡 故事中不设置插槽,插槽必须是在 slot_was_set 步骤之前,以实体或者自定义动作来进行设置。

5)检查点

检查点是通过 checkpoint: 键来指定,可以出现在开头,也可以是在故事的结尾出现。

检查点是将故事连接在一起的一种方式,它可以是故事中的第一步或者最后一步。如果是最后一步,则该故事将在训练模型时与其他以相同检查点名的故事进行连接。以下是一个示例,第一个故事以一个检查点结束,另一个故事以相同的检查点开始,这两个故事将会是相连的:

  1. stories:
  2. - story: story_with_a_checkpoint_1
  3. steps:
  4. - intent: greet
  5. - action: utter_greet
  6. - checkpoint: greet_checkpoint
  7. - story: story_with_a_checkpoint_2
  8. steps:
  9. - checkpoint: greet_checkpoint
  10. - intent: book_flight
  11. - action: action_book_flight

当检查点作为故事的开头时,它也可以插槽的设置为条件,示例如下:

  1. stories:
  2. - story: story_with_a_conditional_checkpoint
  3. steps:
  4. - checkpoint: greet_checkpoint
  5. # This checkpoint should only apply if
  6. # slots are set to the specified value
  7. slot_was_set:
  8. - context_scenario: holiday
  9. - holiday_name: thanksgiving
  10. - intent: greet
  11. - action: utter_greet_thanksgiving

检查点可以帮助你简化训练数据,并减少冗余,但是不要过度使用他们,使用太多的检查点将会使你的故事很难理解。如果在不同的故事中总是重复一些步骤,那么插槽的设置是有必要的,但就一般来言,没有检查点的故事很容易读写。

6)或语句

当我们需要以相同的方式来处理多个意图时,可以使用 or 步骤,这可以避免我们给每一个意图编写单独的故事。例如,如果想让用户确认某些东西,你可能想用相同的方式来处理 affirmthankyou 意图。在训练时,包含 or 步骤的故事将被转换为多个单独的故事。

例如,下面的示例在训练时将会被转换为两个故事:

  1. stories:
  2. - story: story with OR
  3. steps:
  4. - intent: signup_newsletter
  5. - action: utter_ask_confirm
  6. - or:
  7. - intent: affirm
  8. - intent: thanks
  9. - action: action_signup_newsletter

和检查点一样,或语句也是非常有用的,但是如果你需要使用很多的检查点和或语句,那么最好的方式是重构你的领域和意图。

:::warning ⛔ 连用检查点和 or 特性,将会使训练时间变慢。 :::

3.2 规则

Rasa 通过 rules 键来定义具体的规则,它看起来这与故事的格式非常类似。规则中包含一个 steps 键,所包含的内容与故事一致。在规则中还可以额外添加 conversation_startedconditions 键,他们被用与指定应用规则的条件。

以下是包含条件的规则示例:

  1. rules:
  2. - rule: Only say `hey` when the user provided a name
  3. condition:
  4. - slot_was_set:
  5. - user_provided_name: true
  6. steps:
  7. - intent: greet
  8. - action: utter_greet

4. 测试故事

测试故事用于检测消息是否被正确分类以及预测动作是否正确,除了在用户消息步骤可以包括 user 键来指定实际的文本内容,以及用户消息的实体标注,其余与故事相同的格式。我们来看一个测试故事的示例:

  1. stories:
  2. - story: A basic end-to-end test
  3. steps:
  4. - user: |
  5. hey
  6. intent: greet
  7. - action: utter_ask_howcanhelp
  8. - user: |
  9. show me [chinese]{"entity": "cuisine"} restaurants
  10. intent: inform
  11. - action: utter_ask_location
  12. - user: |
  13. in [Paris]{"entity": "location"}
  14. intent: inform
  15. - action: utter_ask_price

你可以通过以下命令来运行这些测试:

  1. $ run test

5. 端对端训练

:::info 💡 2.2 版本新特性
端对端训练目前还是处于实验中的功能。 :::

通过端对端的训练,我们不再需要处理从消息经过 NLU 管道提取的特殊意图。相反地,你可以使用 user 键将用户消息的文本直接放在故事中。

端对端的用户消息遵循以下格式:

  1. stories:
  2. - story: user message structure
  3. steps:
  4. - user: the actual text of the user message
  5. - action: action_name

此外,我们可以添加经过 TEDPolicy 提取的实体标签,实体标签的语法与 NLU 训练数据是相同的。例如,下面的故事包含了用户说“I can always go for sushi”。通过使用 NLU 训练数据中 [sushi](cuisine) 的语法,你可以将 sushi 标记为类型为 cuisine 的实体。

  1. stories:
  2. - story: story with entities
  3. steps:
  4. - user: I can always go for [sushi](cuisine)
  5. - action: utter_suggest_cuisine

同样地,你可以使用 bot 键(后面跟你想要机器人所说的内容),将机器人话术直接放在故事中。

  1. stories:
  2. - story: story with an end-to-end response
  3. steps:
  4. - intent: greet
  5. entities:
  6. - name: Ivan
  7. - bot: Hello, a person with a name!

你还可以有一个混合的端到端的故事:

  1. stories:
  2. - story: full end-to-end story
  3. steps:
  4. - intent: greet
  5. entities:
  6. - name: Ivan
  7. - bot: Hello, a person with a name!
  8. - intent: search_restaurant
  9. - action: utter_suggest_cuisine
  10. - user: I can always go for [sushi](cuisine)
  11. - bot: Personally, I prefer pizza, but sure let's search sushi restaurants
  12. - action: utter_suggest_cuisine
  13. - user: Have a beautiful day!
  14. - action: utter_goodbye

Rasa 端到端训练已经完全集成到标准的 Rasa 方法中,这意味着你可以将通过动作或意图的一些步骤,和直接通过用户消息或者机器人响应的其他步骤混合在故事中。