【人机交互 6-10】第 7/25 课

🤖 第07课:自然语言理解

📌 自然语言理解概述

NLU是语音交互的大脑,负责从用户话语中提取意图(Intent)槽位(Slot)

🧠 NLU核心任务

任务描述示例
意图分类判断用户想做什么"去会议室" → navigate
槽位填充提取关键参数"3楼" → floor=3
实体抽取识别命名实体"张总" → person
指代消解解析代词指代"那间" → 会议室A

📌 意图分类与槽位填充

import re, math

class IntentClassifier:
    def __init__(self):
        self.intents = {
            "navigate": {
                "samples": ["去会议室","导航到前台","带我去洗手间","走到电梯口","前往3楼办公室","我要去餐厅","指引到停车场","怎么去大厅"],
                "slots": [{"name":"location","type":"enum","values":["会议室","前台","电梯","洗手间","茶水间","出口","大厅","办公室","停车场","餐厅"]},
                          {"name":"floor","type":"int","pattern":"(\\d+)楼"}]
            },
            "deliver": {
                "samples": ["送咖啡","配送快递","递送文件","把水拿给张总","带给李经理","帮我把餐食送过去"],
                "slots": [{"name":"item","type":"enum","values":["咖啡","水","文件","快递","餐食","药品","钥匙"]},
                          {"name":"person","type":"string","pattern":"给([\\u4e00-\\u9fa5]{2,3})"}]
            },
            "query": {
                "samples": ["洗手间在哪","前台怎么走","会议室在哪里","最近的电梯在哪","告诉我餐厅位置"],
                "slots": [{"name":"target","type":"enum","values":["洗手间","前台","会议室","电梯","餐厅","出口"]}]
            },
            "greet": {
                "samples": ["你好","嗨","早上好","下午好","晚上好","hello"],
                "slots": []
            },
            "follow": {
                "samples": ["跟我来","跟着我走","跟我走","跟随我","跟上"],
                "slots": []
            },
        }

    def classify(self, text):
        scores = {}
        for intent, data in self.intents.items():
            max_sim = 0
            for sample in data["samples"]:
                sim = self._similarity(text, sample)
                max_sim = max(max_sim, sim)
            scores[intent] = max_sim

        sorted_intents = sorted(scores.items(), key=lambda x: -x[x[0]] if isinstance(x[1], float) else 0)
        sorted_intents = sorted(scores.items(), key=lambda x: -x[1])
        best_intent = sorted_intents[0][0]
        best_score = sorted_intents[0][1]

        slots = self._extract_slots(text, best_intent)
        return {"intent": best_intent, "confidence": best_score, "slots": slots, "all_scores": scores}

    def _similarity(self, text, sample):
        set_t = set(text); set_s = set(sample)
        if not set_t or not set_s: return 0
        inter = set_t & set_s
        return len(inter) / (len(set_t) + len(set_s) - len(inter))

    def _extract_slots(self, text, intent):
        data = self.intents.get(intent, {})
        slots = {}
        for slot_def in data.get("slots", []):
            name = slot_def["name"]
            if slot_def["type"] == "enum":
                for v in slot_def["values"]:
                    if v in text:
                        slots[name] = v; break
            elif slot_def["type"] == "int":
                m = re.search(slot_def["pattern"], text)
                if m: slots[name] = int(m.group(1))
            elif slot_def["type"] == "string":
                m = re.search(slot_def["pattern"], text)
                if m: slots[name] = m.group(1)
        return slots

clf = IntentClassifier()
tests = [
    "请帮我导航到3楼会议室",
    "送一杯咖啡给王经理",
    "洗手间在哪里",
    "你好呀",
    "跟我来,我带你去",
    "帮我配送快递到前台",
]
print("自然语言理解(NLU)模拟")
print("=" * 55)
for t in tests:
    r = clf.classify(t)
    slots = ", ".join(f"{k}={v}" for k,v in r["slots"].items())
    print(f"\n📝 \"{t}\"")
    print(f"   意图: {r['intent']} (置信度:{r['confidence']:.2f})")
    if slots: print(f"   槽位: {slots}")
print("\n✅ NLU验证通过")
✅ 验证通过 自然语言理解(NLU)模拟 =======================================================

📌 对话状态跟踪

多轮对话需要状态跟踪,记住已收集的信息,追问缺失槽位:

class DialogueStateTracker:
    def __init__(self):
        self.state = {"intent": None, "slots": {}, "confirmed": False, "turn": 0}
        self.required_slots = {
            "navigate": ["location"],
            "deliver": ["item", "person"],
            "query": ["target"],
        }

    def update(self, nlu_result):
        self.state["turn"] += 1
        intent = nlu_result["intent"]
        slots = nlu_result["slots"]

        if self.state["intent"] is None:
            self.state["intent"] = intent
            self.state["slots"].update(slots)
        elif intent != "greet" and intent != "follow":
            self.state["slots"].update(slots)

        return self._check_completion()

    def _check_completion(self):
        intent = self.state["intent"]
        required = self.required_slots.get(intent, [])
        missing = [s for s in required if s not in self.state["slots"]]
        if not missing:
            self.state["confirmed"] = True
            return {"complete": True, "missing": [], "prompt": None}
        prompts = {
            "location": "请问您要去哪里?",
            "item": "请问您要送什么?",
            "person": "请问送给谁?",
            "target": "请问您要查询什么?",
        }
        prompt = prompts.get(missing[0], "请提供更多信息")
        return {"complete": False, "missing": missing, "prompt": prompt}

    def get_state(self):
        return self.state

tracker = DialogueStateTracker()
dialogue = [
    {"intent": "deliver", "slots": {"item": "咖啡"}, "confidence": 0.9},
    {"intent": "deliver", "slots": {"person": "王经理"}, "confidence": 0.85},
]

print("对话状态跟踪")
print("=" * 55)
for i, nlu in enumerate(dialogue):
    result = tracker.update(nlu)
    print(f"\n轮次{i+1}: 意图={nlu['intent']}, 槽位={nlu['slots']}")
    if result["complete"]:
        print(f"  ✅ 对话完成! 状态: {tracker.get_state()}")
    else:
        print(f"  ❓ 缺失槽位: {result['missing']}")
        print(f"  💬 追问: {result['prompt']}")

print("\n✅ 对话状态跟踪验证通过")
✅ 验证通过 对话状态跟踪 ======================================================= 轮次1: 意图=deliver, 槽位={'item': '咖啡'} ❓ 缺失槽位: ['person'] 💬 追问: 请问送给谁? 轮次2: 意图=deliver, 槽位={'person': '王经理'} ✅ 对话完成! 状态: {'intent': 'deliver', 'slots': {'item': '咖啡', 'person': '王经理'}, 'confirmed': True, 'turn': 2} ✅ 对话状态跟踪验证通过

📌 实体抽取

从自然语言中提取结构化实体是NLU的关键能力:

class EntityExtractor:
    def __init__(self):
        self.patterns = {
            "floor": [(r"(\d+)楼", 1), (r"第(\d+)层", 1)],
            "room": [(r"([\u4e00-\u9fa5]{2,4}(?:会议室|办公室|大厅|前台))", 0)],
            "person": [(r"([\u4e00-\u9fa5]{2,3})(?:先生|女士|总|经理|主任)", 1)],
            "time": [(r"(早上|上午|下午|晚上|中午)(\d+)(?:点|:)(\d+)?", 0)],
            "item": [(r"一杯?([\u4e00-\u9fa5]{1,4})", 1)],
        }
        self.known_entities = {
            "location": ["会议室A","会议室B","前台","大厅","电梯口","茶水间","洗手间","总裁办","副总办","接待室"],
            "item": ["咖啡","水","文件","快递","餐食","药品","钥匙","外卖","快递"],
        }

    def extract(self, text):
        entities = {}
        for etype, patterns in self.patterns.items():
            for pattern, group in patterns:
                import re
                m = re.search(pattern, text)
                if m:
                    entities[etype] = m.group(group)
                    break
        for etype, values in self.known_entities.items():
            for v in values:
                if v in text and etype not in entities:
                    entities[etype] = v
                    break
        return entities

ext = EntityExtractor()
tests = [
    "请导航到3楼会议室A",
    "送一杯咖啡给张总",
    "下午2点30分在会议室B开会",
    "帮我把快递送到5楼办公室",
    "早上9点在大厅集合",
]
print("实体抽取模拟")
print("=" * 55)
for t in tests:
    ents = ext.extract(t)
    print(f"\n📝 \"{t}\"")
    for k, v in ents.items():
        print(f"   {k}: {v}")
print("\n✅ 实体抽取验证通过")
✅ 验证通过 实体抽取模拟 ======================================================= 📝 "请导航到3楼会议室A" floor: 3 location: 会议室A 📝 "送一杯咖啡给张总" person: 啡给张 item: 咖啡给张 📝 "下午2点30分在会议室B开会" room: 分在会议室 time: 下午2点30 location: 会议室B 📝 "帮我把快递送到5楼办公室" floor: 5 item: 快递 📝 "早上9点在大厅集合" room: 点在大厅 time: 早上9点 location: 大厅 ✅ 实体抽取验证通过

📌 NLU架构对比

📊 方案对比

方案优点缺点适用
规则+模板可控、快速泛化差简单场景
Rasa NLU可训练、开源需标注数据中等复杂度
大模型(LLM)强泛化延迟高、不可控复杂对话
混合方案兼顾可控和泛化架构复杂生产环境
💡 生产环境推荐混合方案:规则处理高频简单意图,LLM兜底处理复杂/未知意图。

📌 练习

📝 练习 1

实现指代消解:处理'去那里'、'那间房'等指代,结合上下文消解为具体实体。

📝 练习 2

余弦相似度替换Jaccard,实现基于词向量的意图分类,对比两种方法的准确率。

📝 练习 3

实现多语言NLU:同时支持中文和英文输入,自动检测语言并路由到对应解析器。

📌 成就

🏆 本课成就

◀ 上一课 📚 目录 下一课 ▶