Rasa學(xué)習(xí)筆記1--Rasa NLU

1. RASA整體結(jié)構(gòu)


上圖是RASA執(zhí)行的結(jié)構(gòu)圖,

  1. 一句話輸入,經(jīng)過Interpreter處理,使用NLU,將這句話進行解析,結(jié)果就是一組字典,包括:原始句子,意圖,識別的實體等。
  2. 將上一步的結(jié)果傳入Tracker來追蹤對話狀態(tài)。
  3. Policy接收上一步的狀態(tài),然后決定下一步要采取的Action
  4. 這個Action再依據(jù)Tracker來生成最終的回復(fù)。

2. Rasa NLU


2.1 Pileine

在Rasa NLU中,一句話需要經(jīng)過一系列的組件(Component)來處理,這個處理過程是組件依次執(zhí)行,并且下一個組件會使用上一個組件的結(jié)果,這個過程就被稱之為Pipeline.
通常一個pipeline會包含的組件有:預(yù)處理,實體抽取,意圖分類等。
下面是由一系列組件得到的結(jié)果,比如,實體(entities)是有實體抽取組件得到的。

{
"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類,放進pipeline中,pipeline其實就是一個list啦。

2.3 Component

每一個組件實例都可以執(zhí)行幾個特定的方法,而在一個pipeline中,這些方法會以固定的順序依次執(zhí)行。
假設(shè)我們的pipeline中定義了三個組件:"pipeline": ["Component A", "Component B", "Last Component"],下圖中顯示了在訓(xùn)練過程各組建方法的調(diào)用順序以及組建的生存周期:


Component類包含的方法有: create(),train(),persis(),process(),load()等。
Component是所有獨立組件的父類,每個獨立組建需要具體實現(xiàn)父類的每個方法。
當(dāng)用create()方法創(chuàng)造一個Component實例,一個context就別生成了(其實就是python里面的一個dict),然后我們就使用這個context在組建之間傳遞信息。
所以,當(dāng)所有的組件訓(xùn)練完成并且持久化了,最終的context字典就用來持久化最終這個模型的元數(shù)據(jù)(metadata)。

介紹幾個Component:

2.2.1 詞向量

2.2.2 特征提取器

列出一些可供選擇的Featurizers: MitieFeaturizer,SpacyFeaturizer,NGramFeaturizer,RegexFeaturizer,CountVectorsFeaturizer,具體介紹可以到官網(wǎng)教程上看。

SpacyFeaturizer就是將一句話變成一個向量

2.2.3 意圖分類器

  • KeywordIntentClassifier,只使用關(guān)鍵字來進行意圖識別。
  • MitieIntentClassifier, 是MITIE中提供的分類器,使用SVM,輸入需要分詞Tokenizers和featurizer。
  • SklearnIntentClassifier, 是sklearn中提供的一個svm分類器,另外會使用grid search進行參數(shù)優(yōu)化搜索。輸入需要featurizer。
  • EmbeddingIntentClassifier,
    這個分類器的實現(xiàn)是基于StarSpace,就是將輸入和對應(yīng)的label映射到相同空間,然后最大化他們之間的相似度。具體的實現(xiàn)中另外增加了一層隱層并使用dropout。輸入需要featurizer。
StarSpace模型
  • 模型解釋:StarSpace模在于學(xué)習(xí)entities,而每一個entity是由一組離散的特征(features)組成,這些特征構(gòu)成一個特征字典。比如,當(dāng)把一篇document或者一個sentence看作一個entity,組成他的特征就是詞代或者n-grams。或者,當(dāng)把一個人看作entity,他就能通過一組文章、電影、喜歡的東西等來描述。
    StarSpace將不同類別的entity通過embedding映射到相同空間,這樣,就可以比較任何不同類別的entity。
    令特征字典D有特征F,他是一個D*d維的矩陣,其中F_i表示第i個特征(一行),就是一個d維的向量。然后,我們embed一個實體a的表示為:\sum_{i\in{a}}F_i

  • 模型初始化:首先給特征字段中的每個特征分配一個d維的向量。然后一個實體有一組特征組成,也就是由一組上述d維向量。
    訓(xùn)練模型:需要學(xué)習(xí)如何取比較實體。然后最小化下面的loss function:


解釋上述公式:

  1. positive entity pairs(a, b)是從正例集合E^+中生成,而這個數(shù)據(jù)集不同任務(wù)不同。
  2. negetive entiies b是從負例集合E^-中生成。k-negative sampling的策略可以是每次從batch中任意選取k個label。
  3. 相似度函數(shù)sim(.,.)可以使用cosine similarity或者inner product,對于小數(shù)量label(比如分類任務(wù)),兩者效果相似。但是,一般來講,cosine similarity更適用于大數(shù)據(jù)label(比如句子和文檔相似度)。
  4. L is the loss function that compares the positive pair (a, b) with the negative pairs.
  • 模型的使用:
    我們可以直接使用學(xué)習(xí)到的函數(shù) sim(.,.) 來計算entity之間的相似度。比如,對于分類任務(wù),對于輸入a,直接計算 max_{\hat}sim(a, \hat),\hat表示所有可能的label。對于ranking任務(wù),可以直接使用相似度進行排序。

  • 各任務(wù)的使用(構(gòu)造E_+E_-

  1. text classification
  2. multilabel classification
  3. information retrieval and document embeddings
    如果有現(xiàn)成監(jiān)督學(xué)習(xí)數(shù)據(jù)集,a是搜索關(guān)鍵詞,b是相關(guān)的文檔,而b^-是不相關(guān)的文檔。如果只有非監(jiān)督數(shù)據(jù)集,a表示文檔中任選的關(guān)鍵詞,而b表示文檔中剩下的詞語。
  4. Learning Word Embeddings
    一個window的詞語作為a,中間的一個詞語看作b。
  5. Learning Sentence Embeddings
    相同文檔中選取sentence pair看作a, b。而b^-來自其他文檔中。
  • Rasa中的實現(xiàn)

_create_tf_embed_nn()用來創(chuàng)建encoder部分,a和b的encoder是相同的網(wǎng)絡(luò)。其實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。主要包括三個部分:1. positive similarity, 2. negtive similarity, 3. similarity between intent.
網(wǎng)絡(luò)的默認超慘設(shè)置

  • mu_pos=0.8,這個參數(shù)表示對于正例對(a,b),你要盡量讓a,b之間的相似度等于mu_pos,這個值在0.0-1.0之間。
  • mu_neg=-0.4,表示負例對最大相似度,值在-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

先看一個例子:

{
    "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"}
        ]
      }
    }
}

使用與意圖識別相同的模型 EmbeddingIntentClassifier,將用戶輸入與回答的內(nèi)容嵌入到相同的空間進行比較。只是訓(xùn)練意圖識別模型的時候label是意圖分布,而訓(xùn)練response selector 模型的時候,label就是所有答案分布。
可以直接用于構(gòu)建一個答案檢索模型,從一組答案中直接預(yù)測答案。
另外,此組件可以通過配置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,每個component都是繼承Component類。然后順序地每個Component執(zhí)行各自的train函數(shù),每個Component執(zhí)行后的改變:1.數(shù)據(jù)改變更新到working_data中。2.conponent自己改變(訓(xùn)練參數(shù))。
比如SpacyNLP會將語料變成vector等,SpacyTokenizer將語料分詞,SpacyFeaturizer把語料數(shù)字化(這里就是自己而去SpacyNLP中產(chǎn)生的vector),EmbeddingIntentClassifier訓(xùn)練意圖分類器模型,CRFEntityExtractor訓(xùn)練實體識別模型,EntitySynonymMapper進行加載訓(xùn)練語料中的同義詞對。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容