Enhancing Rasa NLU models with Custom Components
自定義組件增強(qiáng)nlu模型
我們認(rèn)為自定義的機(jī)器學(xué)習(xí)模型對(duì)于構(gòu)建成功的AI助手至關(guān)重要。開(kāi)源的rasa給你為意圖分類和實(shí)體提取構(gòu)建好的nlu模型提供給你一個(gè)堅(jiān)實(shí)基礎(chǔ)享潜,但是如果你想用你自己自定義組件來(lái)增強(qiáng)現(xiàn)有的rasa nlu模型娇掏,我們做了很多的工作使得rasa nlu模塊化,這樣你就可以做到這一點(diǎn)湃鹊。想要學(xué)習(xí)如何去實(shí)現(xiàn)它村缸?在這篇文章汉形,你將學(xué)習(xí)到如何如何自定義組件并且將其添加到nlu管道讓你的AI助手到達(dá)一個(gè)新的水平权均。
概要
1.介紹rasa nlu管道
2.介紹自定義組件
3.添加自定義的情感分析組件到rasa nlu
建立自定義情感分析組件類
如果我想使用一個(gè)預(yù)先訓(xùn)練好的情緒分析模型呢?
4.總結(jié)
5.資源
介紹rasa nlu管道
一個(gè)處理管道是rasa nlu模型的主要的基本構(gòu)成要素胁勺。它定義傳入的用戶消息在產(chǎn)生模型輸出之前必須經(jīng)過(guò)處理階段。這些階段可以是符號(hào)化薄腻,特征化岛马,意圖分類棉姐,實(shí)體提取,模式匹配等等啦逆。默認(rèn)的伞矩,rasa nlu已經(jīng)給你自帶了一些預(yù)先構(gòu)建的組件。下面是一個(gè)rasa nlu的管道配置夏志。
pipeline:
name: "SpacyNLP"
name: "SpacyTokenizer"
name: "SpacyFeaturizer"
name: "RegexFeaturizer"
name: "CRFEntityExtractor"
name: "EntitySynonymMapper"
name: "SklearnIntentClassifier"
一旦定義了管道乃坤,每個(gè)組件都會(huì)被一個(gè)接一個(gè)的調(diào)用,并且產(chǎn)生的輸出要么直接添加到rasa nlu模型的輸出沟蔑,或者是被用做其他管道的輸入湿诊。這是非常重要的,如何在配置文件中定義組件瘦材。例如厅须,如果你在你的管道中定義了三個(gè)組件[‘Component1’, ‘Component2’, ‘Component3’],component1的方法將在第一個(gè)被調(diào)用食棕,下面這個(gè)圖片展示了組件的生命周期:
組件通過(guò)三個(gè)主要的階段:
創(chuàng)建:訓(xùn)練之前初始化組件
訓(xùn)練:組件使用上下文和前面組件的輸出對(duì)自己進(jìn)行訓(xùn)練
持久化:保存訓(xùn)練組件到磁盤朗和,以備將來(lái)使用
在初始化第一個(gè)組件之前错沽,創(chuàng)建一個(gè)所謂的上下文,用于組件之間傳遞信息眶拉。例如千埃,一個(gè)組件可以計(jì)算訓(xùn)練數(shù)據(jù)的特征向量,將其儲(chǔ)存在上下文中忆植,并且可以從上下文中檢索那些特征向量并進(jìn)行意圖分類放可。一旦所有組件創(chuàng)建,訓(xùn)練和持久化完成朝刊,就會(huì)創(chuàng)建描述整個(gè)nlu模型的模型元數(shù)據(jù)耀里。
介紹自定義組件
使用預(yù)構(gòu)建的rasa nlu組件,你可以自由的定制模型坞古。但是在有些情況下备韧,你想要添加的組件并沒(méi)有在rasa nlu中預(yù)先實(shí)現(xiàn)劫樟。例如痪枫,你可能想添加情感分析讓你的助手根據(jù)用戶的心情產(chǎn)生不同的響應(yīng),或者是希望添加一個(gè)拼寫檢查器來(lái)糾正用戶信息拼寫錯(cuò)誤叠艳,然后對(duì)意圖進(jìn)行分類并提取實(shí)體并用于API調(diào)用或數(shù)據(jù)庫(kù)查找奶陈。向nlu管道添加自定義組件是使用必需的方法實(shí)現(xiàn)自定義組件類,并在Rasa NLU管道配置文件中引用它的過(guò)程附较。通常吃粒,您可能要使用兩種類型的自定義組件:
已經(jīng)預(yù)訓(xùn)練模型的組件(例如:在不同的數(shù)據(jù)集或包上訓(xùn)練,如 python libraries, .pkl files, etc)
在你的rasa nlu數(shù)據(jù)上訓(xùn)練的組件拒课,并在你更改訓(xùn)練示例或添加更多訓(xùn)練示例時(shí)得到改進(jìn)
在這篇文章的下一步徐勃,你將學(xué)習(xí)如何在實(shí)踐中實(shí)現(xiàn)這兩種情況。
添加自定義的情感分析組件到rasa nlu
我們拿一個(gè)情感分析的實(shí)際例子添加到rasa nlu管道中早像。首先僻肖, 你應(yīng)該學(xué)習(xí)如何去設(shè)計(jì)它,當(dāng)你添加更多的訓(xùn)練數(shù)據(jù)時(shí)提高你組件的性能卢鹦。因?yàn)榍楦蟹治鍪怯斜O(jiān)督的分類問(wèn)題臀脏,這意味著對(duì)于這個(gè)特例,你將必須在nlu訓(xùn)練數(shù)據(jù)中分配更多的標(biāo)簽—情感的性質(zhì)(積極冀自,消極揉稚,中性)。其中一個(gè)方法是將這些新的標(biāo)簽儲(chǔ)存在一個(gè)單獨(dú)的文件中熬粗。例如搀玖,你的rasa nlu訓(xùn)練數(shù)據(jù)如下:
intent: feedback
It’s very helpful
I had the best experience speaking with you
no feedback
ok
You are the most stupid bot I have ever seen
the worst
對(duì)應(yīng)的標(biāo)簽如下:
pos
pos
neu
neu
neg
neg
接下來(lái),這都是關(guān)于構(gòu)建實(shí)際組件的驻呐。我們開(kāi)始如何來(lái)實(shí)現(xiàn)灌诅!
建立自定義情感分析組件類
自定義組件類將定義如何訓(xùn)練組件葛超,哪些詳細(xì)信息作為輸入,以及成哪些詳細(xì)信息作為輸出延塑。定義組件绣张,創(chuàng)建一個(gè)新的文件(如:sentiment.py),并且開(kāi)始設(shè)置你自定義組件類的名字以及描述該組件的細(xì)節(jié)如下:
name: 組件命名
provides: 自定義組件產(chǎn)生哪些輸出
require: 該組件需要消息的哪些屬性
defaults: 一個(gè)組件的默認(rèn)配置參數(shù)
language_list:與組件兼容的語(yǔ)言列表
在下面的例子中,自定義組件類命名為 SentimentAnalyzer 关带,組件的真實(shí)名字為 sentiment侥涵。為了對(duì)話管理模型訪問(wèn)該組件的細(xì)節(jié),并使它能基于用戶情緒驅(qū)動(dòng)對(duì)話宋雏,情感分析的結(jié)果將作為實(shí)體進(jìn)行保留芜飘。處于這個(gè)原因,情感分析的組件配置包括組件提供的entities磨总。因?yàn)榍楦心P徒邮茏址鳛檩斎豚旅鳎赃@些細(xì)節(jié)可以從其他負(fù)責(zé)符號(hào)化的管道組件獲取。這就是為什么下面的組件配置聲明自定義組件需要的tokens蚪燕。最后娶牌,因?yàn)檫@個(gè)例子包含情感分析模型僅僅在英文運(yùn)行,語(yǔ)言列表中包括en馆纳。
定義好之后诗良,你可以繼續(xù)實(shí)現(xiàn)這個(gè)類的主要方法:
_init_: 組件初始化
train: 該方法負(fù)責(zé)訓(xùn)練組件
process: 該方法分析傳入的用戶信息
persist: 該方法保存訓(xùn)練后的組件到磁盤上供以后使用
下面的代碼顯示了對(duì)這個(gè)特定例子的方法的實(shí)現(xiàn)。讓我們逐步介紹鲁驶。
_init_:方法使用組件的配置初始化自定義組件類鉴裹,在管道配置文件中引用自定義組件時(shí)定義。
train():前面的組件產(chǎn)生分詞作為輸入钥弯,訓(xùn)練數(shù)據(jù)径荔,由加載情緒標(biāo)簽和格式化數(shù)據(jù),形成一個(gè)情感分類脆霎。
process():該函數(shù)用于將新用戶消息的分詞和情感分析模型預(yù)測(cè)作為實(shí)體消息類总处。
persist():方法訓(xùn)練情感模型保存為 .pkl文件,供以后使用绪穆。
load() :方法定義如何加載持久化的情感模型辨泳。
from rasa.nlu.components import Component
from rasa.nlu import utils
from rasa.nlu.model import Metadata
import nltk
from nltk.classify import NaiveBayesClassifier
import os
import typing
from typing import Any, Optional, Text, Dict
SENTIMENT_MODEL_FILE_NAME = "sentiment_classifier.pkl"
class SentimentAnalyzer(Component):
"""A custom sentiment analysis component"""
name = "sentiment"
provides = ["entities"]
requires = ["tokens"]
defaults = {}
language_list = ["en"]
print('initialised the class')
def __init__(self, component_config=None):
super(SentimentAnalyzer, self).__init__(component_config)
def train(self, training_data, cfg, **kwargs):
"""Load the sentiment polarity labels from the text
file, retrieve training tokens and after formatting
data train the classifier."""
with open('labels.txt', 'r') as f:
labels = f.read().splitlines()
training_data = training_data.training_examples # list of Message objects
tokens = [list(map(lambda x: x.text, t.get('tokens'))) for t in training_data]
processed_tokens = [self.preprocessing(t) for t in tokens]
labeled_data = [(t, x) for t, x in zip(processed_tokens, labels)]
self.clf = NaiveBayesClassifier.train(labeled_data)
def convert_to_rasa(self, value, confidence):
"""Convert model output into the Rasa NLU compatible output format."""
entity = {"value": value,
"confidence": confidence,
"entity": "sentiment",
"extractor": "sentiment_extractor"}
return entity
def preprocessing(self, tokens):
"""Create bag-of-words representation of the training examples."""
return ({word: True for word in tokens})
def process(self, message, **kwargs):
"""Retrieve the tokens of the new message, pass it to the classifier
and append prediction results to the message class."""
if not self.clf:
# component is either not trained or didn't
# receive enough training data
entity = None
else:
tokens = [t.text for t in message.get("tokens")]
tb = self.preprocessing(tokens)
pred = self.clf.prob_classify(tb)
sentiment = pred.max()
confidence = pred.prob(sentiment)
entity = self.convert_to_rasa(sentiment, confidence)
message.set("entities", [entity], add_to_output=True)
def persist(self, file_name, model_dir):
"""Persist this model into the passed directory."""
classifier_file = os.path.join(model_dir, SENTIMENT_MODEL_FILE_NAME)
utils.json_pickle(classifier_file, self)
return {"classifier_file": SENTIMENT_MODEL_FILE_NAME}
@classmethod
def load(cls,
meta: Dict[Text, Any],
model_dir=None,
model_metadata=None,
cached_component=None,
**kwargs):
file_name = meta.get("classifier_file")
classifier_file = os.path.join(model_dir, file_name)
return utils.json_unpickle(classifier_file)
就是這樣! 您剛剛實(shí)現(xiàn)了一個(gè)自定義組件,它解析傳入的用戶消息玖院,并將情感作為一個(gè)名為“sentiment”的實(shí)體返回菠红。要使用這個(gè)組件,請(qǐng)確保在Rasa NLU管道配置文件中引用它难菌。你引用自定義組件试溯,可以像引用python模塊一樣引用它 —module_name.class_name。由于此自定義組件需要tokens郊酒,所以應(yīng)該將其添加到生成tokens的組件之后遇绞。下面的示例管道配置意味著键袱,將在SpacyTokenizer之后調(diào)用sentiment組件的方法。
pipeline:
- name: "SpacyNLP"
- name: "SpacyTokenizer"
- name: "sentiment.SentimentAnalyzer"
- name: "SpacyFeaturizer"
- name: "RegexFeaturizer"
- name: "CRFEntityExtractor"
- name: "EntitySynonymMapper"
- name: "SklearnIntentClassifier"
在使用定制的情感分析組件訓(xùn)練Rasa NLU模型之后摹闽,您可以測(cè)試如何去執(zhí)行它!
注意: 要確保Rasa獲取您的組件蹄咖,確保將項(xiàng)目目錄添加到PYTHONPATH中。要做到這一點(diǎn)付鹿,你可以運(yùn)行:
export PYTHONPATH=/path_to_your_project_dir/:$PYTHONPATH
下面是一個(gè)帶有自定義情感分析組件的Rasa NLU模型的示例澜汤,當(dāng)助手被一個(gè)相當(dāng)不禮貌的用戶打招呼時(shí),輸出是什么樣的:
{
'intent':{
'name':'greet',
'confidence':0.44503513568867775
},
'entities':[
{
'value':'neg',
'confidence':0.9933702940854111,
'entity':'sentiment',
'extractor':'sentiment_extractor'
}
],
'intent_ranking':[
{
'name':'greet',
'confidence':0.44503513568867775
},
{
'name':'chitchat',
'confidence':0.20129539551108508
},
{
'name':'inform',
'confidence':0.09576408290307896
},
{
'name':'goodbye',
'confidence':0.08987117551991658
},
{
'name':'decline',
'confidence':0.08840002616908385
},
{
'name':'affirm',
'confidence':0.04842063587016189
},
{
'name':'restaurant',
'confidence':0.03121354833799584
}
],
'text':'Hello stupid bot'
}
如果我想使用一個(gè)預(yù)先訓(xùn)練好的情緒分析模型呢?
上面的自定義組件示例包括一個(gè)相當(dāng)簡(jiǎn)單的sentiment模型舵匾,當(dāng)您添加更多的NLU訓(xùn)練示例時(shí)俊抵,它將得到改進(jìn)。如果你更喜歡使用預(yù)先訓(xùn)練過(guò)的模型坐梯,自定義組件類的實(shí)現(xiàn)會(huì)非常相似徽诲,除了一些細(xì)節(jié):
您不必實(shí)現(xiàn)train()和persist()方法類,因?yàn)槟慕M件已經(jīng)經(jīng)過(guò)了訓(xùn)練和持久化(可能作為python模塊或持久化模型)吵血。
您可以在process()方法中更改傳遞給模型的細(xì)節(jié)谎替。例子: 你的預(yù)先訓(xùn)練情感模型將未經(jīng)處理的文本消息作為輸入,而不是tokens践瓷。
為了說(shuō)明這種情況院喜,讓我們修改之前實(shí)現(xiàn)的自定義組件的代碼亡蓉,代替訓(xùn)練自定義的情感模型晕翠,我們使用一個(gè)由NLTK自然語(yǔ)言工具包提供的預(yù)訓(xùn)練的情感分析器模型:
from rasa.nlu.components import Component
from rasa.nlu import utils
from rasa.nlu.model import Metadata
import nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer
import os
class SentimentAnalyzer(Component):
"""A pre-trained sentiment component"""
name = "sentiment"
provides = ["entities"]
requires = []
defaults = {}
language_list = ["en"]
def __init__(self, component_config=None):
super(SentimentAnalyzer, self).__init__(component_config)
def train(self, training_data, cfg, **kwargs):
"""Not needed, because the the model is pretrained"""
pass
def convert_to_rasa(self, value, confidence):
"""Convert model output into the Rasa NLU compatible output format."""
entity = {"value": value,
"confidence": confidence,
"entity": "sentiment",
"extractor": "sentiment_extractor"}
return entity
def process(self, message, **kwargs):
"""Retrieve the text message, pass it to the classifier
and append the prediction results to the message class."""
sid = SentimentIntensityAnalyzer()
res = sid.polarity_scores(message.text)
key, value = max(res.items(), key=lambda x: x[1])
entity = self.convert_to_rasa(key, value)
message.set("entities", [entity], add_to_output=True)
def persist(self, model_dir):
"""Pass because a pre-trained model is already persisted"""
pass
在這種情況下,train()和persist()方法都是pass砍濒,因?yàn)槟P鸵呀?jīng)被預(yù)先訓(xùn)練并持久化為NLTK方法淋肾。另外,由于模型將未處理的文本作為輸入爸邢,所以process()方法檢索實(shí)際的消息并將它們傳遞給模型樊卓,由模型執(zhí)行所有處理工作并進(jìn)行預(yù)測(cè)。
總結(jié)
本教程中杠河,學(xué)習(xí)了如何創(chuàng)建自定義組件并將其添加到 Rasa NLU pipeline 中碌尔,你可以添加任意自定義組件,但重要的是要了解它們?nèi)绾闻c其他處理組件配合使用券敌,以及它們應(yīng)該產(chǎn)生什么輸出唾戚,將某些內(nèi)容傳遞給 pipeline 中的其他組件或者向模型的輸出添加某些內(nèi)容。
有用的資源
****Custom Components documentation****
****Rasa Community Forum****