数据来源

  • C2 坐席半年的对话日志数据(20220301~20220420),通过二八分拆出训练集和评测集;

数据清洗

  • 全小写
  • 替换html标签、时间(年月日时分秒)、手机号&尾号等
  • 过滤无意义数据(如:“转人工”、“服务评价”、“感谢您的咨询”)
  • 过滤无意义单字Query
  • 添加特殊占位符[pic]、[http]、[subphone]、[phone]、[alnum]、[ques]、[know]
  • 按时间 createtime 对dataframe排序
  • 过滤重复上下文 && 过滤重复答案
  • 全局随机采样负例(easy),使得测试集中「正负比例」=1:1

训练目标

对于话术推荐,输入一个问句,希望输出话术库中与其最合适的回复(以坐席的回复作为话术库)。抛开某些约束规则不说,可以发现该任务本质上都可以抽象为Seq2Seq,即:“输入一个句子,输出另一个句子”。进一步利用前缀树来约束解码过程,即可保证生成的结果在要求的话术库中。

模型输入数据构造

将用户上下文 query (context=3)以及坐席客服回复 A(type=out)用[SEP]占位符拼接;

  • 坐席辅助话术推荐2-详细技术方案 - 图1

其中 Q 表示用户输入 query(type=in),每条的最大长度为 32;A 表示坐席回复 answer(type=out),每条的最大长度为 64。注意:如果存在坐席连续回复,则通过句号将连续的多个回复拼接起来;

模型结构

训练阶段

以“BERT+UniLM”为基础架构,训练一个Seq2Seq模型。

  • 采用Seq2Seq的经典训练方式 Teacher Forcing,输入上下文Q和坐席回复A[:-1],预测A[1:],即将目标A错开一位来训练。PLM 使用腾讯UER开源的BERT权重,优化器是Adam,学习率是2e-5,batch_size=16,大概需要训练15~20个epoch,以 bleu 评测结果作为模型保存依据。

seq2seq+trie.drawio.png

  • 借鉴 UNILM 方案,通过添加一个特别的Mask矩阵,直接用单个Bert模型实现 Seq2Seq LM 任务(无需修改模型架构,且可以直接沿用Bert的 Masked Language Model 预训练权重)。Mask矩阵如下图所示,作用是让Bert输入部分的Attention是双向的,输出部分的Attention是单向,从而满足Seq2Seq的要求。

seq2seq+mask.drawio.png
白色方格代表0,蓝色方格代表 1

损失函数

seq2seq 在生成输出序列的时候是一个time_step生成一个字,换句话说,在每个时间步都在解决一个分类问题。所以这里选择最常用的交叉熵损失函数,但需要利用上述的Mask来屏蔽掉上下文部分的loss(置为零)

推理阶段

利用坐席回复(即答案A)构建前缀树,然后按照前缀树进行 beam search 解码

  • 在 beam search 中采用基于Trie树的剪枝方法:将目标词建立成Trie树结构,在decode过程中根据beam中的前项序列在Trie树中搜索候选的后项token。然后,把模型预测其他字的概率都置零,保证解码过程只走前缀树的分支,而且必须走到最后,这样可以大幅缩减搜索空间及计算开销,同时也保证了生成的句子必然在话术库中;

image.png

  • 前缀树叶节点保存坐席回复对应的富文本格式;
  • 目前支持beam_search 和 random_sample 两种解码策略,通过如下公式计算序列概率:

坐席辅助话术推荐2-详细技术方案 - 图5

评估指标

参考《2022-03-18-生成模型的评估指标》,主要以 BLEU 指标作为选模型的依据;