基于bert的文本匹配任务(二)

文本匹配任务是nlp中非常常见的任务,最常用的场景包括文本搜索、智能客服、推荐等。简单的文本匹配算法有字面匹配,包括词频,ngram等,基本上通过tf-idf,ngram等算法统计词频,得到句子的数值向量,然后进行距离计算,得到文本的距离数值,距离越小则代表文本之间的相似度越高。但是通过词频统计得到的句子向量有两个弊端:其中之一是由于词的类别太多,得到的向量为稀疏向量,维度太高;其二,词频的匹配基本都是字面意义上的匹配,相同的词出现越多代表两个文本越相似,忽视了句子本身的语义信息,具有一定的局限性。后续出现的word2vec等浅层语义词向量则是一种用稠密向量表示词义的算法。而bert作为一种深层语义信息表示的预训练语言模型也在文本匹配的任务有非常不错的效果,下面就介绍如何使用bert训练文本匹配模型。

一、数据处理

文本匹配的训练数据格式如下:

sentence1sentence2label
用微信都6年,微信没有微粒贷功能4。  号码来微粒贷0

0表示句子不相似,1表示句子相似。这里在数据输入和命名实体识别的任务不同,它的输入是两个句子,分别为query和doc,因此数据处理的代码会不一样,处理代码如下:

    def gen_data(self, inputs_idx, labels_idx):
        '''
        生成批次数据
        :return:
        '''
        query_word_ids, query_segment_ids, query_word_mask, query_sequence_length, \
        sim_word_ids, sim_segment_ids, sim_word_mask, sim_sequence_length = inputs_idx[0], inputs_idx[1],inputs_idx[2],\
                                                                                       inputs_idx[3],inputs_idx[4],inputs_idx[5],\
                                                                                       inputs_idx[6],inputs_idx[7]
        batch_word_ids_a, batch_segment_ids_a, batch_word_mask_a, batch_sequence_length_a, \
        batch_word_ids_b, batch_segment_ids_b, batch_word_mask_b, batch_sequence_length_b, batch_output_ids= [], [], [], [], [], [], [], [], []

        for i in range(len(query_word_ids)):
            batch_word_ids_a.append(query_word_ids[i])
            batch_segment_ids_a.append(query_segment_ids[i])
            batch_word_mask_a.append(query_word_mask[i])
            batch_sequence_length_a.append(query_sequence_length[i])

            batch_word_ids_b.append(sim_word_ids[i])
            batch_segment_ids_b.append(sim_segment_ids[i])
            batch_word_mask_b.append(sim_word_mask[i])
            batch_sequence_length_b.append(sim_sequence_length[i])

            batch_output_ids.append(labels_idx[i])


            if len(batch_output_ids) == self.batch_size:
                yield dict(
                input_word_ids_a=np.array(batch_word_ids_a, dtype="int32"),
                input_mask_a=np.array(batch_word_mask_a, dtype="int32"),
                input_type_ids_a=np.array(batch_segment_ids_a, dtype="int32"),
                input_word_ids_b=np.array(batch_word_ids_b, dtype="int32"),
                input_mask_b=np.array(batch_word_mask_b, dtype="int32"),
                input_type_ids_b=np.array(batch_segment_ids_b, dtype="int32"),
                input_target_ids=np.array(batch_output_ids, dtype="float32")
                )
                batch_word_ids_a, batch_segment_ids_a, batch_word_mask_a, batch_sequence_length_a, \
                batch_word_ids_b, batch_segment_ids_b, batch_word_mask_b, batch_sequence_length_b, batch_output_ids = [], [], [], [], [], [], [], [], []

输入数据中加入了sequence_length,是考虑到后续去句子向量的时候如果做均值池化操作则需要截取原始句子长度的向量做平均池化。

二、模型结构

模型结构就是常见的双塔模型,也就是simBert,将两个句子分别通过bert得到句子向量,然后计算余弦相似度得到损失数值,结构图如下:

左边是交互式的匹配,右边是特征式的匹配模型,一般来说离线训练采用的是特征式的匹配模型,也就是右边的结构。

bert内部结构在上一篇基于bert的命名实体识别任务(一)_donruo的博客-CSDN博客_bert命名实体识别

已经介绍过了,这里就不介绍了。

网络结构源码如下:

class SimBert(tf.keras.Model):
  """
  bert句子相似度模型
  """

  def __init__(self,
               network,
               config,
               initializer='glorot_uniform',
               dropout_rate=0.1,
               ):
      self._self_setattr_tracking = False
      self._network = network
      self._config = {
          'network': network,
          'initializer': initializer,
      }
      self.config = config
      #定义两个句子的输入
      # 定义输入
      word_ids_a = tf.keras.layers.Input(shape=(None,), dtype=tf.int32, name='input_word_ids_a')
      mask_a = tf.keras.layers.Input(shape=(None,), dtype=tf.int32, name='input_mask_a')
      type_ids_a = tf.keras.layers.Input(shape=(None,), dtype=tf.int32, name='input_type_ids_a')
      word_ids_b = tf.keras.layers.Input(shape=(None,), dtype=tf.int32, name='input_word_ids_b')
      mask_b = tf.keras.layers.Input(shape=(None,), dtype=tf.int32, name='input_mask_b')
      type_ids_b = tf.keras.layers.Input(shape=(None,), dtype=tf.int32, name='input_type_ids_b')
      input_a = [word_ids_a, mask_a, type_ids_a]
      input_b = [word_ids_b, mask_b, type_ids_b]

      #计算encoder
      outputs_a = network.predict_step(input_a)
      outputs_b = network.predict_step(input_b)

      cls_output_a = outputs_a[1]
      query_embedding_output = tf.keras.layers.Dropout(rate=dropout_rate)(cls_output_a)

      cls_output_b = outputs_b[1]
      sim_query_embedding_output = tf.keras.layers.Dropout(rate=dropout_rate)(cls_output_b)

      # 余弦函数计算相似度
      # cos_similarity余弦相似度[batch_size, similarity]
      query_norm = tf.sqrt(tf.reduce_sum(tf.square(query_embedding_output), axis=-1), name='query_norm')
      sim_query_norm = tf.sqrt(tf.reduce_sum(tf.square(sim_query_embedding_output), axis=-1), name='sim_query_norm')

      dot = tf.reduce_sum(tf.multiply(query_embedding_output, sim_query_embedding_output), axis=-1)
      cos_similarity = tf.divide(dot, (query_norm * sim_query_norm), name='cos_similarity')
      self.similarity = cos_similarity

      # 预测为正例的概率
      cond = (self.similarity > self.config["neg_threshold"])
      pos = tf.where(cond, tf.square(self.similarity), 1 - tf.square(self.similarity))
      neg = tf.where(cond, 1 - tf.square(self.similarity), tf.square(self.similarity))
      predictions = [[neg[i], pos[i]] for i in range(self.config['batch_size'])]

      self.logits = self.similarity
      outputs = dict(logits=self.logits, predictions=predictions)

      super(SimBert, self).__init__(inputs=[input_a, input_b], outputs=outputs)

三、构建损失值

损失函数采用的也是常见的对比损失,具体解释可以参考之前的一篇

基于tensorflow2.x的文本匹配任务(四)_donruo的博客-CSDN博客

loss的代码如下:

    def build_losses(self, labels, model_outputs, metrics, aux_losses=None) -> tf.Tensor:
        '''
        构建损失
        '''
        with tf.name_scope('TextMatchTask/losses'):
            if self.config['model_name'] == 'simbert':
                # 构建对比损失
                y = tf.reshape(labels, (-1,))
                similarity = model_outputs['logits']
                cond = (similarity < self.config["neg_threshold"])
                zeros = tf.zeros_like(similarity, dtype=tf.float32)
                ones = tf.ones_like(similarity, dtype=tf.float32)
                squre_similarity = tf.square(similarity)
                neg_similarity = tf.where(cond, squre_similarity, zeros)

                pos_loss = y * (tf.square(ones - similarity) / 4)
                neg_loss = (ones - y) * neg_similarity
                losses = pos_loss + neg_loss
                loss = tf.reduce_mean(losses)
                return loss

            metrics = dict([(metric.name, metric) for metric in metrics])
            losses = tf.keras.losses.sparse_categorical_crossentropy(labels,
                                                                     tf.cast(model_outputs['predictions'], tf.float32),
                                                                     from_logits=True)

            loss = tf.reduce_mean(losses)

            return loss

完整的代码可以参考我的github,里面有完整的从数据处理到模型训练预测的全部过程。https://github.com/dextroushands/pretraind_model_for_nlp_tasks

四、后续

考虑到文本匹配在生产环境中的应用,由于bert模型的体量大,部署起来需要较高的性能,后续考虑模型蒸馏技术让模型更轻量化。

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BERT是一种预训练的深度学习模型,可以用于文本分类、文本相似度计算等自然语言处理任务。下面是基于BERT实现文本相似计算的主要步骤: 1. 数据预处理:将文本转换为向量表示,可以使用BERT的tokenizer将文本转换为token ids,并添加特殊标记如[CLS]和[SEP]。 2. 加载预训练的BERT模型:可以使用Hugging Face的transformers库加载预训练的BERT模型。 3. Fine-tuning:在训练集上对BERT模型进行微调,以便更好地处理具体任务。 4. 相似度计算:使用微调后的BERT模型计算文本之间的相似度。可以使用余弦相似度计算两个向量之间的相似度。 下面是一个基于BERT文本相似计算的示例代码: ```python from transformers import BertTokenizer, BertModel import torch.nn.functional as F import torch tokenizer = BertTokenizer.from_pretrained('bert-base-uncased') model = BertModel.from_pretrained('bert-base-uncased') def get_bert_embedding(text): input_ids = torch.tensor(tokenizer.encode(text, add_special_tokens=True)).unsqueeze(0) outputs = model(input_ids) last_hidden_state = outputs.last_hidden_state mean_last_hidden_state = torch.mean(last_hidden_state, dim=1) return mean_last_hidden_state def calculate_similarity(text1, text2): embedding1 = get_bert_embedding(text1) embedding2 = get_bert_embedding(text2) similarity = F.cosine_similarity(embedding1, embedding2).item() return similarity ``` 在上面的代码中,`get_bert_embedding`函数将文本转换为BERT向量表示,`calculate_similarity`函数使用余弦相似度计算两个文本之间的相似度。可以使用这些函数计算任意两个文本之间的相似度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值