详解Transformer结构
Transformer在深度学习中一直占据着很重要的地位,不仅在NLP领域中会使用到,也创建CV领域,在2021年Transformer也在CV领域大杀四方。本文详解Transformer结构,带大家熟悉Transformer每个部分的原理和作用。
本文主要回答以下几个问题:
- Transformer背景介绍?
- Transformer的整体架构是什么样的?
- Transformer中每个细节部分的原理和作用是什么?
论文:https://arxiv.org/abs/1706.03762
Transformer背景介绍?
注意力(Attention)机制由Bengio团队2015年提出(链接如下),近些年广泛应用在深度学习等各个领域。
https://arxiv.org/pdf/1409.0473.pdf
Bengio 大神其实是借鉴了生物在观察、学习、思考行为中的过程的一种独特的生理机制,这种机制就是 Attention 机制。大家都能有感觉,我们在获取信息的时候,通常是先从宏观上建立一个比较模糊的认识,然后又在宏观的认识下,发现一些比较重要的信息,对于这些重要的信息,我们会花费更多的注意力进行观察、学习和思考。例如在计算机视觉方向用于捕捉图像上的感受野,或者NLP中用于定位关键token或者特征,这些都是注意力的作用。
正如论文的题目所说,Transformer抛弃了传统的CNN和RNN,整个网络结构完全是由Attention机制组成。作者采用Attention机制的原因是考虑到RNN(或者LSTM,GRU等)的计算限制为顺序的,也就是说RNN系列算法只能从左向右依次计算或者从右向左依次计算,这种机制带来了两个问题:
- 时间步t的计算依赖t-1时刻的计算结果,这样限制了模型的并行能力;
- 顺序计算反向传播过程中会出现梯度爆炸和梯度消失的现象,尽管LSTM等门机制的结构在一定程度上缓解了这种现象,但是并未完全解决。
Transformer的提出解决了上面两个问题,首先它使用了Attention机制,将序列中的任意两个位置之间的距离是缩小为一个常量,在分析预测更长的文本时,捕捉间隔较长的语义关联效果更好;其次它不是类似RNN的顺序结构,因此具有更好的并行性,符合现有的GPU框架,能够利用分布式GPU进行并行训练,提升模型训练效率。
下面是一张测评比较图,可以看出Attention在预测长文本任务中效果最佳:
在著名的SOTA机器翻译榜单上, 几乎所有排名靠前的模型都使用Transformer,如图2所示。
Transformer的整体架构是什么样的?
首先来说一下Transformer的用途:
- 基于seq2seq架构的Transformer模型可以完成NLP领域的一些典型任务, 如机器翻译, 文本生成,同时又可以通过构建预训练语言模型,用于不同任务的迁移学习。
Transformer的整体架构可以分为四个模块:
- 输入模块
- 编码模块
- 解码模块
- 输出模块
输入模块结构:
- 源文本嵌入层及其位置编码器
- 目标文本嵌入层及其位置编码器
编码器模块结构:
- 由N个编码器层堆叠而成
- 每个编码器层由两个子层连接结构组成
- 第一个子层连接结构包括一个多头自注意力子层、规范化层和一个残差连接
- 第二个子层连接结构包括一个前馈全连接子层、规范化层和一个残差连接
解码器模块:
- 由N个解码器层堆叠而成
- 每个解码器层由三个子层连接结构组成
- 第一个子层连接结构包括一个多头自注意力子层、规范化层和一个残差连接
- 第二个子层连接结构包括一个多头注意力子层、规范化层和一个残差连接
- 第三个子层连接结构包括一个前馈全连接子层、规范化层和一个残差连接
输出模块结构:
- 线性层
- softmax层
Transformer中每个细节部分的原理和作用是什么?
这里我们按照上面的结构依次对每个模块的细节部分进行原理分析和代码讲解。
输入模块
1. 文本嵌入层的作用
无论是源文本嵌入还是目标文本嵌入,都是为了将文本中词汇的数字表示转变为向量表示, 希望在高维向量空间中捕捉词汇间的关系。
- 文本嵌入层的代码分析:
# 导入必备的工具包
import torch
import torch.nn as nn
import math
from torch.autograd import Variable
# 定义Embeddings类来实现文本嵌入层,这里s说明代表两个一模一样的嵌入层, 他们共享参数.
class Embeddings(nn.Module):
def __init__(self, d_model, vocab):
"""类的初始化函数,有两个参数,
d_model: 指词嵌入的维度,
vocab: 指词表的大小
"""
super(Embeddings, self).__init__()
# 调用nn中的预定义层Embedding, 获得一个词嵌入对象self.lut
self.lut = nn.Embedding(vocab, d_model)
# 将d_model传入类中
self.d_model = d_model
def forward(self, x):
# 将x传给self.lut并与根号下self.d_model相乘作为结果返回
return self.lut(x) * math.sqrt(self.d_model)
2. 位置编码器的作用
在Transformer的编码器结构中, 并没有针对词汇位置信息的处理,因此需要在Embedding层后加入位置编码器,将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中, 以弥补位置信息的缺失.
- 位置编码器的代码分析:
# 定义位置编码器类
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout, max_len=5000):
"""位置编码器类的初始化函数, 共有三个参数
分别是d_model: 词嵌入维度
dropout: 置0比率
max_len: 每个句子的最大长度
"""
super(PositionalEncoding, self).__init__()
# 实例化nn中预定义的Dropout层, 并将dropout传入其中, 获得对象self.dropout
self.dropout = nn.Dropout(p=dropout)
# 初始化一个位置编码矩阵, 它是一个0阵,矩