Transformer——从原理、模型架构到内部细节介绍

1. 概述

Transformer 模型是一种基于注意力机制的深度学习模型,最早由Google在2017年的论文《Attention Is All You Need》中提出。自其问世以来就迅速席卷了自然语言处理他(NLP)领域,并在各类主流任务上取得了新的突破,包括机器翻译、语言建模、序列标注和文本分类等。在 NLP 领域取得巨大成功后,迅速扩展到了计算机视觉(CV)领域,并带来了革命性的变化。这种基于自注意力机制的深度学习模型,不仅在 NLP 任务中力压群雄,也成为 CV 领域的新宠。

论文地址:https://arxiv.org/pdf/1706.03762v7
代码地址:https://github.com/tunz/transformer-pytorch/tree/e7266679f0b32fd99135ea617213f986ceede056
在这里插入图片描述

2. 模型结构

Transformer 抛弃了以往深度学习任务里面使用到的 CNN 和 RNN ,完全依赖注意力机制来捕捉输入和输出之间的全局依赖关系。

标准的 Transformer 模型主要由两个模块构成:

  • 编码器(Encoder)
  • 解码器(Decoder)

编码器负责将输入序列编码为固定长度的向量,理解输入文本,为每个输入构造对应的语义表示(语义特征)。解码器负责生成输出,使用 Encoder 输出的语义表示结合其他输入来生成目标序列。
在这里插入图片描述

一个完整的 Transformer 由多个堆叠的 encoder block 和 decoder block 构成。在论文中 Encoder 由 6 个 encoder block 堆叠在一起,Decoder也一样。

在这里插入图片描述

2.1 Encoder

Encoder 由 6 个 encoder block 组成,其中,每个 encoder block 层又由两部分组成(先不讨论残差连接与正则化操作),包括一个多头自注意力机制(Multi-Head Self-Attention Mechanism)子层和前馈神经网络(FeedForward Neural Network,FNN)子层。
在这里插入图片描述

encoder block 接收输入矩阵 X ( n ∗ d ) X_{(n*d)} X(nd) ,并输出一个矩阵 O ( n ∗ d ) O_{(n*d)} O(nd) 。每一个 encoder block 输出的矩阵维度与输入完全一致。

第一个 encoder block 的输入为句子单词的表示向量矩阵,后续 encoder block 的输入是前一个 encoder block 的输出,最后一个 encoder block 输出的矩阵就是编码信息矩阵 C,这一矩阵后续会用到 Decoder 中。

在这里插入图片描述

2.1 Decoder

Decoder 由 6 个 decoder block 组成,其中,每个 decoder block 由三部分组成(同样先不讨论残差与正则),其中包含了 encoder block 提到的两层网络,但是在这两层中间还插入了第三个子层,该子层对编码器堆栈的输出执行多头注意力(Multi-Head Attention)。decoder block 顶层的输出依次经过线性变换和归一化得到目标词的概率。

在这里插入图片描述
此外,decoder block 结构与 encoder block 相似,但是存在一些区别:第一个 Multi-Head Attention 层采用了 Masked 操作。第二个 Multi-Head Attention 层的 K, V 矩阵使用 Encoder 的编码信息矩阵C进行计算,而 Q 使用上一个 decoder block 的输出计算。
在这里插入图片描述

2.3 完整架构

完整的 Transformer 网络架构如下图所示。在每个层周围引入了残差连接(Residual Connection)和层归一化(Layer Normalization)。为了便于这些残差连接,模型中的所有子层以及嵌入层(Embedding Layers)均生成维度为 512 的输出。

残差连接用于防止网络退化,层归一化用于对每一层的激活值进行归一化。

在这里插入图片描述

接下来将具体介绍模型的内部细节。

3. 输入嵌入

首先,模型需要对输入的数据进行一个 embedding 操作,enmbedding 结束之后,输入到 encoder层。

  • 词嵌入:将输入序列中的每个词(或字符、子词等)映射到一个固定维度的向量空间中。例如,对于一个词汇量为 n 的词表,每个词可以表示为一个维度为d的向量 (论文中 d = 512 )。词嵌入矩阵的形状为(n,d),通过查表的方式将词索引转换为词向量。

  • 位置编码:因为 Transformer 不采用像循环神经网络(RNN)那样的时间序列结构,而是使用全局信息,不能利用单词的顺序信息,也就无法捕捉序列中词的位置信息。而这部分信息对于 NLP 来说非常重要。因此,需要在词嵌入的基础上添加位置编码,以使模型能够感知词在序列中的位置。

    位置编码可以采用正弦、余弦函数等方法生成(论文中使用余弦生成),其维度与词嵌入维度相同。将词嵌入向量与位置编码向量相加,得到最终的输入嵌入向量。

    位置编码公式如下,其中 p o s pos pos 代表当前词汇在整个输入句子中的相对位置,而 i i i 代表了词向量维度,在原文中是 512。(也可以替换为可学习的位置向量,即随模型训练而更新。)
    P E ( p o s , 2 i ) = sin ⁡ ( p o s 1000 0 2 i d m o d e l ) P_{E(pos, 2i)} = \sin\left(\frac{pos}{10000^{\frac{2i}{d_{model}}}}\right) PE(pos,2i)=sin(10000dmodel2ipos)
    在这里插入图片描述

4. 注意力机制

Transformer 模型之所以如此强大,是因为它抛弃了之前广泛采用的循环网络和卷积网络,而采用了一种特殊的结构——注意力机制 (Attention) 来建模文本。前面也说过,提出 Transformer 结构的论文名字就叫《Attention Is All You Need》。顾名思义,注意力层的作用就是让模型在处理文本时,将注意力只放在某些词语上。

例如要将英文 “You like this course” 翻译为法语,由于法语中 “like” 的变位方式因主语而异,因此需要同时关注相邻的词语 “You”。同样地,在翻译 “this” 时还需要注意 “course” ,因为 “this” 的法语翻译会根据相关名词的极性而变化。对于复杂的句子,要正确翻译某个词语,甚至需要关注离这个词很远的词。

Transformer 模型本来是为了翻译任务而设计的。在 Encoder 中,由于翻译一个词语需要依赖于上下文,因此注意力层可以访问句子中的所有词语;而 Decoder 是顺序地进行解码,在生成每个词语时,注意力层只能访问前面已经生成的单词。

例如,假设翻译模型当前已经预测出了三个词语,我们会把这三个词语作为输入送入 Decoder,然后 Decoder 结合 Encoder 所有的源语言输入来预测第四个词语。

实际训练中为了加快速度,会将整个目标序列都送入 Decoder,然后在注意力层中通过 Mask 遮盖掉未来的词语来防止信息泄露。例如我们在预测第三个词语时,应该只能访问到已生成的前两个词语,如果 Decoder 能够访问到序列中的第三个(甚至后面的)词语,就相当于作弊了。

其中,Decoder 中的第一个注意力层关注 Decoder 过去所有的输入,而第二个注意力层则是使用 Encoder 的输出,因此 Decoder 可以基于整个输入句子来预测当前词语。这对于翻译任务非常有用,因为同一句话在不同语言下的词语顺序可能并不一致(不能逐词翻译),所以出现在源语言句子后部的词语反而可能对目标语言句子前部词语的预测非常重要。

4.1 为什么引用注意力机制???

Transformer 首先对输入文本进行编码,常规的做法是首先对句子进行分词,然后将每个词语 (token) 都转化为对应的词向量 (token embeddings),这样文本就转换为一个由词语向量组成的矩阵 X = ( x 1 , x 2 , . . . , x n ) X=(x_1,x_2,...,x_n) X=(x1,x2,...,xn) ,其中 x i x_i xi 就表示第 i 个词语的词向量,维度为 d ,故 X ∈ R n × d X \in \mathbb{R}^{n \times d} XRn×d
在 Transformer 模型提出之前,对 token 序列 X X X 的常规编码方式是通过循环网络 (RNNs) 和卷积网络 (CNNs)。

  • RNN(例如 LSTM)的方案很简单,每一个词语 x t x_t xt 对应的编码结果 y t y_t yt 通过递归地计算得到:
    y t = f ( y t − 1 , x t ) y_t=f(y_{t-1},x_t) yt=f(yt1,xt)
    RNN 的序列建模方式虽然与人类阅读类似,但是递归的结构导致其无法并行计算,因此速度较慢。而且 RNN 本质是一个马尔科夫决策过程,难以学习到全局的结构信息。

  • CNN 则通过滑动窗口基于局部上下文来编码文本,例如核尺寸为 3 的卷积操作就是使用每一个词自身以及前一个和后一个词来生成嵌入式表示:
    y t = f ( x t − 1 , x t , x t + 1 ) y_t=f(x_{t-1},x_t,x_{t+1}) yt=f(xt1,xt,xt+1)
    CNN 能够并行地计算,因此速度很快,但是由于是通过窗口来进行编码,所以更侧重于捕获局部信息,难以建模长距离的语义依赖。

4.2 自注意力

自注意力(Self-Attention)用于捕获序列中不同位置之间的关系和依赖性。它通过让序列的每个元素对同一序列中的其他元素施加注意力,生成一个全局感知的表示。
在这里插入图片描述

在计算的时候需要用到矩阵 Q(查询), K(键值), V(值)。在实际中,Self-Attention 接收的是输入(单词的表示向量 x 组成的矩阵 X ) 或者上一个 encoder block 的输出。而 Q, K, V 正是通过 Self-Attention 的输入进行线性变换得到的。

(1)Q, K, V 的计算

这三个向量是输入与一个矩阵相乘得到的结果,这个矩阵是随机初始化的,是可学习的权重矩阵,维度为(64,512)。注意 X, Q, K, V 的每一行都表示一个单词。
在这里插入图片描述

(2)计算注意力分数

计算矩阵 Q 和 K 每一行向量的内积。Q 乘以 K 的转置后,得到的矩阵行列数都为 n,n 为句子单词数,这个矩阵可以表示单词之间的注意力分数。
Attention Score i j = q i ⋅ k j = dot ( q i , k j ) \text{Attention Score}_{ij} = q_i \cdot k_j = \text{dot}(q_i, k_j) Attention Scoreij=qikj=dot(qi,kj)
在这里插入图片描述

(3)归一化注意力分数

使用 Softmax 函数将注意力分数归一化, 计算每一个单词对于其他单词的 attention 系数,得到注意力权重。 该权重矩阵表示输入序列中各个词之间的相关性。Softmax 是对矩阵的每一行进行 Softmax,即每一行的和都变为 1。
α i j = exp ⁡ ( q i ⋅ k j / d k ) ∑ j = 1 n exp ⁡ ( q i ⋅ k j / d k ) \alpha_{ij} = \frac{\exp\left(q_i \cdot k_j / \sqrt{d_k}\right)}{\sum_{j=1}^n \exp\left(q_i \cdot k_j / \sqrt{d_k}\right)} αij=j=1nexp(qikj/dk )exp(qikj/dk )
这里 d k \sqrt{d_k} dk 是缩放因子,用于防止点积值过大。
在这里插入图片描述
(4)加权求和

将注意力权重矩阵与 V 相乘,得到加权后的值向量,即自注意力输出。在这里插入图片描述

4.3 多头自注意力

为了能够从不同的角度捕捉序列中的信息,Transformer 引入了多头注意力机制。将输入向量进行多个自注意力头计算,然后再将多个头的输出拼接起来,并通过一个可学习的线性变换矩阵进行整合。
在这里插入图片描述

为什么需要多头自注意力?

  • 单头自注意力机制只能关注输入序列的一种特定关系或模式,多头机制可以在多个子空间中关注不同的关系和特征。
  • 多头机制允许模型在每个注意力头中学习不同的注意力分布,使得最终的输出更加多样化和全面。

简单来说,Multi-Head Attention 包含多个 Self-Attention 层,首先将输入向量分别传递到 h 个不同的 Self-Attention 中,计算得到 h 个输出矩阵 Z。下图是 h=8 时候的情况,此时会得到 8 个输出矩阵 Z。
在这里插入图片描述
得到 8 个输出矩阵之后,Multi-Head Attention 将它们拼接在一起 (Concat),然后传入一个 Linear 层,得到 Multi-Head Attention 最终的输出 Z。输出矩阵 Z 与其输入矩阵 X 的维度是一样的。
在这里插入图片描述

4.4 掩码自注意力

在解码器中,生成序列是逐步进行的。第一个 Multi-Head Attention 层采用了 Masked 操作,用以确保当前位置的预测只依赖于之前已经生成的输出,而不能看到未来的输出。假设正在生成第 i 个输出,为了保证模型的因果性(causality),必须确保第 i 个位置的注意力只能依赖于第 i-1,i-2 等之前的输出,而不能看到第 i+1,i+2 等未来位置的信息。

下面以 “我有一只猫” 翻译成 “I have a cat” 为例,了解一下 Masked 操作。首先根据输入 “” 预测出第一个单词为 “I”,然后根据输入 “ I” 预测下一个单词 “have”。
在这里插入图片描述

实现掩码的方式

掩码通过构造一个上三角矩阵(上三角部分为负无穷,或非常小的值),对自注意力机制中的点积结果进行遮盖。例如,输入矩阵包含 “ I have a cat” (0, 1, 2, 3, 4) 五个单词的表示向量,Mask 是一个 5×5 的矩阵。在 Mask 可以发现单词 0 只能使用单词 0 的信息,而单词 1 可以使用单词 0, 1 的信息,即只能使用之前的信息。
在这里插入图片描述

注意: Mask 操作是在 Self-Attention 的 Softmax 之前使用的。

(1)计算注意力权重

这一步同介绍自注意力过程一样,这里省略。得到的注意力分数矩阵 A 是 nxn 的。
在这里插入图片描述
(2)添加掩码(Masking)

构造与注意力分数矩阵大小维度一样的掩码矩阵 M,大小为 nxn 。满足如下公式:
M [ i , j ] = { 0 if  j ≤ i − ∞ if  j > i M[i, j] = \begin{cases} 0 & \text{if } j \leq i \\ -\infty & \text{if } j > i \end{cases} M[i,j]={0if jiif j>i
M [ i , j ] = − ∞ M[i, j] = -\infty M[i,j]=表示位置 i 不允许关注位置 j > i 。

然后在注意力分数 A 上添加掩码:
A ′ = A + M A' = A + M A=A+M
在这里插入图片描述
(3)Softmax+加权求和

对掩码后的注意力分数 A ′ A' A 进行 Softmax 归一化,每一行的和都为 1。但是单词 0 在单词 1, 2, 3, 4 上的 attention score 都为 0。并与 V 相乘,得到加权后的值向量,即自注意力输出。
Attention ( Q , K , V ) = Softmax ( A ′ ) V \text{Attention}(Q, K, V) = \text{Softmax}(A') V Attention(Q,K,V)=Softmax(A)V

由于掩码矩阵中有 − ∞ −∞ ,对应的注意力分数会被置为 0,从而有效屏蔽未来位置。

在这里插入图片描述

4.5 掩码多头自注意力

掩码操作被直接集成到多头自注意力中,对每个头的注意力计算进行遮盖。每个头独立计算注意力分数,并添加掩码,将所有头的输出拼接后,经过线性变换得到最终输出。

4.6 交叉多头自注意力

decoder block 第二个 Multi-Head Attention 变化不大, 主要的区别在于其中 Self-Attention 的 K, V 矩阵不是使用上一个 decoder block 的输出计算的,而是使用 Encoder 的编码信息矩阵 C 计算的。

根据 Encoder 的输出 C 计算得到 K, V,根据上一个 decoder block 的输出 Z 计算 Q (如果是第一个 decoder block 则使用输入矩阵 X 进行计算),后续的计算方法与之前描述的一致。

这样做的好处是在 Decoder 的时候,每一位单词都可以利用到 Encoder 所有单词的信息 (这些信息无需 Mask)。

5. 前馈神经网络

前馈神经网络是一个逐位置(position-wise)的全连接网络,包含两个全连接层,其操作不涉及序列中不同位置的交互。对序列中每个位置的输入向量,都分别通过这两个层进行独立的非线性变换。

第一个层将输入维度被映射到一个更高维的空间,以增加特征表达能力。然后应用非线性激活函数(ReLU)引入非线性。第二个层再将高维特征压缩回原来的输入维度 ,以保持特征维度一致。这一过程可以对自注意力输出进行进一步的特征转换和提取。
max ⁡ ( 0 , X W 1 + b 1 ) W 2 + b 2 \max\left(0, X W_{1}+b_{1}\right) W_{2}+b_{2} max(0,XW1+b1)W2+b2

6. 层归一化

Layer Normalization 负责将一批 (batch) 输入中的每一个都标准化为均值为零且具有单位方差。Skip Connections 则是将张量直接传递给模型的下一层而不进行处理,并将其添加到处理后的张量中。

向 Transformer Encoder/Decoder 中添加 Layer Normalization 目前共有两种做法:
在这里插入图片描述

  • Post layer normalization:Transformer 论文中使用的方式,将 Layer normalization 放在 Skip Connections 之间。 但是因为梯度可能会发散,这种做法很难训练,还需要结合学习率预热 (learning rate warm-up) 等技巧。
  • Pre layer normalization:目前主流的做法,将 Layer Normalization 放置于 Skip Connections 的范围内。这种做法通常训练过程会更加稳定,并且不需要任何学习率预热。

7. 预训练任务

Transformer 模型本质上都是预训练语言模型,大都采用自监督学习 (Self-supervised learning) 的方式在大量生语料上进行训练,也就是说,训练这些 Transformer 模型完全不需要人工标注数据。自监督学习是一种训练目标可以根据模型的输入自动计算的训练方法。
例如下面两个常用的预训练任务:

  • 基于句子的前 n 个词来预测下一个词,因为输出依赖于过去和当前的输入,因此该任务被称为因果语言建模 (causal language modeling);
    在这里插入图片描述
  • 基于上下文(周围的词语)来预测句子中被遮盖掉的词语 (masked word),因此该任务被称为遮盖语言建模 (masked language modeling)。
    这里插入图片描述
    这些预训练模型虽然可以对训练过的语言产生统计意义上的理解,例如可以根据上下文预测被遮盖掉的词语,但是如果直接拿来完成特定任务,效果往往并不好。因此,我们通常还会采用迁移学习 (transfer learning) 方法,使用特定任务的标注语料,以有监督学习的方式对预训练模型参数进行微调 (fine-tune),以取得更好的性能。

8. 迁移学习

预训练是一种从头开始训练模型的方式:所有的模型权重都被随机初始化,然后在没有任何先验知识的情况下开始训练:
在这里插入图片描述
这个过程不仅需要海量的训练数据,而且时间和经济成本都非常高。

因此,大部分情况下,我们都不会从头训练模型,而是将别人预训练好的模型权重通过迁移学习应用到自己的模型中,即使用自己的任务语料对模型进行“二次训练”,通过微调参数使模型适用于新任务。

这种迁移学习的好处是:

  • 预训练时模型很可能已经见过与我们任务类似的数据集,通过微调可以激发出模型在预训练过程中获得的知识,将基于海量数据获得的统计理解能力应用于我们的任务;
  • 由于模型已经在大量数据上进行过预训练,微调时只需要很少的数据量就可以达到不错的性能;
  • 换句话说,在自己任务上获得优秀性能所需的时间和计算成本都可以很小。

例如,我们可以选择一个在大规模英文语料上预训练好的模型,使用 arXiv 语料进行微调,以生成一个面向学术/研究领域的模型。这个微调的过程只需要很少的数据:我们相当于将预训练模型已经获得的知识“迁移”到了新的领域,因此被称为迁移学习。

在这里插入图片描述

与从头训练相比,微调模型所需的时间、数据、经济和环境成本都要低得多,并且与完整的预训练相比,微调训练的约束更少,因此迭代尝试不同的微调方案也更快、更容易。实践证明,即使是对于自定义任务,除非你有大量的语料,否则相比训练一个专门的模型,基于预训练模型进行微调会是一个更好的选择。

在绝大部分情况下,我们都应该尝试找到一个尽可能接近我们任务的预训练模型,然后微调它,也就是所谓的“站在巨人的肩膀上”。

### Transformer Model 中的 Feed-Forward Neural Network (FNN) 在Transformer架构中,每一个编码器层和解码器层内部都包含了两个子层:一个多头自注意力机制(Multi-head Self-Attention Mechanism),以及一个全连接前馈神经网络(Fully Connected Feed-Forward Neural Network, FNN)。对于后者而言,其设计目的是为了引入非线性变换能力,从而增强模型表达复杂函数的能力。 #### 前馈神经网络结构 该部分由两层线性变换组成,并且在这两者之间加入了一个激活函数。具体来说: 1. **第一层**是一个线性映射操作,将输入向量维度转换成较高维空间; 2. **中间层**应用了特定类型的非线性激活函数来增加模型灵活性;值得注意的是,在某些变体如LLaMA中采用了Shazeer(2020)所提出的SwiGLU作为替代方案[^2]; 3. **第二层**再经过另一个线性投影回到原始特征空间大小。 整个过程可以用如下公式表示: \[ \text{FFN}(x)=\max(0,xW_1+b_1)W_2+b_2 \] 其中\( W_1,W_2,b_1,b_2 \)均为待优化参数矩阵/向量。 下面是基于PyTorch实现的一个简单版本: ```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__() # Define two linear transformations with ReLU activation in between. 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)))) ``` 此代码片段定义了一个`PositionwiseFeedForward`类,实现了上述描述的功能。需要注意的是,默认情况下这里使用ReLU作为激活函数,但在实际应用中也可以替换为其他形式,例如上面提到过的SwiGLU。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值