这是之前学习paddle时候的笔记,对Transformer框架进行了拆解,附图解和代码,希望对大家有帮助
写在前面
最近在学习paddle相关内容,质量比较高的参考资料好像就paddle官方文档[1]。所以如果大家想学习一下的话,可以先简单过一遍文档,如果你之前有tensorflow或者torch的基础,看起来应该会比较快,都差不多的嘛。然后细节的部分就可以去实战看(写)代码了。下面是一个用paddle实现的目前NLP领域最火的Transformer模型,包括模型详细的拆解可视化以及对应每一步的代码实现,enjoy!
- Encoder Part
- Residuals & Layer Norm
- Feed Forward
- Self-Attention
- 完整Encoder代码
- Decoder Part
- Masked Multi-Head Attention
- Encoder-Decoder Attention
- 整体Decoder代码
- Full Transformer
一、Encoder Part
下图是一个encoder block,可以看到主要由以下四部分组成:
- Self-Attention
- Feed Forward
- Residual Connection
- Layer Norm
下面我们由简单至复杂来搭建每一部分并给出对应代码。
1、Residuals & Layer Norm
也就是上图中浅绿色框框中的内容。
- 「残差层」:大多数关于transformer的文章对于Residual的介绍都比较简单,但是实际上残差层在整个网络中的作用非常重要,它可以有效解决网络模型层数增大而引起的信息损失问题,来自kaiming大神的Deep residual learning for image recognition[2];
- 「归一化」:神经网络中关于归一化的做法有很多,这里使用的是层归一化,更详细的资料推荐张俊林老师的深度学习中的Normalization模型[3]
2、Feed Forward
前馈层,上图中蓝色框框,包括两个线性变换,使用的激活函数为「relu」
注意,self-attention是对所有输入统一处理的,而前馈层是position-wise的。参数在同一层是共享的,但是在层与层之间是独立的。输入和输出的维度 ,内层的维度
3、Self-Attention
Transformer中最重要的组成部分。在所有的attention中最关键就是要理解三个概念: 、 、 。很多同学可能都对这三个词非常熟悉,但是对其具体对应的实体非常模糊,这里我们以检索为例介绍一下其原理,看看能不能理解。(已经了解的自行跳过haha)
比如你现在想要了解一下「自然语言处理」的相关信息,你就会去搜索框里输入
自然语言处理
,这时候你输入的就是 ;接着搜索库里会有很多文档,想要返回你的搜索结果,一个简单的思路就是看看你的搜索内容和文档title的相似度,这时候每个文档title就是 ,将 和每个 做点乘计算相似度,我们会得到一系列数值 ,然后用这一组值对文档,也就是我们的
所以本质上我们可以将Attention简单理解为「加权和」。更为具体的注意力介绍可以参考我之前的博客:理解Attention机制原理及模型[4]
3.1 Scaled Dot Product Attention
下面来看看具体在文本中是怎么运算的。
- 每一个输入经过Embedding层后转化成词向量 ;
- 所有token都会经过三个可学习矩阵分别映射为三个向量 、 、 ,
- 对每一个单词,将其作为 ,所有单词作为 ,计算相似度得分;
- 将相似度得分进行规一化后与对应的
公式为:
其可视化过程如下图,
转换成矩阵形式可以简化表示为,
注意scaled_dot_product_attention
函数中有一个attn_bias
的操作,作用是mask掉指定的位置。在整个transformer的结构中,使用的地方有三处:
- Encoder的self-attention,作用是mask掉padding的位置;
- Decoder的encoder-self-attention,作用是mask掉padding的位置;
- Decoder的masked-self-attention,作用是解码过程mask掉当前词之后的词信息
3.2 Multi-Head Attention
multi-head的出发点是为了让模型在多个不同的子空间中学习到不同方面的信息,帮助模型捕获更丰富的特征。
操作也非常容易理解,
- 首先将输入映射为 、 、 ,
- 接着拆分成
个注意力头,并行地运算上一节中的
Scaled Dot Product Attention
, - 最后将结果进行拼接。
整体计算公式为:
可视化如下图所示:
3.2.1 拆分
在输入张量的最后一个维度上进行reshape以拆分出多头,然后转置方便后续运算。具体而言,将输入形状为[bs,max_sequence_length,n_head * hidden_dim]
转换为[bs,n_head,max_sequence_length,hidden_dim]
3.2.2 合并
可以认为是上一节拆分
的逆操作,先是transpose
,再是reshape
。具体而言,将输入形状为[bs,n_head,max_sequence_length,hidden_dim]
转换为[bs,max_sequence_length,n_head * hidden_dim]
3.2.3 整体
有了前面几节的函数操作之后,就可以构建整体multi-head attention了
4、完整Encoder代码
有了上面的铺垫之后,我们就可以写出encoder的代码框架了。具体代码篇幅原因就不再粘贴,可以根据开篇所述方式获取。
二、Decoder Part
ok,我们先停下来回顾一下前面都解决了哪些内容:
- Encoder包含的几个部分:self-attention、feed-forward、add&norm
- 详细介绍了Scaled Dot Product Attention原理及代码实现
- 详细介绍了Multi-Head Attention原理及代码实现
接下去来看看transformer的右半部分:Decoder。如下图所示,是一个decoder block,主要由五部分组成:
- Encoder-Decoder Attention
- (Masked)Self-Attention
- Feed Forward
- Residual Connection
- Layer Norm
1、Masked Multi-Head Attention
关于这个,我们在前面3.1节其实有过说明,当解码第
那么具体怎么做呢?其实也不难:构造一个mask矩阵,上三角全为0,表示无法attend未来的信息,如下,
2、Encoder-Decoder Attention
其实Deocer的五个组件我们在Encoder Part里面已经完成了四个部分,只剩下一个Encoder-Decoder Attention
是没有涉及的。其实这个跟encoder_layer
的差不多,只不过是它的
和
来自encoder的输出,而
可视化动图就更清楚了,
3、整体Decoder代码
不啰嗦了,跟encoder很类似,看代码也很直观。
三、Full Transformer
终于快写完了....
最后我们就像搭积木一样,把前面的部分组建成一个完整的transformer网络,如下图
积木搭好了,我们怎么调用呢?下面就可以写一个create_net
函数,接受输入为is_training
(是否训练阶段),model_input
(模型输入),args
(一些词表、维度、长度等模型参数)
本文参考资料
[1]
paddle官方文档: https://www.paddlepaddle.org.cn/
[2]
Deep residual learning for image recognition: https://arxiv.org/abs/1512.03385
[3]
深度学习中的Normalization模型: https://zhuanlan.zhihu.com/p/43200897
[4]
- END -