2021SC@SDUSC
经过阅读paddle的源码,我理解了动态图下的Transformer encoder源码实现,由于这个实现比较复杂,因此我将通过两个博客来对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.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
bert中hidden_act(激活函数)是gelu。
?
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
|