NLU是语音交互的大脑,负责从用户话语中提取意图(Intent)和槽位(Slot):
| 任务 | 描述 | 示例 |
|---|---|---|
| 意图分类 | 判断用户想做什么 | "去会议室" → 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验证通过")
多轮对话需要状态跟踪,记住已收集的信息,追问缺失槽位:
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✅ 对话状态跟踪验证通过")
从自然语言中提取结构化实体是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✅ 实体抽取验证通过")
| 方案 | 优点 | 缺点 | 适用 |
|---|---|---|---|
| 规则+模板 | 可控、快速 | 泛化差 | 简单场景 |
| Rasa NLU | 可训练、开源 | 需标注数据 | 中等复杂度 |
| 大模型(LLM) | 强泛化 | 延迟高、不可控 | 复杂对话 |
| 混合方案 | 兼顾可控和泛化 | 架构复杂 | 生产环境 |
实现指代消解:处理'去那里'、'那间房'等指代,结合上下文消解为具体实体。
用余弦相似度替换Jaccard,实现基于词向量的意图分类,对比两种方法的准确率。
实现多语言NLU:同时支持中文和英文输入,自动检测语言并路由到对应解析器。