最近在做dialog policy相關(guān)的研究昧穿,實(shí)現(xiàn)就用了rasa的輪子,看源碼順便寫篇文章首尼。水平有限,還請指正言秸。
先吹一波
rasa core的代碼質(zhì)量非常高非常高非常高软能!我知道有許多中國工程師參與了開發(fā),牛逼举畸!
整體思路
我們先從執(zhí)行的角度來分析tracker的源碼
- 是什么查排?如何初始化
- 輸入是什么
- 跟蹤了什么內(nèi)容
- 如何更新狀態(tài)
- 狀態(tài)如何表達(dá)
如何初始化 —— init
下面是init的全部代碼,非常簡單抄沮,我做了一些注釋方便理解雹嗦,其實(shí)源碼里面的注釋很多,原來的注釋大家就直接看源碼吧合是。多說一句,rasa的源碼寫得太漂亮了锭环,注釋詳細(xì)聪全,格式規(guī)范,讀起來就是享受辅辩。
def __init__(self, sender_id, slots,
max_event_history=None):
"""Initialize the tracker.
A set of events can be stored externally, and we will run through all
of them to get the current state. The tracker will represent all the
information we captured while processing messages of the dialogue."""
# 可以跟蹤的最長歷史难礼,tracker記錄狀態(tài)是以event為單位的
self._max_event_history = max_event_history
# 歷史事件列表
self.events = self._create_events([])
# 這個(gè)id和rasa的chenel特性有關(guān)系
self.sender_id = sender_id
# slot列表
self.slots = {slot.name: copy.deepcopy(slot) for slot in slots}
###
# current state of the tracker - MUST be re-creatable by processing
# all the events. This only defines the attributes, values are set in
# `reset()`
###
# 暫停標(biāo)志
self._paused = None
# 一些action記錄
self.followup_action = ACTION_LISTEN_NAME
self.latest_action_name = None
self.latest_message = None
# bot的上一個(gè)返回內(nèi)容
self.latest_bot_utterance = None
self._reset()
從init函數(shù)中我們可以知道些什么呢?
- tracker是記錄一個(gè)用戶對話狀態(tài)的對象
- tracker基于Event對象跟蹤對話狀態(tài)
Event
既然tracker是基于Event的玫锋,我們就來看看Event是啥
簡單來說蛾茉,Event就是對bot一切行為的抽象,每一個(gè)具體的事件類都繼承自Event基類
class Event(object):
"""Events describe everything that occurs in
a conversation and tell the :class:`DialogueStateTracker`
how to update its state."""
type_name = "event"
def __init__(self, timestamp=None):
self.timestamp = timestamp if timestamp else time.time()
這種設(shè)計(jì)很優(yōu)秀撩鹿,使得tracker可以跟蹤系統(tǒng)預(yù)定義以外的事件谦炬,只要你自己實(shí)現(xiàn)一個(gè)Event的子類就行。說起來這是應(yīng)該是面向?qū)ο蟮幕驹O(shè)計(jì)思維,但是真正編碼的時(shí)候很難考慮周全键思。
rasa-core內(nèi)部實(shí)現(xiàn)了以下Event
名字一看就知道大概什么意思了
下面我們看一下Event核心的方法apply_to()
class UserUttered(Event):
def apply_to(self, tracker):
# type: (DialogueStateTracker) -> None
tracker.latest_message = self
tracker.clear_followup_action()
看一個(gè)就行础爬,這是在干嘛呢?就是給tracker改屬性吼鳞,把一些和自己有關(guān)的內(nèi)容更新了看蚜。
為什么要有這個(gè)方法呢?因?yàn)槊總€(gè)Event需要修改的屬性不一樣赔桌,把這部分邏輯放到子類自己實(shí)現(xiàn)供炎,調(diào)用邏輯在tracker實(shí)現(xiàn),最大化復(fù)用代碼疾党。這同樣應(yīng)該屬于基礎(chǔ)思維音诫,那么自己做到了么(逃
狀態(tài)更新 —— update
def update(self, event):
# type: (Event) -> None
"""Modify the state of the tracker according to an ``Event``. """
if not isinstance(event, Event): # pragma: no cover
raise ValueError("event to log must be an instance "
"of a subclass of Event.")
self.events.append(event)
event.apply_to(self)
就是這么簡單
輸出
上面說的內(nèi)容就是tracker的核心部分了,抽象非常優(yōu)美仿贬。題外話纽竣,推薦大家讀一讀Flask的源碼,我讀了一部分茧泪,說賞心悅目不為過蜓氨,那種架構(gòu)設(shè)計(jì)的嚴(yán)謹(jǐn)優(yōu)雅看著是真tm舒服。
tracker記錄了整個(gè)交流的過程队伟,提供了生成Story的接口和生成Dialog的接口
def export_stories(self):
# type: () -> Text
"""Dump the tracker as a story in the Rasa Core story format.
Returns the dumped tracker as a string."""
from dqn_policy.training.structures import Story
story = Story.from_events(self.applied_events())
return story.as_story_string(flat=True)
def as_dialogue(self):
# type: () -> Dialogue
"""Return a ``Dialogue`` object containing all of the turns.
This can be serialised and later used to recover the state
of this tracker exactly."""
return Dialogue(self.sender_id, list(self.events))
其他接口
tracker還實(shí)現(xiàn)了很多接口穴吹,涉及到了rasa的各個(gè)部分,就不一一細(xì)說了嗜侮。里面很多是用來featurize的輔助接口港令,我也還沒把這部分研究透,后面會再寫一篇聊featurize锈颗,這是rasa core的核心組件
總結(jié)一哈
tracker是rasa core中承上啟下的一環(huán)顷霹,它記錄來自前端輸入的數(shù)據(jù),又為模型訓(xùn)練的featurize提供基礎(chǔ)击吱。從tracker出發(fā)基本能摸清楚整個(gè)rasa core的框架結(jié)構(gòu)淋淀。rasa core抽象做得非常好,代碼質(zhì)量賊高覆醇,必須吹一波朵纷。這部分源碼相對比較簡單,注釋非常詳細(xì)永脓,讀起來很舒服袍辞,推薦大家都讀一讀。