背景痛点:为什么“听懂人话”这么难?
智能客服每天面对的不是标准考题,而是“人话”——充满省略、倒装、方言甚至错别字。把一句“我那个啥,咋还没到账?”准确映射到“查询转账进度”这个意图,比想象的要难:
- 多轮对话歧义:用户上一句说“我要退”,下一句补“那个保险”,两句话隔了3轮,模型得把“退保”拼回来。
- 方言与口语:粤语“唔该查下我条数”如果按字面切词,会把“条数”当成“一条数学”。
- 领域迁移:在电商场景训练好的模型,放到银行场景,“余额”一词从“优惠券余额”变成“账户余额”,准确率瞬间掉半。
- 长尾意图:1000个意图里,有800个样本数<10,模型一学就忘。
- 实时性:高峰期并发1w QPS,BERT-base原模型一张A10 GPU只能扛500 QPS,延迟还飙到400 ms。
一句话,意图识别既要“准”,又要“快”,还要“省”。
技术对比:从正则到Transformer
| 方案 | 准确率(Top-1) | 平均响应时间 | 训练成本 | 备注 |
|---|---|---|---|---|
| 正则+关键词 | 62% | 2 ms | 几乎0 | 规则冲突后维护爆炸 |
| SVM+TF-IDF | 78% | 8 ms | 1 h/1w样本 | 特征工程苦力活 |
| TextCNN+Word2Vec | 84% | 12 ms | 2 h/1w样本 | 对语序不敏感 |
| BERT-base微调 | 91.5% | 90 ms | 1 h/5w样本 | GPU显存≥8 G |
| BERT+蒸馏(TinyBERT) | 89% | 18 ms | +2 h蒸馏 | 精度掉2%以内,省5×显存 |
结论:
- 冷启动/样本<1k,正则+SVM先顶,快速迭代。
- 样本>5k且延迟<50 ms,必须走“微调→蒸馏→量化”三步跳。
核心实现:PyTorch微调BERT
下面用银行客服场景demo,意图共10类,样本极度不平衡(“查询余额”占60%,其余分散)。
1. 数据预处理
# dataset.py import torch, json, random from transformers import BertTokenizer from sklearn.utils import resample tokenizer = BertTokenizer.from_pretrained("bert-base-chinese") def load_samples(path): with open(path, encoding="utf-8") as f: data = [json.loads(l) for l in f] # 分层采样,解决imbalance df = pd.DataFrame(data) balanced = df.groupby("label", group_keys=False).apply( lambda x: resample(x, n_samples=2000, replace=True, random_state=42) ) return balanced.values.tolist() def encode(text, max_len=32): return tokenizer(text, max_length=max_len, padding="max_length", truncation=True)2. 自定义Dataset与过采样
class IntentDataset(torch.data.Dataset): def __init__(self, samples): self.samples = samples def __getitem__(self, idx): text, label = self.samples[idx] encoded = encode(text) return {k: torch.tensor(v) for k,v in encoded.items()}, \ torch.tensor(int(label)) def __len__(self): return len(self.samples)3. 损失函数优化:Focal Loss压制头部类
class FocalLoss(torch.nn.Module): def __init__(self, alpha=1, gamma=2): super().__init__() self.alpha, self.gamma = alpha, gamma def forward(self, logits, labels): ce = torch.nn.functional.cross_entropy(logits, labels, reduction="none") p = torch.exp(-ce) return (self.alpha * (1-p)**self.gamma * ce).mean()4. 微调脚本
# train.py from transformers import BertForSequenceClassification, Trainer, TrainingArguments model = BertForSequenceClassification.from_pretrained( "bert-base-chinese", num_labels=10) training_args = TrainingArguments( output_dir="./ckpt", per_device_train_batch_size=64, learning_rate=2×10⁻⁵, num_train_epochs=3, logging_steps=50, save_total_limit=2, load_best_model_at_end=True, metric_for_best_model="f1" ) trainer = Trainer( model=model, args=training_args, train_dataset=IntentDataset(load_samples("train.json")), eval_dataset=IntentDataset(load_samples("dev.json")), compute_metrics=lambda p: {"f1": f1_score(p.label, p.predictions.argmax(-1))}, # 关键:替换默认CE loss_fct=FocalLoss(alpha=1, gamma=2) ) trainer.train()5. Attention权重可视化(调试神器)
def show_attention(sentence, layer=11, head=0): inputs = encode(sentence) with torch.no_grad(): out = model.bert(**{k:v.unsqueeze(0) for k,v in inputs.items()}, output_attentions=True) att = out.attentions[layer][0, head].cpu() # [seq_len, seq_len] sns.heatmap(att, xticklabels=tokenizer.convert_ids_to_tokens(inputs["input_ids"]))性能优化:让BERT跑在20 ms以内
- 知识蒸馏:用BERT-base当teacher,TinyBERT(4层)当student,蒸馏后精度掉1.8%,延迟降到18 ms。
- 量化+TensorRT:
- PyTorch→ONNX→INT8校准(500句随机语料)→TensorRT engine
- 显存占用从6.3 G→1.7 G,QPS提升3.2×,P99延迟28 ms。
- 异步流水线:
- 前置“快速正则”过滤高置信FAQ,命中率35%,直接返回;
- 长尾请求入模型队列,批量攒64条再推理,GPU利用率>75%。
避坑指南:上线后才懂的那些坑
- 领域偏差:预训练BERT对“基金”“理财”偏向财经语料,银行客服却常指“货基理财产品”。解决:继续预训练(Additional Pre-training)+ 领域语料2万句,MLM任务3 epoch,准确率再提1.7%。
- 在线学习回滚:新版本模型灰度5%流量,一旦异常指标(Top-1下降>2%或负反馈率>基准1.5×)触发,10 s内切换回旧版。实现:TensorRT engine双版本+共享内存热插拔。
- 标注一致性:三人标注团队,Kappa<0.65,模型永远“背锅”。先对齐Guideline,再训练;否则再强的BERT也救不了。
延伸思考:留给读者的三个开放题
- 小样本场景下,如何用Prompt Tuning让意图识别只凭10条样本就达到85%精度?
- 多模态意图:用户发语音+截图,语音转写误差10%,如何把图文信息融合进统一意图空间?
- 强化学习能否用来做“拒答”策略,让客服勇敢说“我不会”,而不是胡乱回复?
把这三个问题想明白,你的智能客服就离“真智能”又近了一步。