超详细解读Transformer框架

Transformer是由谷歌大脑2017年在论文《Attention is All You Need》中提出的一种序列到序列(Seq2Seq)模型。自提出伊始,该模型便在NLP和CV界大杀四方,多次达到SOTA效果。NLP领域中,我们所熟知的BERT和GPT就是从Transformer中衍生出来的预训练语言模型。

本篇将对Transformer框架进行详细的解读,和大家一起深入理解Transformer的原理和机制。

1. 什么是Transformer?

首先我们先对Transformer来个直观的认识。Transformer出现以前,NLP领域应用基本都是以RNN或LSTM循环处理完成,一个token一个tokrn输入到模型中。模型本身是一种顺序结构,包含token在序列中的位置信息。但是存在了一些问题:

  1. 会出现梯度消失现象,无法支持长时间序列。
  2. 句子越靠后的token对结果的影响越大。
  3. 只能利用上文信息,无法获取下文信息。
  4. 循环网络逐个token输入,也就是句子有多长就要循环多少遍,计算的效率低。

而Transformer的出现得以解决了上述的一系列问题。

2. Transformer架构

2.1. 宏观层面

首先将Transformer可以看成是一个黑箱操作的序列到序列(seq2seq)模型,输入是单词/字母/图像特征序列,输出是另外一个序列。一个训练好的Transformer模型如下图所示:

在这里插入图片描述

在机器翻译中,就是输入一种语言(一连串单词),经Transformer输出另一种语言(一连串单词)。

在这里插入图片描述

拆开这个黑箱,可以看到模型本质就是一个Encoder-Decoder结构,由编码组件、解码组件和它们之间的连接组成。

在这里插入图片描述

每个Encoders中分别由6层Encoder组成,而每个Decoders中同样也是由6层Decoder组成。

在这里插入图片描述

每一层Encoder的结构都是相同的,但是它们的权重参数不同。

每个Encoder里面又分为两层。

  • Self-Attention Layer
  • Feed Forward Neural Network,前馈神经网络

输入Encoder的文本数据,首先会经过一个self-attention层,这个层处理一个词的时候,不仅会使用这个词本身的信息,还会关注上下文其它词的信息。

self-attention的输出会被传入一个全连接的前馈神经网络,每个Encoder的前馈神经网络参数个数都是相同的,但是他们的作用是独立的。如下图:

在这里插入图片描述

每个Decoder也同样具有这样的层级结构,但是在这之间有一个Attention层,这个层能帮助Decoder聚焦于输入句子的相关部分。

2.2. 微观层面

Transformer内部结构如下图所示,由Encoder和Decoder两大部分组成。其中,Encoder负责将输入的自然语言序列映射成为隐藏层,然后Decoder将隐藏层映射为自然语言序列。

如下图所示是以机器翻译为例,说明模型具体运行过程:

  1. 输入自然语言序列到Encoder:Why do we work?(我们为什么工作);
  2. Encoder输出的隐藏层,再输入到解码器;
  3. 输入<start>(起始)符号到解码器;
  4. 得到第一个字"为";
  5. 将得到的第一个字"为"落下来再输入到解码器;
  6. 得到第二个字"什";
  7. 将得到的第二字再落下来,直到解码器输出<end>(终止符),即序列生成完成。

在这里插入图片描述

2.2.1. Encoder

下面我们再逐步拆开理解,首先是Encoder,即把自然语言序列映射为隐藏层的数学表达的过程。下图为一个Encoder block结构图。

在这里插入图片描述

2.2.1.1. 输入嵌入(input Embedding)

输入数据X维度为:[batch_size, sequence_length],比如输入“你好啊。最近正在忙什么呢?明天有空出来喝茶。几个朋友都来。”,batch size指的是句子数,sequence length指的是输入的句子中最长的句子的字数。这里共四句话,所以bacth_size为4。最长的句子是8个,所以sequence_length为8。输入数据维度为[4,8]。

但是,我们不能直接将这些语句输入到Encoder中,因为Transformer不认识,所以需要先进行Embedding,找到每个字的数学表达,即得到图中的input Embedding,通过查表得到字向量,它的维度就变为[batch_size,sequence_length,embedding_dimmension],embedding_dimmension表示字向量的长度。

简单来说,就是字->词向量的转换,这种转换是将字转换为计算机能够辨识的数学表示,用到的方法是Word2Vec。得到的 X e m b e d d i n g X_{embedding} Xembedding的维度是[batch_size,sequence_length,embedding_dimmension]。其中,embedding_dimmension的大小由Word2Vec算法决定,例如Transformer采用512长度的词向量。因此, X e m b e d d i n g X_{embedding} Xembedding的维度是[4,8,512]。如下图所示。

在这里插入图片描述

2.2.1.2. 位置编码(Positional Encoding)

Transformer以token作为输入,将token进行input Embedding之后,再和Positional Encoding相加。注意这里不是拼接,而是对应位置上的数值进行加和。

i n p u t = i n p u t E m b e d d i n g + P o s i t i o n a l E m b e d d i n g input= input Embedding+ Positional Embedding input=inputEmbedding+PositionalEmbedding

上文我们提到,input Embedding的维度是512,由于是相加关系,自然而然地,这里Positional Encoding的维度也是512。

为什么要使用Positional Encoding呢?我们知道,NLP领域中,模型的输入是一串文本,也就是序列Sequence。

而在以前的模型(RNN或LSTM)中,NLP的每个序列都是一个字一个字的输入到模型当中。比如有一句话是“我喜欢吃洋葱”,那么输入模型的顺序就是“我”,“喜”,“欢“,”吃“,”洋“,”葱”,一个字一个字的。

在这里插入图片描述

这样的输入方式其实就引入了一个问题。一个模型每次只吃了一个字,那么模型只能学习到前后两个字的信息,无法知道整句话讲了什么。为了解决这个问题,Transformer模型引用了Self-attention来解决这个问题。Self-attention的输入方式如下:

在这里插入图片描述

它可以一次性输入所有的字。但是NLP的输入文本要按照一定的顺序才可以,因为不同的语序,语义很有可能是不同的。比如下面两句话:

句子1:我喜欢吃洋葱
句子2:洋葱喜欢吃我

所以,对于Transformer结构而言,为了更好的发挥并行输入的特点,首先要解决的问题就是要让模型输入具有一定的位置信息。因此,Transformer加入了Positional Encoding机制。

2.2.1.2.1. 构造位置编码

用整型值标记位置

说起标记位置,大概首先能想到的方法就是给第一个字标记1,第二个字标记2…,以此类推。但是,

  • 在处理不同长度的句子时,模型可能碰到比训练的序列更长的句子,这将不利于模型泛化。
  • 随着序列长度增加,位置标记值越来越大,模型将很难学习到这些位置信息。

用[0,1]范围标记位置

这时候可以将模型位置值限制在[0,1]范围内,来解决上述问题。其中,第一个字标记0,最后一个字标记1,中间的均分,比如共3个字,位置信息就是[0, 0.5, 1];4个字位置信息就是[0, 0.33, 0.69, 1]。

但是这样,序列长度不同时,字与字的相对距离是不同的。

所以,理想情况下,Positional Encoding的设计应该满足以下条件:

  • 为每个字输出唯一的编码可以表示每个字在序列中的绝对位置;
  • 不同长度的序列之间,任意两个字的距离/相对位置应该保持一致;
  • 可以表示模型在训练中未遇到过的句子长度。

绝对位置:“我”是第一个字,“喜”是第二个字,…
相对位置:“喜”在“我”的后面一位,“吃”在“喜”的后面两位…
两个字之间的距离:“我”和“喜”差1个位置,“我”和“吃”差3个位置…

用二进制向量标记位置

位置信息是作用在input Embedding上的,因此可以用一个和input Embedding维度一致的向量表示位置。这里以d_model=3为例,位置向量可以表示为下图所示。

在这里插入图片描述

这样满足所有值都是有界的(位于0、1之间),且Transformer中d_model=512,足够将每个位置都编码出来了。

但是,这样编码的位置向量,处在一个离线空间,不同位置间的变化是不连续的。

用周期函数(sin)标记位置

到这里,我们更明确了,需要的是有界且连续的函数,最先想到的,正弦函数sin可以满足吧。

我们可以将位置向量中的每个元素都用一个sin函数表示,第t个字的位置可以表示为:

P E t = [ s i n ( 1 2 0 t ) , s i n ( 1 2 1 t ) , . . . , s i n ( 1 2 i − 1 t ) , . . . , s i n ( 1 2 d m o d e l − 1 t ) ] PE_t=[sin({\frac{1}{2^0}}t),sin({\frac{1}{2^1}}t),...,sin({\frac{1}{2^{i-1}}}t),...,sin({\frac{1}{2^{d_{model}-1}}}t)] PEt=[sin(201t),sin(211t),...,sin(2i11t),...,sin(2dmodel11t)]

在这里插入图片描述

如下图所示,每一行表示一个 P E t PE_t PEt,每一列表示 P E t PE_t PEt中的第i个元素。旋钮用于调整精度,越往右边的旋钮,需要调整的精度越大,因此指针移动的步伐越小。

每一排的旋钮都在上一排的基础上进行调整(函数中t的作用)。通过频率 1 2 i − 1 \frac{1}{2^{i-1}} 2i11控制sin函数的波长,频率不断减小,则波长不断变大,此时sin函数对t的变动越不敏感,以此来达到越向右的旋钮,指针移动步伐越小的目的。

为什么波长与频率成反比?

对于三角函数,
y = A s i n ( B x + C ) + D y=Asin(Bx+C)+D y=Asin(Bx+C)+D

周期是 2 π B \frac{2π}{B} B2π,频率是 B 2 π \frac{B}{2π} 2πB,因此B越大,频率越大,一个周期内函数图像重复次数越多,波长越短。

这也类似于二进制编码,每一位上都是0和1的交互,越往低位(左移),交互的频率越慢。

在这里插入图片描述

此外,由于sin是周期性函数,纵向看,如果函数频率偏大,引起波长偏短,则不同t对应的位置向量可能会存在重合。例如下图中(d_model=3),图中点表示序列中每个字的位置向量,颜色越深,字的位置越靠后,在频率偏大时,位置向量点连成了一个闭环。

在这里插入图片描述

为了避免这种情况,则要尽量把波长拉长。最简单的方法就是把所有的频率尽可能设小。因为,在Transformer论文中,频率设为 1 1000 0 i / d m o d e l − 1 \frac{1}{10000^{i/{d_{model}-1}}} 10000i/dmodel11

所以,到这里,位置向量可以表示为:

P E t = [ s i n ( w 0 t ) , s i n ( w 1 t ) , . . . , s i n ( w i − 1 t ) , . . . , s i n ( w d m o d e l − 1 t ) ] PE_t=[sin(w_0t),sin(w_1t),...,sin(w_{i-1}t),...,sin(w_{d_{model}-1}t)] PEt=[sin(w0t),sin(w1t),...,sin(wi1t),...,sin(wdmodel1t)]

其中, w i = 1 1000 0 i / d m o d e l − 1 w_i=\frac{1}{10000^{i/{d_{model}-1}}} wi=10000i/dmodel11

用sin和cos交替标记位置

目前为止,位置向量已经满足:

  1. 每个字的向量唯一(每个sin函数频率足够小,波长足够长,不会重合)。
  2. 位置向量的值有界,且连续。模型在处理位置向量时泛化性强,即更好处理长度和训练数据分布不一致的序列。

但是,我们还希望不同的位置向量是可以通过线性变换得到的。这样,不仅能表示一个字的绝对位置,还可以表示一个字的相对位置。即:

P E t + △ t = T △ t ∗ P E t PE_{t+\triangle t}=T_{\triangle t}*PE_t PEt+t=TtPEt

这里,T表示一个线性变化矩阵。假设t是一个角度值, △ t \triangle t t就是旋转的角度,则上面的公式可以写为:

( s i n ( t + △ t ) c o s ( t + △ t ) ) = ( c o s △ t s i n △ t − s i n △ t c o s △ t ) ⋅ ( s i n t c o s t ) \begin{pmatrix}sin{(t+\triangle t)} \\ cos{(t+\triangle t)} \end{pmatrix}=\begin{pmatrix} cos{\triangle t}&sin{\triangle t} \\ -sin{\triangle t}&cos{\triangle t} \end{pmatrix}\cdot\begin{pmatrix}sin{t} \\ cos{t} \end{pmatrix} (sin(t+

### Transformer 架构详解 #### 编码器-解码器框架 Transformer 使用了一种编码器-解码器架构来处理序列到序列的任务。这种架构由多个相同的层堆叠而成,每一层都包含了多头自注意力机制和前馈神经网络[^3]。 #### 多头自注意力机制 自注意力机制允许模型在不同的位置关注输入的不同部分,从而更好地捕捉上下文信息。具体来说,在计算过程中,每个位置的表示会综合考虑其他所有位置的信息加权求和的结果。为了增强表达能力,引入了多头机制,即并行运行多次独立的自注意力操作后再拼接起来作为最终输出[^1]。 #### 前向传播过程中的残差连接与归一化 为了避免梯度消失问题以及加速训练收敛速度,在每个多头自注意力子层之后都会加上一个简单的线性变换再加上原始输入形成残差连接;紧接着通过Layer Normalization 层对数据做标准化处理以稳定数值范围。 #### Position-wise Feed-Forward Networks (FFNs) 除了上述提到的核心组件外,还有一个重要的组成部分就是position-wise feed-forward networks(FFNs),它是由两个线性的转换中间夹着一层ReLU激活函数构成的小型全连接网络。值得注意的是这个结构对于任何位置都是共享参数的。 ```python import torch.nn as nn class PositionwiseFeedForward(nn.Module): "Implements FFN equation." def __init__(self, d_model, d_ff, dropout=0.1): super(PositionwiseFeedForward, self).__init__() self.w_1 = nn.Linear(d_model, d_ff) self.w_2 = nn.Linear(d_ff, d_model) self.dropout = nn.Dropout(dropout) def forward(self, x): return self.w_2(self.dropout(torch.relu(self.w_1(x)))) ``` #### 位置编码 由于Transformer摒弃了传统的RNN/LSTM等依赖于顺序执行的方式来进行特征提取,因此需要额外加入绝对或相对的位置信息给词嵌入以便让模型能够感知单词间的距离关系。通常采用正弦/余弦波形函数生成固定长度的位置编码并与原embedding相加。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穿着帆布鞋也能走猫步

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值