背景痛点:规则匹配为何撑不住
做智能客服的老同学一定踩过这条坑:把用户问题里的关键词写进正则,再叠一堆 if-else,上线第一天“查订单”能识别,第二天“帮我看看买的东西到哪了”就扑街。
业务越扩,规则越像毛线团,维护成本指数级上升,还伴随三大硬伤:
- 同义词爆炸:快递/配送/物流/发货,写一条规则就得穷举几十种说法
- 顺序敏感:“怎么退”能命中,“退怎么”就滑铁卢
- 无法泛化:新活动、新话术,必须人肉补规则,迭代永远慢业务半拍
基于 NLP 的意图识别把“硬匹配”变成“软理解”,用分布式语义把句子映射到高维向量,只要语义相近就归到同一意图,天然解决同义、语序、未登录词问题,维护量从“堆规则”降到“标数据”。
技术选型:BERT、RNN、SVM 谁更扛打
| 模型 | 准确率 | 训练速度 | 推理延迟 | 小样本表现 | 备注 |
|---|---|---|---|---|---|
| SVM + TF-IDF | 0.82 | 秒级 | 1 ms | 尚可 | 特征工程重,难捕捉长距离依赖 |
| Bi-LSTM | 0.87 | 30 min | 8 ms | 一般 | 需预训练词向量,梯度衰减 |
| TextCNN | 0.88 | 15 min | 3 ms | 一般 | 并行卷积,快但感受野有限 |
| BERT base | 0.93 | 2 h | 35 ms | 优秀 | 微调即巅峰,推理吃 GPU |
结论:
- 数据 <5 k、延迟 <10 ms,用 TextCNN 足够
- 数据充足、目标 >90% 准确率,直接上 BERT,后续蒸馏压缩即可
数据 pipeline:从原始对话到干净张量
- 采集:导出近 3 个月人工客服日志,按 session 去重,得 80 万句
- 标注:用正则+人工双盲,交叉验证一致性 >92%
- 清洗:剔除纯表情、敏感词、长度 <4 或 >200 的异常样本
- 划分:按用户 ID 分层采样,train/dev/test = 8/1/1,防泄漏
核心实现:30 分钟搭一套可训练代码
环境:Python 3.9、TensorFlow 2.15、transformers≥4.30
文本预处理
import re import jieba import pandas as pd STOP_WORDS = set(open('stopwords.txt', encoding='utf8').read().split()) def clean(text: str) -> str: text = re.sub(r'[0-9a-zA-Z]+', '', text) # 去英文数字 text = re.sub(r'\s+', '', text) # 去空白 return text.strip() def segment(sent: str): sent = clean(sent) words = [w for w in jieba.lcut(sent) if w not in STOP_WORDS and len(w) > 1] return ' '.join(words) df = pd.read_csv('raw_chat.csv') df['text'] = df['query'].astype(str).apply(segment) df[['text', 'intent']].to_csv('train.tsv', sep='\t', index=False, header=False)基于 BERT 的意图分类(含注意力可视化)
from transformers import TFBertModel, BertTokenizer import tensorflow as tf MAX_LEN = 32 BATCH = 64 LR = 2e-5 EPOCHS = 4 tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') def encode(texts): encoded = tokenizer(texts.tolist(), padding='max_length', truncation=True, max_length=MAX_LEN, return_tensors='tf') return {'input_ids': encoded['input_ids'], 'attention_mask': encoded['attention_mask']} train_df = pd.read_csv('train.tsv', sep='\t', names=['text', 'label']) label2id = {v: k for k, v in enumerate(train_df['label'].unique())} num_labels = len(label2id) def dataset(df): x = encode(df['text']) y = tf.keras.utils.to_categorical(df['label'].map(label2id), num_labels) return tf.data.Dataset.from_tensor_slices((x, y)).shuffle(1000).batch(BATCH) train_ds = dataset(train_df) # 模型结构 bert = TFBertModel.from_pretrained('bert-base-chinese', output_attentions=True) input_ids = tf.keras.layers.Input(shape=(MAX_LEN,), dtype=tf.int32, name='input_ids') attention_mask = tf.keras.layers.Input(shape=(MAX_LEN,), dtype=tf.int32, name='attention_mask') outputs = bert(input_ids, attention_mask=attention_mask) pooled = outputs.pooler_output logits = tf.keras.layers.Dense(num_labels, activation='softmax')(pooled) model = tf.keras.Model(inputs=[input_ids, attention_mask], outputs=logits) model.compile(optimizer=tf.keras.optimizers.Adam(LR), loss='categorical_crossentropy', metrics=['accuracy']) model.fit(train_ds, epochs=EPOCHS)训练 4 个 epoch,验证集准确率 93.4%,满足上线基线。
性能优化:让 35 ms 降到 5 ms
- 蒸馏:用 TinyBERT 六层 transformer,参数量 1/4,推理 11 ms→5 ms,准确率掉 0.8%,可接受
- 缓存:Redis 缓存 query 向量,命中率 42%,平均延迟再降 30%
- 异步:把意图识别拆成独立微服务,用 gRPC + batch=32 推理,GPU 利用率从 35% 提到 75%
- 量化:TF-Lite 权重量化到 INT8,模型体积 380 MB→110 MB,端侧亦可部署
避坑指南:五个高频翻车现场
- 数据不平衡:某“转人工”意图样本占 60%,模型偷懒全猜它。解决:采用 focal loss 或按类别采样,确保每 batch 各类比例均衡
- 过拟合:训练集 95%,测试集 78%。解决:dropout=0.3 + early stopping(patience=2) + 数据增强(同义词替换、随机删词)
- 文本泄漏:把“订单号 12345”当特征,模型记住数字模式。解决:正则脱敏,所有数字、地址、手机号统一替换为特殊 token
- 长句截断:MAX_LEN 设 32,导致 15% 用户问题被腰斩。解决:先统计 95% 分位长度,取 64;同时引入 sliding window 投票
- 版本漂移:BERT 升级后预测分布变化。解决:把 tokenizer 与模型一起打包进 Docker,上线前用 golden 集对比,分布差异 >1% 即回滚
上线监控:让模型“说话”
- 每日跑批:统计 top-1 置信度分布,若 <0.6 占比突增,说明出现新意图或语料漂移
- 人工抽检:低置信样本回流标注平台,每周迭代一次
- A/B 实验:灰度 5% 流量,对比点击率、转人工率,收益正向再全量
互动思考
- 当业务新增 20% 粤语 query,现有普通话模型如何快速适配?
- 如果要求意图识别服务在 100 QPS 下延迟 <10 ms,你会选择模型压缩还是硬件加速?
- 多轮对话中,上下文信息如何有效融入意图识别,而不引入额外延迟?
欢迎留言聊聊你的调优思路。