共计 2636 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点:为什么需要更智能的意图识别?
在构建对话系统时,我们最早尝试用正则表达式和关键词列表来做意图识别。这种方式在简单场景下运行良好,比如:

- 用户输入 ” 查看余额 ”,系统匹配关键词 ” 余额 ” 触发查询功能
- 用户输入 ” 重置密码 ”,通过正则
/ 重置 | 忘记.* 密码 /匹配
但随着业务复杂化,我们遇到了典型问题:
- 语义模糊:用户说 ” 钱不见了 ” 可能是查询余额,也可能是投诉盗刷
- 表达变异:” 怎么还款 ” 和 ” 我要还信用卡 ” 需要识别为同一意图
- 长尾问题:每月新增约 15% 的未覆盖表达方式,规则维护成本指数上升
技术方案对比:从规则到 AI 的演进
规则引擎:高精准但难扩展
早期金融领域常用 Drools 等规则引擎,其优势在于:
- 100% 准确率(在规则覆盖范围内)
- 可解释性强,符合审计要求
但维护成本令人头疼:
# 典型规则维护场景
rule "贷款咨询"
when
$input: String()
(matches(".* 怎么贷款.*", $input) ||
matches(".* 借钱条件.*", $input))
then
setIntent("LOAN_INQUIRY");
end
每新增一个表达方式都需要:
- 业务分析
- 规则编写
- 回归测试
- 生产部署
传统机器学习:特征工程的噩梦
采用 SVM/RandomForest 时,90% 精力花在特征工程:
- 词袋模型(Bag-of-Words)
- TF-IDF 权重
- 人工设计对话特征(句子长度、是否含疑问词等)
主要问题:
- 需要标注大量数据(万级样本)
- 难以捕捉上下文关系
- 准确率通常卡在 82-88% 的瓶颈
深度学习:Transformer 的突破
BERT 等预训练模型带来质变:
| 指标 | 规则引擎 | 传统 ML | BERT-base |
|---|---|---|---|
| 准确率 | 100%* | 85% | 93% |
| 召回率 | 62% | 83% | 91% |
| 维护成本 | 高 | 中 | 低 |
* 注:规则引擎准确率仅针对已覆盖场景
实战:基于 HuggingFace 的意图识别
数据准备
典型的银行场景意图分类数据集:
import pandas as pd
data = [["如何申请信用卡", "card_apply"],
["信用卡怎么办理", "card_apply"],
["还款日是哪天", "payment_query"],
# 至少 2000 组样本...
]
df = pd.DataFrame(data, columns=["text", "intent"])
# 标签编码
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df["label"] = le.fit_transform(df["intent"])
模型微调
使用 DistilBERT 平衡精度与效率:
from transformers import DistilBertTokenizer, DistilBertForSequenceClassification
# 1. 加载预训练模型
tokenizer = DistilBertTokenizer.from_pretrained("distilbert-base-uncased")
model = DistilBertForSequenceClassification.from_pretrained(
"distilbert-base-uncased",
num_labels=len(le.classes_)
)
# 2. 数据预处理
def encode_text(texts):
return tokenizer(texts.tolist(),
padding=True,
truncation=True,
max_length=64,
return_tensors="pt"
)
# 3. 训练循环(简化版)for epoch in range(3):
for batch in dataloader:
inputs = encode_text(batch["text"])
outputs = model(**inputs, labels=batch["label"])
loss = outputs.loss
loss.backward()
optimizer.step()
关键技巧
-
Attention Mask 处理:
# 标记真实文本长度(避免 pad 干扰)inputs = { "input_ids": tokens, "attention_mask": (tokens != tokenizer.pad_token_id).int()} -
类别不平衡解决方案:
# 使用加权损失函数 from torch.nn import CrossEntropyLoss weights = torch.tensor([1.0, 2.0, 0.5]) # 根据样本数调整 criterion = CrossEntropyLoss(weight=weights)
生产环境优化
模型量化
将 FP32 转为 INT8 提升推理速度:
from transformers import convert_graph_to_onnx
convert_graph_to_onnx.convert(
framework="pt",
model=model,
output_path="intent_model.onnx",
opset=12,
quantization=True
)
实测效果:
- 模型大小:从 260MB → 65MB
- 推理速度:从 120ms → 28ms
Fallback 机制设计
处理低置信度预测:
probs = torch.softmax(outputs.logits, dim=-1)
top_prob, pred_label = torch.max(probs, dim=-1)
if top_prob < 0.7: # 阈值可调
return "FALLBACK"
else:
return le.inverse_transform([pred_label])[0]
避坑指南
- 数据不平衡:
- 对少样本意图进行数据增强(同义词替换、回译)
-
采用分层抽样(Stratified Sampling)划分数据集
-
过拟合:
- 早停机制(Early Stopping)
-
冻结底层参数只微调顶层
for param in model.base_model.parameters(): param.requires_grad = False -
版本控制:
- 使用 MLflow 记录模型版本
- A/ B 测试新模型效果
开放问题
当用户输入完全超出预设意图(如银行场景下问 ” 怎么煮咖啡 ”),你的系统会:
- 直接回复 ” 不理解 ”?
- 引导用户重新表达?
- 触发人工客服?
建议尝试:
- 用 RoBERTa-large 替换当前模型对比效果
- 测试少样本学习(Few-shot Learning)应对新意图
欢迎在评论区分享你的解决方案!
正文完
