当前位置:   article > 正文

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

bert 中文文本匹配任务

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

一、数据处理

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

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

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

  1. def gen_data(self, inputs_idx, labels_idx):
  2. '''
  3. 生成批次数据
  4. :return:
  5. '''
  6. query_word_ids, query_segment_ids, query_word_mask, query_sequence_length, \
  7. sim_word_ids, sim_segment_ids, sim_word_mask, sim_sequence_length = inputs_idx[0], inputs_idx[1],inputs_idx[2],\
  8. inputs_idx[3],inputs_idx[4],inputs_idx[5],\
  9. inputs_idx[6],inputs_idx[7]
  10. batch_word_ids_a, batch_segment_ids_a, batch_word_mask_a, batch_sequence_length_a, \
  11. batch_word_ids_b, batch_segment_ids_b, batch_word_mask_b, batch_sequence_length_b, batch_output_ids= [], [], [], [], [], [], [], [], []
  12. for i in range(len(query_word_ids)):
  13. batch_word_ids_a.append(query_word_ids[i])
  14. batch_segment_ids_a.append(query_segment_ids[i])
  15. batch_word_mask_a.append(query_word_mask[i])
  16. batch_sequence_length_a.append(query_sequence_length[i])
  17. batch_word_ids_b.append(sim_word_ids[i])
  18. batch_segment_ids_b.append(sim_segment_ids[i])
  19. batch_word_mask_b.append(sim_word_mask[i])
  20. batch_sequence_length_b.append(sim_sequence_length[i])
  21. batch_output_ids.append(labels_idx[i])
  22. if len(batch_output_ids) == self.batch_size:
  23. yield dict(
  24. input_word_ids_a=np.array(batch_word_ids_a, dtype="int32"),
  25. input_mask_a=np.array(batch_word_mask_a, dtype="int32"),
  26. input_type_ids_a=np.array(batch_segment_ids_a, dtype="int32"),
  27. input_word_ids_b=np.array(batch_word_ids_b, dtype="int32"),
  28. input_mask_b=np.array(batch_word_mask_b, dtype="int32"),
  29. input_type_ids_b=np.array(batch_segment_ids_b, dtype="int32"),
  30. input_target_ids=np.array(batch_output_ids, dtype="float32")
  31. )
  32. batch_word_ids_a, batch_segment_ids_a, batch_word_mask_a, batch_sequence_length_a, \
  33. 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命名实体识别

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

网络结构源码如下:

  1. class SimBert(tf.keras.Model):
  2. """
  3. bert句子相似度模型
  4. """
  5. def __init__(self,
  6. network,
  7. config,
  8. initializer='glorot_uniform',
  9. dropout_rate=0.1,
  10. ):
  11. self._self_setattr_tracking = False
  12. self._network = network
  13. self._config = {
  14. 'network': network,
  15. 'initializer': initializer,
  16. }
  17. self.config = config
  18. #定义两个句子的输入
  19. # 定义输入
  20. word_ids_a = tf.keras.layers.Input(shape=(None,), dtype=tf.int32, name='input_word_ids_a')
  21. mask_a = tf.keras.layers.Input(shape=(None,), dtype=tf.int32, name='input_mask_a')
  22. type_ids_a = tf.keras.layers.Input(shape=(None,), dtype=tf.int32, name='input_type_ids_a')
  23. word_ids_b = tf.keras.layers.Input(shape=(None,), dtype=tf.int32, name='input_word_ids_b')
  24. mask_b = tf.keras.layers.Input(shape=(None,), dtype=tf.int32, name='input_mask_b')
  25. type_ids_b = tf.keras.layers.Input(shape=(None,), dtype=tf.int32, name='input_type_ids_b')
  26. input_a = [word_ids_a, mask_a, type_ids_a]
  27. input_b = [word_ids_b, mask_b, type_ids_b]
  28. #计算encoder
  29. outputs_a = network.predict_step(input_a)
  30. outputs_b = network.predict_step(input_b)
  31. cls_output_a = outputs_a[1]
  32. query_embedding_output = tf.keras.layers.Dropout(rate=dropout_rate)(cls_output_a)
  33. cls_output_b = outputs_b[1]
  34. sim_query_embedding_output = tf.keras.layers.Dropout(rate=dropout_rate)(cls_output_b)
  35. # 余弦函数计算相似度
  36. # cos_similarity余弦相似度[batch_size, similarity]
  37. query_norm = tf.sqrt(tf.reduce_sum(tf.square(query_embedding_output), axis=-1), name='query_norm')
  38. sim_query_norm = tf.sqrt(tf.reduce_sum(tf.square(sim_query_embedding_output), axis=-1), name='sim_query_norm')
  39. dot = tf.reduce_sum(tf.multiply(query_embedding_output, sim_query_embedding_output), axis=-1)
  40. cos_similarity = tf.divide(dot, (query_norm * sim_query_norm), name='cos_similarity')
  41. self.similarity = cos_similarity
  42. # 预测为正例的概率
  43. cond = (self.similarity > self.config["neg_threshold"])
  44. pos = tf.where(cond, tf.square(self.similarity), 1 - tf.square(self.similarity))
  45. neg = tf.where(cond, 1 - tf.square(self.similarity), tf.square(self.similarity))
  46. predictions = [[neg[i], pos[i]] for i in range(self.config['batch_size'])]
  47. self.logits = self.similarity
  48. outputs = dict(logits=self.logits, predictions=predictions)
  49. super(SimBert, self).__init__(inputs=[input_a, input_b], outputs=outputs)

三、构建损失值

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

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

loss的代码如下:

  1. def build_losses(self, labels, model_outputs, metrics, aux_losses=None) -> tf.Tensor:
  2. '''
  3. 构建损失
  4. '''
  5. with tf.name_scope('TextMatchTask/losses'):
  6. if self.config['model_name'] == 'simbert':
  7. # 构建对比损失
  8. y = tf.reshape(labels, (-1,))
  9. similarity = model_outputs['logits']
  10. cond = (similarity < self.config["neg_threshold"])
  11. zeros = tf.zeros_like(similarity, dtype=tf.float32)
  12. ones = tf.ones_like(similarity, dtype=tf.float32)
  13. squre_similarity = tf.square(similarity)
  14. neg_similarity = tf.where(cond, squre_similarity, zeros)
  15. pos_loss = y * (tf.square(ones - similarity) / 4)
  16. neg_loss = (ones - y) * neg_similarity
  17. losses = pos_loss + neg_loss
  18. loss = tf.reduce_mean(losses)
  19. return loss
  20. metrics = dict([(metric.name, metric) for metric in metrics])
  21. losses = tf.keras.losses.sparse_categorical_crossentropy(labels,
  22. tf.cast(model_outputs['predictions'], tf.float32),
  23. from_logits=True)
  24. loss = tf.reduce_mean(losses)
  25. return loss

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

四、后续

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/780798
推荐阅读
相关标签
  

闽ICP备14008679号