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