bert4keras源码及矩阵计算
前言
bert4keras是苏剑林通过keras封装Bert,可以更快速、更友好的使用Bert。
bert4keras链接:https://github.com/bojone/bert4keras
本文是对该链接下bert4keras/bert4keras/的源码进行解析
源码重要性
个人认为了解源码非常重要,主要有两个看法:
- 了解源码会让你对原理有个更好的了解,在做下游任务或者模改的时候就更加游刃有余;
- 源码中会有很多在代码实现的骚套路,学学后会大大提升自己写代码的能力和简洁性。
在解析bert4keras源码前,需要各位具备如下知识:
- BERT的基本原理(或Transformer原理)
- Keras中层(Layer)的定义方式和使用(可以参考苏神科学空间的:“让Keras更酷一些!”:精巧的层与花式的回调:https://kexue.fm/archives/5765)
- MultiAttention(可以参考本人写的:“基于keras的MultiAttention实现及实例”:https://zhuanlan.zhihu.com/p/231631291)
Bert4keras框架
bert4keras的总体架构由6个代码文件组成,主文件是models.py,其余的文件均为主文件服务,或者为下游任务的建模过程中服务。
models.py:主文件,主体Transformer类,其余的都是以Transformer为父类实现其算法,包括BERT,ALBERT,NEZHA,ELECTRA,GPT2_ML,T5的算法及其优化
layers.py: 实现各类功能的层,类似Keras中的layer,这里包括Embedding,MultiHeadAttention(多头注意力),LayerNormalization,PositionEmbedding(位置编码),RelativePositionEmbedding(相对位置编码),FeedForward等实现
本篇从主文件models.py开始介绍
Transformer类
Transformer类写好了bert等这类预训练模型的整体框架,主要在build,call两个函数中实现。
包括三大内容(在build函数中):Input(输入),self.call(算法计算流程),Model(建模)
实现框架如下图:
apply函数的妙用:在Transformer类中的apply函数的妙用:通过apply调用层会自动重用同名层,每次层进来都会存储在self.layers字典中,相同名字的层会重复使用,这样很好用而且在后面的Albert中要共享参数的时候也方便实现。
def apply(self, inputs, layer=None, arguments=None, **kwargs):
"""通过apply调用层会自动重用同名层
inputs: 上一层的输出;
layer: 要调用的层类名;
arguments: 传递给layer.call的参数;
kwargs: 传递给层初始化的参数。
"""
if layer is Dropout and self.dropout_rate == 0:
return inputs
arguments = arguments or {
}
name = kwargs.get('name') # name='Embedding-Token'
if name not in self.layers:
layer = layer(**kwargs)
name = layer.name
self.layers[name] = layer # 保存下来,方便重复调用
return self.layers[name](inputs, **arguments) # 可以看Bert中的详细用法
算法实现(以Bert为例)
依照Transformer类的逻辑顺序,总结如下:
- Input(输入)
get_inputs函数:
实现bert的输入: token(x_in)和segment(s_in)
shape均为:shape=(btz, seq_len)
输出:[x_in, s_in] - self.call(算法计算流程)
-
apply_embedding:转成字向量
x embedding – s embedding – x&s add – Position embedding – LN – dropout
输出shape=(btz, seq_len, hidden_size) -
apply_main_layers:计算流程主体
循环num_hidden_layers次:
Bert计算主体:Att --> DROPOUT --> Add --> LN --> FFN --> DROPOUT --> Add --> LN
输出shape=(btz, seq_len, intermediate_size) -
apply_final_layers:根据下流任务调整输出
with_pool:提取CLS向量,用CLS向量表示这句话的句向量 shape=(btz, 768)
with_nsp:预测是否是下一句,shape=(btz, 2)
with_mask:Mask LM mask语言模型,在预训练的时候会用到,或者要预测句中某个字或词的时候可以用到
代码及矩阵张量运算见代码注释:
class BERT(Transformer):
"""构建BERT模型
"""
def __init__(
self,
max_position, # 序列最大长度
with_pool=False, # 是否包含Pool部分
with_nsp=False, # 是否包含NSP部分
with_mlm=False, # 是否包含MLM部分
**kwargs # 其余参数
):
super(BERT, self).__init__(**kwargs)
self.max_position = max_position
self.with_pool = with_pool
self.with_pool = with_pool
self.with_nsp = with_nsp
self.with_mlm = with_mlm
def get_inputs(self):
"""BERT的输入是token_ids和segment_ids
"""
x_in = Input(shape=(self.sequence_length,