本文基于PaddlePaddle 1.7版本,解析动态图下的Transformer encoder源码实现。
Transformer的每个Encoder子层(bert_base中包含12个encoder子层)包含 2 个小子层 :
- Multi-Head Attention
- Feed Forward
(Decoder中还包含Masked Multi-Head Attention)
class 有如下几个:
PrePostProcessLayer | 用于添加残差连接、正则化、dropout |
PositionwiseFeedForwardLayer | 全连接前馈神经网络 |
MultiHeadAttentionLayer | 多头注意力层 |
EncoderSubLayer | encoder子层 |
EncoderLayer | transformer encoder层 |
在Paddle动态图中,网络层的实现继承paddle.fluid.dygraph.Layer,类内方法__init__是对网络层的定义,forward是跑前向时所需的计算。
具体实现如下,对代码的解释在注释中:
一些必要的导入
"dygraph transformer layers" from __future__ import absolute_import from __future__ import division from __future__ import print_function import numpy as np import paddle import paddle.fluid as fluid from paddle.fluid.dygraph import Embedding, LayerNorm, Linear, Layer
PrePostProcessLayer
可选模式:{ a: 残差连接,n: 层归一化,d: dropout}
残差连接
图中Add+Norm
层。每经过一个模块的运算, 都要把运算之前的值和运算之后的值相加, 从而得到残差连接,残差可以使梯度直接走捷径反传到最初始层。
残差连接公式:
y=f(x)+x
x 表示输入的变量,实际就是跨层相加。
层归一化
LayerNorm实际就是对隐含层做层归一化,即对某一层的所有神经元的输入进行归一化(沿着通道channel方向),使得其加快训练速度:
层归一化公式:
x : 该层神经元的向量表示
H : 层中隐藏神经元个数
ϵ : 添加较小的值到方差中以防止除零
g : 可训练的比例参数
b : 可训练的偏差参数
dropout
丢弃或者保持x的每个元素独立。Dropout是一种正则化手段,通过在训练过程中阻止神经元节点间的相关性来减少过拟合。根据给定的丢弃概率,dropout操作符按丢弃概率随机将一些神经元输出设置为0,其他的仍保持不变。
dropout op可以从Program中删除,提高执行效率。
class PrePostProcessLayer(Layer): """ PrePostProcessLayer """ def __init__(self, process_cmd, d_model, dropout_rate, name): super(PrePostProcessLayer, self).__init__() self.process_cmd = process_cmd # 处理模式 a n d, 可选多个 self.functors = [] # 处理层 self.exec_order = "" # 根据处理模式,为处理层添加子层 for cmd in self.process_cmd: if cmd == "a": # add residual connection self.functors.append(lambda x, y: x + y if y else x) self.exec_order += "a" elif cmd == "n": # add layer normalization self.functors.append( self.add_sublayer( # name "layer_norm_%d" % len( self.sublayers(include_sublayers=False)), LayerNorm( normalized_shape=d_model, # 需规范化的shape,如果是单个整数,则此模块将在最后一个维度上规范化(此时最后一维的维度需与该参数相同)。 param_attr=fluid.ParamAttr( # 权重参数 name=name + "_layer_norm_scale", # 常量初始化函数,通过输入的value值初始化输入变量 initializer=fluid.initializer.Constant(1.)), bias_attr=fluid.ParamAttr( # 偏置参数 name=name + "_layer_norm_bias", initializer=fluid.initializer.Constant(0.))))) self.exec_order += "n" elif cmd == "d": # add dropout if dropout_rate: self.functors.append(lambda x: fluid.layers.dropout( x, dropout_prob=dropout_rate, is_test=False)) self.exec_order += "d" def forward(self, x, residual=None): for i, cmd in enumerate(self.exec_order): if cmd == "a": x = self.functors[i](x, residual) else: x = self.functors[i](x) return x
PositionwiseFeedForwardLayer
class PositionwiseFeedForwardLayer(Layer): """ PositionwiseFeedForwardLayer """ def __init__(self, hidden_act, # 激活函数 d_inner_hid, # 中间隐层的维度 d_model, # 最终输出的维度 dropout_rate, param_initializer=None, name=""): super(PositionwiseFeedForwardLayer, self).__init__() # 两个fc层 self._i2h = Linear( input_dim=d_model, output_dim=d_inner_hid, param_attr=fluid.ParamAttr( name=name + '_fc_0.w_0', initializer=param_initializer), bias_attr=name + '_fc_0.b_0', act=hidden_act) self._h2o = Linear( input_dim=d_inner_hid, output_dim=d_model, param_attr=fluid.ParamAttr( name=name + '_fc_1.w_0', initializer=param_initializer), bias_attr=name + '_fc_1.b_0') self._dropout_rate = dropout_rate def forward(self, x): """ forward :param x: :return: """ hidden = self._i2h(x) # dropout if self._dropout_rate: hidden = fluid.layers.dropout( hidden, dropout_prob=self._dropout_rate, upscale_in_train="upscale_in_train", is_test=False) out = self._h2o(hidden) return out