前言
通常而言,通用聊天機器人(比如小冰等)底層技術是采用類似Seq2Seq等“生成”技術的。但是這種機器人屬于探索性質,無法
提供特定的服務。而Siri則是兼具閑聊以及垂直領域功能的,比如可以預約提醒,打電話,定餐廳等特定功能。相信Siri在實作特定預約提醒,打電話功能等,則是使用了“語言模闆”比對技術以及結合分類器來實作精度的控制和定向動作的執行。
對于聊天機器人我個人是相當感興趣的,奈何現在的已經公開的文章都“相對初級和入門”,或者說過于專注裡面的某個算法,比如問答比對算法。是以萌生了寫一篇文章的想法。本文基于自己開發相應系統的經驗,理論上會給大家帶來一些幫助。但是因為是内部系統,隻能談及一些較為公開的思想。
現在我們的目标是探讨是如何設計和實作一個,隻要通過簡單配置就完成一個特定主題對話的機器人。有需要的話,可以經過插件(元件)開發,為其增加新垂直領域對話功能。這些插件,就如我前面所言,可能需要內建大量的針對特定領域問題的算法。
核心技術要點
- 語言模闆引擎
- 對話配置系統
- 機器學習相關技術
語言模闆可以保證意圖識别的準确性,機器學習除了能否增加覆寫度以外,同時也是對話配置系統的核心所在。在今天這篇文章裡我們會重點探讨1,2兩點,3有需要的時候會提及。
語言模闆引擎
語言模闆很好了解,就是一句話比對上了某個模闆,這個模闆會指向一個動作,進而能夠給出一個響應。一個大緻的配置如下:
{
"action": "how to deal with <xdisease>",
"template": [
"<疾病>*怎麼辦",
"<疾病>*怎麼*治療"
],
"reg": "",
"id": "24"
}
複制
比如
糖尿病怎麼辦
,就會比對上這個模闆,并且執行action動作。如果怕模闆定制的過于寬泛,可以再通過reg (正則)來進行限制。id 則是這個模闆的唯一編号。 * 表示中間可以比對任何字元。<疾病> 則表示特定實體,這需要有海量的實體詞典支援。
通常語言比對模闆引擎會有比較明顯的性能問題。當你有幾千個上萬個模闆,每個模闆裡面又有幾十個甚至上百個子模闆,那麼一次比對的成本會相當高,對CPU壓力也會非常大。是以通常我們會采用反向索引技術,比如
<xdisease>*怎麼*治療
會進行如下編碼:
怎麼 -> [24,100...... ]
治療 -> [24,36...... ]
複制
比如
糖尿病應該怎麼辦治療
,我們會提取出”怎麼“,”治療“,兩個詞彙,然後獲得他們的倒排清單,求交集,就能得到24,并且再做一次實體檢查,就能實作快速查找了。當然具體如何做倒排清單,如何做抽詞,包括做實體識别,實體積累我們在本文不做詳解。
語言模闆引擎是聊天機器人裡較為核心的元件,通常算法在這種場景裡是補充。
對話配置系統
對話配置系統,其實就是chatbot framework, 據說有一些開源實作,不過我沒具體了解過。我這裡說說我的設計。
通常對于一次性對話(一問一答)這個比較好處理,依托于上面的語言模闆引擎基本就能實作了。對于有一個”對話引導流程“的會話,這種多倫對話則需要一個較為完善的對話配置系統。
對話配置系統會涉及到幾個概念:
- 會話主題。每次對話應該都是圍繞一個主題的,比如幫助使用者完成轉賬流程,這期間要和使用者發生多次互動,直到最後幫使用者搞定。
- 跳轉。 根據使用者的回報,又分為會話内跳轉,和會話間跳轉。因為一個會話會有多次互動,是以會有會話内跳轉。會話間跳轉,可以通過一個簡單的例子來解釋:比如使用者問附近哪家餐廳比較好,你可能會詢問使用者是要西餐還是中餐,這個時候使用者不搭理你了,說給我安排一個日程吧,這個時候時候就需要主題間的跳轉。主題通常依托在特定會話中。
- 對話樹。一個對話對話是一個樹狀結構。同時我們又會有多個對話,對話之間不一定是互通的,最終有個會話森林的概念。
- 對話樹節點。前面我們提及,一個會話會有多倫互動,是以為了完成一個會話, 配置上至少有兩種類型的節點:一個是條件節點,一個回答節點。
上面都是一些要點,我這裡會舉一個最簡單的配置例子:
{
"對話名稱": {
"id": 1,
"intercept": [
{
"name": "......ConversationChangeInterceptor",
"params": {
"match": "template:6,8,9,10",
"target_step": {
"match_one": "3:1",
"no_match": "2:0"
}
}
}
],
"conversation": [
{
"chain_type": "condition",
"match": [
"template:6,7,8"
],
"desc": "",
"msg": "",
"step": 0,
"target_step": {
"question_finish": 11,
"no_match": 10
}
},
{
"chain_type": "conversation",
"match": [
],
"desc": "",
"format_class": {
"name": ".....ClassifyFormatter",
"params": {
"url": "..../prediction"
}
},
"msg": "您好 ${name}先生",
"step": 6,
"target_step": -1
}
複制
intercept表示會話攔截,在對話流程裡任何一個環節,都需要檢查下是不是發現了主題變更,如果符合,則會根據target_step實作對應的跳轉。在interceptor的target_step 被表示為 A:B 這種形式,意思是跳轉到A對話裡的B節點上。
match 表示比對了哪些模闆,當然,也可以是一個算法模型,比如"model:com.org.QuestionClassify",比如我需要判定使用者是不是在描述自己的身體狀況,這個時候用模闆顯然是不行的,可能需要繼承一個算法分類器。顯然上面的配置是支援這種內建的。
如果比對上了則跳轉到對應的step 11,如果沒有比對則跳轉到step 10。根據類型(chain_type),這是一個條件節點,是以他不會對使用者做任何輸出,而是默默的根據條件往其他節點條。
msg 表示應答的語句,如果你想動态調整這個輸出,可以配置format_class。format_class主要實作複雜動态的應答邏輯。另外還有一個類似配置是query_class,會攔截進來使用者的問題,并且改寫使用者的問題。
step 表示目前處于會話的那個節點,這個節點處理完後的下一個節點會是target_step。 通過适當形态,可以實作會話之間的跳轉。我舉的例子隻是展示了内部跳轉。
有了這套配置引擎後,比如做一個客服機器人,就變得很簡單了,把使用者的常見問題羅列下,之後執行特定的動作。當然,很多傳統的客服機器人為了簡單期間,主要是通過依托于QA算法做比對,并不會采用這種我說的方案。
我們根據這套配置引擎,可以實作一個很複雜的對話。而且可以配置很多有趣的功能,比如 功能導航對話,比如吃飯請按1,睡覺輕按2 這種。隻要配置一個新對話即可,然後這個對話作為作為起始對話,通過會話間跳轉來完成導航功能。
因為千人千面,是以在實際的引擎實作過程中,我們需要記錄每個使用者目前所處的會話以及所在的節點,還包括會話期間的一些資訊搜集,這也是一個較為複雜的話題了。我們底層采用redis做這個存儲。
總結
一般而言,我們無法使用“某個”算法就實作一個複雜的系統,當然,“某個”算法可能很重要,甚至是系統能否成功的關鍵。一個複雜的系統通常都是根據每個環節的需求不同,綜合利用了方方面面的算法,而每個算法使用的資料又是其他算法處理而來的。ChatBot framework 本身能夠通過配置,複用一些已有的元件完成一些基礎的對話功能,但是如果要實作更複雜的對話,則需要更多算法群組件的支援。