别再说“AI听不懂人话”:从0到1手把手搭一个意图识别 + 槽位提取系统
说句实话,我见过太多所谓“智能对话系统”,本质就是:
关键词匹配 + if-else 地狱
用户说一句:“帮我订明天去上海的机票”
系统回一句:“你是要查天气吗?”
——这不叫AI,这叫“人工智障”。
今天我们就来聊一个对话系统里最核心、也是最容易被低估的能力:
👉 意图识别(Intent Recognition) + 槽位提取(Slot Filling)
说白了就是:
- 你想干嘛?(意图)
- 你说了哪些关键信息?(槽位)
如果这两件事没做好,后面什么大模型、推荐系统,全是空中楼阁。
一、先把问题讲明白:什么是意图 + 槽位?
举个最简单的例子:
“帮我订一张明天从北京到上海的机票”
我们要解析成:
{
"intent": "book_flight",
"slots": {
"date": "明天",
"from_city": "北京",
"to_city": "上海"
}
}
你看,本质上就是一个“结构化理解”。
二、为什么这件事很难?
很多人一开始觉得:
👉 不就分类 + NER 吗?
但真正做起来你会发现:
1️⃣ 表达太多样
- 明天去上海
- 我要飞上海
- 帮我订张去魔都的票
👉 全是一个意图
2️⃣ 槽位不完整
- “帮我订票” → 缺日期
- “明天去上海” → 缺出发地
👉 系统必须能“追问”
3️⃣ 噪声极多
- “我想大概后天 maybe 去上海吧?”
👉 NLP 的现实就是 messy
三、先来一个最朴素版本(能跑最重要)
我们先不用大模型,直接用传统方法搞一版。
Step 1:意图识别(文本分类)
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
texts = [
"帮我订机票",
"我要买机票",
"查一下天气",
"今天上海天气怎么样"
]
labels = [
"book_flight",
"book_flight",
"check_weather",
"check_weather"
]
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(texts)
model = LogisticRegression()
model.fit(X, labels)
def predict_intent(text):
x = vectorizer.transform([text])
return model.predict(x)[0]
print(predict_intent("帮我订一张票"))
👉 这一步解决:
- 用户“想干嘛”
Step 2:槽位提取(规则版)
import re
def extract_slots(text):
slots = {
}
# 简单城市识别
if "北京" in text:
slots["from_city"] = "北京"
if "上海" in text:
slots["to_city"] = "上海"
# 日期识别
if "明天" in text:
slots["date"] = "明天"
return slots
print(extract_slots("帮我订明天从北京到上海的机票"))
👉 很土,但有效
四、升级一下:用深度学习做联合建模
真正工业级的做法是:
Intent + Slot 一起建模(Joint Model)
为什么?
👉 因为它们是强相关的。
一个简单的 BiLSTM + CRF 示例
import torch
import torch.nn as nn
class JointModel(nn.Module):
def __init__(self, vocab_size, hidden_dim, num_intents, num_slots):
super().__init__()
self.embedding = nn.Embedding(vocab_size, 128)
self.lstm = nn.LSTM(128, hidden_dim, bidirectional=True, batch_first=True)
self.intent_fc = nn.Linear(hidden_dim * 2, num_intents)
self.slot_fc = nn.Linear(hidden_dim * 2, num_slots)
def forward(self, x):
emb = self.embedding(x)
output, _ = self.lstm(emb)
# intent 用最后一个 hidden
intent_logits = self.intent_fc(output[:, -1, :])
# slot 用每个 token
slot_logits = self.slot_fc(output)
return intent_logits, slot_logits
👉 核心思想:
- 一次输入
- 两个输出(intent + slot)
五、再往上走一步:用预训练模型(推荐)
现在还手写 LSTM 的人,说实话已经不多了。
直接上 BERT:
from transformers import BertTokenizer, BertModel
import torch.nn as nn
class BertNLU(nn.Module):
def __init__(self, num_intents, num_slots):
super().__init__()
self.bert = BertModel.from_pretrained("bert-base-chinese")
self.intent_fc = nn.Linear(768, num_intents)
self.slot_fc = nn.Linear(768, num_slots)
def forward(self, input_ids, attention_mask):
outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask)
sequence_output = outputs.last_hidden_state
pooled_output = outputs.pooler_output
intent_logits = self.intent_fc(pooled_output)
slot_logits = self.slot_fc(sequence_output)
return intent_logits, slot_logits
👉 优点:
- 少量数据也能起飞
- 表达能力强
- 更鲁棒
六、对话系统真正难的,其实不是模型
我讲点“踩坑经验”,这部分很多文章不会说。
1️⃣ 槽位标准化(巨坑)
用户说:
- “魔都” → 上海
- “帝都” → 北京
你要统一:
city_map = {
"魔都": "上海",
"帝都": "北京"
}
👉 否则系统逻辑全乱
2️⃣ 多轮对话状态管理(核心)
dialog_state = {
"intent": "book_flight",
"slots": {
"from_city": "北京"
}
}
用户下一句:
“明天的”
👉 你必须补齐:
dialog_state["slots"]["date"] = "明天"
3️⃣ 缺失槽位要追问
required_slots = ["from_city", "to_city", "date"]
def check_missing(slots):
return [s for s in required_slots if s not in slots]
👉 这才像“会聊天”
七、一个完整流程(工程视角)
我帮你总结一个落地架构:
用户输入
↓
意图识别
↓
槽位提取
↓
状态管理(多轮)
↓
缺失补齐(追问)
↓
业务执行
八、说点真心话:大模型时代,这一套还重要吗?
很多人现在会问:
有大模型了,还需要意图识别吗?
我的答案是:
👉 更需要了。
原因很现实:
- 大模型不稳定
- 成本高
- 可控性差
而:
意图 + 槽位 = 可控的“确定性系统”
最佳实践是:
👉 小模型做结构化理解 + 大模型做补充理解
九、我的一点感受
我做对话系统这么多年,有一个越来越深的体会:
对话系统不是“让机器更聪明”,而是“让系统更可控”。
很多团队一味追求 fancy:
- 用最牛的模型
- 做最复杂的 pipeline
结果:
👉 一上线就崩
反而是那些:
- 意图清晰
- 槽位干净
- 流程简单
的系统,活得最长。
十、结尾:别做“听不懂人话”的系统
如果你现在的系统:
- 靠关键词匹配
- 经常理解错
- 无法处理多轮对话
那你真的该重构一遍了。
因为:
对话系统的本质,不是“说话”,而是“理解”。
而“理解”的第一步,就是:
👉 意图识别 + 槽位提取