贝壳找房【深度语义匹配模型】原理篇二:交互篇

贝壳找房【深度语义匹配模型 】原理篇一:表示型

深度语义匹配模型系列文章的终结篇,带大家走进贝壳找房智能客服的匹配。

一、背景

贝壳找房作为房产领域领先的服务平台,业务场景中包含大量自然语言处理的任务。经纪人与用户的交互过程会产生大量的文本语料,无所不能的小贝助手能够帮助经纪人更好的理解用户的需求。

经纪人在面对客户时会遇到很多业务上的问题,比如二套房的贷款比例,税费如何计算等,而经纪人自己也有需要解答的问题,比如公司某项规章制度的解读,等级积分制度,公积金如何提取等。经过长期的积累,运营人员根据线上用户的提问做总结,沉淀下来的知识形成了房产领域的FAQ库(知识库)。我们的智能客服场景致力于解决经纪人的问题,提供一个便捷的搜索入口,以便后续有人再有相同或相似问题时可以直接搜到答案。

智能客服相比于人工客服具有响应速度快、always online、维护成本低等优势,在有FAQ库的前提下,通过智能化手段辅助人工解决经纪人的问题已逐渐被越来越多的经纪人所接受并优先选择。

二、匹配算法的应用场景

整个智能客服的架构如下图所示:

  • NLU层:NLU层是对用户输入的query进行解析,包含意图识别、分词与词法分析、短语改写等功能。意图识别是为了识别出query的意图,我们通过日志分析总结出了116个意图,其中包含7个Task意图,1个寒暄意图,和108个具体业务意图,会根据不同的意图识别结果走不同的bot。这里的意图识别模型用的是fastText,该模型有高效的训练速度和较高的识别准确率,做出来的结果也可以达到上线使用的标准。词法分析维护了一些词典,通过词典匹配能获得query中的关键词和关键短语。短语改写的目的是为了纠错,比如“搏学考试”手误输入成了“博学考试”,短语改写便能将其纠正,query改写后能更容易召回正确答案。
  • 召回层:召回层是将候选答案从FAQ库中拿回,获得待排序的候选集。此处用了两种召回方式:检索召回和语义召回。检索召回会根据NLU层的意图识别与词法分析结果进行关键词和意图的加权,同时,n-gram的使用也一定程度上减弱了分词错误带来的负面影响。语义召回是将相似问构建成一个faiss索引,这些相似问已经由知识库运营人员总结成标准问,并和知识产生了挂接。query和相似问会被映射到同一个语义向量空间,通过KNN能快速拿到和query最相近的相似问,从而拿到最可能回答query的答案。下图直观展示了语义召回的过程:

  • 排序层:排序层是将召回层拿到的候选知识进行排序,将和query最相关的知识尽可能往前排。排序模型采用了GBDT,GBDT作为一种常用的树模型,可天然地对原始特征进行特征划分、特征组合和特征选择,并得到高阶特征属性和非线性映射。我们考虑用GBDT可以组合多种特征,可扩展性强,并且后期验证GBDT的效果好于单独使用匹配算法效果,因此,当前 匹配算法在排序层中作为一种特征来使用
  • 返回层:query经过上述处理之后会对处理结果进行返回。四种返回层场景展示,依次是Chat-bot,Task-Bot,KBQA和QA-Bot:

«< 左右滑动见更多 »>

三、匹配算法的实践

3.1 数据准备

随着标准问数量的增加,知识库中存在大量query和标准问的匹配对,将这些匹配对当作正样本,label为1。将query随机抽取一条与之不匹配的标准问作为负样本,label为0。共产生12w+的训练样本和5000条测试样本,正负样本比例为1:1。标准问是由人工标注总结,因此数据集质量较高,正样本匹配关系很强,数据比较规整。训练样本展示:

3.2 模型训练

我们对4种交互型的深度语义匹配模型进行尝试,分别是ABCNN(ABCNN-1,ABCNN-2,ABCNN-3),PairCNN,ESIM和Bimpm。使用交互型匹配模型的原因如下:

  1. 交互型的深匹配模型能很好地把握语义焦点,对上下文重要性进行合理建模。
  2. 从训练集数据来看,正样本是人工标注的有监督数据,交互型模型能保证较高的准确率。

模型原理传送门 ( 【深度语义匹配模型】原理篇二:交互篇)。四种模型都有优秀的表现效果,因线上使用的模型是ABCNN-2,这里为大家展示该模型的部分实现代码。

#attention矩阵
def make_attention_mat(x1, x2):
            # 作者论文中提出计算attention的方法 在实际过程中反向传播计算梯度时 容易出现NaN的情况 这里面加以修改
            # euclidean = tf.sqrt(tf.reduce_sum(tf.square(x1 - tf.matrix_transpose(x2)), axis=1))
            # return 1 / (1 + euclidean)

            x1 = tf.transpose(tf.squeeze(x1, [-1]), [0, 2, 1])
            # 矩阵乘法einsum('ij,jk->ik', m0, m1)  # output[i,k] = sum_j m0[i,j] * m1[j, k]
            attention = tf.einsum("ijk,ikl->ijl", x1, tf.squeeze(x2, [-1]))
            return attention

#全部cnn层相关操作
 def CNN_layer(variable_scope, x1, x2, d):
            with tf.variable_scope(variable_scope):

                #卷积
                left_conv = convolution(name_scope="left", x=pad_for_wide_conv(x1), d=d, reuse=False)
                right_conv = convolution(name_scope="right", x=pad_for_wide_conv(x2), d=d, reuse=True)

                #获得注意力矩阵
                left_attention, right_attention = None, None
                att_mat = make_attention_mat(left_conv, right_conv)
                left_attention, right_attention = tf.reduce_sum(att_mat, axis=2), tf.reduce_sum(att_mat, axis=1)

                #pooling(average pooling(w-ap)和qverage pooling(all-ap))
                left_wp = w_pool(variable_scope="left", x=left_conv, attention=left_attention)
                left_ap = all_pool(variable_scope="left", x=left_conv)
                right_wp = w_pool(variable_scope="right", x=right_conv, attention=right_attention)
                right_ap = all_pool(variable_scope="right", x=right_conv)

                return  left_ap, right_ap


#x1,x2是需要匹配的文本embedding后的结果
x1_expanded = tf.expand_dims(x1, -1)
x2_expanded = tf.expand_dims(x2, -1)

#average pooling(all-ap)最终获得为列向量
LO_0 = all_pool(variable_scope="input-left", x=x1_expanded)
RO_0 = all_pool(variable_scope="input-right", x=x2_expanded)

#x1,x2全部cnn层相关操作后所得特征
 LO_1,  RO_1 = CNN_layer(variable_scope="CNN-1", x1=x1_expanded, x2=x2_expanded, d=d0)

#计算相似度
#作者论文中提出计算相似度的方法是将L01_1,R0_1拼接后进行逻辑回归,这里为了简化计算直接