天天看點

ChatBot framework 開發實踐

前言

通常而言,通用聊天機器人(比如小冰等)底層技術是采用類似Seq2Seq等“生成”技術的。但是這種機器人屬于探索性質,無法

提供特定的服務。而Siri則是兼具閑聊以及垂直領域功能的,比如可以預約提醒,打電話,定餐廳等特定功能。相信Siri在實作特定預約提醒,打電話功能等,則是使用了“語言模闆”比對技術以及結合分類器來實作精度的控制和定向動作的執行。

對于聊天機器人我個人是相當感興趣的,奈何現在的已經公開的文章都“相對初級和入門”,或者說過于專注裡面的某個算法,比如問答比對算法。是以萌生了寫一篇文章的想法。本文基于自己開發相應系統的經驗,理論上會給大家帶來一些幫助。但是因為是内部系統,隻能談及一些較為公開的思想。

現在我們的目标是探讨是如何設計和實作一個,隻要通過簡單配置就完成一個特定主題對話的機器人。有需要的話,可以經過插件(元件)開發,為其增加新垂直領域對話功能。這些插件,就如我前面所言,可能需要內建大量的針對特定領域問題的算法。

核心技術要點

  1. 語言模闆引擎
  2. 對話配置系統
  3. 機器學習相關技術

語言模闆可以保證意圖識别的準确性,機器學習除了能否增加覆寫度以外,同時也是對話配置系統的核心所在。在今天這篇文章裡我們會重點探讨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, 據說有一些開源實作,不過我沒具體了解過。我這裡說說我的設計。

通常對于一次性對話(一問一答)這個比較好處理,依托于上面的語言模闆引擎基本就能實作了。對于有一個”對話引導流程“的會話,這種多倫對話則需要一個較為完善的對話配置系統。

對話配置系統會涉及到幾個概念:

  1. 會話主題。每次對話應該都是圍繞一個主題的,比如幫助使用者完成轉賬流程,這期間要和使用者發生多次互動,直到最後幫使用者搞定。
  2. 跳轉。 根據使用者的回報,又分為會話内跳轉,和會話間跳轉。因為一個會話會有多次互動,是以會有會話内跳轉。會話間跳轉,可以通過一個簡單的例子來解釋:比如使用者問附近哪家餐廳比較好,你可能會詢問使用者是要西餐還是中餐,這個時候使用者不搭理你了,說給我安排一個日程吧,這個時候時候就需要主題間的跳轉。主題通常依托在特定會話中。
  3. 對話樹。一個對話對話是一個樹狀結構。同時我們又會有多個對話,對話之間不一定是互通的,最終有個會話森林的概念。
  4. 對話樹節點。前面我們提及,一個會話會有多倫互動,是以為了完成一個會話, 配置上至少有兩種類型的節點:一個是條件節點,一個回答節點。

上面都是一些要點,我這裡會舉一個最簡單的配置例子:

{
  "對話名稱": {
    "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 本身能夠通過配置,複用一些已有的元件完成一些基礎的對話功能,但是如果要實作更複雜的對話,則需要更多算法群組件的支援。