Sentence-BERT 句子语义匹配模型的tensorflow实现以及训练trick

  • 论文模型回顾
  • 建模与训练
    • 模型代码部分
    • 数据处理
    • 训练
  • 模型训练Trick
    • trick1 warm up
      • 代码实现:
    • trick2 focal loss
      • 代码实现:
  • 总结与思考

论文模型回顾

论文链接:https://arxiv.org/abs/1908.10084
文章在已有的语义匹配模型的基础上提出了基于Bert的句义匹配孪生网络

模型介绍:将两个句子通过Bert(注意:在对句子相似度建模时,两个句子经过的Bert层应该是共享权重的,及同一个Bert)进行特征提取后,取最后一层的hidde_layers进行pooling,文章试验了直接取CLS向量、max_pooling、mean_pooling,结果显示mean_pooling效果最好。将pooling后得到的两个句子向量进行特征交叉,文章尝试了多种交叉方式,|u-v|的效果最好,当然使用者可以根据具体任务和场景自行尝试多种交叉方法;最后通过softmax层。
训练好模型之后,我们可以将语料库中的句子通过单塔转化为对应的句子向量,当待匹配句子进入时,通过向量相似度检索来直接搜索相似句子,节省了大量的模型推理时间。



建模与训练

tensorflow 2.0.0
transformers 3.1.0

模型代码部分

class BertNerModel(tf.keras.Model):dense_layer = 512class_num = 2drop_out_rate = 0.5def __init__(self,pretrained_path,config,*inputs,**kwargs):super(BertNerModel,self).__init__()config.output_hidden_states = Trueself.bert = TFBertModel.from_pretrained(pretrained_path,config=config,from_pt=True)self.liner_layer = tf.keras.layers.Dense(self.dense_layer,activation='relu')self.softmax = tf.keras.layers.Dense(self.class_num,activation='softmax')self.drop_out = tf.keras.layers.Dropout(self.drop_out_rate)      def call(self,input_1):hidden_states_1,_,_ = self.bert((input_1['input_ids'],input_1['token_type_ids'],input_1['attention_mask']))hidden_states_2,_,_ = self.bert((input_1['input_ids_2'],input_1['token_type_ids_2'],input_1['attention_mask_2']))hidden_states_1 = tf.math.reduce_mean(hidden_states_1,1)hidden_states_2 = tf.math.reduce_mean(hidden_states_2,1)concat_layer = tf.concat((hidden_states_1,hidden_states_2,tf.abs(tf.math.subtract(hidden_states_1, hidden_states_2))),1,)drop_out_l = self.drop_out(concat_layer)Dense_l = self.liner_layer(drop_out_l)outputs = self.softmax(Dense_l)print(outputs.shape)return outputs

这里比较难受的是,在自定义模型的时候本来想直接继承transformers的TFBertPreTrainedModel类,但是发现这传入训练数据的时候需要以元组的形式传入,但是在tf model.fit的时候会报错无法识别元组+datasets的数据,因此这里改为继承tf.keras.Model,在类中直接加入TFBertModel.from_pretrained加载之后的TFBertModel,再在后面接自定义的层。

数据处理

def data_proceed(path,batch_size,tokenizer):data = pd.read_csv(path)data = data.sample(frac=1)inputs_1 = tokenizer(list(data['sentence1']), padding=True, truncation=True, return_tensors="tf",max_length=30)inputs_2 = tokenizer(list(data['sentence2']), padding=True, truncation=True, return_tensors="tf",max_length=30)inputs_1  = dict(inputs_1)inputs_1['input_ids_2'] = inputs_2['input_ids']inputs_1['token_type_ids_2'] = inputs_2['token_type_ids']inputs_1['attention_mask_2'] = inputs_2['attention_mask']label = list(data['label'])steps = len(label)//batch_sizex = tf.data.Dataset.from_tensor_slices((dict(inputs_1),label))return x,steps

训练

    optimizer = tf.keras.optimizers.Adam(learning_rate=5e-5)bert_ner_model.compile(optimizer=optimizer,loss='sparse_categorical_crossentropy',metrics=['acc'])bert_ner_model.fit(train_data,epochs=5,verbose=1,steps_per_epoch=steps_per_epoch,validation_data=test_data,validation_steps=validation_steps)

模型训练Trick

trick1 warm up

原文中提到了在训练时warm up learning rate的训练技巧

由于刚开始训练时,模型的权重(weights)是随机初始化的,此时若选择一个较大的学习率,可能带来模型的不稳定(振荡),选择Warmup预热学习率的方式,可以使得开始训练的几个epoches或者一些steps内学习率较小,在预热的小学习率下,模型可以慢慢趋于稳定,等模型相对稳定后再选择预先设置的学习率进行训练,使得模型收敛速度变得更快,模型效果更佳。
该段摘自深度学习训练策略-学习率预热Warmup

代码实现:

class WarmupExponentialDecay(Callback):def __init__(self,lr_base=0.0002,decay=0,warmup_epochs=0,steps_per_epoch=0):self.num_passed_batchs = 0   #一个计数器self.warmup_epochs=warmup_epochs  self.lr=lr_base #learning_rate_baseself.decay=decay  #指数衰减率self.steps_per_epoch=steps_per_epoch #也是一个计数器def on_batch_begin(self, batch, logs=None):# params是模型自动传递给Callback的一些参数if self.steps_per_epoch==0:#防止跑验证集的时候呗更改了if self.params['steps'] == None:self.steps_per_epoch = np.ceil(1. * self.params['samples'] / self.params['batch_size'])else:self.steps_per_epoch = self.params['steps']if self.num_passed_batchs < self.steps_per_epoch * self.warmup_epochs:K.set_value(self.model.optimizer.lr,self.lr*(self.num_passed_batchs + 1) / self.steps_per_epoch / self.warmup_epochs)else:K.set_value(self.model.optimizer.lr,self.lr*((1-self.decay)**(self.num_passed_batchs-self.steps_per_epoch*self.warmup_epochs)))self.num_passed_batchs += 1def on_epoch_begin(self,epoch,logs=None):#用来输出学习率的,可以删除print("learning_rate:",K.get_value(self.model.optimizer.lr))

trick2 focal loss

在实际应用中,负样本往往来自于负采样,大量的负采样会时训练时负样本数量远多余正样本数量导致训练样本不平衡,且软负采样的负样本往往非常弱,在模型推理时置信度一般较高,加入focal loss可以让模型专注于那些置信度低的比较难区分的样本,提高模型的训练效果。
详细可以查看我的之前的博客Tensorlfow2.0 二分类和多分类focal loss实现和在文本分类任务效果评估

代码实现:

def sparse_categorical_crossentropy(y_true, y_pred):y_true = tf.reshape(y_true, tf.shape(y_pred)[:-1])y_true = tf.cast(y_true, tf.int32)y_true = tf.one_hot(y_true, K.shape(y_pred)[-1])return tf.keras.losses.categorical_crossentropy(y_true, y_pred)def loss_with_gradient_penalty(model,epsilon=1):def loss_with_gradient_penalty_2(y_true, y_pred):loss = tf.math.reduce_mean(sparse_categorical_crossentropy(y_true, y_pred))embeddings = model.variables[0]gp = tf.math.reduce_sum(tf.gradients(loss, [embeddings])[0].values**2)return loss + 0.5 * epsilon * gpreturn loss_with_gradient_penalty_2#使用方法:
model.compile(optimizer=optimizer, loss=softmax_focal_loss,metrics=[tf.keras.metrics.SparseCategoricalAccuracy()])

效果:
在公开数据集上:该focal_loss可以很好的抑制模型过拟合且模型效果也有1个多点的提升。


总结与思考

本次训练使用的数据集:LCQMC 是哈尔滨工业大学在自然语言处理国际顶会 COLING2018 构建的问题语义匹配数据集,其目标是判断两个问题的语义是否相同。准确率能达到90%+

但在实际测试时发现,模型推理相似的问句条件比较严格,无法做到真的根据语义进行匹配,(对于同义词、别名等无法识别区分)需要应用到实际生产工作则对训练样本的要求比较严格。

拓展思考:由于该孪生模型的两个句子共享一个bert参数,因此要求两个句子的分布或者说两个句子必须来自统一场景,需要在格式、长度、风格、句式上比较相近。因此在问句匹配、句子相似度判断等工作上能有不错的表现。但可能不适用于类似评论于商品相关度等任务的分析(因为评论文本于商品介绍文本不统一,经过同一个Bert会产生偏差)因此思考对于此类问题,借鉴双塔模型,使用两个不同的Bert来提取两种分布的句子特征,或许仍能有不错的标签,之后有机会会试验一下~

语义匹配(一)【NLP论文复现】Sentence-BERT 句子语义匹配模型的tensorflow实现以及训练Trick相关推荐

  1. 文本生成(二)【NLP论文复现】Relative position representations 相对位置编码突破Bert的文本长度限制!

    Relative position representations 相对位置编码突破Bert文本512长度的限制 前言 Self-Attention with Relative Position Re ...

  2. 将SimCLR应用于NLP预训练模型,提升句子语义表征效果

    ©PaperWeekly 原创 · 作者|张琨 学校|中国科学技术大学博士生 研究方向|自然语言处理 论文标题: CLEAR: Contrastive Learning for Sentence Re ...

  3. 信息抽取(四)【NLP论文复现】Multi-head Selection和Deep Biaffine Attention在关系抽取中的实现和效果

    Multi-head Selection和Deep Biaffine Attention在关系抽取中的应用 前言 Multi-head Selection 一.Joint entity recogni ...

  4. 文本生成(一)【NLP论文复现】Unified Language Model 文本生成从未如此轻松

    Unified Language Model 文本生成从未如此轻松 前言 UniLM How to build UniLM Get 2D MASK Send 2D MASK to Bert 使用Uni ...

  5. 组队瓜分百万奖金池,资深算法工程师带你挑战飞桨论文复现赛!

    你是否正在焦虑找不到好的论文? 好不容易找到了paper,无法复现出code? 缺少科研同行交流,只能独自一人闭门造车? 是的,论文复现是要想最快的学习和了解AI领域的方式,复现困境也被叫做" ...

  6. 顶尖!百度AI算法专家手把手带你论文复现打比赛!

    你是否正在焦虑找不到好的论文? 好不容易找到了paper,无法复现出code? 缺少科研同行交流,只能独自一人闭门造车? 是的,论文复现是要想最快的学习和了解AI领域的方式,复现困境也被叫做" ...

  7. 这个顶会论文复现比赛,单篇最高现金奖3W!

    飞桨论文复现挑战赛(第六期)和春天一起来啦!本次挑战赛再度升级,无论是奋战过前五期比赛的复现冠军,还是首次接触论文复现的潜力选手,都有惊喜赛题任务等你来挑战. 论文复现是深入掌握前沿模型原理的最优方式 ...

  8. FCOS论文复现:通用物体检测算法

    摘要:本案例代码是FCOS论文复现的体验案例,此模型为FCOS论文中所提出算法在ModelArts + PyTorch框架下的实现.本代码支持FCOS + ResNet-101在MS-COCO数据集上 ...

  9. 百万奖池 十大方向 | 第六期论文复现挑战赛如约而至!

    飞桨论文复现挑战赛(第六期)和春天一起来啦!本次挑战赛再度升级,无论是奋战过前五期比赛的复现冠军,还是首次接触论文复现的潜力选手,都有惊喜赛题任务等你来挑战. 论文复现是深入掌握前沿模型原理的最优方式 ...

最新文章

  1. 一文详解非线性优化算法:保姆级教程-基础理论
  2. 在Ngnix上部署Flask应用
  3. C++ 中的this指针详解及实例
  4. cocos2d-x游戏实例(3)-获得地图索引
  5. 使用泛型前 VS 使用泛型后
  6. 从入门到精通!java可视化编程软件哪个好
  7. MongoDB 4.2 内核解析 - Change Stream
  8. JSON实现桌面可移动的小便签
  9. mysql常用系统函数归类
  10. 三星Galaxy Note20系列价格曝光:大小杯差距明显
  11. java windows linux 乱码_Linux 中 Windows 中文乱码
  12. zookeeper在linux环境安装
  13. oracle的查询数据(检索数据)
  14. 2018中国方案商500强榜单
  15. Redis数据倾斜与JD开源hotkey源码分析揭秘
  16. ”左行右立“折寿探秘
  17. 19. 卫健委官网医院查询爬虫+验证码识别(云打码)综合案例
  18. 美通企业日报 | 茅台在海外市场供不应求;默克广东创新中心将正式投入使用...
  19. 从人工智能到物联网……这些公司如何改变农业与食品工业
  20. 解决Android自定义相机预览和照片分辨率差异的问题

热门文章

  1. linux fork, system, exec()
  2. javascript嵌套的对象被修改时log不符合的问题
  3. POJ 3468 A Simple Problem with Integers (1)
  4. 解决方案:用户 'sa' 登录失败。原因: 未与信任 SQL Server 连接相关联。
  5. 如何发表自己的第一篇SCI?
  6. 第23天学习Java的笔记-抽象类
  7. java ing印版,Java中有趣的事【汇总】持续更新ing
  8. linux虚拟主机有哪几种实现方案?_常见的国内外Linux服务器控制面板介绍
  9. 计算机视觉--Python实现人体姿态估计
  10. Win10重装系统后更改用户文件夹名称的方法