背景痛点:多轮对话里的“语义漂移”
智能客服上线后,最常收到的用户吐槽不是“答非所问”,而是“聊着聊着就歪楼”。
根本原因在于三点:
- 动态语义理解:用户会在第 3 轮突然把“退货”说成“想退掉”,第 5 轮又冒出“那个订单我不要了”。字面差异大,但意图一致;规则引擎靠关键词叠加,维护成本指数级上升。
- 多轮状态漂移:传统 slot-filling 把每一轮当独立分类任务,缺少全局上下文,导致实体被重复抽取或覆盖。
- 并发峰刺:大促期间 QPS 从 200 飙到 3 k,同步推理 + 数据库锁让 P99 延迟直接破 2 s,用户体验“秒变智障”。
技术选型:规则、ML、DL 三维对比
| 维度 | 规则引擎 | 传统 ML( fastText + LR ) | 微调 BERT |
|---|---|---|---|
| 准确率 | 85 %(上限明显) | 90 % | 94 % |
| QPS/核 | 4000 | 8000 | 1200 |
| 维护成本 | 随规则线性增长 | 需持续标注 | 一次性标注 + 增量 |
| 领域迁移 | 重写规则 | 重训模型 | 继续微调 |
| 硬件成本 | 低 | 低 | 单卡 A10 即可 |
结论:
- 对长尾意图用 BERT 打主力,对头部高频问题用规则做 fallback,兼顾准确率与吞吐。
- GPT 类生成模型在客服场景幻觉风险高,暂按“人工兜底”策略,不进入核心链路。
核心实现
1. BERT 微调(PyTorch 1.13)
数据约定:
- 每行 JSON
{"text":"我要退货","intent":"return_goods"} - 训练集 2.1 w,验证集 3 k,共 32 意图
# data.py import json, torch from torch.utils.data import Dataset from transformers import BertTokenizer tokenizer = BertTokenizer.from_pretrained("bert-base-chinese") MAX_LEN = 32 class IntentDataset(Dataset): def __init__(self, path): with open(path, encoding="utf8") as f: samples = [json.loads(l) for l in f] self.texts = [s["text"] for s in samples] self.labels = [s["intent"] for s in samples] self.label2id = {l: i for i, l in enumerate(sorted(set(self.labels)))} def __len__(self): return len(self.texts) def __getitem__(self, idx): enc = tokenizer(self.texts[idx], max_length=MAX_LEN, truncation=True, padding="max_length") return {k: torch.tensor(v) for k, v in enc.items()}, \ torch.tensor(self.label2id[self.labels[idx]])# model.py import torch.nn as nn from transformers import BertModel class IntentClassifier(nn.Module): def __init__(self, n_classes): super().__init__() self.bert = BertModel.from_pretrained("bert-base-chinese") self.drop = nn.Dropout(0.3) self.out = nn.Linear(768, n_classes) def forward(self, input_ids, attention_mask): pooled = self.bert(input_ids, attention_mask).pooler_output return self.out(self.drop(pooled))# train.py from torch.cuda.amp import autocast, GradScaler from sklearn.metrics import f1_score model = IntentClassifier(n_classes=32).cuda() optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5) scheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr=2e-5, steps_per_epoch=len(train_loader), epochs=5) scaler = GradScaler() loss_fn = nn.CrossEntropyLoss() for epoch in range(5): for batch, labels in train_loader: batch = {k: v.cuda() for k, v in batch.items()} optimizer.zero_grad() with autocast(): logits = model(**batch) loss = loss_fn(logits, labels.cuda()) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() scheduler.step()时间复杂度:
- 训练阶段:O(N·L²) 其中 N 为样本数,L 为平均 token 数,主要来自 BERT 自注意力矩阵计算。
- 推理阶段:batch=1 时延迟 12 ms(T4),QPS≈1200,满足线上 80 % 流量。
2. 异步消息架构
sequenceDiagram participant C as 客户端 participant G as API 网关 participant K as Kafka participant W as 意图 Worker participant R as Redis 缓存 C->>G: 发送问题 G->>K: 消息{key=uid, value=question} G-->>C: 返回"处理中" K->>W: 拉取分区 W->>W: BERT 推理 W->>R: 缓存结果 TTL=300s W->>K: 发送回包{uid, answer} G->>C: 推送答案背压机制:
- Worker 消费速率低于生产速率时,Kafka 消费 lag 上升;当 lag>阈值,网关直接返回“排队中”,避免无限堆积。
- 每条消息带 16 bit 递增 sequence,网关用 uid+sequence 做幂等,防止重复推送。
生产考量
1. 模型冷启动
首日上线仅 200 条标注样本,BERT 微调严重过拟合。采用“迁移+增量”两阶段:
- 用历史 6 个月工单日志做远监督,先跑式聚类生成 1.8 w 弱标签数据,训练 3 epoch 获得基线模型。
- 线上部署后,把用户点踩/点赞作为标注,每日凌晨用
lr=5e-6继续微调 1 epoch;loss 下降 <0.01 自动停止,防止灾难性遗忘。
2. 对话状态幂等
多轮场景下,用户可能重复发送同一句话。网关层为每个 uid 维护单调递增dialog_seq,Worker 处理前先在 Redis 查询last_seq:
- 若当前 seq ≤ last_seq,直接返回缓存结果,保证重试安全。
- 状态存储采用 Hash 结构
uid:{seq:int, slots:dict, intent:int},TTL 15 min,兼顾内存与体验。
避坑指南
领域适配陷阱
预训练 BERT 对电商“优惠券”“定金尾款”等词义不敏感。直接微调 30 epoch 后,指标仍掉 6 %。
解决:在 tokenizer 阶段加入 5 k 领域词做 Whole Word Masking,再训练 MLM 1 w 步,然后接下游意图任务,准确率回升 3.8 %。缓存雪崩
大促 0 点整点秒杀,缓存同时失效,请求直击推理节点,CPU 打马上飙 100 %。
预防:- 给每个 key 加随机 jitter TTL∈[250,350]s;
- 采用 Redis 6 bucket 集群 + local 热点缓存(Caffeine)二级架构,命中率 96 %;
- 限流:令牌桶算法,桶容量=Worker 数×QPS×0.8。
延伸思考:小样本学习的边界
当新品类(如“海外购”)上线,仅有 30 条样本时,继续微调 BERT 反而降低旧类指标。尝试两种方案:
- Prototypical Network 做 5-way-5-shot,F1 92 %,但推理需额外向量检索,延迟 +8 ms。
- 用 GPT-3.5 做数据增强:少样本 prompt → 生成 2 k 相似句,再人工抽检 10 %,回炉微调 BERT,F1 90 %,无额外延迟。
结论:小样本场景下,生成式增强 > 度量学习,但仍需人工质检,不能完全无人化。
把规则、深度模型、异步架构按“三明治”方式叠在一起后,系统 P99 延迟从 2.1 s 降到 380 ms,意图准确率由 85 % 提到 94 %,运维人天从每月 30 h 降到 5 h。
对中小团队来说,先用 BERT 解决“准”,再用 Kafka 解决“快”,最后用缓存解决“稳”,是当下性价比最高的落地路径。