1. RASA整體結(jié)構(gòu)
上圖是RASA執(zhí)行的結(jié)構(gòu)圖讽挟,
- 一句話輸入懒叛,經(jīng)過Interpreter處理,使用NLU耽梅,將這句話進(jìn)行解析薛窥,結(jié)果就是一組字典,包括:原始句子眼姐,意圖诅迷,識(shí)別的實(shí)體等。
- 將上一步的結(jié)果傳入Tracker來追蹤對(duì)話狀態(tài)众旗。
- Policy接收上一步的狀態(tài)罢杉,然后決定下一步要采取的Action
- 這個(gè)Action再依據(jù)Tracker來生成最終的回復(fù)。
2. Rasa NLU
2.1 Pileine
在Rasa NLU中贡歧,一句話需要經(jīng)過一系列的組件(Component)來處理滩租,這個(gè)處理過程是組件依次執(zhí)行赋秀,并且下一個(gè)組件會(huì)使用上一個(gè)組件的結(jié)果,這個(gè)過程就被稱之為Pipeline.
通常一個(gè)pipeline會(huì)包含的組件有:預(yù)處理律想,實(shí)體抽取猎莲,意圖分類等。
下面是由一系列組件得到的結(jié)果技即,比如著洼,實(shí)體(entities)是有實(shí)體抽取組件得到的。
{
"text": "I am looking for Chinese food",
"entities": [
{"start": 8, "end": 15, "value": "chinese", "entity": "cuisine", "extractor": "CRFEntityExtractor", "confidence": 0.864}
],
"intent": {"confidence": 0.6485910906220309, "name": "restaurant_search"},
"intent_ranking": [
{"confidence": 0.6485910906220309, "name": "restaurant_search"},
{"confidence": 0.1416153159565678, "name": "affirm"}
]
}
看一下初始化nlu模型中創(chuàng)造pipeline的代碼:
def _build_pipeline(
cfg: RasaNLUModelConfig, component_builder: ComponentBuilder
) -> List[Component]:
"""Transform the passed names of the pipeline components into classes"""
pipeline = []
# Transform the passed names of the pipeline components into classes
for i in range(len(cfg.pipeline)):
component_cfg = cfg.for_component(i)
component = component_builder.create_component(component_cfg, cfg)
pipeline.append(component)
return pipeline
傳入RasaNLUModelConfig和ComponentBuilder類而叼,然后創(chuàng)造config中定義的所有Component類身笤,放進(jìn)pipeline中,pipeline其實(shí)就是一個(gè)list啦澈歉。
2.3 Component
每一個(gè)組件實(shí)例都可以執(zhí)行幾個(gè)特定的方法展鸡,而在一個(gè)pipeline中,這些方法會(huì)以固定的順序依次執(zhí)行埃难。
假設(shè)我們的pipeline中定義了三個(gè)組件:"pipeline": ["Component A", "Component B", "Last Component"]莹弊,下圖中顯示了在訓(xùn)練過程各組建方法的調(diào)用順序以及組建的生存周期:
Component類包含的方法有: create(),train(),persis(),process(),load()等。
Component是所有獨(dú)立組件的父類涡尘,每個(gè)獨(dú)立組建需要具體實(shí)現(xiàn)父類的每個(gè)方法忍弛。
當(dāng)用create()方法創(chuàng)造一個(gè)Component實(shí)例,一個(gè)context就別生成了(其實(shí)就是python里面的一個(gè)dict)考抄,然后我們就使用這個(gè)context在組建之間傳遞信息细疚。
所以,當(dāng)所有的組件訓(xùn)練完成并且持久化了川梅,最終的context字典就用來持久化最終這個(gè)模型的元數(shù)據(jù)(metadata)疯兼。
介紹幾個(gè)Component:
2.2.1 詞向量
2.2.2 特征提取器
列出一些可供選擇的Featurizers: MitieFeaturizer,SpacyFeaturizer贫途,NGramFeaturizer吧彪,RegexFeaturizer,CountVectorsFeaturizer丢早,具體介紹可以到官網(wǎng)教程上看姨裸。
SpacyFeaturizer就是將一句話變成一個(gè)向量
2.2.3 意圖分類器
- KeywordIntentClassifier,只使用關(guān)鍵字來進(jìn)行意圖識(shí)別怨酝。
- MitieIntentClassifier傀缩, 是MITIE中提供的分類器,使用SVM农猬,輸入需要分詞Tokenizers和featurizer赡艰。
- SklearnIntentClassifier, 是sklearn中提供的一個(gè)svm分類器斤葱,另外會(huì)使用grid search進(jìn)行參數(shù)優(yōu)化搜索瞄摊。輸入需要featurizer勋又。
- EmbeddingIntentClassifier,
這個(gè)分類器的實(shí)現(xiàn)是基于StarSpace,就是將輸入和對(duì)應(yīng)的label映射到相同空間换帜,然后最大化他們之間的相似度楔壤。具體的實(shí)現(xiàn)中另外增加了一層隱層并使用dropout。輸入需要featurizer惯驼。
StarSpace模型
模型解釋:StarSpace模在于學(xué)習(xí)entities蹲嚣,而每一個(gè)entity是由一組離散的特征(features)組成,這些特征構(gòu)成一個(gè)特征字典祟牲。比如隙畜,當(dāng)把一篇document或者一個(gè)sentence看作一個(gè)entity,組成他的特征就是詞代或者n-grams说贝∫槎瑁或者,當(dāng)把一個(gè)人看作entity乡恕,他就能通過一組文章言询、電影、喜歡的東西等來描述傲宜。
StarSpace將不同類別的entity通過embedding映射到相同空間运杭,這樣,就可以比較任何不同類別的entity函卒。
令特征字典有特征辆憔,他是一個(gè)維的矩陣,其中表示第i個(gè)特征(一行)报嵌,就是一個(gè)d維的向量虱咧。然后,我們embed一個(gè)實(shí)體a的表示為:模型初始化:首先給特征字段中的每個(gè)特征分配一個(gè)d維的向量锚国。然后一個(gè)實(shí)體有一組特征組成腕巡,也就是由一組上述d維向量。
訓(xùn)練模型:需要學(xué)習(xí)如何取比較實(shí)體跷叉。然后最小化下面的loss function:
解釋上述公式:
- positive entity pairs(a, b)是從正例集合中生成逸雹,而這個(gè)數(shù)據(jù)集不同任務(wù)不同营搅。
- negetive entiies b是從負(fù)例集合中生成云挟。k-negative sampling的策略可以是每次從batch中任意選取k個(gè)label。
- 相似度函數(shù)可以使用cosine similarity或者inner product,對(duì)于小數(shù)量label(比如分類任務(wù))转质,兩者效果相似园欣。但是,一般來講休蟹,cosine similarity更適用于大數(shù)據(jù)label(比如句子和文檔相似度)沸枯。
- L is the loss function that compares the positive pair (a, b) with the negative pairs.
模型的使用:
我們可以直接使用學(xué)習(xí)到的函數(shù) 來計(jì)算entity之間的相似度日矫。比如,對(duì)于分類任務(wù)绑榴,對(duì)于輸入a哪轿,直接計(jì)算 ,表示所有可能的label。對(duì)于ranking任務(wù)赤套,可以直接使用相似度進(jìn)行排序飘痛。各任務(wù)的使用(構(gòu)造和)
- text classification
- multilabel classification
- information retrieval and document embeddings
如果有現(xiàn)成監(jiān)督學(xué)習(xí)數(shù)據(jù)集,a是搜索關(guān)鍵詞容握,b是相關(guān)的文檔宣脉,而是不相關(guān)的文檔。如果只有非監(jiān)督數(shù)據(jù)集剔氏,a表示文檔中任選的關(guān)鍵詞塑猖,而b表示文檔中剩下的詞語。 - Learning Word Embeddings
一個(gè)window的詞語作為a介蛉,中間的一個(gè)詞語看作b萌庆。 - Learning Sentence Embeddings
相同文檔中選取sentence pair看作a, b。而來自其他文檔中币旧。
- Rasa中的實(shí)現(xiàn)
_create_tf_embed_nn()用來創(chuàng)建encoder部分践险,a和b的encoder是相同的網(wǎng)絡(luò)。其實(shí)Rasa中就是使用多層的神經(jīng)網(wǎng)絡(luò)吹菱。
def _create_tf_embed_nn(
self, x_in: "Tensor", is_training: "Tensor", layer_sizes: List[int], name: Text
) -> "Tensor":
"""Create nn with hidden layers and name"""
reg = tf.contrib.layers.l2_regularizer(self.C2)
x = x_in
for i, layer_size in enumerate(layer_sizes):
x = tf.layers.dense(
inputs=x,
units=layer_size,
activation=tf.nn.relu,
kernel_regularizer=reg,
name="hidden_layer_{}_{}".format(name, i),
)
x = tf.layers.dropout(x, rate=self.droprate, training=is_training)
x = tf.layers.dense(
inputs=x,
units=self.embed_dim,
kernel_regularizer=reg,
name="embed_layer_{}".format(name),
)
return x
_tf_loss()d用來定義網(wǎng)絡(luò)的loss巍虫。主要包括三個(gè)部分:1. positive similarity, 2. negtive similarity, 3. similarity between intent.
網(wǎng)絡(luò)的默認(rèn)超慘設(shè)置
- mu_pos=0.8,這個(gè)參數(shù)表示對(duì)于正例對(duì)(a,b),你要盡量讓a,b之間的相似度等于mu_pos鳍刷,這個(gè)值在0.0-1.0之間占遥。
- mu_neg=-0.4,表示負(fù)例對(duì)最大相似度,值在-1.0-1.0之間输瓜。
另外loss還包括意圖embedding之間相似度,意思是要讓相同意圖的編碼盡量相似瓦胎。
def _tf_loss(self, sim: "Tensor", sim_emb: "Tensor") -> "Tensor":
"""Define loss"""
# loss for maximizing similarity with correct action
loss = tf.maximum(0.0, self.mu_pos - sim[:, 0])
if self.use_max_sim_neg:
# minimize only maximum similarity over incorrect actions
max_sim_neg = tf.reduce_max(sim[:, 1:], -1)
loss += tf.maximum(0.0, self.mu_neg + max_sim_neg)
else:
# minimize all similarities with incorrect actions
max_margin = tf.maximum(0.0, self.mu_neg + sim[:, 1:])
loss += tf.reduce_sum(max_margin, -1)
# penalize max similarity between intent embeddings
max_sim_emb = tf.maximum(0.0, tf.reduce_max(sim_emb, -1))
loss += max_sim_emb * self.C_emb
# average the loss over the batch and add regularization losses
loss = tf.reduce_mean(loss) + tf.losses.get_regularization_loss()
return loss
2.2.4 selector
先看一個(gè)例子:
{
"text": "What is the recommend python version to install?",
"entities": [],
"intent": {"confidence": 0.6485910906220309, "name": "faq"},
"intent_ranking": [
{"confidence": 0.6485910906220309, "name": "faq"},
{"confidence": 0.1416153159565678, "name": "greet"}
],
"response_selector": {
"faq": {
"response": {"confidence": 0.7356462617, "name": "Supports 3.5, 3.6 and 3.7, recommended version is 3.6"},
"ranking": [
{"confidence": 0.7356462617, "name": "Supports 3.5, 3.6 and 3.7, recommended version is 3.6"},
{"confidence": 0.2134543431, "name": "You can ask me about how to get started"}
]
}
}
}
使用與意圖識(shí)別相同的模型 EmbeddingIntentClassifier,將用戶輸入與回答的內(nèi)容嵌入到相同的空間進(jìn)行比較尤揣。只是訓(xùn)練意圖識(shí)別模型的時(shí)候label是意圖分布搔啊,而訓(xùn)練response selector 模型的時(shí)候,label就是所有答案分布北戏。
可以直接用于構(gòu)建一個(gè)答案檢索模型负芋,從一組答案中直接預(yù)測(cè)答案。
另外嗜愈,此組件可以通過配置retrieval_intent
旧蛾,從而只在指定意圖上訓(xùn)練response selector 模型莽龟。
2.2.5 Entity Extraction
3. 代碼解析
訓(xùn)練入口:
training_data = load_data(ROOT_DIR + training_data_file) # 加載數(shù)據(jù),封裝成TrainingData
trainer = Trainer(config.load(ROOT_DIR + config_file)) # 初始化Trainer锨天,傳入config(RasaNLUModelConfig)和訓(xùn)練數(shù)據(jù)(TrainingData),構(gòu)建pipeline
trainer.train(training_data)
model_path = os.path.join(model_directory, model_name)
model_directory = trainer.persist(model_path, fixed_model_name="nlu")
開始訓(xùn)練Trainer.train():
def train(self, data: TrainingData, **kwargs: Any) -> "Interpreter":
...
for i, component in enumerate(self.pipeline):
logger.info("Starting to train component {}".format(component.name))
component.prepare_partial_processing(self.pipeline[:i], context)
updates = component.train(working_data, self.config, **context)
logger.info("Finished training component.")
if updates:
context.update(updates)
return Interpreter(self.pipeline, context)
pipeline:
- name: "SpacyNLP"
- name: "SpacyTokenizer"
- name: "SpacyFeaturizer"
- name: "EmbeddingIntentClassifier"
- name: "CRFEntityExtractor"
- name: "EntitySynonymMapper"
self.pipeline中順序放置config中的所有Component,比如上面的pipeline毯盈,每個(gè)component都是繼承Component類。然后順序地每個(gè)Component執(zhí)行各自的train函數(shù)病袄,每個(gè)Component執(zhí)行后的改變:1.數(shù)據(jù)改變更新到working_data中奶镶。2.conponent自己改變(訓(xùn)練參數(shù))。
比如SpacyNLP會(huì)將語料變成vector等陪拘,SpacyTokenizer將語料分詞厂镇,SpacyFeaturizer把語料數(shù)字化(這里就是自己而去SpacyNLP中產(chǎn)生的vector),EmbeddingIntentClassifier訓(xùn)練意圖分類器模型左刽,CRFEntityExtractor訓(xùn)練實(shí)體識(shí)別模型捺信,EntitySynonymMapper進(jìn)行加載訓(xùn)練語料中的同義詞對(duì)。