欢迎光临
我们一直在努力

RexUniNLU实战教程:将RexUniNLU集成进Rasa对话系统作为前端NLU组件

RexUniNLU实战教程:将RexUniNLU集成进Rasa对话系统作为前端NLU组件

1. 引言:为什么需要零样本NLU?

想象一下,你正在搭建一个智能客服机器人。传统的方法需要你收集成千上万条用户对话,然后一条条地标注出用户的“意图”(比如“查询订单”、“投诉问题”)和“槽位”(比如“订单号”、“问题描述”)。这个过程耗时耗力,而且一旦业务需求变了,比如新增一个“预约维修”的功能,你又得重新标注数据。

有没有一种方法,能让我们像搭积木一样,简单地告诉系统“我需要识别‘出发地’、‘目的地’、‘时间’和‘订票意图’”,它就能立刻理解用户的订票请求,而无需准备任何训练数据呢?

这就是RexUniNLU要解决的问题。它是一个基于Siamese-UIE架构的零样本自然语言理解框架。简单来说,你只需要用自然语言定义好你想要识别的标签(Schema),它就能直接工作,真正实现了“定义即识别”。

今天,我们就来手把手教你,如何将这个强大的零样本NLU引擎,集成到流行的开源对话系统框架——Rasa中,作为其前端NLU组件。这样一来,你就能用Rasa来管理复杂的对话流程,同时享受RexUniNLU带来的零数据标注、快速迭代的便利。

2. 环境准备与项目结构

在开始集成之前,我们需要准备好双方的环境。假设你已经有一个基础的Python开发环境(3.8+)。

2.1 安装RexUniNLU

首先,我们来安装RexUniNLU。根据其项目说明,核心依赖是ModelScope。

# 1. 创建并进入一个专门的项目目录
mkdir rasa_rexnlu_integration && cd rasa_rexnlu_integration

# 2. 创建虚拟环境(推荐)
python -m venv venv
# Windows 激活: venv\\Scripts\\activate
# Linux/Mac 激活: source venv/bin/activate

# 3. 安装ModelScope和RexUniNLU可能需要的依赖
pip install modelscope torch

# 4. 克隆RexUniNLU项目(这里假设从GitHub获取,请根据实际来源调整)
# git clone <RexUniNLU仓库地址> RexUniNLU
# 由于我们主要是调用其核心功能,我们可以将其核心代码放在我们的项目里。
# 我们创建一个 `rexnlu` 目录来模拟其结构。
mkdir -p rexnlu

接下来,我们需要RexUniNLU的核心推理代码。根据提供的项目结构,核心逻辑在test.py的analyze_text函数中。为了集成,我们需要将其抽象成一个独立的模块。

我们在rexnlu目录下创建 core.py:

# rexnnu/core.py
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks

class RexUniNLUEngine:
"""RexUniNLU 核心引擎封装类"""

def __init__(self, model_repo='damo/nlp_structbert_siamese-uie_nano_zh'):
"""
初始化引擎。
model_repo: ModelScope上的模型仓库地址,默认为轻量版Nano模型。
"""
print(f"正在加载模型: {model_repo}… (首次运行会下载模型,请耐心等待)")
# 创建信息抽取pipeline
self.pipeline = pipeline(
task=Tasks.siamese_uie,
model=model_repo,
model_revision='v1.0.0'
)
print("模型加载完毕!")

def parse(self, text: str, schema: list):
"""
解析用户语句。
Args:
text: 用户输入的自然语言文本。
schema: 需要识别的标签列表,例如 ['出发地', '目的地', '时间', '订票意图']。
Returns:
dict: 包含意图和实体槽位的结构化结果。
"""
if not text or not schema:
return {'intent': None, 'entities': []}

# 调用模型进行预测
raw_result = self.pipeline(input=text, schema=schema)

# 格式化结果,适配Rasa NLU的输出格式
# RexUniNLU返回的格式类似:{'出发地': ['上海'], '目的地': ['北京'], '时间': ['明天']}
# 我们需要区分哪些是意图,哪些是实体。
# 这里做一个简单的启发式规则:如果标签名包含“意图”,则认为是意图,否则是实体。
# **注意:这是一个简化策略。实际应用中,你的schema设计应更明确地区分意图和实体。**

intent = None
entities = []

for label, values in raw_result.items():
if values: # 只处理有识别结果的标签
if '意图' in label:
# 取第一个值作为意图名称,或者直接使用标签名
intent = {'name': label, 'confidence': 1.0} # 零样本模型难以提供置信度,这里设为1.0
else:
# 对于实体,遍历所有识别出的值
for value in values:
entities.append({
'entity': label,
'value': value,
'start': text.find(value), # 简化处理,实际模型可能不返回位置
'end': text.find(value) + len(value),
'confidence': 1.0
})
# 如果没有识别出明确的意图,可以设置一个默认意图,或者用文本分类模型另行判断
if intent is None:
intent = {'name': 'nlu_fallback', 'confidence': 0.5}

return {
'text': text,
'intent': intent,
'entities': entities
}

# 提供一个便捷函数
def create_engine(model_repo='damo/nlp_structbert_siamese-uie_nano_zh'):
return RexUniNLUEngine(model_repo)

同时,创建 rexnlu/__init__.py 文件,使其成为一个包:

# rexnlu/__init__.py
from .core import RexUniNLUEngine, create_engine

2.2 安装Rasa

接下来,在同一个虚拟环境中安装Rasa。

pip install rasa

安装完成后,你可以初始化一个新的Rasa项目,或者使用现有的项目。为了演示,我们初始化一个新的:

rasa init –no-prompt

这个命令会创建一个基本的Rasa项目结构,包括 data/, actions/, models/, config.yml 等目录和文件。

现在,我们的项目结构大致如下:

rasa_rexnlu_integration/
├── venv/ # Python虚拟环境
├── rexnlu/ # 我们封装的RexUniNLU模块
│ ├── __init__.py
│ └── core.py
└── your_rasa_project/ # Rasa初始化生成的项目目录
├── actions/
├── data/
├── models/
├── config.yml
├── credentials.yml
├── domain.yml
├── endpoints.yml
└── …

3. 创建自定义Rasa NLU组件

Rasa的NLU管道(Pipeline)是由一系列组件构成的。我们需要创建一个自定义组件,来桥接Rasa和RexUniNLU。

在Rasa项目根目录下(your_rasa_project/),创建一个新的Python文件,例如 custom_components/rexnlu_component.py。

# your_rasa_project/custom_components/rexnlu_component.py
from typing import Any, Text, Dict, List, Type
from rasa.engine.recipes.default_recipe import DefaultV1Recipe
from rasa.engine.graph import ExecutionContext, GraphComponent
from rasa.engine.storage.resource import Resource
from rasa.engine.storage.storage import ModelStorage
from rasa.shared.nlu.training_data.message import Message
from rasa.shared.nlu.training_data.training_data import TrainingData
import sys
import os

# 将我们上一级目录(rasa_rexnlu_integration)加入路径,以便导入rexnlu
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
from rexnlu import create_engine

# 使用Rasa V1配方装饰器注册组件
@DefaultV1Recipe.register(
[DefaultV1Recipe.ComponentType.INTENT_CLASSIFIER], is_trainable=False
)
class RexUniNLUComponent(GraphComponent):
"""自定义NLU组件,使用RexUniNLU进行零样本意图和实体识别。"""

@classmethod
def create(
cls,
config: Dict[Text, Any],
model_storage: ModelStorage,
resource: Resource,
execution_context: ExecutionContext,
) -> GraphComponent:
"""在训练时创建组件(由于不可训练,这里主要做初始化)。"""
return cls(config, model_storage, resource, execution_context)

def __init__(
self,
config: Dict[Text, Any],
model_storage: ModelStorage,
resource: Resource,
execution_context: ExecutionContext,
) -> None:
"""初始化组件。"""
super().__init__()
self._config = config
# 从配置中读取schema定义
# 配置示例:在config.yml中定义 rexnlu_schema: ["查询意图", "城市", "时间"]
self.schema = config.get("rexnlu_schema", [])
# 初始化RexUniNLU引擎
model_repo = config.get("rexnlu_model_repo", "damo/nlp_structbert_siamese-uie_nano_zh")
self.engine = create_engine(model_repo)
print(f"RexUniNLU组件初始化完成,Schema: {self.schema}")

def process(self, messages: List[Message]) -> List[Message]:
"""处理消息列表,进行NLU解析。"""
for message in messages:
text = message.get("text")
if text:
# 调用RexUniNLU引擎进行解析
nlu_result = self.engine.parse(text, self.schema)
# 将结果设置到Rasa的Message对象中
if nlu_result['intent']:
message.set("intent", nlu_result['intent'], add_to_output=True)
if nlu_result['entities']:
message.set("entities", nlu_result['entities'], add_to_output=True)
return messages

def process_training_data(self, training_data: TrainingData) -> TrainingData:
"""处理训练数据(本组件无需训练,直接返回)。"""
# 这是一个零样本组件,不依赖于Rasa的训练数据。
# 我们可以选择在这里用训练数据来优化schema,或者直接跳过。
return training_data

@classmethod
def get_default_config(cls) -> Dict[Text, Any]:
"""返回组件的默认配置。"""
return {
"rexnlu_schema": ["意图", "实体"], # 默认schema,强烈建议在config.yml中覆盖
"rexnlu_model_repo": "damo/nlp_structbert_siamese-uie_nano_zh"
}

关键点解释:

  • @DefaultV1Recipe.register: 将这个类注册为Rasa NLU管道的一个组件,类型为INTENT_CLASSIFIER。is_trainable=False表明它不需要训练。
  • __init__: 从Rasa的配置文件(config.yml)中读取我们自定义的配置项,主要是schema(标签列表)和模型仓库地址,然后初始化RexUniNLU引擎。
  • process: 这是核心方法。对于每条用户消息,调用self.engine.parse进行解析,并将解析出的意图和实体设置回Rasa的Message对象中。
  • process_training_data: 由于是零样本组件,我们不需要用它来学习训练数据,直接返回即可。
  • 4. 配置Rasa使用自定义组件

    现在,我们需要修改Rasa的配置文件,告诉它使用我们刚创建的自定义组件,并定义好业务所需的schema。

    编辑 your_rasa_project/config.yml 文件:

    # config.yml
    version: "3.1"

    recipe: default.v1

    language: zh # 使用中文

    pipeline:
    # 可以保留或移除原有的Tokenizer,RexUniNLU本身不需要分词,但其他组件可能需要
    # – name: "JiebaTokenizer"
    # – name: "LanguageModelTokenizer"
    # – name: "WhitespaceTokenizer"

    # 我们的自定义零样本NLU组件
    – name: "custom_components.rexnlu_component.RexUniNLUComponent"
    # 在这里定义你的业务schema
    # 标签设计是关键:明确区分意图和实体。
    # 意图标签建议包含动词,如“查询天气意图”;实体标签用名词,如“城市”、“时间”。
    rexnlu_schema: ["查询天气意图", "查询新闻意图", "播放音乐意图", "城市", "日期", "音乐类型", "歌手"]
    # 可选:指定其他模型,如更精确但更大的模型
    # rexnlu_model_repo: "damo/nlp_structbert_siamese-uie_base_zh"

    # 由于RexUniNLU已经提供了意图和实体,我们通常不需要Rasa自带的意图分类器和实体提取器。
    # 但你可以根据需要添加其他后处理组件,例如实体同义词映射。
    # – name: "EntitySynonymMapper"

    policies:
    – name: MemoizationPolicy
    – name: RulePolicy
    – name: "TEDPolicy"
    max_history: 5
    epochs: 100
    – name: "UnexpecTEDIntentPolicy"
    max_history: 5
    epochs: 100

    重要提示: rexnlu_schema 的配置是集成的核心。你需要根据你的对话机器人要处理的任务,仔细设计这个标签列表。好的标签设计能极大提升零样本识别的准确率。

    5. 定义领域与规则

    接下来,我们需要更新 domain.yml 文件,定义意图、实体以及对应的回复和动作。

    # domain.yml
    version: "3.1"

    intents:
    – nlu_fallback # RexUniNLU未识别出意图时的回退意图
    – 查询天气意图
    – 查询新闻意图
    – 播放音乐意图

    entities:
    – 城市
    – 日期
    – 音乐类型
    – 歌手

    slots:
    城市:
    type: text
    mappings:
    – type: from_entity
    entity: 城市
    日期:
    type: text
    mappings:
    – type: from_entity
    entity: 日期
    音乐类型:
    type: text
    mappings:
    – type: from_entity
    entity: 音乐类型
    歌手:
    type: text
    mappings:
    – type: from_entity
    entity: 歌手

    responses:
    utter_greet:
    – text: "你好!我是你的智能助手。"
    utter_ask_city_for_weather:
    – text: "你想查询哪个城市的天气呢?"
    utter_provide_weather:
    – text: "好的,正在为你查询{city}的天气。"
    utter_play_music:
    – text: "好的,即将为你播放{音乐类型}风格的歌曲,歌手是{歌手}。"
    utter_fallback:
    – text: "抱歉,我还没学会处理这个请求。你可以试着问我天气、新闻或者播放音乐。"

    actions:
    – action_query_weather
    – action_play_music
    – utter_greet
    – utter_ask_city_for_weather
    – utter_provide_weather
    – utter_play_music
    – utter_fallback

    session_config:
    session_expiration_time: 60
    carry_over_slots_to_new_session: true

    然后,我们可以在 data/rules.yml 中定义一些简单的规则来处理识别出的意图:

    # data/rules.yml
    version: "3.1"

    rules:
    – rule: 激活问候
    steps:
    – intent: greet
    – action: utter_greet

    – rule: 处理查询天气意图
    steps:
    – intent: 查询天气意图
    – action: utter_ask_city_for_weather
    # 这里可以连接一个自定义Action来真正调用天气API

    – rule: 处理播放音乐意图(有实体)
    condition:
    – slot_was_set:
    – 音乐类型: true
    – 歌手: true
    steps:
    – intent: 播放音乐意图
    – action: utter_play_music

    – rule: 默认回退
    steps:
    – intent: nlu_fallback
    – action: utter_fallback

    6. 运行与测试

    一切就绪,让我们来启动机器人并测试集成效果。

    第一步:启动Rasa NLU服务(用于测试解析)

    在终端中,进入你的Rasa项目目录,运行:

    rasa shell nlu

    这个命令会启动一个交互式shell,专门测试NLU解析。加载完成后,它会提示你输入消息。

    试着输入一些句子:

    • “今天北京天气怎么样?” (期望: 意图=查询天气意图, 实体=城市=“北京”, 日期=“今天”)
    • “播放一首周杰伦的流行音乐” (期望: 意图=播放音乐意图, 实体=歌手=“周杰伦”, 音乐类型=“流行”)
    • “帮我查一下新闻” (期望: 意图=查询新闻意图)

    观察输出。Rasa会显示它解析出的意图和实体。这些信息就来自于我们自定义的RexUniNLUComponent。

    第二步:训练并运行完整的对话机器人

    如果你定义了更多的故事(stories.yml)和自定义动作,可以训练完整的模型并运行交互式对话。

    # 训练模型
    rasa train

    # 启动动作服务器(如果需要自定义Action)
    # rasa run actions &

    # 启动shell进行完整对话
    rasa shell

    在完整的对话中,Rasa会基于NLU解析的结果(意图和实体),结合对话管理策略(Policies)来决定下一步该说什么或做什么。

    7. 调试与优化建议

    集成过程中可能会遇到一些问题,这里提供一些排查思路和优化建议:

  • Schema设计是关键:零样本识别的效果高度依赖于你提供的标签(Schema)。确保标签:

    • 语义清晰:使用完整、无歧义的中文词语,如“出发城市”比“出发地”更好。
    • 区分意图与实体:在标签名上做约定,例如意图都以“意图”结尾。
    • 覆盖全面:列出所有需要识别的概念。如果发现某个概念总是识别不出,考虑是否要将其加入Schema,或者用更常见的同义词。
  • 处理识别冲突:有时一个词可能被同时识别为多个标签。在我们的简单parse方法中,采取了“意图”关键词判断。在实际应用中,你可能需要更复杂的策略,比如维护一个明确的“意图标签列表”。

  • 性能考虑:RexUniNLU模型在CPU上运行可能较慢。对于生产环境,建议部署在GPU服务器上,并将我们的自定义组件封装成独立的微服务(类似原项目的server.py),Rasa通过HTTP调用它,而不是在同一个进程内加载模型。

  • 与Rasa原生组件结合:我们的组件完全替代了Rasa的DIETClassifier和CRFEntityExtractor。你也可以设计一个混合管道,让RexUniNLU处理零样本或少样本的新领域,而用Rasa原生组件处理有大量标注数据的核心领域。

  • 查看日志:在config.yml中增加日志级别,有助于调试。

    log_level: DEBUG

  • 8. 总结

    通过本教程,我们成功地将零样本自然语言理解框架RexUniNLU集成到了Rasa对话系统中。我们创建了一个自定义的Rasa NLU组件,它能够在无需任何标注数据的情况下,根据我们预先定义的Schema,实时识别用户语句中的意图和实体。

    这种集成方式带来了显著的优势:

    • 快速启动:新业务场景下,无需数据标注即可获得可用的NLU能力。
    • 灵活迭代:修改业务逻辑只需调整Schema定义,无需重新标注和训练数据。
    • 技术栈融合:保留了Rasa强大的对话管理、故事学习、表单处理等功能,同时增强了其NLU的冷启动能力。

    当然,零样本并非万能。对于非常复杂、歧义性高的语言,或者对准确率要求极高的场景,可能仍然需要一定量的标注数据来微调模型。但对于原型验证、快速试错、处理长尾需求来说,RexUniNLU + Rasa的组合无疑是一把利器。

    你可以在此基础上继续探索,例如优化Schema设计策略、将RexUniNLU服务化以提升性能、或者结合Rasa的主动学习功能来收集那些模型不确定的样本进行人工标注,从而实现一个持续进化的智能对话系统。


    获取更多AI镜像

    想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

    赞(0)
    未经允许不得转载:171主机测评 » RexUniNLU实战教程:将RexUniNLU集成进Rasa对话系统作为前端NLU组件
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址