背景痛点:人工抽检的“三座大山”
做客服的同学都懂,每天上万通录音,质检组只能随机抽 3%~5%。结果往往是:
- 漏检:客户已经投诉到微博上了,内部还没发现哪句话踩了红线。
- 主观:同一条录音,A 质检员给 85 分,B 质检员给 60 分,最后谁也说服不了谁。
- 低效:听 30 分钟录音、填 15 分钟表单,人均日处理量不到 20 单,人力成本却年年涨。
业务侧给技术部的需求很明确:覆盖率 100%、实时性 < 300 ms、F1-score ≥ 0.85,还要能随着话术变化一周一迭代。传统正则+关键词的规则引擎,在第一版就败下阵来——新活动上线 2 小时,话术就变了,规则还没来得及热更新。
技术选型:为什么直接上深度学习
我们先后跑了三组对照实验,数据如下(内部 10 万通录音,标注 1.2 万通):
| 方案 | 准确率 | F1-score | 迭代周期 | 备注 |
|---|---|---|---|---|
| 规则引擎 | 0.68 | 0.65 | 1 d | 维护 1 800 条正则,崩溃 |
| 传统 ML(TF-IDF+SVM) | 0.78 | 0.76 | 3 d | 特征工程占 70% 工作量 |
| 深度学习(BERT+CRF) | 0.89 | 0.87 | 0.5 d | 端到端,无需人工特征 |
结论很直观:深度学习在“准、快、省”三个维度全面胜出。再加上迁移学习,能把通用语料里练好的权重 Warm-start 到客服领域,冷启动数据只需 3 000 通即可收敛。
核心实现:三大模块拆解
语音识别模块(Kaldi 链式模型)
我们采用 Kaldi 的「chain」TDNN-F 模型,16 kHz 采样,训练语料 5 000 小时混合客服场景。输出带时间戳的 JSON,方便后续对齐。
关键脚本(精简版):
# 1. 特征提取 steps/make_mfcc.sh --cmd "$train_cmd" --nj 32 data/train exp/make_mfcc/train mfcc # 2. 训练 chain 模型 local/chain/run_tdnn.sh --stage 0 --train-set train --gmm gmm_ali # 3. 解码并输出词级时间戳 online2-wav-nnet3-latgen-faster \ --online=false --do-endpointing=false \ --config=exp/chain/tdnn1a/conf/online.conf \ "ark:echo utterance-id1 utterance-id1|" "scp:echo utterance-id1 audio.wav|" \ ark:/dev/null输出示例:
{"uid": "call-123", "words": [ {"word": "你好", "start": 0.32, "end": 0.68}, {"word": "请问", "start": 0.70, "end": 0.95} ]}文本情感分析(BERT 微调)
目标:识别「负面情绪」「威胁投诉」两类标签。采用 bert-base-chinese,加一层 256 维 BiLSTM 后接 Attention。
损失函数: $$ \mathcal{L} = -\sum_{i=1}^{N} y_i \log(p_i) + \lambda |\theta|^2 $$
训练 3 个 epoch,batch=32,lr=2e-5,F1 达到 0.91。
# train_sentiment.py from transformers import BertTokenizer, TFBertForSequenceClassification import tensorflow as tf tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') model = TFBertForSequenceClassification.from_pretrained( 'bert-base-chinese', num_labels=2) def encode_batch(texts): return tokenizer(texts, padding=True, truncation=True, max_length=128, return_tensors='tf') train_enc = encode_batch(train_texts) val_enc = encode_batch(val_texts) optimizer = tf.keras.optimizers.Adam(learning_rate=2e-5) model.compile(optimizer=optimizer, loss=tf.keras.losses.SparseCategoricalEntropy(), metrics=['accuracy']) model.fit(train_enc['input_ids'], train_labels, validation_data=(val_enc['input_ids'], val_labels), epochs=3, batch_size=32)关键信息抽取(NER)
客服场景要抽「订单号」「手机号」「违规关键词」。用 BERT+CRF,标签采用 BIO 方案。CRF 层保证标签合法性,避免 I-ORDER 跟在 B-PHONE 后面这种荒诞序列。
解码得分: $$ S(X, y) = \sum_{i=0}^{n} A_{y_i,y_{i+1}} + \sum_{i=1}^{n} P_{i,y_i} $$
其中 $A$ 是转移矩阵,$P$ 是 BERT 输出的 logits。维特比解码即可。
# ner_predict.py from transformers import BertTokenizer from tensorflow_addons.text import crf_decode tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') logits, seq_len, trans_params = model(input_ids) # model 已训练 decoded, _ = crf_decode(logits, trans_params, seq_len)代码示例:端到端推理流程
把三大模块串起来,一个 200 ms 内完成质检:
# inference.py import asyncio, json, time async def asr_predict(wav_path): proc = await asyncio.create_subprocess_exec( 'online2-wav-nnet3-latgen-faster', ..., stdout=asyncio.subprocess.PIPE) stdout, _ = await proc.communicate() return json.loads(stdout) async def sentiment_predict(text): encoded = tokenizer(text, return_tensors='tf', max_length=512, truncation=True) logits = bert_model(encoded['input_ids']).logits prob = tf.nn.softmax(logits)[0].numpy() return prob[1] # 负面情绪概率 async def ner_predict(text): logits, seq_len, trans = ner_model(tokenizer(text)) decoded, _ = crf_decode(logits, trans, seq_len) return decoded.numpy() async def qc_pipeline(wav_path): t0 = time.time() asr_out = await asr_predict(wav_path) text = ''.join([w['word'] for w in asr_out['words']]) sent_score, ner_tags = await asyncio.gather( sentiment_predict(text), ner_predict(text)) return {'text': text, 'sentiment': sent_score, 'entities': ner_tags, 'latency': time.time()-t0}实测单通录音 15 s,P99 延迟 180 ms,满足实时坐席辅助场景。
性能优化:让 GPU 不白烧
- 模型量化:BERT 权重用 TensorRT INT8 量化,大小 330 MB → 87 MB,推理提速 2.3×,F1 只掉 0.008。
- 异步批处理:把 50 ms 窗口内的请求动态 batch,GPU 利用率从 35% 提到 78%。
- 缓存策略:同一批次手机号、订单号出现频率极高,NER 结果做 LRU 缓存,命中率 62%,QPS 提升 40%。
避坑指南:踩过的坑,帮你填平
| 坑 | 现象 | 根因 | 解法 |
|---|---|---|---|
| 数据不平衡 | 负面情绪样本只占 7%,模型全预测为负 | 标注成本高 | 采用 focal loss,$\gamma=2$,召回率提升 19% |
| 冷启动 | 新业务 0 数据 | 无标注 | 先用规则跑 1 周收集高置信数据,再人工复核 500 通,半监督自训练 |
| 语音截断 | ASR 断句错误导致情感错位 | VAD 阈值保守 | 引入句级情感平滑,滑动窗口投票 |
| 版本回滚 | 上线后 F1 掉 5% | 训练/线上分布漂移 | 增加 weekly retrain,监控 KL 散度 > 0.02 自动回滚 |
总结与扩展:下一步往哪走
把中文模型搬到英文、日文,只需替换预训练 BERT 为多语言版,再灌 1 万通对应语料,两周就能复现。更远的计划:
- 加入说话人分离,区分坐席与客户,分别质检;
- 引入强化学习,把投诉率作为奖励,自动优化话术策略;
- 对接企业微信、飞书,把质检结果推送给一线主管,实现分钟级纠偏。
如果你也在做客服系统,不妨从「情感+NER」两个小模型先跑通,再逐步把 ASR、实时告警、可视化报表串成闭环。深度学习不是银弹,但用对了,确实能把 40% 的准确率差距挣回来。祝各位迭代顺利,少踩坑,多上线。