[TOC]
Rasa学习笔记2--Rasa Core
1. 概念介绍
首先引出Rasa的设计理念:
Learning from real conversations is more important than designing hypothetical ones,我觉得这是非常重要的事情,我们在构建自己的bot的时候 ,我们往往想绞尽脑汁来排列组合出各种意图的story,但是在实际应用中,我们发现不管我们的story库有多丰富,还是会有大量的unhappy path无法处理,所以我们应该想方设法从真实的对话中来抽象sotry。
1.1 Stories
sotry就是Rasa用来训练对话管理模型的数据集。格式如下:*开头的表示某一轮用户输入的intent和slot信息。-开头的表示分配给bot对应的action。
## greet + location/price + cuisine + num people <!-- name of the story - just for debugging -->
* greet
- action_ask_howcanhelp
* inform{"location": "rome", "price": "cheap"} <!-- user utterance, in format intent{entities} -->
- action_on_it
- action_ask_cuisine
* inform{"cuisine": "spanish"}
- action_ask_numpeople <!-- action that the bot should execute -->
* inform{"people": "six"}
- action_ack_dosearch
1.2 Domains
定义了整个系统需要操作的所有元素,包括:intent,slot,entity,template,actions。
1.3 Actions
定了了bot具体执行动作。在Rasa中定了四种action,包括:
- Utterance actions,直接返回template中定义的话术。
- Retrieval actions,闲聊(small talk)和FAQ(small questions)的方式。
- Custom actions,用户自定义。用户自定义的action,需要发布action server,然后通过HTTP请求来获取执行action的答案。
- Defailt actions,比如:action_listen, action_restart, action_default_fallback等
1.4 Policies
所谓policy就是bot在对话中的每一步决定要执行哪一个action。我们可以在config.yml中配置多个policies,Agent会根据policy的执行优先级来选择最终的action。
Max_history ,bot再决定action时会向前考虑的对话轮数。
Data Augmentation, 在训练模型是,Rasa会从stories.md中随机取出sotry拼接一起看作一个story,用来做数据扩充。
agent决定action的一般原则是选择这些policy给的得分最大的action,但是当两个policy给出了相同的最高得分,这时候就需要根据policy的priority来下决定采取哪一个policy的结果。在Rasa中推荐的设置是:
5. FormPolicy
4. FallbackPolicy and TwoStageFallbackPolicy
3. MemoizationPolicy and AugmentedMemoizationPolicy
2. MappingPolicy
1. EmbeddingPolicy, KerasPolicy, and SklearnPolicy
1.4.1 KarasPolicy
构造训练数据和模型输入:
-
对于每个story,会先封装成一个TrackerWithCachedStates对象实例,里面包括:
sender_id: story标识
_states: 这个story可以生成的所有对话状态的组合,内容是[最大max_history的状态+一下个状态]
domain
slots,此story包含的slots和值
latest_message,
latest_action_name,
latest_bot_utterance,
event,包括ActionExcuted,UserUttered,SlotSet等,封装domain中的所有内容
等 -
构造对话状态与预测action
分别是:trackers_as_states和trackers_as_actions,他们是一一对应的关系,去重之后会有n个组合。其中:
trackers_as_states,所有max_history状态的组合,比如:
trackers_as_actions,预测的action,比如对应上面005:
-
将上述结果编码,构造模型输入X,Y
X:shape(n, max_history, feature_num),其中feature_num=envent的数量,使用one-hot编码,为1的位置,表示当前state的Event。因为Event的定义就是用来描述对话中的所有内容。
Y: shape(n, actins_num),因为policy只预测action,所以Y的one-hot用来表示预测的actin来。
1.4.2 Embedding Policy (Transformer Embedding Dialogue Policy)
1.5 Forms
现看一个使用Form实现的对话情形:
所谓form就是slot filling,是我们在实现任务型对话的一般思路,及:首先定义一些必须槽位,每个槽位会构建一个反问话术,然后bot会顺序反问用户,收集信息,直到填满所有槽位。FormAction就是来实现这个功能。
名词解释:happy path,就是指你问用户什么信息,用户就配个回答给bot这个信息。
比如下面这个happy path:
## happy path
* greet
- utter_greet
* request_restaurant
- restaurant_form
- form{"name": "restaurant_form"}
- form{"name": null}
- utter_slots_values
* thankyou
- utter_noworries
当用户意图是request_restaurant,bot会接着执行restaurant_form action。form{"name": "restaurant_form"}会激活这个form,form{"name": null}会关闭这个form。对于unhappy path的情形,bot可以执行任意其他的action,只要这个form还是active的。
我们可以使用Rasa提供的sdk来定制自己的FormAction,需要实现下面三个方法:
- name(),aciton的名字
- required_slots(),列出来所有必须的槽位
- submit()方法,当所有槽位被填充,此action产生的结果或者执行的动作。
一旦form action被激活,FormPolicy会对应的被激活,FormPolicy非常简单,它预测的一下个动作永远还是form action。
定制化slot mapping
有时候我们填充槽位不一定只是用抽取到的slot value,也可以是多种形式的回答。比如当bot问:"您是要坐在外面吗?",用户可能的回答有:“是的”/"不是"/“我更喜欢坐在里面”。这几个回答都是不同的意图或者是相同实体中的其他value,但却都是正确的回答,所以,我们要将这些答案mapping到这一轮的槽位上。
FormAction可以支持将yes/no以或者自由文本映射到slot中,只需要实现slot_mappings()方法。
def slot_mappings(self):
# type: () -> Dict[Text: Union[Dict, List[Dict]]]
return { "outdoor_seating": [self.from_entity(entity="seating"),
self.from_intent(intent='affirm', value=True),
self.from_intent(intent='deny', value=False)]}
如上所示,我们将所有mapping策略函数封装在list中,赋值给目的槽位"outdoor_seating"。详细来讲:
- from_entity,将抽取出来的"seating"(seating表示实体)的value填充到outdoor_seating
- from_intent,如果识别的意图是affirm,True填充到outdoor_seating,反之,False填充到outdoor_seating。
还有其他的复制策略函数,from_trigger_intent,from_text。
slot value validation
有时候我们需要对用户输入的slot value进行个性化的确认,我们可以在FormAction中使用 validate_{slot-name} 方法来实现。当然,我们也可以在validate的过程中来终止form,即放validate方法返回self.deactivate(),或者重新反问。
处理unhappy path
在FormAction执行过程中,如果用户不配合,比如:问其他问题、闲聊或者改变了主意,这时候form会引起ActionExecutionRejection,所以,你需要采取措施不让这种情形产生。比如,
处理闲聊情形的story如下:
## chitchat
* request_restaurant
- restaurant_form
- form{"name": "restaurant_form"}
* chitchat
- utter_chitchat
- restaurant_form
- form{"name": null}
当用户改变注意,不在询问最初的需求,这时候bot就不能继续询问最初form的槽位。便可以使用action_deactivate_form来关闭此form:
## chitchat
* request_restaurant
- restaurant_form
- form{"name": "restaurant_form"}
* stop
- utter_ask_continue
* deny
- action_deactivate_form
- form{"name": null}
处理待条件的slot的逻辑
这个功能是为了让对话更加灵活和有个性化,比如某个人回答要吃希腊菜,bot可以反问他是不是需要露天的座位。可以在FormAction中通过required_slots()来实现:
@staticmethod
def required_slots(tracker) -> List[Text]:
"""A list of required slots that the form has to fill"""
if tracker.get_slot('cuisine') == 'greek':
return ["cuisine", "num_people", "outdoor_seating",
"preferences", "feedback"]
else:
return ["cuisine", "num_people",
"preferences", "feedback"]
1.6 检索动作(retrieval action)
这个是Rasa在实验的新功能,主要作用是为了在解决闲聊(small talk)和Faq(simple question)问题时,可以简化构建story,因为这些都是单轮的对话,可以尝试通过一个aciton解决掉。
比如,我们可以不在使用下面这些story:
## weather
* ask_weather
- utter_ask_weather
## introduction
* ask_name
- utter_introduce_myself
...
而是用一个chitchat
意图来将上面所有意图包装起来:
## chitchat
* chitchat
- respond_chitchat
然后retrival action使用NLU组件中的response selector模型来学习和预测结果。
训练数据
准备NLU训练数据:
## intent: chitchat/ask_name
- what's your name
- who are you?
- what are you called?
## intent: chitchat/ask_weather
- how's weather?
- is it sunny where you are?
首先所有上面的sample将被放在一起用来训练意图识别模型用来预测chitchat类别。然后通过一个'/'符号来分隔出来的后缀就是对应的答案,将作为训练模型的label,这其实就是一个问题-答案的qq模型。
然后,在另外一个response.md
文件中准备response语料:
## ask name
* chitchat/ask_name
- my name is Sara, Rasa's documentation bot!
## ask weather
* chitchat/ask_weather
- it's always sunny where I live
1.7 interactive actions
1.8 Fallback Action
当NLU得分低于某个阈值或者policy预测低于某个阈值,需要进行对话回退,也就是尝试让用户重新表达。有下面两种用法:
- 使用
FallbackPolicy
:
policies:
- name: "FallbackPolicy"
nlu_threshold: 0.4
core_threshold: 0.3
fallback_action_name: "action_default_fallback"
action_default_fallback
这个action会对应执行模板中定义的utter_default
来反问客户。
- 使用
TwoStageFallbackPolicy
:
policies:
- name: TwoStageFallbackPolicy
nlu_threshold: 0.3
core_threshold: 0.3
fallback_core_action_name: "action_default_fallback"
fallback_nlu_action_name: "action_default_fallback"
deny_suggestion_intent_name: "out_of_scope"
当用户输入低于nlu阈值,TwoStageFallbackPolicy
会采用多个阶段来处理:
- 如果用户输入低于nlu阈值,bot执行
action_default_ask_affirmation
让用户确认输入: 如果用户说yes,则把输入看作大于阈值的输入, sotry继续。如果用户否认,让用户重新输入。 - 执行
action_default_ask_rephrase
让用户重新输入:如果用户用户重新输入的句子大于阈值,则sotry继续。如果用户输入再一次低于阈值,再次让用户确定意图。 - 执行
action_default_ask_affirmation
让用户二次确认:如果用户确认,则story继续。如果用户否认,用户输入会被认定为'deny_suggestion_intent_name',就会执行最终的召回动作fallback_nlu_action_name
(比如转人工)。
1.8 Knowledge Base Actions
目前Rasa使用这个action主要解决两个问题:
- 当用户需要具体问某个实体的信息和属性
-
解决指代问题
如下面这个例子:
1.8.1 Knowledge Base
rasa_sdk中提供数据库加载的抽象类:InMemoryKnowledgeBase,直接:knowledge_base = InMemoryKnowledgeBase("knowledge_base_data.json")
来构造实例。文档中提供的一个数据库例子如下:
{
"restaurant": [
{
"id": 0,
"name": "Donath",
"cuisine": "Italian",
"outside-seating": true,
"price-range": "mid-range"
},
{
"id": 1,
"name": "Berlin Burrito Company",
"cuisine": "Mexican",
"outside-seating": false,
"price-range": "cheap"
},
{
"id": 2,
"name": "I due forni",
"cuisine": "Italian",
"outside-seating": true,
"price-range": "mid-range"
}
],
"hotel": [
{
"id": 0,
"name": "Hilton",
"price-range": "expensive",
"breakfast-included": true,
"city": "Berlin",
"free-wifi": true,
"star-rating": 5,
"swimming-pool": true
},
{
"id": 1,
"name": "Hilton",
"price-range": "expensive",
"breakfast-included": true,
"city": "Frankfurt am Main",
"free-wifi": true,
"star-rating": 4,
"swimming-pool": false
},
{
"id": 2,
"name": "B&B",
"price-range": "mid-range",
"breakfast-included": false,
"city": "Berlin",
"free-wifi": false,
"star-rating": 1,
"swimming-pool": false
},
]}
这是一个餐馆的数据,其中id和name一般都是要配置的。当然我们也可以定制自己的数据库。
1.8.2 准备NLU data
首先定义一个新的意图query_knowledge_base
来表示“查询数据库”的用户意图,然后bot对应的执行``ActionQueryKnowledgeBase `动作,目前rasa中可以解决的两种分类是:1. 列举出用户指定属性的所有对象。2. 用户想知道某个对象的某个属性。对于上述的餐馆领域,可以构造如下的nlu数据:
## intent:query_knowledge_base
- what [restaurants](object_type:restaurant) can you recommend?
- list some [restaurants](object_type:restaurant)
- can you name some [restaurants](object_type:restaurant) please?
- can you show me some [restaurant](object_type:restaurant) options
- list [German](cuisine) [restaurants](object_type:restaurant)
- do you have any [mexican](cuisine) [restaurants](object_type:restaurant)?
- do you know the [price range](attribute:price-range) of [that one](mention)?
- what [cuisine](attribute) is [it](mention)?
- do you know what [cuisine](attribute) the [last one](mention:LAST) has?
- does the [first one](mention:1) have [outside seating](attribute:outside-seating)?
- what is the [price range](attribute:price-range) of [Berlin Burrito Company](restaurant)?
- what about [I due forni](restaurant)?
- can you tell me the [price range](attribute) of [that restaurant](mention)?
- what [cuisine](attribute) do [they](mention) have?
...
其中:
object_type: 标注数据库中的某个实体。
mention: 标注指代。
attribute: 数据库中包含的属性。
另外,使用synonyms
来将数据中标记的单词映射到数据库中使用的标准格式,比如:'restaurants'->'restaurant'。
上面有提到目前rasa可以解决的两种情景:
- 从数据库中查询用户指定属性的所有对象。
当用户想要从数据库中查询对象,问句中必须包含某个对象,即'object_type',同时需要指定某些属性。
比如:
What Italian restaurant options in Berlin do I have?
用户想得到'object_type'='restaurant', 条件是{'cuisine'='Italian','city'='Berlin'},然后action就通过这两个条件来筛选符合的对象。
当然想要解析上面的问句需要如下的标注:
What [Italian](cuisine) [restaurant](object_type) options in [Berlin](city) do I have?.
- 用户想知道某个对象的某个属性。
这种情形,比如:
What is the cuisine of Berlin Burrito Company?
用户想知道'Berlin Burrito Company'的'cuisine'。需要的标注如下:
What is the [cuisine](attribute) of [Berlin Burrito Company](restaurant)?
1.8.3 解决指代
rasa中定义两种指代,1. 顺序指代(ordinal mentions),比如:'the first one',2. 普通指代,比如'it'。
- 顺序指代
在bot输出轮,当给用户是一系列象列,可在解析用户回答的时候,可以使用一个顺序指代映射来解决用户问题中的指代词。这个顺序指代映射需要在KnowledgeBase类中定义:
{
"1": lambda l: l[0],
"2": lambda l: l[1],
"3": lambda l: l[2],
"4": lambda l: l[3],
"5": lambda l: l[4],
"6": lambda l: l[5],
"7": lambda l: l[6],
"8": lambda l: l[7],
"9": lambda l: l[8],
"10": lambda l: l[9],
"ANY": lambda l: random.choice(list),
"LAST": lambda l: l[-1],
}
key就是对应到nlu标注数据中的mention后面的值,比如:does the [first one](mention:1) have [outside seating](attribute:outside-seating)?
-
普通指代
rasa的处理方案是将上文识最近别到的对象作为指代的对象。 -
编写ActionQueryKnowledgeBase
2. 代码结构分析和源码学习
- DialogueStateTracker 保存对话状态,包括:
网友评论