機器人的「語料」,如何獲取?

GitChat技術雜談發表於2017-11-28

640.gif?wxfrom=5&wx_lazy=1

本文來自作者 李燁 在 GitChat 上分享「應用聚類模型獲得聊天機器人語料」,閱讀原文」檢視交流實錄

文末高能

編輯 | 嘉仔

0. 聊天機器人系列第三部

之前筆者開過兩個關於聊天機器人開發的 Chat:從零開始,開發一款聊天機器人,和聊天機器人語言理解模組開發實踐

前者講述開發一款聊天機器人的過程;後者則細述了在不利用現有工具的情況下,實現語言理解模型的方法——對意圖識別和實體抽取模型的原理和演算法步驟進行了說明。

一個模型光有演算法不行,還必須有資料。本 chat 就專門來講一講,如何獲取聊天機器人語言理解模型的訓練資料,尤其是,如何利用已有的積累獲取有效訓練資料。

同時,本 chat 也用來向大家展示一下:如何在工程實踐中利用學術研究的成果

本次 chat 描述的過程,是一個在人工智慧工程類工作中非常典型的例項:通過閱讀論文,實踐論文內容來解決實際問題。

1. 從使用者日誌中挖掘訓練語料

1.1 語言理解模型的語料

所謂語料(Utterance),就是語言理解模型的訓練資料。因為意圖識別和實體抽取都是有監督模型,因此他們所需要的訓練資料都是標註資料。在資料被標註之前,我們稱其為原始語料。

聊天機器人語言理解模型所需要的原始語料,是人類對聊天機器人說的話(query),這些query在經過標註後成為訓練語料。

原始語料一般有兩個來源:

i)人工生成——在毫無積累的情況下,只能人為編寫,或者藉助部分手段(e.g. 基於規則的語言片段替換等)半自動生成。

ii)從使用者日誌中選取——有些聊天機器人所對應的場景,早已經有人類客服為之工作。

在這種情況下,使用者日誌中就會記載大量之前使用者和人類客服之間的對話。這些對話,都有可能用來作為訓練機器人語言理解的語料。

1.2 語料對標註的影響

從零開始,開發一款聊天機器人中,我們專門講過語料標註的方法。

意圖識別是分類模型,因此,每一條用於訓練的語料都有一個標籤,這個標籤就是最終該語料被判定的意圖。

在進行標註資料的時候,總共有哪些意圖(標籤),都已經確定了。所有的意圖是一個有限集合,且理論上在標註過程中不應變動(雖然在實際操作中變動時常發生,但如果發生變動,已標註資料都需要被重新標註)。

這個意圖集合是如何產生的?當然是開發者定義的。開發者可以完全憑空定義嗎?當然不是!

一個聊天機器人的意圖,直接影響了該機器人在獲取使用者query後可能產生的回覆。因此,意圖定義必須和這個聊天機器人在真實應用中要回答的使用者問題存在直接的關聯。

例如:一個電商的客服機器人經常要處理使用者關於郵寄/快遞費用的詢問,自然,在訓練它的LU模型時,就應定義與之對應的意圖。

一個培訓學校的客服需要經常提供選課服務,它也應有相應意圖。反過來,電商客服不需要“選擇課程”意圖,培訓客服一般也不需要“減免郵費”意圖。

有些意圖我們可以通過已經掌握的領域知識來直接定義,比如:只要是電商客服,一定需要“查詢商品”意圖。

但是,作為程式設計師,我們不太可能對所有客戶的業務領域瞭如指掌,因此也就不能保證自己拍腦袋想出來的意圖集合完備且必要。

在這種情況下,通過對使用者日誌的分析能夠幫我們瞭解使用者的日常業務,並進一步為如何定義意圖及實體提供意見。

1.3 分析使用者日誌

拿到使用者日誌的第一步工作是:閱讀——通過閱讀既往客服和使用者之間交流的記錄來了解場景、業務,必不可少。

但是要客觀的分析使用者資料,對其有一個整體的瞭解和掌握,僅僅靠直觀感性認識還是不夠的。我們還需要採取一些資料分析的手段。

如果我們知道使用者的問題大致能夠分為那些“類”,必然對之後定義意圖的工作有極大幫助。但是,此時我們偏偏不知道如何對日誌語料進行*分類*操作。我們能用的方法是:聚類。

所謂聚類,指一系列機器學習演算法,它們能夠把內容相近的語料放到一起,形成一個“簇”。

這裡面的“簇”,都不是預先定義的,而是依據各自演算法的區分原則,對資料做處理後自然形成的結果

現在,我們要做的,就是對使用者日誌中的使用者query進行聚類。

2. 對使用者日誌語料進行聚類

2.1 聚類演算法選取

說到聚類,最常見的模型當然是 KMeans。

不過如果使用 KMeans 的話,需要指定K的值,也就是要在訓練前指定最後的結果被分為幾“簇”。當我們分析某客戶的使用者日誌時,並不知道K是幾。K是我們希望通過聚類發現的。

我們還希望聚類結果形成的若干簇,大小不要差異太大(如果結果是一個簇包含了90%的語料。

另外若干簇分擔10%,那麼顯然對我們定義意圖沒有太大幫助)。而KMeans的結果並不能保證每簇中的個體數量低於某個量值。

因此,KMeans並不符合當前任務的要求。

那應該用什麼模型呢?經過一通搜尋調研,以及請教公司內部的資料科學家,最終確定了聚類演算法:基於圖切割的譜聚類(Spectral Clustering)。

2.2 根據論文實現演算法

通過搜尋,筆者發現一篇論文:《A Tutorial on Spectral Clustering》( http://www.kyb.mpg.de/fileadmin/user_upload/files/publications/attachments/luxburg06_TR_v2_4139%5B1%5D.pdf ) ,其中非常詳細的講述了譜聚類演算法的步驟,並且有嚴格的推導公式。

筆者於是決定,以本論文為基礎依據,同時結合其他同事在圖切割方法上的實踐經驗,自己編碼實現譜聚類演算法。

2.3 聚類演算法步驟

根據論文和專家意見,經過一番嘗試,最終按照以下步驟實現了這個聚類程式:

【步驟1】 將每個 Utterance 轉化為一個詞袋,並經過 normalization 和去停用詞操作。

【步驟2】生成一張圖 G = < V,E >,每個詞袋是其中一個頂點(vertex),每兩個頂點之間的邊則是這兩個詞袋之間的 Jaccard 距離:

Jaccard_Distance = 1 – (|A ∩ B|/ |A U B|)

這樣一張圖用矩陣 C = (cij) 儲存; 其中:

cij =Jaccard_Distance(wordbag_i, wordbag_j)

【步驟3】確定距離閾值 threshold_C, 將所有cij > threshold_C 的兩個頂點 i 和 j 視為斷開。據此將完整的G分割為若干連通圖{G1, G2, … , Gn}。

計算每一個子圖的 radius (最遠邊緣節點到中心節點的距離) 和 size(包含頂點數)。

如果(cluster_radius<= threshold_radius) && (cluster_size <= threshold_size) 則該連通圖本身就是一個獨立的簇,否則,對該簇進行下一個步驟。

【步驟4】圖切割:

4-1 將待切割圖G切割為2個子圖Gs1 和Gs2, 達到Gs1和Gs2之間距離儘量大而兩個子圖內部個節點間距離儘量小的目的。具體切割過程如下:

4-1-1 構造一個和C同等大小的矩陣W = (wij)

wij = exp(-||xi - xj||2/σ2) ; 這裡用到了高斯相似度函式(Gaussian similarity function),其中σ 是一個用來控制變換速度的引數。

具體到本例子, ||xi-xj||就用 wordbag_i 和 wordbag_j 的 Jaccard_distance 來計算即可。也就是說 ||xi-xj|| = cij。

4-1-2 構造一個對角矩陣D:

對角項 di = Σj wij

4-1-3 令 L = D - W,構造等式:

Lv = (D-W)v = λv,其中λ為常數,v為向量。

4-1-4 計算上述等式的倒數第二小的特徵值所對應特徵向量f。

設被分割圖 G一共包含n個頂點,則其由一個nxn矩陣表達,因此得出的特徵向量f也是n維向量。f中的每一維代表一個頂點(即一個Utterance)。

f = (f1, f2, …, fn);

IF fi >= 0 THEN vertex_i 屬於 Gs1 IF fi < 0  THEN vetex_i 屬於 Gs2

這樣就把G分成了兩部分:Gs1和Gs2。

4-2 計算Gs1和Gs2的大小和半徑,然後

IF ((cluster_radius > threshold_radius) && (cluster_size > threshold_size))   THEN        重複 4-1,直到所有被分割的結果滿足上述條件為止。

【步驟5】【步驟4】運用到所有【步驟3】中所有連通圖{G1, G2, … , Gn}上。

3. 工程實踐

3.1 程式語言和執行結果

因為當時所處理的日誌是記錄在 PostgresDB 中的結構化資料,要用一種能夠方便運算元據庫的語言。

因此譜聚類程式的第一個版本是用內嵌R的 PostgreSQL 編寫的。

總共40多萬條(平均每條不超過1K),用SQL+R也並不是很慢。

R的矩陣運算功能使得整個程式編寫相當容易,將threshold_size設為2000,threshold_radius設為50之後,整體執行結果還不錯。最後得到了17個簇,人工看結果也比較reasonable。

雖然最終的意圖定義並非和這些分出來的簇一一對應,但是經過反覆調參得到的聚類結果,包括整個調參過程,都讓我們對聊天機器人的應用場景有了進一步深入的瞭解。

3.2 被質疑的演算法

不過在團隊內部分享演算法實現的時候,有同事說:這不就是算詞頻嘛。態度頗為不屑。

當時的感覺是有些無言以對。

詞袋模型(用詞袋來表示Utterance)本身確實是沒有考慮單詞出現順序對文件含義的影響,而且採用 Jaccard Distance 計算的是兩個詞袋之間單詞交集和並集的比例,也確實和詞語出現的次數相關。

但是如果說譜聚類就是算詞頻,則相差太遠了。

可是具體差在哪裡呢?好像又有點說不清楚。只是朦朧的覺得,如果不是採用了相似度矩陣,如果不是進行了矩陣運算,則根本得不出“簇”的結果——

要算單個文件(Utterance)的詞頻或者總體詞頻都很容易。但是如果不是矩陣運算,又怎麼可能把這些文件歸為若干簇,使得每一個簇內文件相互之間的距離儘量小,而簇之間的距離儘量大呢?

模模糊糊有如上這樣的想法。但是,其實自己並不明白,即使採用了譜聚類,又是怎麼能做到不同的簇高內聚低耦合的。

還有,為什麼要做那麼一大堆奇怪的矩陣變換?為什麼要把一個好好的相似度矩陣扭曲成很奇怪的樣子?這樣做到底是為什麼呢?

憑直覺猜想肯定是不行的,要面對質疑,就不能只停留在依據步驟實現演算法,必須從理論層面徹底掌握演算法的原理及數學推導過程!

筆者回頭從理論層面一步步推導譜聚類的數學原理,從頭到尾理清之後,才對上述的疑問有了解答。

4 掌握原理,面對質疑

4.1 基本原理

譜聚類的目的就是要找到一種合理的分割,使得分割後形成若干子圖,連線不同的子圖的邊的權重儘可能低,即“截”最小,同一子圖內的邊的權重儘可能高。

4.2 推導過程

具體過程是經歷了以下這些操作,實現的:

4.2.1 構造矩陣

步驟 4-1-1 中根據對稱陣C構造的矩陣W,也是一個對稱陣。它描述了G中各節點間的相似度。

NOTE 1: 在步驟2中構造的矩陣 C = (cij) 中,cij表示點 i 到點 j 的距離,cij值越大說明距離越遠。但是到了矩陣W中的節點:wij = exp(-(cij)2/σ2)。cij 越大,則wij越小。也就是說W中的節點wij 的數值越小,它所表示的對應的兩個點之間的距離也就越大。

步驟 4-1-2 則是構造了W的對角矩陣D。

步驟 4-1-3 中,由相似度矩陣W和其對角矩陣D,我們構造了一個新的矩陣:

L= D-W

L是一個拉普拉斯(Laplacian)矩陣,稱作非規範化的拉普拉斯矩陣(Unnormalized Laplacian Matrix)。

4.2.2 拉普拉斯矩陣性質

因拉普拉斯矩陣性質得知:

(i)  L是對稱半正定矩陣;

(ii)  Laplacian矩陣L的最小特徵值是0,相應的特徵向量是I;

(iii) Laplacian矩陣L有n個非負實特徵值:0 = λ1 <= λ2 <= … <= λn

又因為L = D - W,對於任一實向量f,都可以做如下計算:

0?wx_fmt=jpeg

因此,對於任一實向量f都有下面的式子成立:

0?wx_fmt=jpeg

4.2.3 圖分割和矩陣運算的關係

現在我們回過頭來,看圖切割這件事情。

假設我們把L所對應的原圖進行圖切割,成兩個新的圖:A和 

也就是說,之前n x n矩陣L所對應的n個頂點被分為了兩部分,一部分屬於A,另一部分屬於它的補 

到底哪些點被分給了A,哪些點被分給了它的補呢?我們可以用一個向量來表示。

假設存在一個向量f = (f1, f2, …, fn)’, 其中不同維度的值可以指示該維度對應的頂點屬於新分出來的哪個子圖。具體如下:

0?wx_fmt=jpeg

將f帶入到上面(iii)中的公式:0?wx_fmt=jpeg,又因為當i,j同屬於A或者0?wx_fmt=jpeg時,fi – fj 為0。

因此,f’Lf 就可以被轉化為如下形式:

0?wx_fmt=jpeg
<式子1>

取出上面<式子1>中的後一部分:

0?wx_fmt=jpeg

其中:k表示不同類別的個數,這裡k=2。 0?wx_fmt=jpeg表示子圖A和 0?wx_fmt=jpeg之間連通邊的權重。

這裡的定義的Cut函式,又可以被稱為“截函式”。

當一個圖被劃分成為兩個子圖時,“截”即指子圖間的連線密度。即被切割後的子圖之間,原本是連通狀態(但在切割時被截斷)的邊的值加權和。

我們要找到一種分割,使得分割後,連線被分割出來的兩個子圖的邊的權重儘可能低,即“截”最小。

因此,Cut函式就是我們求取圖切割方法的目標函式。

因為在這個例子裡面,Cut 函式中的值是就是 wij (頂點i 位於A, 頂點 j 位於0?wx_fmt=jpeg)。根據前面的 NOTE 1 可知 wij 越小,則對應的兩點間的距離越大。

我們既然要讓切割出來的結果是兩個子圖之間的加權距離儘量大,那麼自然,我們就要讓 0?wx_fmt=jpeg的值儘量小。

由此可知,我們要做的就是最小化Cut函式。

我們再將Cut函式帶回到<式子1>中,得到結果如下:

0?wx_fmt=jpeg

其中|V|表示的是頂點的數目,對於確定的圖來說是個常數。

由上述的推導可知,由f’Lf推匯出了RatioCut函式。到此,我們得出了:

0?wx_fmt=jpeg

因為 Cut 函式和 RatioCut 函式相差的是一個常數,因此求Cut的最小值就是求 RatioCut 的最小值。

又因為|V|是常數,因此我們求 RatioCut 函式的最小值就是求f’Lf的最小值。

到此時,圖切割問題,就變成了求 f’Lf 的最小值的問題。

4.2.4 通過求f’Lf的最小值來切割圖

假設 λ 是 Laplacian 矩陣L的特徵值,f是特徵值λ對應的特徵向量,則有:Lf = λf

在上式的兩端同時左乘f’,得到:

f’Lf = λf’f

已知 ||f|| = n1/2 則 f’f = n,上式可以轉化為:f’Lf = λn

既然我們的目標是求 0?wx_fmt=jpeg ,那麼我們只需求得最小特徵值λ。

由 Laplacian 矩陣的性質可知,Laplacian 矩陣的最小特徵值為0,相應的特徵向量是I。

向量I中所有維度 都為1,無法將對應頂點分為兩份。因此我們用L第二小的特徵值(也就是最小的非零特徵值)來近似取 RatioCut 的最小值。(本自然段描述純為筆者的理解,此處背後實際的理論依據是 Rayleigh-Ritz 理論。)

我們先求出L第二小的特徵矩陣f,再通過如下變換,將f轉化為一個離散的指示向量:

對於求解出來的特徵向量f = (f1,f2,…, fn)’ 中的每一個分量fi,根據每個分量的值來判斷對應的點所屬的類別:

0?wx_fmt=jpeg

這也就是步驟4-1-4中描述的內容。

4.2.5 從切割成2份到切割成k份的推演

如果不是要一次將圖切成2份,而是要切成k份,那麼就要先求L的前k小個特徵向量。

若前k個特徵向量為0?wx_fmt=jpeg,這樣便由特徵向量構成如下的特徵向量矩陣:

0?wx_fmt=jpeg

將特徵向量矩陣中的每一行作為一個樣本,利用K-Means聚類方法對其進行聚類。

即對n個k維向量進行聚類,將其聚為k個簇。聚類完成之後,如果特徵矩陣中的第i個k維向量被聚集到了第j個簇中,則原本圖中的第i個點就被聚集到了第j個簇中。

以上,就是根據非規範化拉普拉矩陣進行基於圖切割的譜聚類的演算法原理。

4.2.6 規範化拉普拉斯矩陣

L 也可以被規範化,D-1/2L D-1/2 就是L的規範化形式。

L’ = D-1/2L D-1/2 又稱為規範化的拉普拉斯矩陣(Normalized Laplacian Matrix)。

對於規範化的拉普拉斯矩陣,不能直接求其特徵值和特徵向量來做圖切割。不過大體過程和思路與非規範化拉普拉斯矩陣一致,在此不贅述。

5. 篩選出訓練語料

當然,譜聚類僅是分析使用者日誌的工具之一。除此之外,還有許多手段可以用來幫助我們瞭解資料。

比如,我們可以:

  • 用 word2vec 訓練詞向量模型;然後再針對每條語料生成詞袋模型,通過拼接詞袋中各詞詞向量的方式生成詞袋向量;

    再計算不同詞袋向量之間的相似度;設定相似度閾值,來將所有語料分為若干“簇”。

  • 根據一些基於先驗知識的簡單規則,對語料做一次基於規則的“粗分類”;然後計算各語料中非停用詞的tf-idf和資訊熵,以資訊熵為依據進行聚類。

  • 應用LDA模型進行聚類。

  • 等等……

以上各種方法可以組合或疊加使用,包括其中各種將自然語言語料轉化為向量空間模型的方式,也可以替換或者組合使用。

筆者實踐了譜聚類和上面列出的三種聚類方法,最終實際的體會:還是譜聚類的效果最好。這可能和聊天機器人語料的客觀特點有關——使用者query 一般比較簡短,不屬於長文件;

不同意圖的分佈又不甚平均;有些意圖之間的區分度也不是非常明顯;加之中文語料,需要先進行分詞處理,分詞程式的質量也直接影響著最終效果。

種種原因導致最終譜聚類勝出,為最終定義意圖、實體和篩選語料做出了貢獻。

因為考慮到資料標註的人力成本,雖然有幾十萬條備選語料,我們最終還是採用簡單規則+隨機的方法,從中抽取了幾千條進行標註,最終生成了語言理解模型的訓練資料。

近期熱文

輕鬆幾招你也可以架構高效能網站

手把手教你如何向 Linux 核心提交程式碼

Java 實現 Web 應用中的定時任務

沉迷前端,無法自拔的人,如何規劃職業生涯?

TensorFlow 計算與智慧基礎

突破技術發展瓶頸、成功轉型的重要因素


從 TensorFlow 開始學習人工智慧

0?wx_fmt=jpeg

「閱讀原文」看交流實錄,你想知道的都在這裡

相關文章