NLP入门系列五:基于双塔结构的语义匹配模型

语义匹配

文本语义匹配一般是计算两端文本之前的语义相关性或相似度,在搜索和自动问答中应用广泛。用深度学习方法解决这类问题一般有两种思路,以搜索中的query和doc为例:一种是query和doc分别学习一个向量表示,最后计算cos相似度;另一种是query和doc先融合,然后接多层网络,最后变成分类问题来优化。
详细可参考:《搜索与推荐中的深度学习匹配》之搜索篇

本文主要将的是第一种情况,就是所谓的双塔结构。

双塔结构

双塔结构总体如下图,一般有两个输入,然后分别接一个表示层,表示层输出向量,最后计算两个向量的相似度得到匹配分。这个结构的可改动点一般就是中间的表示层,表示层可大可小,小点就是RNN和CNN,大一点就是Transformer和Bert模型,都可以使用。输出的匹配层一般用cos相似度计算。
在这里插入图片描述

下面以TextCNN和CDSSM模型为基础表示层,构建双塔结构模型做语义匹配。

基于TextCNN的语义匹配

完整示例代码参考zh_sent_semantic_match.ipynb:https://github.com/huanghao128/zh-nlp-demo

TextCNN模型主要用在文本分类任务,通过不同的卷积窗口大小,来捕捉文本序列信息。模型结构如下图所示。
在这里插入图片描述
下面利用TextCNN的倒数第2层的输出来作为文本向量表示,完成双塔模型构建。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import backend as K

print(tf.__version__)

class BaseTextCNN(keras.Model):
    def __init__(self, filters, kernel_sizes, output_dim, name):
        super(BaseTextCNN, self).__init__(name=name)
        self.kernel_sizes = kernel_sizes
        self.conv_layers = []
        self.max_poolings = []
        for kernel_size in kernel_sizes:
            self.conv_layers.append(
                keras.layers.Conv1D(filters=filters, kernel_size=kernel_size, 
                                activation='relu', padding="same")
            )
            self.max_poolings.append(keras.layers.GlobalMaxPool1D())
        self.concatenate = keras.layers.Concatenate()
        self.dense = keras.layers.Dense(output_dim, activation='tanh')
        
    def call(self, inputs):
        convs = []
        for i in range(len(self.kernel_sizes)):
            x = self.conv_layers[i](inputs)
            x = self.max_poolings[i](x)
            convs.append(x)
        x = self.concatenate(convs)
        output = self.dense(x)
        return output

语义匹配的完整模型:

def match_model(vocab_size, max_len, embedding_size, filters, kernel_sizes, output_dim):
    input1 = keras.layers.Input(name='sent1', shape=(max_len,))
    input2 = keras.layers.Input(name='sent2', shape=(max_len,))

    embedding = keras.layers.Embedding(vocab_size, embedding_size)
    sent1_embed = embedding(input1)
    sent2_embed = embedding(input2)

    output_sent1 = BaseTextCNN(filters, kernel_sizes, output_dim, name="textcnn_left")(sent1_embed)
    output_sent2 = BaseTextCNN(filters, kernel_sizes, output_dim, name="textcnn_right")(sent2_embed)

    cosine_output = keras.layers.Dot(axes=[1, 1], normalize=True)([output_sent1, output_sent2])
    outputs = keras.layers.Dense(1, activation='linear', name="output")(cosine_output)

    model = keras.models.Model(inputs=[input1, input2], outputs=outputs)
    
    return model

初始化一些参数,并打印模型结构。

K.clear_session()

max_len = 15 # 文本最大长度
vocab_size = len(vocab2id) # 词典大小
embedding_size = 128
filters = 200
kernel_sizes = [3,4,5]
output_dim = 100

model = match_model(vocab_size, max_len, embedding_size, filters, kernel_sizes, output_dim)

print(model.summary())

在这里插入图片描述
训练完后保存模型需要注意,上面BaseTextCNN是自定义模型,直接model.save()好像会有问题,可以用save_weights()或者tf.saved_model.save()

# 只保存模型权重,使用时模型结构需要重新初始化
model.save_weights("output/match_model_weight.h5")

# 保存为pb格式,可以直接使用tf-serving部署调用
tf.saved_model.save(model, "output/match_model")

基于CNN-DSSM的语义匹配

CNN-DSSM主要针对英文这种以字符为最小单元的语言,来做双塔结构的语义匹配。其中卷积的单元是letter-trigram,也就是把单词拆分成3个连续的字符,作为语义表示的最小单元。这种方法可以解决出现不在词典中的词问题,也能学习到语义表示。CNN-DSSM在双胎模型中的表示层结构图如下:
在这里插入图片描述

这里写一个简单的实现版本。

from tensorflow import keras
from tensorflow.keras import backend as K

def base_network(inputs, filters, kernel_size, output_dim, name):
    x = keras.layers.Conv1D(filters=filters, kernel_size=kernel_size, strides=1, 
                            activation='relu', padding="same")(inputs)
    x = keras.layers.Dropout(0.2)(x)
    x = keras.layers.GlobalMaxPool1D()(x)
    output = keras.layers.Dense(output_dim, activation='tanh', name=name)(x)
    return output

def create_model(max_len, trigram_size, filters, kernel_size, output_dim):
    sent_left = keras.layers.Input(name='sent_left', shape=(max_len, trigram_size))
    sent_right = keras.layers.Input(name='sent_right', shape=(max_len, trigram_size))

    left_output = base_network(sent_left, filters, kernel_size, output_dim, name="left")
    right_output = base_network(sent_right, filters, kernel_size, output_dim, name="right")

    cosine_output = keras.layers.Dot(axes=[1, 1], normalize=True)([left_output, right_output])
    outputs = keras.layers.Dense(num_classes, activation='linear')(cosine_output)

    model = keras.models.Model(inputs=[sent_left, sent_right], outputs=outputs)
    
    return model

模型初始化例子

max_len = 10 # 句子最大长度
trigram_size = 30000 # trigram大小
filters = 300
kernel_size = 3
output_dim = 128

model = create_model(max_len, trigram_size, filters, kernel_size, output_dim)
print(model.summary())

在这里插入图片描述

其他语义匹配模型

上面讲的是representation-based的方法,还有一类是interaction-based方法,如ARC-II、MatchPyramid、Match-SRNN等,这些在开源项目MatchZoo中都有实现。


参考文档
[1]. https://www.jiqizhixin.com/articles/2017-06-15-5
[2]. https://zhuanlan.zhihu.com/p/38296950
[3]. https://www.cnblogs.com/guoyaohua/p/9229190.html
[4]. https://github.com/NTMC-Community/MatchZoo

posted @ 2020-09-20 18:16  黄然小悟  阅读(1158)  评论(0编辑  收藏  举报