【Rasa-Core原始碼閱讀】Tracker
最近在做dialog policy相關的研究,實現就用了rasa的輪子,看原始碼順便寫篇文章。水平有限,還請指正。
先吹一波
rasa core的程式碼質量非常高非常高非常高!我知道有許多中國工程師參與了開發,牛逼!
整體思路
我們先從執行的角度來分析tracker的原始碼
- 是什麼?如何初始化
- 輸入是什麼
- 跟蹤了什麼內容
- 如何更新狀態
- 狀態如何表達
如何初始化 —— init
下面是init的全部程式碼,非常簡單,我做了一些註釋方便理解,其實原始碼裡面的註釋很多,原來的註釋大家就直接看原始碼吧。多說一句,rasa的原始碼寫得太漂亮了,註釋詳細,格式規範,讀起來就是享受。
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記錄狀態是以event為單位的 self._max_event_history = max_event_history # 歷史事件列表 self.events = self._create_events([]) # 這個id和rasa的chenel特性有關係 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()` ### # 暫停標誌 self._paused = None # 一些action記錄 self.followup_action = ACTION_LISTEN_NAME self.latest_action_name = None self.latest_message = None # bot的上一個返回內容 self.latest_bot_utterance = None self._reset() 複製程式碼
從init函式中我們可以知道些什麼呢?
- tracker是記錄一個使用者對話狀態的物件
- tracker基於Event物件跟蹤對話狀態
Event
既然tracker是基於Event的,我們就來看看Event是啥
簡單來說,Event就是對bot一切行為的抽象,每一個具體的事件類都繼承自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() 複製程式碼
這種設計很優秀,使得tracker可以跟蹤系統預定義以外的事件,只要你自己實現一個Event的子類就行。說起來這是應該是面向物件的基本設計思維,但是真正編碼的時候很難考慮周全。
rasa-core內部實現了以下Event

名字一看就知道大概什麼意思了
下面我們看一下Event核心的方法 apply_to()
class UserUttered(Event): def apply_to(self, tracker): # type: (DialogueStateTracker) -> None tracker.latest_message = self tracker.clear_followup_action() 複製程式碼
看一個就行,這是在幹嘛呢?就是給tracker改屬性,把一些和自己有關的內容更新了。
為什麼要有這個方法呢?因為每個Event需要修改的屬性不一樣,把這部分邏輯放到子類自己實現,呼叫邏輯在tracker實現,最大化複用程式碼。這同樣應該屬於基礎思維,那麼自己做到了麼(逃
狀態更新 —— 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) 複製程式碼
就是這麼簡單
輸出
上面說的內容就是tracker的核心部分了,抽象非常優美。題外話,推薦大家讀一讀Flask的原始碼,我讀了一部分,說賞心悅目不為過,那種架構設計的嚴謹優雅看著是真tm舒服。
tracker記錄了整個交流的過程,提供了生成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還實現了很多介面,涉及到了rasa的各個部分,就不一一細說了。裡面很多是用來featurize的輔助介面,我也還沒把這部分研究透,後面會再寫一篇聊featurize,這是rasa core的核心元件
總結一哈
tracker是rasa core中承上啟下的一環,它記錄來自前端輸入的資料,又為模型訓練的featurize提供基礎。從tracker出發基本能摸清楚整個rasa core的框架結構。rasa core抽象做得非常好,程式碼質量賊高,必須吹一波。這部分原始碼相對比較簡單,註釋非常詳細,讀起來很舒服,推薦大家都讀一讀。