Rasa2 NLU 架構及源碼解析(一)
神州信息
李丹 鄭飛 杜昕宸 韓彤 秦帥帥
Rasa是當前智能機器人中最流行的的聊天機器人框架,是基于機器學習和自然語言處理技術開發的系統,用于構建上下文AI助手和聊天機器人。
1.
背景
近年來,聊天機器人受到了學術界和工業界的廣泛關注。人工智能技術的快速發展突破了聊天機器人原有的技術瓶頸,并且實踐證明,聊天機器人的使用不僅能夠為企業減少一大筆人力成本,而且能夠明顯提高工作效率,國內外多家企業紛紛布局聊天機器人行業。微軟推出了基于情感計算的聊天機器人小冰,百度推出了用于交互式搜索的聊天機器人小度,進而推動了聊天機器人產品化的發展。聊天機器人系統可以看作是機器人產業與“互聯網+”的結合,符合國家的科研及產業化發展方向。
隨著人工智能在銀行和金融科技的客戶服務方面取得了重大改進,客戶越來越習慣于獲得快速響應。金融機構必須全天候回答客戶問題和進行交易。金融機構業務擴展的加速使人工客服的成本大幅攀升的同時又無法持續滿足服務質量,人工智能機器人通過金融機構長期積累的業務經驗和數據培訓聊天機器人,可明顯改善客戶體驗?;谏鲜鐾袋c和需求,各類聊天機器人框架應運而生。根據社區活躍度、技術的成熟完備度及被引用、點贊等指標,我們采用Rasa作為人機交互對話機器人基本框架。
2.
Rasa簡介
Rasa Open Source有兩個主要模塊:
●Rasa NLU :用于理解用戶消息,包括意圖識別和實體識別。以pipeline的方式處理用戶對話,可在config.yml中配置。
●Rasa Core:主要負責對話管理。根據NLU輸出的信息、以及Tracker記錄的歷史信息,得到上下文的語境,從而預測用戶當前步最可能執行哪一個action。
其中,Rasa NLU主要依賴自然語言處理技術,是可以獨立的、與整體框架解耦的模塊,可支持大量NLP前沿技術,以組件的形式,可以靈活與其他開源、自研框架搭配使用。
3.
Rasa NLU架構及源碼解析
3.1 Rasa NLU概覽
3.1.1 Rasa-NLU 架構圖
Rasa NLU 架構圖
注:(1)FallbackClassifier應出現在某一個意圖分類器之后,利用其輸出的結果,即intent、confidence、intent ranking,如果意圖的置信度比預設的threshold低,或排名前兩位的意圖的置信度的差距小于預設的ambiguity_threshold,則將該輸入的意圖分類為“nlu_fallback”
3.1.2 Rasa NLU訓練流程
Rasa NLU訓練流程圖
rasa.model_training.train_async:
讀取config, domain和訓練data到file_importer: TrainingDataImporter, 并輸入到_train_async_internal訓練Rasa model (nlu and core).
rasa.model_training._train_async_internal:
判定模型需要重新訓練的部分并將判定結果寫入fingerprint_comparison作為_do_training方法的參數fingerprint_comparison_result的值輸入_do_training完成相應部分的訓練, 訓練結束后將模型打包成trained_model,通過trained_model輸入TrainingResult返回回至上層。
rasa.model_training._do_training:
通過fingerprint_comparison_result帶入的結果判斷是否重新訓練nlu, core和nlg, 并進入相應模塊進行訓練。
rasa.model_training._train_nlu_with_validated_data:
按rasa.nlu.train.train各參數要求讀取和整理參數值,輸入rasa.nlu.train.train開始nlu模塊的訓練。
rasa.nlu.train.train:
通過初始化trainer=rasa.nlu.model.Trainer(...),構建config.yml中pipeline下的所有組件。讀取TrainingDataImporter中的nlu數據后,將數據輸入trainer.train開始訓練。
rasa.nlu.model.Trainer.train:
遍歷pipeline所有components,對每個component調用component.train方法完成訓練。
3.1.3 Rasa NLU推理流程解析
Rasa NLU推理流程圖
rasa.model_testing.test_nlu:nlu測試入口,使用get_model函數加載model并解壓(unpack),創建結果輸出目錄,調用測試過程
rasa.nlu.test.run_evaluation:測試過程入口函數,加載nlu模型初始化Interpreter實例,加載測試數據,調用get_eval_data進行測試
rasa.nlu.test.get_eval_data:在測試數據上運行模型,并提取真實標簽和預測結果,輸入interpreter實例和測試數據,返回意圖測試結果(包括意圖的標簽和預測結果,原始消息,即message,及預測結果的置信度),response測試結果(包括response的目標值和預測結果),還有實體測試結果(實體的目標值,預測結果,和對應的token)
rasa.nlu.model.Interpreter.parse:一個interpreter對應一個訓好的pipeline,其parse方法依次調用pipeline中的每一個component的process方法,來對輸入文本一次進行解析和分類等操作,并返回處理結果(包括意圖和實體)
每個component都有一個process入口方法,用于測試和實際預測,在process方法中再調用各component的內部方法(包含真正的處理邏輯),上圖虛線框中即展示了一個基本的pipeline預測示例。
pipeline中Rasa自帶的classifiers和extractors各組件(component)的具體介紹如下。
3.1 Classifier
3.1.1 Classifier架構
Rasa NLU Classifier 架構圖
3.1.2 主流技術支持情況
3.1.3 DIET Classifier
3.1.3.1 架構
DIET ( Dual Intent and Entity Transformer ) 架構
3.1.3.2 模型支持說明
對在HuggingFace 中上傳的所有預訓練模型(Huggingface模型列表),Rasa DIET可以支持滿足以下條件的所有模型:
點擊Huggingface模型列表(https://huggingface.co/models?pipeline_tag=text-classification&sort=downloads)->選中一個模型->點擊進入模型頁面->點擊Files and version
●檢查 config.json 中的 model_type 是否列在上表的 模型名稱 列中
●檢查文件 tf_model.h5 是否存在
●模型使用默認tokenizer, config.json 中不包含支持自定義的 tokenizer_class
對滿足上述條件的模型,通過2.1.3.3中描述的方式可開箱即用。
3.1.3.3 DIET支持Huggingface的配置樣例
在Rasa2.0中,若想在DIET架構中使用Huggingface提供的預訓練模型,除在rasa的config文件中指定使用DIETClassifier外,還需要配合使用對應的模塊:
1) HFTransformersNLP
主要參數:model_name: 預訓練模型config.json 中的 model_type的值;model_weights: Huggingface模型列表提供的預訓練模型名稱
2) LanguageModelTokenizer:確保訓練數據token對應的token_id與預訓練模型的token_id保持一致
3) LanguageModelFeaturizer:生成經預訓練模型轉換后的特征向量,做為架構后續模塊的輸入。
●DIET樣例代碼包位置:examples/hf_demo
●DIET樣例代碼調用方式:項目根目錄/main.py
●涉及的源碼改動:
如按 ‘樣例代碼調用方式’ 直接跑報錯... set from_pt=true, 請修改: 項目根目錄/rasa/nlu/utils/hugging_face/hf_transformers.py: class HFTransformersNLP中的def _load_model_instance中
改為
3.1.3.4 DIET核心代碼解析
rasa.nlu.model.Trainer.train:遍歷pipeline所有components,對每個component調用component.train方法完成訓練。在component遍歷到DIETClassifier之前,HFTransformersNLP等組件已經提取好了DIETClassifier訓練需要的特征。遍歷至DIETClassifier后,DIETClassifier開始利用已經提取好的特征進入訓練。
rasa.nlu.classifiers.DIETClassifier.train: 該方法主要完成三件事:
●語料準備:
通過DIETClassifier類中的方法preprocess_train_data,將訓練數據和之前提取的特征整理成符合RasaModelData格式的model_data。RasaModelData格式為。。。。。之后將整理好的model_data按batch_size整理成data_generator供batch訓練用。
●指定模型:將DIETClassifier類的成員self.model通過初始化DIET類完成指定DIET模型訓練。
■DIET模型類繼承自TransformerRasaModel類
■TransformerRasaModel繼承自RasaModel類
■RasaModel繼承自TmpKerasModel:通過重寫tf.keras.Model中的train_step(), test_step(), predict_step(), save()和load()方法,實現自定義的Rasa模型。
◆train_step()使用自定義的batch_loss并對該loss做了正則化。batch_loss需由其子類實現。
◆predict_step()使用自定義的batch_predict()。需由其子類實現。
◆save()只使用tf.keras.Model.save_weights()。
◆load()生成模型結構后加載weights.
■TmpKerasModel繼承自tf.keras.models.Model:重寫了tf.keras.models.Model的fit方法來使用自定義的數據適配器。將數據轉寫成CustomDataHandler后由其處理迭代 epoch 級別的 `tf.data.Iterator` 對象。
●訓練
3.1.4 SKLearn Classifier
3.1.4.1 架構
3.1.4.2 模型支持說明
Rasa 對 Sklearn中的所有分類器都支持,包括并不限于以下:
3.1.4.3 配置樣例
3.1.4.4 核心代碼解析
LabelEncoder()函數:標簽編碼,類別標簽數值化
transform_labels_str2num() 函數:標簽到數值
transform_labels_() 函數:輸入數值,輸出標簽文本
GridSearchCV() 函數:網格搜索參數,通過循環遍歷,嘗試每一種參數組合,返回最好的得分值的參數組合。
SVC() 函數:創建模型訓練器
process()函數:模型推理
3.1.5 Mitie Classifier
3.1.5.1 架構
MitieIntentClassifier分類器使用MITIE進行意圖分類,底層分類器使用的是具有稀疏線性核的多類線性支持向量機,MITIE是在dlib機器學習庫之上開發的NLP工具包。其架構如下圖:
3.1.5.2 模型支持說明
rasa Mitie Classifier目前只支持Mitie Classifier中具有稀疏線性核的多類線性支持向量機。適用于少樣本數據的分類。
3.1.5.3 配置樣例
每個mitie組件都依賴與MitieNLP,因此它需要被放到pipeline中所有mitie組件之前,初始化mitie結構。
結果意圖中沒有intent_ranking,輸出結果如下:
3.1.5.4 核心代碼解析
訓練代碼流程圖:
●訓練數據輸入到train
●獲取預訓練的詞向量文件
●如果模型文件不存在則報錯,否則實例化trainer
●把training_data.intent_examples中examples 轉換成token
●添加token、intent到training instance
●訓練,把訓練模型保存
預測流程:
●用戶message輸入到process
●判斷分類模型是否存在,若不存在則設置intent為None,confidence為0,否則,把message轉成token,然后計算intent,confidence,并設置到message中
3.1.6 Keyword Classifier
3.1.6.1 工作機制
當訓練集中的原句再次出現時,keyword意圖分類器能夠迅速對其分類。該分類器在訓練過程中主動收集整理遇到的文本及對應意圖,供后期使用時比對判斷用。
3.1.6.2 使用樣例
該分類器專門針對原句出現的場景,因此常常作為補充,與其他分類器配合使用,如下圖。
注意事項:
由于Keyword意圖分類涉及python自帶的re包,因此提出特定的版本要求:
1) Rasa 2.6.0 + Python 3.6(python 3.8報錯)
2) 訓練數據中可能出現標點符號問題(原句清洗),如中英文括號混用,將影響re的使用
3) 針對中文數據,需要將源碼中re.search函數中pattern部分的r"\b"去掉
4) 通過消融實驗發現,KeywordIntentClassifier
●在pipeline中需要放在主Classifier之后
●與Response Selector共同使用時,先后順序不限,依然遵循上條規則
5) 由于Keyword意圖分類器位置在pipeline后段,因此不論是否命中原句,其分類結果都將覆蓋之前組件結果,因此對源碼作如下更改,使得未命中原句情況下,Keyword分類結果不覆蓋。這意味著,非原句將采用其他分類器的結果
3.1.6.3 核心代碼解析
KeywordIntentClassifier類主要由train,process,persist和load四部分組成,
Train主要在訓練中進行兩輪數據驗證,存在沖突的以下兩類樣本不被統計:
●相同文本歸屬不同意圖
●子文本(被父文本包含)歸屬不同意圖,此輪驗證由子函數_validate_keyword_map實現
Process主要在訓練后對輸入的語句進行分類:
模型將輸入Message與維護的intent_keyword_map進行比對:如果是原句,則返回查詢到的意圖,confidence置1;否則返回None,confidence為0,具體的比對任務由函數_map_keyword_to_intent完成。
Persist負責模型保存,即將所維護的intent_keyword_map存為json文件到指定位置
Load將從指定位置的文件中還原出KeywordIntentClassifier
P.S.
數據結構
intent_keyword_map {text1: intent1, text2: intent2,.....}
training_data.intent_examples [eg1, eg2,...]
eg1 {text: xxx, intent: yy, ...}
3.1.7 Fallback Classifier
3.1.7.1 工作機制
主要功能:當識別出的意圖confidence過小或者是top2的兩個意圖confidence 很接近時,設置當前文本的意圖為nlu_fallback。
Fallback_classifier 處理流程圖:
工作流程:
●用戶message輸入process
●調用_should_fallback函數進行判斷是否需要設置成fallback意圖
■_should_fallback 主要通過兩方面進行判斷:
1. 通過_nlu_confidence_below_threshold函數判斷意圖的最高confidence是否小于設置的threshold,如果小于,則直接返回True,否則,繼續下一步判斷。
2. 通過_nlu_prediction_ambiguous函數,首先判斷意圖個數是否大于等于2,如果否,則直接返回False,否則繼續判斷top 2 的兩個意圖confidence之差是否小于ambiguity_threshold,如果是則返回True, 否則返回False
●如果_should_fallback 返回False 則process 直接return,不進行fallback設置,否則,進行fallback_confidence的計算,并將其設置到message中
3.1.7.2 使用樣例
分類器說明:
主要用于判斷當前輸入文本是否Intent 得分過小,或者排名靠前的兩個得分相近。FallbackClassifier不能單獨使用,需要在pipeline 中,使用FallbackClassifier之前配置其他的意圖識別組件。
參數說明:
threshold: 意圖閾值,如果所有的intent confidence 都小于這個閾值,則設置當前意圖為 fallback
ambiguity_threshold: 模糊意圖閾值,如果top 2 的閾值之差小于這個閾值,則設置當前意圖為 fallback
參數默認值:
在rasa/core/constants.py中設置,threshold為0.3,
ambiguity_threshold為0.1
使用配置文件:
TextLenClassifier 為自定義的意圖識別組件,基于用戶輸入文本的長度對其進行分類(主要用于配合FallbackClassifier進行demo設計)。
FallbackClassifier:
threshold 設置為0.3 ,ambiguity_threshold設置為0.05
3.1.7.3 核心代碼解析
Fallback_classifier 函數調用關系圖:
函數說明:
●def process(self, message: Message, **kwargs: Any) -> None:
FallbackClassifier組件入口函數
●def _should_fallback(self, message: Message) -> bool:
是否需要將意圖設置為fallback
●def _nlu_confidence_below_threshold(self, message: Message) -> Tuple[bool, float]:
判斷所有意圖是否都低于配置閾值
●def _nlu_prediction_ambiguous(self, message: Message) -> Tuple[bool, Optional[float]]:
判斷是否存在模糊的意圖
●def _fallback_intent(confidence: float) -> Dict[Text, Union[Text, float]]:
格式化輸出意圖
FallbackClassifier樣例代碼包位置:
examples/fallback_demo
樣例運行結果:
配置說明:
Domain配置:兩個意圖,intent_small, intent_big
Config配置:
模擬分類器-TextLenClassifier:
基于文本長度進行分類,設置不同的confidence,TextLen 小于 3時,intent_small為0.1,intent_big為0.2;TextLen 大于等3小于5時,intent_small為0.58,intent_big為0.6;TextLen大于5時,intent_small為0.7,intent_big為0.8。
運行結果展示:
TextLen> =5
3<=TextLen<5
TextLen< 3