1. 程式人生 > >智慧問答中的NLU意圖識別流程梳理

智慧問答中的NLU意圖識別流程梳理

#### NLU意圖識別的流程說明 基於智慧問答的業務流程,所謂的NLU意圖識別就是針對已知的訓練語料(如語料格式為$(x,y)$格式的元組列表,其中$x$為訓練語料,$y$為期望輸出類別或者稱為意圖)採用選定的演算法構建一個模型,而後基於構建的模型對未知的文字進行分類。流程梳理如下: - 準備訓練資料,按照固定的格式進行; - 抽取所需要的特徵,形成特徵向量; - 抽取的特徵向量與對應的期望輸出(也就是目標label)一起輸入到機器學習演算法中,訓練出一個預測模型; - 對新到的資料採取同樣的特徵抽取,得到用於預測的特徵向量; - 使用訓練好的預測模型,對處特徵處理後的新資料進行預測,並返回結果。 從流程梳理看,NLU的意圖識別從根本上看是有監督的機器學習,即基於給定的人工篩選資料進行特徵處理,構建模型用於預測。處理流程圖如下所示: ![image](https://note.youdao.com/yws/public/resource/c388d7862c02facd6c2a03d6e17d9180/xmlnote/DED0DC29757E48DE9F55FB83A846A957/3963) #### 針對NLU意圖識別原理的例子說明 基於上述說明的流程,用一個例子來進行原理說明。 ##### 場景 小明需要訂購一張從上海到北京的機票(意圖:訂機票),在訂票的過程中想要了解下北京的天氣情況(意圖:查天氣),看是否需要準備一把雨傘;同時考慮初到北京對地方不熟悉,希望協助訂餐(意圖:訂餐)。 ##### 準備資料 根據第一部分介紹的流程,需要先準備上述場景涉及意圖的訓練資料,以備模型構建使用,我們準備如下的幾條資料(此處為了描述方便,每個意圖我們僅提供一條資料進行說明,在實際處理中每個意圖至少需要2條語料): - 幫我查詢明天到北京的機票。---------> 訂機票 - 北京明天是否有雨?---------> 查天氣 - 幫我定個烤鴨送到酒店。---------> 訂餐 在第一條的語料“幫我查詢明天到北京的機票。”中,我們可以通過實體抽取提取出“時間:明天、目的地:北京”,通過隱含條件可以推算出出發地為上海(基於使用者當前的定位資料等),這部分資訊可以作為對話過程中的關鍵資訊收集起來,作為後續的訂票主要資訊。因此處我們主要介紹意圖識別,對於實體提取的一些處理流程不做具體的介紹。 ##### 特徵提取 特徵的提取是為了方便後續的計算,在中文文字處理中,常用的特徵處理有詞袋模型(bag of word)、Tf-idf、SVD奇異值分解等,這裡為了方便說明,我們採用詞袋模型(bag of word)。特徵權重的計算方法採用簡單的$1/n$進行,其中$n$為所有語料中該詞出現的個數。 針對上文的資料,首先構造詞典,利用jieba分詞,對上述語料進行分詞,而後統計各個詞的權重,結果如下所示: ``` "幫我查詢明天到北京的機票。"分詞結果如下: ['幫', '我', '查詢', '明天', '到', '北京', '的', '機票', '。'] "北京明天是否有雨?"分詞結果如下: ['北京', '明天', '是否', '有雨', '?'] "幫我定個烤鴨送到酒店。"分詞結果如下: ['幫', '我定', '個', '烤鴨', '送到', '酒店', '。'] 整合後的詞典及詞權重如下所示: ['幫':0.5, '我':1.0, '查詢':1.0, '明天':0.5, '到':1.0, '北京':0.5, '的':1.0, '機票':1.0, '。':0.5, '是否':1.0, '有雨':1.0, '?':1.0, '我定':1.0, '個':1.0, '烤鴨':1.0, '送到':1.0, '酒店':1.0] ``` ##### 模型訓練 基於上步構造的詞典,對樣本資料進行特徵權重構造(也就是模型構建的過程,如果是使用神經網路等深度學習技術的話,就是通過樣本資料學習各個特徵的權重及偏置),如下所示: ``` "幫我查詢明天到北京的機票。"特徵如下: ['幫':0.5, '我':1.0, '查詢':1.0, '明天':0.5, '到':1.0, '北京':0.5, '的':1.0, '機票':1.0, '。':0.5] "北京明天是否有雨?"特徵如下: ['北京':0.5, '明天':1.0, '是否':1.0, '有雨':1.0, '?':1.0] "幫我定個烤鴨送到酒店。"特徵如下: ['幫':0.5, '我定':1.0, '個':1.0, '烤鴨':1.0, '送到':1.0, '酒店':1.0, '。':0.5] ``` ##### 新資料特徵提取 在上述幾步流程中,我們已經通過準備的訓練資料構建好了預測模型,針對新的資料,需要經過同樣的特徵提取流程獲取新資料的特徵向量。 比如新資料為:查詢機票。 結巴分詞結果如下: ``` ['查詢', '機票', '。'] ``` 基於構造的詞典,獲得各個特徵的權重如下: ``` ['查詢':1.0, '機票':1.0, '。':0.5] ``` ##### 模型預測 通過模型計算新資料與各個類別(意圖)的得分(為了方便說明,這裡直接比對新資料與各個類別資料特徵匹配上的個數,而後計算相關的權重得分),如下所示: ``` 訂機票意圖特徵命中:查詢、機票、。 得分:1.0(查詢)+1.0(機票)+0.5(。)=2.5 查天氣意圖特徵命中: 得分:0.0(查詢)+0.0(機票)+0.0(。)=0.0 訂餐意圖特徵命中:。 得分:0.0(查詢)+0.0(機票)+0.5(。)=0.5 ``` 採用得分最高作為最終意圖,則新資料意圖為“訂機票”。 備註:在上述的處理過程中,出現了很多的得分為0項,在實際的處理過程中會做平滑處理,常用的平滑處理有$add-k smoothing、Good-turning$等平滑方法。 #### NLU識別引擎中使用的pipline分析 上面是簡單的描述了文字分類模型的構建及模型使用的介紹,在實際的場景處理中會比較複雜,本節針對我們在使用RASA框架的NLU模組的一個文字處理pipline進行流程分析說明。 pipline如下所示: ``` language: "zh" pipeline: - name: JiebaTokenizer - name: CRFEntityExtractor - name: EntitySynonymMapper - name: CountVectorsFeaturizer - name: EmbeddingIntentClassifier ``` 意圖識別的三個流程涉及JiebaTokenizer(分詞)、CountVectorsFeaturizer(特徵向量表示)、EmbeddingIntentClassifier(分類)三個過程,我們主要對上述三個進行說明。 ##### JiebaTokenizer 分詞 分片語件這裡主要是使用的一個開源分詞jieba分詞,通過結巴分詞將我們的訓練語料或者是傳入的使用者語句進行分詞處理,獲取分詞後的結果。 關注下,語料經過jieba分詞後會得到一個詞在該條語料中的開始start、結束end位置資訊,在最終返回給其他演算法處理時僅返回詞條在訓練語料的開始start位置,結束end資訊後續會通過開始start+len(word)的方式獲得。 分詞的示例: ``` import jieba text = "幫我查詢明天到北京的機票。" tokenized = jieba.tokenize(text) # tokens = [Token(word, start) for (word, start, end) in tokenized] print(list(tokenized)) [('幫', 0, 1), ('我', 1, 2), ('查詢', 2, 4), ('明天', 4, 6), ('到', 6, 7), ('北京', 7, 9), ('的', 9, 10), ('機票', 10, 12), ('。', 12, 13)] ``` ##### CountVectorsFeaturizer 特徵向量表示 CountVectorsFeaturizer 是一種基於特徵的tf表示的向量標識方法,其核心思想與上述示例中的基本一致,也是通過對訓練集資料經過分詞後構建詞典,而後針對每一條訓練文字統計其特徵的相關tf,形成特徵向量表示(或者認為是詞頻矩陣)。這裡需要注意下,CountVectorsFeaturizer 中有一些預設引數,會對分詞後的資料進行一些處理,比如針對英文的一些大小寫轉換、針對中文的單字過濾、停用詞過濾等操作。 我們仍然使用上文示例中出現的3條語料進行CountVectorsFeaturizer 的表示,示例程式碼採用sklearn中的CountVectorizer,如下所示: ``` from sklearn.feature_extraction.text import CountVectorizer # "幫 我 查詢 明天 到 北京 的 機票" 為輸入列表元素,即代表一個文章的經過分詞後的詞,這裡為了便於說明去除了其他一些資訊,每個語料為一條資訊 texts = ["幫 我 查詢 明天 到 北京 的 機票", "北京 明天 是否 有雨", "幫 我定 個 烤鴨 送到 酒店"] # 建立詞袋模型 cv = CountVectorizer() # 詞袋模型構建 cv_fit = cv.fit_transform(texts) # 列印所有的訓練語料形成的詞典中的詞 print(cv.get_feature_names()) 結果: ['北京', '我定', '明天', '是否', '有雨', '機票', '查詢', '烤鴨', '送到', '酒店'] # 列印所有的訓練語料形成的詞典及該詞在詞典中的標號 print(cv.vocabulary_) 結果: {'查詢': 6, '明天': 2, '北京': 0, '機票': 5, '是否': 3, '有雨': 4, '我定': 1, '烤鴨': 7, '送到': 8, '酒店': 9} # 打印出特徵的詞頻標識 print(cv_fit) 結果: (0, 5) 1 0:texts中的第0個元素; 5:詞典中順序為5的詞,即“機票”; 1:詞頻 (0, 0) 1 (0, 2) 1 # 結果轉化為稀疏舉證標識 print(cv_fit.toarray()) 結果: [[1 0 1 0 0 1 1 0 0 0] [1 0 1 1 1 0 0 0 0 0] [0 1 0 0 0 0 0 1 1 1]] ``` 上述示例中單條語料中沒有出現重複的詞,我們對第一條語料增加一個“北京”對比看下,結果如下所示: ``` from sklearn.feature_extraction.text import CountVectorizer # "幫 我 查詢 明天 到 北京 的 機票" 為輸入列表元素,即代表一個文章的字串 texts = ["幫 我 查詢 明天 到 北京 的 機票 北京", "北京 明天 是否 有雨", "幫 我定 個 烤鴨 送到 酒店"] # 建立詞袋模型 cv = CountVectorizer() cv_fit = cv.fit_transform(texts) # print(cv.get_feature_names()) # print(cv.vocabulary_) # print(cv_fit) print(cv_fit.toarray()) 結果:[[2 0 1 0 0 1 1 0 0 0] [1 0 1 1 1 0 0 0 0 0] [0 1 0 0 0 0 0 1 1 1]] ``` 經過處理後就將我們所提供的訓練文字轉換成了特徵的向量表示形式,這些特徵向量在傳入到EmbeddingIntentClassifier中與各條語料的類別標識一同進行訓練成模型。 ##### EmbeddingIntentClassifier 分類(模型構建) rasa框架是通過整合TensorFlow來進行模型構建的,一些細節進行了封裝,為了說明清晰,這裡撇開TensorFlow框架進行分析。 同樣使用上文處理好的稀疏向量為例: ``` 三條語料:幫我查詢明天到北京的機票。 北京明天是否有雨? 幫我定個烤鴨送到酒店。 其對應的特徵向量如下所示: [[1 0 1 0 0 1 1 0 0 0] [1 0 1 1 1 0 0 0 0 0] [0 1 0 0 0 0 0 1 1 1]] 三條語料的意圖分別為:訂機票、查天氣、訂餐 ``` 基於上述的特徵資料,我們構建一個4層的神經網路,其中第一層為輸入層,其接收我們處理後的特徵向量資料,根據上述示例,每個語料有10個向量值,則我們的第一層輸入層對應有10個神經元;最後一層為輸出層,也就是結構層,我們這裡有三個意圖分類,則我們的輸出神經元對應有3個;中間兩層時隱藏層,我們可以根據需要進行設計。 備註:神經網路的主要思想可以看做是通過大量的訓練樣本,自動學習一個模擬函式,進而對未知資料進行預測。或者說神經網路使用樣本資料自動推斷出每一類的的特徵規則,然後應用的新的位置資料上,進而達到分類的目的。 經過上述說明後,設計如下的神經網路結構: ![image](https://note.youdao.com/yws/public/resource/c388d7862c02facd6c2a03d6e17d9180/xmlnote/E736B76DB51F45D8836C18B0EC0B7E7A/3968) 輸入層較多,我們僅畫出部分表示,層與層之間採用全連線的方式,啟用函式我們選擇使用sigmoid函式。 針對上述的神經網路結構,每一個神經元的結構如下所示: ![image](https://note.youdao.com/yws/public/resource/c388d7862c02facd6c2a03d6e17d9180/xmlnote/D431FEB8445743C9B43F70E6CD7CF1AD/3966) 其中$x_1,x_2,...,x_{10}$就是我們上文處理後的特徵向量,我們上文示例是10維度的,一般的情況下輸出的維度都比較大,$z=w_1x_1+w_2x_2+...+w_{10}x_{10}+b$為加權輸入,輸出則為啟用函式作用在加權輸入$z$上,即$y=\sigma(z)$. 上述我們已經構建了一個4層的神經網路,那該神經網路如何與我們的分類結合在一起呢?在數學上,我們一般將這類問題歸納為優化問題,也就是有了訓練資料與相關的資料標識,則可以通過設計相關的優化函式進行。比如我們在上文已經將語料 “幫我查詢明天到北京的機票。” 標識成一個1X10的向量表示 [1 0 1 0 0 1 1 0 0 0],我們設計了一個模擬函式$y=y(x)$標識對應的期望輸出,根據上文示例,這個輸出是一個3維的向量,對於語料 “幫我查詢明天到北京的機票。” 我們期望的輸出應該是$y(x)=(1,0,0)^T$,如何求出這個模擬函式(一般情況下則是求相關的權重和偏置)則是我們模型構建的過程,即上面說的最優化問題。 針對我們上面是設計的神經網路,我們選擇二次代價函式(也稱為均方誤差代價函式): $$ C = \frac{1}{2n}\sum_{x}^{}(y(x)-a)^2 $$ 模型的構建過程就是基於訓練語料對上述代價函式進行最優化的過程,最終得到$y(x)$函式的權重及偏置,則在新的使用者資料請求到來後,直接進行計算則可以得到相應的分類結果。 注:上述最優化過程在深度學習中最常用的是反向傳播演算法,這塊內容因為涉及的細節後數學公式推導較多,可以參考我整理的筆記 [神經網路的幾點記錄](http://note.youdao.com/noteshare?id=f6797a4966d2e73818b61dafbe6a3469&sub=7BD73CAA5CBD4CFF9AE4834BB40883EB) 、[反向傳播的四個基本方程](http://note.youdao.com/noteshare?id=b00893a88993abb1a1da4fee466b6b62&sub=2BBDFBCC1022486CA0BC502A0E66880E) 上述流程即是使用RASA的NLU進行模型構建及模型預測的流程,其思想與上述中的例子所講基本上