記憶回覆功能
Closed this issue · 3 comments
您好,
因為許多麻煩的因素,屬於站點後端的更新我都沒有上傳至 Github,
其中也包括了記憶回覆、情境模組與資料庫的相關設計,我對此感到遺憾也相當抱歉 (´・_・`)
不過,我很樂意和您分享設計流程,就先從後端的主要結構開始吧,
為了讓說明更清楚,我先定義三個關鍵字:
user_id
: 使用者的識別符,每個使用者都有不同的user_id
situation
: 應用情境,像是訂餐廳、訂飯店等等,而每個情境有若干個slot
,用於記錄使用者對該情境的需求,好比說訂餐廳的需求可能有「餐廳類型」、「餐廳地點」、「訂餐時間」三個 slots。只有使用者填滿所有 slots 時,他才會脫離該 situation。
理解這三個關鍵字後,關於後端的設計能考慮如下:
def handle_query(query, user_id):
'''
query: 使用者當次的問句
user_id:
'''
answer = None
user_status = lookup_user_status(user_id) # db work
if already_in_situation(user_status):
situation_handler = get_situation_handler(user_status)
filled_slots = get_filled_slot(user_id) # db work
answer, updated_slots, user_status = situation_handler.parse(query, user_status, filled_slots)
check_user_status(user_id, user_status, updated_slots)
else:
user_status = intent_classifier(query)
if user_status is CHITCHAT:
# do something else to handle chitchat
else:
situation_handler = get_situation_handler(user_status)
answer, updated_slots, user_status = situation_handler.parse(query, user_status, situation_handler.init_slots())
check_user_status(user_id, user_status, updated_slots)
return answer
def check_user_status(user_id, user_status, updated_slots):
if user_status is TERMINATED_STATE:
clean_user_slots_and_status(user_id) # db work
else:
update_filled_slots(user_id, updated_slots) # db work
update_user_status(user_id, user_status) # db work
跟資料庫處理相關的有以 # db work
標註,是以 json 格式儲存。
以此流程我們能知道左圖的結果是怎麼得出的,假設飯店情境的 slots 有地點與時間兩者,讓我們來分別考慮三組對話。
- user: 幫我挑間高雄的飯店:
因為這是使用者第一次進入對話,所以 user_status 為 already_in_situation 為 False,而 intent_classifier (預設採用) 得出該 user 的 user_status = RESERVE_HOTEL_INIT,我們挑出專門處理訂飯店的模組,接下來給定目前的 user_status,目前 user 說了什麼,以及一個全空的 slots,請該模組去 parse 出:
- answer : 該回覆的答案,即「好的,請問要訂哪一天的飯店呢?」
- updated_slots: 將「高雄」填入地點的 slot 中
- user_status : 更新後的 user_status = RESERVE_HOTEL_ASK_DATE
parse() 的實作方式其實很彈性,我一開始選擇的是相當樸素的方法,先硬性的規定每個 situation 的詢問順序,好比說訂飯店總是先問地點再問時間(如果地點還沒被填的話),而抽取出實體的方式也很簡單,先濾除掉一些不可能為 slot 的詞,再將與 slot 值相關的可能說法填入一個表中,再看使用者的輸入是否有匹配到表中的某個值,如果有,就預設抽出這個地點或時間。
我後來在 intent_classifier 與 parse 上做了些改善,先是設計了一些情境模板來產生語料,用於訓練一個簡單的 query encoder,將目前的 query encode 成一個 query vector,再和之前的 user_status embedding concat 起來做為新的 state vector,最後用一個淺層分類器對 state vector 做分類,判斷其應該為哪一個 user_status,並跟據這個 user_status 去 lookup 該做什麼回應,比方說現在走到了 RESERVE_HOTEL_ASK_DATE,就該回覆「好的,請問要訂哪一天的飯店呢?」。
因為我是自己設計模板來產生語料的 (好比說:<時間>的<天氣狀態>怎麼樣),理論上也能用這個模板進行 sequence entity tagger 的訓練,不過不知為何,效果總是不怎麼優,恐怕是我的模板在設想上不過泛化所致。
最後是 check_user_status ,如果說 user_status 走到了 TERMINATED_STATE,也就是這個情境結束了,那我們就將使用者與該情境的互動記錄清空,如果還沒結束,就更新目前的 user_status 與其情境中已填的 slots,因為現在 user_status 不為 TERMINATED_STATE (還有時間沒問呢),所以我們選擇更新 slots
- user: 下禮拜二
因為 already_in_situation(user_status) 為真,這一次我們不會進行 intent 的分類,而是直接進行還未結束的那個情境,先恢復已填補的 slots :
situation_handler = get_situation_handler(user_status)
filled_slots = get_filled_slot(user_id) # db work
再來是延續之前的情境,繼續填補 slot
answer, updated_slots, user_status = situation_handler.parse(query, user_status, filled_slots)
這一次因為時間跟地點都被填滿了,situation_handler 能以 API 幫使用者找到飯店,並傳回了情境終止訊號 TERMINATED_STATE,清空使用者與 RESERVE_HOTEL 的所有互動,讓該 user 的 user_status 回到了 START_POINT
- user: 感謝 :)
因為目前的 user_status 為 START_POINT,already_in_situation 為假,我們要跑一下 intent_classifier,得出的結果為 CHITCHAT,進入了專門處理閒聊的模型,得出回覆「不客氣」。
後端的設計大致上是如此,因為是憑著古老的記憶敲出來的,說明上有顯得有些卡卡的,若有疑問也歡迎再留言,只是最近回覆可能會稍晚些,還請包涵。
什么时候更新这部分功能呢?
這部分的模組已不會更新。