Rasa学习笔记1--Rasa NLU

1. RASA整体结构


上图是RASA执行的结构图,

  1. 一句话输入,经过Interpreter处理,使用NLU,将这句话进行解析,结果就是一组字典,包括:原始句子,意图,识别的实体等。
  2. 将上一步的结果传入Tracker来追踪对话状态。
  3. Policy接收上一步的状态,然后决定下一步要采取的Action
  4. 这个Action再依据Tracker来生成最终的回复。

2. Rasa NLU


2.1 Pileine

在Rasa NLU中,一句话需要经过一系列的组件(Component)来处理,这个处理过程是组件依次执行,并且下一个组件会使用上一个组件的结果,这个过程就被称之为Pipeline.
通常一个pipeline会包含的组件有:预处理,实体抽取,意图分类等。
下面是由一系列组件得到的结果,比如,实体(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模型中创造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类,然后创造config中定义的所有Component类,放进pipeline中,pipeline其实就是一个list啦。

2.3 Component

每一个组件实例都可以执行几个特定的方法,而在一个pipeline中,这些方法会以固定的顺序依次执行。
假设我们的pipeline中定义了三个组件:"pipeline": ["Component A", "Component B", "Last Component"],下图中显示了在训练过程各组建方法的调用顺序以及组建的生存周期:


Component类包含的方法有: create(),train(),persis(),process(),load()等。
Component是所有独立组件的父类,每个独立组建需要具体实现父类的每个方法。
当用create()方法创造一个Component实例,一个context就别生成了(其实就是python里面的一个dict),然后我们就使用这个context在组建之间传递信息。
所以,当所有的组件训练完成并且持久化了,最终的context字典就用来持久化最终这个模型的元数据(metadata)。

介绍几个Component:

2.2.1 词向量

2.2.2 特征提取器

列出一些可供选择的Featurizers: MitieFeaturizer,SpacyFeaturizer,NGramFeaturizer,RegexFeaturizer,CountVectorsFeaturizer,具体介绍可以到官网教程上看。

SpacyFeaturizer就是将一句话变成一个向量

2.2.3 意图分类器

  • KeywordIntentClassifier,只使用关键字来进行意图识别。
  • MitieIntentClassifier, 是MITIE中提供的分类器,使用SVM,输入需要分词Tokenizers和featurizer。
  • SklearnIntentClassifier, 是sklearn中提供的一个svm分类器,另外会使用grid search进行参数优化搜索。输入需要featurizer。
  • EmbeddingIntentClassifier,
    这个分类器的实现是基于StarSpace,就是将输入和对应的label映射到相同空间,然后最大化他们之间的相似度。具体的实现中另外增加了一层隐层并使用dropout。输入需要featurizer。
StarSpace模型
  • 模型解释:StarSpace模在于学习entities,而每一个entity是由一组离散的特征(features)组成,这些特征构成一个特征字典。比如,当把一篇document或者一个sentence看作一个entity,组成他的特征就是词代或者n-grams。或者,当把一个人看作entity,他就能通过一组文章、电影、喜欢的东西等来描述。
    StarSpace将不同类别的entity通过embedding映射到相同空间,这样,就可以比较任何不同类别的entity。
    令特征字典D有特征F,他是一个D*d维的矩阵,其中F_i表示第i个特征(一行),就是一个d维的向量。然后,我们embed一个实体a的表示为:\sum_{i\in{a}}F_i

  • 模型初始化:首先给特征字段中的每个特征分配一个d维的向量。然后一个实体有一组特征组成,也就是由一组上述d维向量。
    训练模型:需要学习如何取比较实体。然后最小化下面的loss function:


解释上述公式:

  1. positive entity pairs(a, b)是从正例集合E^+中生成,而这个数据集不同任务不同。
  2. negetive entiies b是从负例集合E^-中生成。k-negative sampling的策略可以是每次从batch中任意选取k个label。
  3. 相似度函数sim(.,.)可以使用cosine similarity或者inner product,对于小数量label(比如分类任务),两者效果相似。但是,一般来讲,cosine similarity更适用于大数据label(比如句子和文档相似度)。
  4. L is the loss function that compares the positive pair (a, b) with the negative pairs.
  • 模型的使用:
    我们可以直接使用学习到的函数 sim(.,.) 来计算entity之间的相似度。比如,对于分类任务,对于输入a,直接计算 max_{\hat{b}}sim(a, \hat{b}),\hat{b}表示所有可能的label。对于ranking任务,可以直接使用相似度进行排序。

  • 各任务的使用(构造E_+E_-

  1. text classification
  2. multilabel classification
  3. information retrieval and document embeddings
    如果有现成监督学习数据集,a是搜索关键词,b是相关的文档,而b^-是不相关的文档。如果只有非监督数据集,a表示文档中任选的关键词,而b表示文档中剩下的词语。
  4. Learning Word Embeddings
    一个window的词语作为a,中间的一个词语看作b。
  5. Learning Sentence Embeddings
    相同文档中选取sentence pair看作a, b。而b^-来自其他文档中。
  • Rasa中的实现

_create_tf_embed_nn()用来创建encoder部分,a和b的encoder是相同的网络。其实Rasa中就是使用多层的神经网络。

    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用来定义网络的loss。主要包括三个部分:1. positive similarity, 2. negtive similarity, 3. similarity between intent.
网络的默认超惨设置

  • mu_pos=0.8,这个参数表示对于正例对(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,将用户输入与回答的内容嵌入到相同的空间进行比较。只是训练意图识别模型的时候label是意图分布,而训练response selector 模型的时候,label就是所有答案分布。
可以直接用于构建一个答案检索模型,从一组答案中直接预测答案。
另外,此组件可以通过配置retrieval_intent,从而只在指定意图上训练response selector 模型。

2.2.5 Entity Extraction

3. 代码解析

训练入口:

training_data = load_data(ROOT_DIR + training_data_file) # 加载数据,封装成TrainingData
trainer = Trainer(config.load(ROOT_DIR + config_file)) # 初始化Trainer,传入config(RasaNLUModelConfig)和训练数据(TrainingData),构建pipeline
trainer.train(training_data)
model_path = os.path.join(model_directory, model_name)
model_directory = trainer.persist(model_path, fixed_model_name="nlu")

开始训练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执行各自的train函数,每个Component执行后的改变:1.数据改变更新到working_data中。2.conponent自己改变(训练参数)。
比如SpacyNLP会将语料变成vector等,SpacyTokenizer将语料分词,SpacyFeaturizer把语料数字化(这里就是自己而去SpacyNLP中产生的vector),EmbeddingIntentClassifier训练意图分类器模型,CRFEntityExtractor训练实体识别模型,EntitySynonymMapper进行加载训练语料中的同义词对。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容