一句话总结
意图识别 = 从一句话中判断用户"想做什么". 本篇把它应用到群聊管理场景:从管理员的自然语言消息中解析出操作指令(禁言/踢人/警告),以及操作对象和参数.
前置回顾
上一篇完成了 BERT 微调的完整流程:加载模型 → 准备数据 → 训练 → 评估. 那个任务是"对骂检测"(二/三分类). 本篇扩展到另一个实用场景:管理指令解析. 从分类的角度看,意图识别就是多分类--不过类别变成了"禁言""踢人""警告""查询""闲聊"等操作指令.
群聊管理的指令场景
群管理员的日常操作往往用自然语言表达,而不是固定格式的命令:
固定命令格式(程序员思维): /mute @张三 30m /kick @李四 /warn @王五
管理员的实际表达(自然语言): "把张三禁言半小时" "踢了那个发广告的" "警告一下王五,下次再犯直接踢" "最近谁被禁言了" "这群怎么这么安静"
|
如果只做正则匹配,会面临表达多样性的问题:
表达"禁言"的 10+ 种方式: "禁言张三" "把张三禁了" "张三禁言30分钟" "封一下张三的嘴" "让张三闭嘴" "mute张三" "张三别说话了先" "管管张三" ...
正则穷举 → 维护噩梦
|
正则匹配 = Go 的 switch-case 路由. 每种 URL 写一个 case,新增路由就加代码. 10 种 URL 还行,100 种就崩了.
意图识别 = Go 的 http.HandleFunc 模式匹配. 框架理解 URL 的结构,自动路由. 意图识别让系统理解自然语言的结构,自动映射到操作.
任务分解:意图 + 槽位
一条管理指令包含两部分信息:
"把张三禁言半小时"
意图(Intent): mute(禁言) 槽位(Slots): - target: 张三 ← 操作对象 - duration: 30min ← 持续时间
|
| 组件 |
做什么 |
模型任务 |
| 意图识别 |
判断整句话的操作类型 |
句子级分类(和对骂检测一样) |
| 槽位填充 |
提取具体参数 |
序列标注(每个词打标签) |
本篇先聚焦意图识别(分类),槽位填充在后续阶段展开.
意图体系设计
第一步是定义管理场景的意图标签体系:
群聊管理意图体系(v1):
管理指令: mute 禁言某人 "把xx禁了" "禁言xx" kick 踢出某人 "踢了xx" "请xx出去" warn 警告某人 "警告xx" "提醒一下xx" unmute 解除禁言 "解禁xx" "让xx说话" ban_media 禁止发图/链接 "xx不许发图" "禁止xx发链接"
查询类: query_log 查询操作记录 "最近谁被禁言了" "操作日志" query_rule 查询群规 "群规是什么" "什么不能发"
非指令: chat 普通闲聊 "今天天气好" "晚上吃什么" abuse 辱骂(非指令) "你有病吧" "废物"
|
意图粒度的权衡
意图拆得越细,每个意图的训练样本越少. 初期建议 5-8 个意图,等数据量够了再细分.
一个实用的判断标准:两个意图在系统中是否触发不同的处理逻辑? 如果"禁言"和"禁言并警告"最终都调同一个 API,就不需要拆成两个意图.
训练数据构建
意图识别的标注比对骂检测更直接--只需要给每句话标一个意图类别:
data/intent_train.csv:
text,intent "把张三禁言半小时",mute "把那个发广告的禁了",mute "踢了李四",kick "请广告号出去",kick "警告一下王五",warn "最近有谁被禁言",query_log "群规发一下",query_rule "今天天气好",chat "你有病吧",abuse
|
数据量建议
| 意图 |
最低样本量 |
推荐样本量 |
| 高频意图(mute, kick, chat) |
100 |
300+ |
| 中频意图(warn, query) |
50 |
150+ |
| 低频意图(unmute, ban_media) |
30 |
80+ |
样本量不均衡是常见问题. "chat" 和 "abuse" 的样本通常远多于 "unmute". 两种处理方法:
- 过采样少数类:对样本不足的意图做重复采样,使各类别数量均衡
- 训练时加类别权重:给少数类更高的损失权重(
class_weights = 1.0 / 各类样本数),让模型更重视少数类的错误
Few-Shot 场景:数据极少时怎么办
对某些意图(如 "unmute"),可能只收集到 10-20 条样本. 三种应对策略:
- 数据增强:改写同义表达("解禁张三" → "给张三解除禁言" / "让张三可以说话了"),手动扩充到 50+ 条
- 合并意图:样本太少的意图合并进相近类别,在槽位里区分(如 "unmute" 并入 "mute",用 action 槽位区分禁言/解禁)
- Prompt-based 分类:冷启动阶段用 ChatGPT/通义千问做 zero-shot 分类,等积累够数据再训练专用模型
BERT 意图分类实战
意图识别的训练流程和第 12 篇的对骂检测几乎一样--都是 BERT 微调做分类:
INTENT_LABELS = ["mute", "kick", "warn", "unmute", "query_log", "query_rule", "chat", "abuse"] label2id = {label: i for i, label in enumerate(INTENT_LABELS)} id2label = {i: label for i, label in enumerate(INTENT_LABELS)}
model = AutoModelForSequenceClassification.from_pretrained( "hfl/chinese-roberta-wwm-ext", num_labels=len(INTENT_LABELS), label2id=label2id, id2label=id2label, )
def preprocess(examples): tokens = tokenizer(examples["text"], padding="max_length", truncation=True, max_length=64) tokens["labels"] = [label2id[intent] for intent in examples["intent"]] return tokens
|
max_length=64 的原因
管理指令通常很短("把张三禁了"只有 5 个字). 设 max_length=64 既节省计算资源,又不会截断任何管理指令. 对比对骂检测用 128--因为辱骂消息可能更长("你这个人怎么这样啊真的有毛病吧每次都这样...").
规则 + 模型:混合架构
实际系统中,纯模型或纯规则都不是最优解. 混合架构结合两者的优势:
混合处理管线:
输入消息 ↓ ┌─────────────────┐ │ 规则引擎(第一层) │ ← 精确匹配,零延迟 │ /mute @xxx 30m │ → 直接执行,跳过模型 │ /kick @xxx │ └─────────────────┘ ↓ (未命中规则) ┌─────────────────┐ │ 关键词过滤(第二层)│ ← 简单模式匹配 │ "禁言" + @提及 │ → 高置信度,直接执行 └─────────────────┘ ↓ (未命中) ┌─────────────────┐ │ BERT 意图分类 │ ← 处理自然语言表达 │ "管管那个人" │ → 概率输出,按阈值决策 └─────────────────┘ ↓ 后处理 + 执行
|
为什么保留规则层
| 层 |
覆盖场景 |
延迟 |
准确率 |
| 规则引擎 |
/mute, /kick 等固定格式命令 |
< 1ms |
100%(精确匹配) |
| 关键词 |
"禁言" + @提及 |
< 1ms |
95%+(模式明确) |
| BERT 模型 |
"管管那个人""让他闭嘴" |
10-50ms |
85-90%(复杂表达) |
规则能处理的就不走模型--节省算力,减少延迟,避免模型出错.
实现示例
import re
class IntentRouter: def __init__(self, bert_classifier): self.bert = bert_classifier self.command_pattern = re.compile( r"^/(mute|kick|warn|unmute)\s+@(\S+)(?:\s+(\d+[mhd]))?" )
def parse(self, text, sender_role="member"): if sender_role != "admin": return {"intent": "not_admin", "confidence": 1.0}
cmd_match = self.command_pattern.match(text) if cmd_match: return { "intent": cmd_match.group(1), "target": cmd_match.group(2), "duration": cmd_match.group(3), "confidence": 1.0, "source": "rule", }
result = self.bert.predict(text)
if result["confidence"] < 0.7: return {"intent": "uncertain", "confidence": result["confidence"]}
return {**result, "source": "model"}
|
置信度阈值
模型输出的 softmax 概率不等于"真实置信度". 一个 0.85 的概率不意味着 85% 的准确率. 实际部署时需要用测试数据校准阈值:
- 画 precision-confidence 曲线
- 找到满足 precision > 95% 的最低 confidence 阈值(比如 0.75)
- 低于阈值的 case 走兜底逻辑(提示管理员确认,或忽略)
槽位填充基础
意图确定后,还需要提取操作参数. 简单场景用规则提取,复杂场景用序列标注模型:
规则提取(推荐起步)
import re
def extract_slots(text, intent): """从文本中提取槽位(规则方式)""" slots = {}
at_match = re.search(r"@(\S+)", text) if at_match: slots["target"] = at_match.group(1)
duration_match = re.search(r"(\d+)\s*(分钟|小时|天|min|h|d|m)", text) if duration_match: num = int(duration_match.group(1)) unit = duration_match.group(2) unit_map = {"分钟": "m", "min": "m", "m": "m", "小时": "h", "h": "h", "天": "d", "d": "d"} slots["duration"] = f"{num}{unit_map.get(unit, 'm')}"
return slots
extract_slots("把@张三禁言30分钟", "mute")
|
序列标注(进阶)
当规则覆盖不了时(比如"把那个一直发广告的禁了",没有 @ 提及),需要用 NER/序列标注模型:
"把 那个 一直 发 广告 的 禁 了" O B-TGT I-TGT I-TGT I-TGT O O O
B-TGT = 目标实体开始 I-TGT = 目标实体内部 O = 非实体
提取出: target = "那个一直发广告的" → 需要进一步解析:这描述的是哪个用户(结合上下文/最近发广告的人)
|
序列标注用 AutoModelForTokenClassification 实现,架构和分类类似,但输出从"句子级标签"变成"每个 token 的标签". 这超出了本篇范围,后续有需要时再展开.
评估意图识别系统
意图识别的评估除了标准 F1,还需要关注业务指标:
技术指标: - Macro F1(所有意图的平均 F1) - 各意图单独的 Precision / Recall - Confusion Matrix(哪些意图容易混淆)
业务指标: - 指令执行准确率(管理员说"禁言",系统是否正确执行了禁言) - 误触发率(管理员在闲聊,系统错误地执行了管理操作) - 兜底率(多少比例的消息走到了"uncertain"兜底逻辑)
|
误触发比漏识别更危险
管理员说"今天谁做饭",系统理解成"踢人"然后真的踢了人--这种误触发的影响远大于漏识别一条禁言指令. 部署时宁可保守(高阈值 + 二次确认),也不能激进触发管理操作.
混淆矩阵分析
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay import matplotlib.pyplot as plt
cm = confusion_matrix(true_labels, pred_labels) disp = ConfusionMatrixDisplay(cm, display_labels=INTENT_LABELS) disp.plot(xtick_rotation=45) plt.tight_layout() plt.savefig("intent_confusion_matrix.png")
|
常见混淆:
| 真实意图 |
容易误判为 |
原因 |
| mute |
warn |
"管管他" 语义模糊 |
| kick |
mute |
"让他消失" 可能是踢人或禁言 |
| query_log |
chat |
"最近群里怎么样" 可能是查询或闲聊 |
| abuse |
chat |
轻度吐槽("服了")和闲聊边界模糊 |
端到端流程串联
把意图识别和对骂检测放进完整的群聊处理管线中:
群聊消息进入 ↓ 身份判断: 是管理员? ↓ 是 ↓ 否 意图识别管线 内容审核管线 规则 → 关键词 → BERT 对骂检测(BERT) ↓ ↓ 管理指令? 违规? ↓ 是 ↓ 否 ↓ 是 ↓ 否 执行操作 正常放行 自动处理 正常放行 (禁言/踢人) (禁言/警告)
|
两个模型可以是同一个 BERT 的不同微调版本,也可以合并成一个多任务模型(同时输出"内容分类"和"意图类别"). 初期建议分开训练,简化调试.
快速回顾
- 意图识别 = 句子级多分类:判断"想做什么",本质和对骂检测一样是分类任务
- 槽位填充 = 参数提取:初期用正则,复杂场景用序列标注模型
- 混合架构:规则(精确) → 关键词(快速) → BERT(兜底),三层逐步兜底
- 置信度阈值:模型输出概率需要校准,低置信度走兜底逻辑
- 误触发 > 漏识别:管理操作不可撤销(踢人/禁言),宁可保守
- 数据不均衡:少数类用过采样或类别权重处理
动手练习
- 设计意图体系:根据自己的群管理经验,设计一套 5-8 个意图的标签体系. 为每个意图写 10 条示例
- 训练意图分类器:用上面的代码模板,在自己的数据上训练意图分类模型. 目标 macro F1 > 0.80
- 混淆矩阵分析:画出混淆矩阵,找出最容易混淆的两对意图,分析原因并提出改进方案
- 实现混合路由:参考
IntentRouter 的代码,实现一个完整的三层路由(规则 → 关键词 → 模型),用 20 条测试 case 验证正确性
- 阈值校准:在测试集上画 precision-confidence 曲线,找到 precision > 95% 对应的最低阈值