在Rasa项目中,NLU管道定义了将非结构化用户消息转换为意图和实体的处理步骤。 它由一系列组件组成,开发人员可以对其进行配置和定制。
本文的目的是解释各个组件在Rasa NLU管道中扮演的角色,并解释它们之间是如何相互作用的。
一、NLU Pipeline
NLU管道在Rasa的config.yml
文件中定义。 该文件配置了Rasa用于检测意图和实体的所有步骤。 它以文本作为输入开始,一路解析下去,直到将实体和意图作为输出为止。
在这个管道中可以找到不同类型的组件,主要有:
- Tokenizers 分词器
- Featurizers 特征提取器
- Intent Classifiers 意图分类器
- Entity Extractors 实体提取器
在讨论它们如何相互影响之前,需要先了解每种类型组件各自的功能。
二、Components
1. Tokenizers 分词器
第一步是将用户输入拆分为较小的文本块,就是tokens。 这一步操作必须在进行特征提取前完成,这也是为什么通常在管道一开始时就给出分词器的原因,常规的机器学习问题的第一步也是这个。
Details on Tokenizers:
解释一下Lemmatisation:
这个一般在英文语料中会用到,中文一般不用,直译过来就是词形化, 就是这样worse→ bad,thinking→ think,
Rasa中部分分词器会有这一处理过程,例如spaCy,生成的tokens由后续的CountVectorizer使用。
分词器把用户输入中的每个单词拆分为一个单独的token,分词器的输出通常是tokens列表。 对于英语,通常使用WhiteSpaceTokenizer,但对于非英语,通常会选择其他语言。 SpaCy是非英语欧洲语言的不错选择,但Rasa还支持中文的jieba。需要注意的是,分词器不会更改文本大小写,它们只会将文本分成tokens。
2. Featurizers 特征提取器
特征提取器为机器学习模型生成数字特征。 下图显示了如何对单词“ Hi”进行编码。
NLP 中一般有两中类型的文本特征:
- 稀疏特征:通常由 CountVectorizer 生成。需要注意的是,这些计数也可能代表子字(subwords)。 Rasa还有一个LexicalSyntacticFeaturizer,它生成对实体识别有用的基于窗口的特征。 当与spaCy结合使用时,可以将LecticalSyntacticFeaturizer配置为包括部分语音特征。
-
密集特征:密集特征包含许多预训练的 embeddings。通常来自SpaCyFeaturizers或者huggingface的 LanguageModelFeaturizers。在使用它们时,需要在管道中配置特定的分词器。 更多详细信息在文档中。
除了 tokens 的特征外,还为整个句子生成特征, 有时也称为 CLS token,这个其实就是把 Bert-base 的模型最后一层中的 CLS
token 取出来作为句子向量使用了;
Details on sentence features:
__CLS__token中的稀疏特征是token中所有稀疏特征的总和。 密集特征是单词向量的总和/均值(在spaCy的情况下)或整个文本的上下文表示(在huggingface models的情况下)。
在Rasa中,支持使用自定义特征提取工具作为自己的组件。 举例来说,有一个社区维护的项目叫rasa-nlu-examples,其中包含许多针对非英语语言的实验性修饰语。 Rasa并没有正式支持它,但是由于代表了275种以上的语言,因此可以为许多用户提供帮助。
3. Intent Classifiers 意图分类器
一旦为所有tokens和整个句子生成了特征,就可以将其传递给意图分类模型。 Rasa官方建议使用DIET模型,该模型可以处理意图分类以及实体提取。 同时也能够从tokens和句子特征中学习。
Details on DIE
DIET算法的特殊性在于它可以同时进行意图分类和实体抽取。 在这之前,Rasa的大多数算法要么只支持实体检测,要么只支持意图分类,意味着意图分类模型只能够利用句子特征而忽略了tokens特征。
4. Entity Extractors 实体提取器
即使DIET能够学习如何检测实体,但是并不意味着每种类型的实体都要使用它。 例如,遵循结构化模式(例如电话号码)的实体实际上并不需要算法来检测它们。 可以只使用 RegexEntityExtractor处理它。
这就是为什么管道中通常有不止一种类型的实体提取器的原因。
在解释完NLU管道中不同类型的组件之后,就可以解释这些组件如何共享彼此的信息了。
三、交互:传递消息
Rasa NLU管道中的组件是相互依赖的。 要了解其工作原理,可以放大下面示例的config.yml
文件。
NLU管道由一系列的组件串联组成,这些组件按照在管道中列出的顺序进行训练和处理, 这意味着可以将管道配置视为数据需要通过的线性步骤序列。
每当用户与聊天助手交谈时,Rasa 都会在内部通过 Message State
对象跟踪对话话状态, 该对象会被管道中的每个步骤模块进行处理加工,下图解释了处理用户输入时发生的情况。
在这个流程图中可以看出:
- 首先
Message State
最开始仅包含正常的用户输入,其实就是用户输入做的初始化; - 然后
Message State
通过分词器,用户输入被拆分为 tokens,被添加进Message State
中输出给下一个模块。注意,我们在图中将 tokens 表示为字符串,但在代码中它们是由Token
对象表示的。 - 当
Message State
通过 CountVectorsFeaturizer 时,会发现新添加了稀疏特征。序列的特征和整个句子之间是有区别的。另外请注意,在通过第二个特征提取器之后,稀疏特征的大小会增加。 - DIETClassifier 将在
Message State
中查找sparse_features
和dense_features
,以便进行预测。处理完成后,它将意图预测结果添加到Message State
中。
Message State
每经过管道中的一个模块,Message State
就会增加一些新的信息。所以说如果我们想向Message State
中添加信息,那么可以通过向管道中添加额外的模块来达到这个目的,这也是为什么我们可以添加额外的实体提取模型的原因。
管道中的每个步骤都可以将信息添加到
Message State
中。因此我们可以添加多个实体提取模块,而且可以并行进行以将实体添加到Message State
中。
自己检查消息对象
如果想查看消息状态,可以通过下面的代码检查模型的输出。
from rasa.cli.utils import get_validated_path
from rasa.model import get_model, get_model_subdirectories
from rasa.core.interpreter import RasaNLUInterpreter
from rasa.shared.nlu.training_data.message import Message
def load_interpreter(model_dir, model):
path_str = str(pathlib.Path(model_dir) / model)
model = get_validated_path(path_str, "model")
model_path = get_model(model)
_, nlu_model = get_model_subdirectories(model_path)
return RasaNLUInterpreter(nlu_model)
# Loads the model
mod = load_interpreter(model_dir, model)
# Parses new text
msg = Message({TEXT: text})
for p in interpreter.interpreter.pipeline:
p.process(msg)
print(msg.as_dict())
四、预测 Actions
使用 NLU 管道,我们可以检测意图和实体,但是,NLU 无法预测对话中的下一个 Action, 这就是 Policy pipeline 要做的事情。 Policy 利用 NLU 意图识别和实体识别的结果以及到目前为止的会话状态来预测下一步将要采取的 Action。
要了解这些 Policy 如何产生作用,请查看这篇文章:
Dialogue Management in Rasa 2.0
五、总结
在本文中,我们
- 回顾了 Rasa NLU 管道中的组件之间如何相互作用
- 了解组件在管道中的交互方式是非常有用的,因为它可以帮助我们确定哪些组件与我们构建的的对话助手相关
同样重要的是要知晓我们可以完全自定义管道, 如果不需要组件,则可以删除它们。 如果使用的是非英语语言,并且想使用自己熟悉的自定义语言工具,则这一点尤其重要。 如果有兴趣查看此类示例,建议查看 rasa-nlu-examples仓库。 该仓库包含许多 tokenizers,featurizers 和模型,可以从中获取灵感以用于自己的项目。
总的来说,Rasa NLU 管道就是一个完整的意图分类和实体提取的机器学习项目,只不过 Rasa 对每一部分进行了模块化处理,方便开发者使用和自定义自己的特定组件。