原生Bert的训练和使用总结

bert诞生至今已经有2年了,其产生的影响和一直延伸至今的衍生模型固然是不用我来赘述,为了积累下以往的学习内容防止忘记,就把笔记整理下。

本文的主要从模型运行过程的视角来介绍下google的原生bert是怎么运作和调用的。

首先还是上图:

这是一张transformer的模型框架,这个框架左边一半你就可以理解是一个bert的主干网络,真正的bert是由左边这一小块一小块堆叠而成的,所谓的bert(即双向Transformer编码器)就是指这一部分。

我们本文中所有的举例参数都是基于google的tf版本代码,下面开始进入算法的流程。

首先,我从bert最经典的预训练任务开始,也就是一个MLM任务(遮住几个字,猜这几个字)和一个NSP任务(预测上下两句关系)。

此时,输入的文本是需要构建的,具体的构建方式可以参考下我的另外一篇文章:

https://zhuanlan.zhihu.com/p/157806409

根据源码,输入模型的参数有3个,分别是input_id、input_mask及token_typeids。input_id就是每个句子将汉字转成的对应编号,shape是(32, 128);input_mask就是与汉字一一对应的标志,shape也是(32, 128),因为有些句子没有128个字,会在最后补0,input_mask作用就是区分补0和原文;token_typeids是用于分割上下句的,看过我预训练数据解读的朋友应该知道,训练数据是有两个句子的,token_typeids用于区分是第一句还是第二句,内容一般是[0, 0, 0, ... 0, 1, 1, 1, ... 1],shape是(32, 128)。

首先,input_id首先进入模型的embedding部分,该部分就是最传统的embedding层,一般是正态分布初始化,这里的shape是(21168, 768),因此此时的输入句子就会变成(32, 128, 768)。

第二步,经过第一层embedding后,当前数据还需要加入一个position的信息,这是继承了Transformer的思路,但是简单很多,token_typeids便是在此处发挥的作用。加入position信息后,此时的embedding数据shape仍然是(32, 128, 768)。

第三步,此时得到输入模型的是一组shape为(32,128,768)的数据,记此时的输入数据是Xemb,则创建三个权重维度均是(768, 768)的权重记为:Wq,Wk,Wv。将Xemb转换为(32*128, 768),再分别与Wq,Wk,Wv相乘,得到Q、K、V,这三者shape均为(32*128, 768)。

第四步,本节将详细介绍bert的多头自注意力机制。首先,需要定义一个h,这就是“头”的数量,这里要求h必须被768整除,那么此时Q、K、V的shape将被转变成(32, 128, h, 768/h),转置下得到(32, h, 128, 768/h)。见公式:         

这里dk是K的维度,除以开根号dk的原因是Q与K的转置相乘了,值会变大。至此为止,我们得到一个shape为(32, h, 128, 128)的tensor。那么此时input_mask开始发挥作用,因为不可能每个输入句子都正好是128个字,因此不足128字的语句会利用padding来补0,如果这种值存在,则后续利用softmax时,这些值就不会是0,那么这些padding进来的0就参与了计算,那么就会在input_mask以外的无效区加一个很大的bias,这bias是一个很大的负值,从而使得padding部分对算法的影响达到最低。接下来,再进行如下的操作:

最终得到一个shape为(32, h, 128, 768/h)的tensor。

第五步,残差连接。第四步输出的tensor经过reshape后依然是(32, 128, 768),这样与第二步计算结果的embedding数据shape是一样的,tensor对位相加后,要对相加结果进行一个layer normalization操作,感兴趣的朋友可以了解下为什么不用batch normalization。

第六步,Feed Foward。第五步输出的结果经过两层的dense和一个relu即可。

上述的二至六步我们可以理解为是一个bert块,bert正式由若干这样的模型块串联而成的。

 

下面我们再来谈谈整个bert网络的输出,通过阅读源码很容易发现常见的有四种输出:

1.embedding_table

获取方法:model.get_embedding_table()

含义说明:这是bert最初的语义embedding模块。

2.embedding_output

获取方法:model.get_embedding_output()

含义说明:这是对bert的emebedding_table加入了token的type_embeddings信息和token的position_embeddings信息得到的结果。

3.sequence_output

获取方法:model.get_sequence_output()

含义说明:这是经过了bert多头自注意力机制得到的输出结果,shape与上述两个embedding是一样的。

4.pooled_output

获取方法:model.get_pooled_output()

含义说明:这是获取sequence_output第一个tensor进行全链接后的结果,输入是(1,768)的形状,全链接后也是一个(1,768),按照bert论文中的定义表示输入的上下文之间的关系。

 

上述的任务是在源码中runpretraining.py这个文件中完成的,此外google还提供了runclassifier.py和run_squad.py两种应用的举例,分别是分类和阅读理解,理解起来也不难。本文致力于只懂bert原理但是没空读代码的朋友们提供一个更加接地气的视角来理解bert的运行流程。最后,我将附上我的手写笔记,这里的信息更加全面具体。

 

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值