1. 从头搞懂 Transformer模型(图解)

1、Transformer简介

GPT回答:(面试被问到可以这么介绍)

Transformer是一种用于处理序列数据的深度学习模型架构,最初由Vaswani等人在2017年的论文《Attention is All You Need》中提出。它在处理序列到序列(seq2seq)任务时取得了巨大成功,如机器翻译、文本摘要、语音识别等。

Transformer模型的核心思想是自注意力机制(Self-Attention),它允许模型在每一步都能够对输入序列的不同部分进行关注,而不需要通过循环神经网络(RNN)等结构来逐步处理序列。这种并行化的操作使得Transformer在处理长序列时具有很好的性能,同时也减少了训练时间

Transformer模型的基本结构由**编码器(Encoder)解码器(Decoder)组成。编码器负责将输入序列编码成一个中间表示,而解码器则利用这个中间表示来生成输出序列。除了自注意力机制外,Transformer还包含了位置编码(Positional Encoding)残差连接(Residual Connection)**等关键技术,以提高模型的性能和稳定性。

自从提出以来,Transformer模型已经被广泛用于各种自然语言处理任务,并且在很多任务中取得了state-of-the-art的效果。例如,BERT(Bidirectional Encoder Representations from Transformers)、GPT(Generative Pre-trained Transformer)等模型都是基于Transformer架构构建的。

Transformer 的优势

RNNs 和 LSTMs、GRU也是之前 NLP 常用的架构,直到 Transformer 的出现。与 RNN 类型的架构相比,为什么 Transformer 的效果要好?

然而,这有两个限制:

  • 对于长句中相距较远的单词,其间的长距离依赖关系是一个挑战。

  • RNNs 每个时间步值处理输入序列的一个词。这意味着在完成时间步 T-1 计算之前,它无法进行时间步骤 T 的计算。(即无法进行并行计算)这降低了训练和推理速度。

对于CNN来说,所有的输出都可以并行计算,这使得卷积速度大大加快。然而,它们在处理长距离的依赖关系方面也有限制:- 卷积层中,只有图像(或文字,如果应用于文本数据)中足够接近于核大小的部分可以相互作用。对于相距较远的项目,你需要一个有许多层的更深的网络。

Transformer 架构解决了这两个限制。它摆脱了RNNs,完全依靠 Attention的优势:

  • 并行地处理序列中的所有单词,从而大大加快了计算速度。
    在这里插入图片描述
  • 输入序列中单词之间的距离并不重要。Transformer 同样擅长计算相邻词和相距较远的词之间的依赖关系。

Transformer 的整体架构:
在这里插入图片描述

  • 数据在输入编码器和解码器之前,都要经过:
    – 词嵌入层
    – 位置编码层

  • 编码器堆栈包含若干个编码器。每个编码器都包含:
    – 多头注意层
    – 前馈层

  • 解码器堆栈包含若干个解码器。每个解码器都包含:
    – 两个多头注意层
    – 前馈层

  • 输出产生最终输出:
    – 线性层
    – Softmax层。

在这里插入图片描述

2、Transformer 训练和推理

Transformer 的训练和推理有一些细微差别。

首先来看训练。每一条训练数据都包括两部分内容:

  • 输入序列,或称为“源序列”(例如对于一个翻译问题,“You are welcome” 是一个输入序列)
  • 输出序列,或称为“目标序列(上述的翻译问题 “De nada” 即为“You are welcome” 的西班牙语翻译,为输出序列)

而 Transformer 训练的目标就是,通过对训练数据中源序列与目标序列之间规律的学习,在测试或实际的任务中,给定源序列,生成目标序列。
在这里插入图片描述

2.1 Transformer 训练

如上图所示,Transformer在训练过程中,模型对数据的处理过程如下,大体可分为 6 个步骤:

  1. 在送入第一个编码器之前,输入序列 (src_seq) 首先被转换为嵌入(同时带有位置编码),产生词嵌入表示(src_position_embed),之后送入第一个编码器。

  2. 由各编码器组成的编码器堆栈按照顺序对第一步中的输出进行处理,产生输入序列的编码表示(enc_outputs)。

  3. 在右侧的解码器堆栈中,目标序列首先加一个句首标记,被转换成嵌入(带位置编码),产生词嵌入表示(tgt_position_embed),之后送入第一个解码器。

  4. 由各解码器组成的解码器堆栈,将第三步的词嵌入表示(tgt_position_embed),与编码器堆栈的编码表示(enc_outputs)一起处理,产生目标序列的解码表示(dec_outputs)。

  5. 输出层将其转换为词概率和最终的输出序列(out_seq)。

  6. 损失函数将这个输出序列(out_seq)与训练数据中的目标序列(tgt_seq)进行比较。这个损失被用来产生梯度,在反向传播过程中训练模型。

2.2 Transformer 推理

在推理过程中,我们只有输入序列,而没有目标序列作为输入传递给解码器。Transformer 推理的目标是仅通过输入序列产生目标序列

因此,与 Seq2Seq 模型类似,我们在一个时间步的完整循环中生成当前时间步的输出,并在下一个时间段将前一个时间段的输出序列传给解码器作为其输入,直到我们遇到句末标记。

但与 Seq2Seq 模型的不同之处在于,在每个时间步,我们输入直到当前时间步所产生的整个输出序列,而不是只输入上一个时间步产生的词(类似输入序列长度可变的自回归模型)。非常重要,把原文粘过来:The difference from the Seq2Seq model is that, at each timestep, we re-feed the entire output sequence generated thus far, rather than just the last word.
在这里插入图片描述

推理过程中的数据流转如下

  1. 第一步与训练过程相同:输入序列 (src_seq) 首先被转换为嵌入(带有位置编码),产生词嵌入表示(src_position_embed),之后送入第一个编码器。
  2. 第二步也与训练过程相同:由各编码器组成的编码器堆栈按照顺序对第一步中的输出进行处理,产生输入序列的编码表示(enc_outputs)。
  3. 从第三步开始一切变得不一样了:在第一个时间步,使用一个只有句首符号的空序列来代替训练过程中使用的目标序列。空序列转换为嵌入带有位置编码的嵌入(start_position_embed),并被送入解码器堆栈中的第一个解码器。
  4. 解码器堆栈将第三步的空序列嵌入表示(start_position_embed),与编码器堆栈的编码表示(enc_outputs)一起处理,产生目标序列第一个词的编码表示(step1_dec_outputs)。
  5. 输出层将其(step1_dec_outputs)转换为词概率和第一个目标单词(step1_tgt_seq)。
  6. 将这一步产生的目标单词填入解码器输入的序列中的第二个时间步位置。在第二个时间步,解码器输入序列包含句首符号产生的 token 和第一个时间步产生的目标单词。
  7. 回到第3个步骤,与之前一样,将新的解码器序列输入模型。然后取输出的第二个词并将其附加到解码器序列中。重复这个步骤,直到它预测出一个句末标记。需要明确的是,由于编码器序列在每次迭代中都不会改变,我们不必每次都重复第1和第2步。

2.3 Teacher Forcing (输入整个目标序列)

训练时向解码器输入整个目标序列的方法被称为 Teacher Forcing。

训练时,我们本可以使用与推理时相同的方法。即在一个时间步运行 Transformer,从输出序列中取出最后一个词,将其附加到解码器的输入中,并将其送入解码器进行下一次迭代。最后,当预测到句末标记时,Loss 函数将比较生成的输出序列和目标序列,以训练网络。

但这种训练机制不仅会导致训练时间更长,而且还会增加模型训练难度:若模型预测的第一个词错误,则会根据第一个错误的预测词来预测第二个词,以此类推。

相反,通过向解码器提供目标序列,实际上是给了一个提示。即使第一个词预测错误,在下一时间步,它也可以用正确的第一个词来预测第二个词,避免了错误的持续累加。

此外,这种机制保证了 Transformer 在训练阶段并行地输出所有的词,而不需要循环,这大大加快了训练速度。

3、Transformer结构分析

剖析Transformer模型的核心,深入探索从其注意机制到编码器-解码器( encoder-decoder)结构的每个关键组件。

3.1 Attention 在做什么?

Transformer 的突破性表现关键在于其对注意力的使用。

在处理一个单词时,注意力使模型能够关注输入中与该单词密切相关的其他单词。

例如,在以下的英文句子中,ball 与 blue 、hold 密切相关。另一方面,boy 与 blue 没有关系。
在这里插入图片描述
Transformer 通过将输入序列中的每个词与其他词关联起来(同一序列中),形成 self-attention 机制。
考虑以下两个句子:

  • The cat drank the milk because it was hungry.

  • The cat drank the milk because it was sweet.

第一个句子中,单词 ‘it’ 指‘cat’;第二个句子中,‘it’ 指 ‘milk’。当模型处理 'it’这个词时,self-attention 给了模型更多关于 ‘it’ 意义的信息,这样就能把 'it '与正确的词联系起来。
在这里插入图片描述
为了使模型能够处理有关于句子意图和语义的更多细微差别,Transformer 对每个单词都进行注意力打分。

3.2 自注意力是如何工作的?

以下是自注意力在一个简单示例中的工作原理:

考虑一句话:“The cat sat on the mat.”

  • 嵌入
    首先,模型将输入序列中的每个单词嵌入到一个高维向量表示中。这个嵌入过程允许模型捕捉单词之间的语义相似性。

  • 查询、键和值向量
    模型为序列中的每个单词计算三个向量:查询向量、键向量和值向量。在训练过程中,模型学习这些向量,每个向量都有不同的作用。查询向量表示单词的查询,即模型在序列中寻找的内容。键向量表示单词的键,即序列中其他单词应该注意的内容。值向量表示单词的值,即单词对输出所贡献的信息。

  • 注意力分数
    一旦模型计算了每个单词的查询、键和值向量,它就会为序列中的每一对单词计算注意力分数。这通常通过取查询向量和键向量的点积来实现,以评估单词之间的相似性。

  • SoftMax 归一化
    使用 softmax 函数对注意力分数进行归一化,以获得注意力权重。这些权重表示每个单词应该关注序列中其他单词的程度。注意力权重较高的单词被认为对正在执行的任务更为关键。

  • 加权求和
    最后,使用注意力权重计算值向量的加权和。这产生了每个序列中单词的自注意力机制输出,捕获了来自其他单词的上下文信息。

在这里插入图片描述

3.3 词嵌入层与位置编码

为了深入理解每个组件的作用,在翻译任务中 step-by-step 地训练 Transformer。使用只有一个样本的训练数据,其中包括一个输入序列(英语的 “You are welcome”)和一个目标序列(西班牙语的 “De nada”)。

Transformer 的输入需要关注每个词的两个信息:该词的含义和它在序列中的位置

  • 第一个信息,可通过嵌入层对词的含义进行编码。

  • 第二个信息,可通过位置编码层表示该词的位置。

Transformer 通过添加下面两个层来完成两种不同的信息编码:

3.3.1. 嵌入层(Embedding)

Transformer 的编码器和解码器各有一个嵌入层(Embedding )。
在编码器中,输入序列被送入编码器的嵌入层,被称为输入嵌入( Input Embedding)。
在这里插入图片描述
在解码器中,目标序列右移一个位置,然后在第一个位置插入一个Start token 后被送入解码器的嵌入层。注意,在推理过程中,我们没有目标序列,而是循环地将输出序列送入解码器的嵌入层,这个过程被称为 “输出嵌入”( Output Embedding)。

每个文本序列在输入嵌入层之前,都已被映射成词汇表中单词 ID 的数字序列。嵌入层再将每个数字序列映射成一个嵌入向量,这是该词含义一个更丰富的表示。

在这里插入图片描述

3.3.2. 位置编码(Position Encoding)

RNN 在循环过程中,每个词按顺序输入,因此隐含地知道每个词的位置。

然而,Transformer一个序列中的所有词都是并行输入的。这是其相对于RNN 架构的主要优势;但同时也意味着位置信息会丢失,必须单独添加回来。

解码器堆栈和编码器堆栈各有一个位置编码层。位置编码的计算是独立于输入序列的,是固定值,只取决于序列的最大长度。

  • 第一项是一个常数代码,表示第一个位置。
  • 第二项是一个表示第二位置的常量代码。
    在这里插入图片描述
    pos 是该词在序列中的位置,d_model 是编码向量的长度(与嵌入向量相同)和 i 是这个向量的索引值。公式表示的是矩阵第 pos 行、第 2i 列和 (2i+ 1) 列上的元素。
    在这里插入图片描述
    换句话说,位置编码交织了一系列正弦曲线和一系列余弦曲线,对于每个位置 pos,当 i 为偶数时,使用正弦函数计算;当 i 为奇数时,使用余弦函数计算。

3.3.3. 矩阵维度(Matrix Dimensions)

深度学习模型一次处理一批训练样本。嵌入层和位置编码层对一批序列样本的矩阵进行操作。嵌入层接受一个 (samples, sequence_length) 形状的二维单词ID矩阵,将每个单词ID编码成一个单词向量,其大小为 embedding_size,从而得到一个(samples, sequence_length, embedding_size) 形状的三维输出矩阵。位置编码使用的编码尺寸等于嵌入尺寸。所以它产生一个类似形状的矩阵,可以添加到嵌入矩阵中。
在这里插入图片描述
由嵌入层和位置编码层产生的(samples, sequence_length, embedding_size) 形状在模型中被保留下来,随数据在编码器和解码器堆栈中流动,直到它被最终的输出层改变形状。
[实际上变成了(samples, sequence_length, vocab_size)] 。

以上对 Transformer 中的矩阵维度有了一个形象的认识。为了简化可视化,从这里开始,暂时放弃第一个维度(samples 维度),并使用单个样本的二维表示。
在这里插入图片描述

3.4 Encoder

编码器和解码器堆栈分别由几个(通常是 6 个)编码器和解码器组成,按顺序连接。

在这里插入图片描述

  • 堆栈中的第一个编码器从嵌入和位置编码中接收其输入。堆栈中的其他编码器从前一个编码器接收它们的输入。
  • 当前编码器接受上一个编码器的输入,并将其传入当前编码器的自注意力层。当前自注意力层的输出被传入前馈层,然后将其输出至下一个编码器。
  • 自注意力层和前馈网络都会接入一个残差连接,之后再送入正则化层。注意,上一个解码器的输入进入当前解码器时,也有一个残差连接。

在这里插入图片描述

  • 参考:在李沐老师的书,《动手学深度学习》中,P416页有对“基于位置的前馈网络”的详细解释。具体来说,该前馈网络由一个线性层,一个激活函数和另外一个线性层组成。并且这个前馈网络同样不会改变输入的形状。
    在这里插入图片描述
  • 编码器堆栈中的最后一个编码器的输出,会送入解码器堆栈中的每一个解码器中。

3.5 Decoder

解码器的结构与编码器的结构非常类似,但有一些区别。

  1. 与编码器一样,解码器堆栈中的第一个解码器从嵌入层(词嵌入+位置编码)中接受输入;堆栈中的其他解码器从上一个解码器接受输入。
  2. 在一个解码器内部,输入首先进入自注意力层,这一层的运行方式与编码器自注意力层的区别在于:
    (1) 训练过程中,解码器的自注意力层接收整个输出序列。但为了避免在生成每个输出时看到未来的数据(即避免信息泄露),使用所谓的“掩码”技术,确保在生成第 i 个词时,模型只能看到第 1 个到 第 i 个词。
    (2) 推理过程中,每个时间步的输入,是直到当前时间步所产生的整个输出序列
  3. 解码器与编码器的另一个不同在于,解码器有第二个注意层层,即编码器-解码器注意力层 (Encoder-Decoder-attention) 层。其工作方式与自注意力层类似,只是其输入来源有两处:位于其前的自注意力层及解码器堆栈的输出。
    在这里插入图片描述
  4. 编码器-解码器注意力层的输出被传入前馈层,然后将其输送至下一个解码器。
  5. 解码器中的每一个子层,包括自注意力层、编码器-解码器注意力层、前馈层,均由一个残差连接,并进行层规范化。
    在这里插入图片描述

3.6 注意力-Attention

Transformer 中,注意力被用在三个地方:

1、Encoder 中的 Self-attention:输入序列对自身的注意力计算;
2、Decoder 中的 Self-attention:目标序列对自身的注意力计算;
3、Decoder 中的Encoder-Decoder-attention:目标序列对输入序列的注意力计算。

注意力层通过三个参数进行计算,这三个参数称为查询(Query)、键(Key)和值(Value)
1、在 Encoder 中的 Self-attention,编码器的输入与相应的参数矩阵相乘,得到Query、Key 和 Value三个参数。
在这里插入图片描述
2、 在 Decoder 中的 Self-attention,解码器的输入通过相同的方式得到 Query、Key 和 Value。

3、在解码器的 Encoder-Decoder-attention中,编码器堆栈中最后一个编码器的输出被传递给 Value 和 Key参数。位于 Encoder-Decoder-attention的 Self-attention 和 Layer Norm 模块的输出被传递给 Query 参数。(关键)
在这里插入图片描述

3.7 多头注意力(Multi-head Attention)

Transformer 将每个注意力计算单元称为注意力头(Attention Head )。多个注意力头并行运算,即所谓的多头注意力:Multi-head Attention。它通过融合几个相同的注意力计算,使注意力计算具有更强大的分辨能力。
在这里插入图片描述
通过每个独立线性层自己的权重参数,即 Query, Key 和 Value,与输入进行矩阵乘法运算,得到 Q、K、V。这些结果通过如下所示的注意力公式组合在一起,产生注意力分数(Attention Score)。
在这里插入图片描述
需要注意的重要一点是,Q、K、V 的值是对序列中每个词的编码表示。注意力计算将每个词与序列中的其他词联系起来,这样注意力分数就为序列中的每个词编码了一个分数。

3.8 注意力掩码(Attention Masks)

在计算 Attention Score 的同时,Attention 模块应用了一个掩码操作。掩码操作有两个目的:

  1. 在 Encoder Self-attention 和 Encoder-Decoder-attention 中:掩码的作用是,在输入序列 padding对应的位置,将输出的注意力分数(Attention Score)归零,以确保 padding 对 Self-attention 的计算没有贡献。
    (1)padding 的作用:由于输入序列可能有不同的长度,因此会像大多数 NLP 方法一样,使用 padding 作为填充 标记,以得到固定长度的向量,从而可以将一个样本的序列作为矩阵被输入到 Transform 中。
    (2)当计算注意力分数(Attention Score)时,在 Softmax 计算之前的分子上进行了掩码。被屏蔽的元素(白色方块)设置为负无穷大,这样Softmax就会把这些值变成零。
    在这里插入图片描述
    对 padding 掩码操作的图示:
    在这里插入图片描述
    Encoder-Decoder-attention 中的掩码操作也是这样:
    在这里插入图片描述
  2. 在 Decoder 中的 Self-attention 中:掩蔽的作用是,防止解码器在当前时间步预测时 ,"偷看 "目标句余下几个时间步的部分:
    (1)解码器处理源序列 source sequence中的单词,并利用它们来预测目标序列中的单词。训练期间,这个过程是通过 Teacher Forcing 进行的,完整的目标序列被作为解码器的输入。因此,在预测某个位置的词时,解码器可以使用该词之前的目标词以及该词之后的目标词。这使得解码器可以通过使用未来 "时间步 "的目标词来 “作弊”。
    (2)举例,如下图所示,当预测 "Word3 "时,解码器应该只参考目标词的前三个输入词,而不含第四个单词 “Ketan”。因此, Decoder 中的 Self-attention 掩码操作掩盖了序列中位于当前时间步之后的目标词。
    在这里插入图片描述
    在这里插入图片描述

3.9 产生输出(Generate Output)

解码器堆栈(Decoder stack)中的最后一个解码器(Decoder)将其输出传给输出组件,输出组件将其转换为最终目标句子。

  1. 线性层将解码器向量投射到单词分数(Word Scores)中,目标词汇中的每个独特的单词在句子的每个位置都有一个分数值。例如,如果我们的最终输出句子有7个词,而目标西班牙语词汇有10000个独特的词,我们为这7个词中的每一个生成10000个分数值。分数值表示词汇中的每个词在句子的那个位置出现的可能性。
  2. Softmax 层将这些分数变成概率(加起来为1.0)。在每个位置,我们找到概率最高的单词索引(贪婪搜索),然后将该索引映射到词汇表中的相应单词。这些词就构成了 Transformer 的输出序列。
    在这里插入图片描述

3.10 训练与损失函数(Training and Loss Function)

训练中使用交叉熵作为损失函数,比较生成的输出概率分布和目标序列。概率分布给出了每个词在该位置出现的概率。
在这里插入图片描述
假设我们的目标词汇只包含四个词。我们的目标是产生一个与我们预期的目标序列 "De nada END "相符的概率分布。

这意味着第一个词位的概率分布中,"De "的概率应该是1,而词汇中所有其他词的概率都是 0 。同样地,在第二和第三词位中,“nada” 和 “END”的概率应该都是 1,而词汇表中其他词的概率都是 0 。

像往常一样,对损失被计算梯度,通过反向传播来训练模型。

4、多头注意力 (细节)

在 Transformer 中,注意力模块会并行多次重复计算。每个并行计算称为一个 注意力头(Attention Head)。注意力模块将其查询 Query 、键 Key和值 Value的参数矩阵进行 N 次拆分,并将每次拆分独立通过一个单独的注意力头。最后,所有这些相同的注意力计算会合并在一起,产生最终的注意力分数。能够更细致地捕捉并表达每个词汇之间的多种联系和微妙差异。
在这里插入图片描述

4.1 注意力超参数(Attention Hyperparameters)

Transformer 内部流动的数据维度由三个超参数决定。

  • Embedding Size:嵌入向量的大小(例子中使用的嵌入向量大小为6)。这个维度在整个 Transformer 模型中都是向前传递的,因此有时也被称为 " model size - 模型大小 "等其他名称。
  • Query Size(与 Key size 和 Value size 相等):查询向量的长度,与键向量和值向量的长度相等,也是分别用来产生Query、Key 和 Value矩阵的三个线性层的权重大小(例子中使用的查询大小为3)
  • Number of Attention heads:注意力头个数。例子中使用注意力头个数为 2 。

此外,还有 Batch_size,批量大小,是一次输入模型中样本的数量。

4.2 输入层(Input Layer)

经过词嵌入和位置编码后,进入编码器之前,输入的数据维度为:

(batch_size, seq_length, embedding_size)

之后数据进入编码器堆栈中的第一个 Encoder,与 Query、Key、Value 矩阵相乘。
在这里插入图片描述
为方便理解,以下的图示与介绍中将去掉 batch_size 维度,聚焦于剩下的维度:
在这里插入图片描述

4.3 线性层(Linear Layers )

Query, Key, Value 实际上是三个独立的线性层。每个线性层都有自己独立的权重。输入数据与三个线性层分别相乘,产生 Q、K、V。
在这里插入图片描述

4.4 通过注意力头切分数据(Splitting data across Attention heads)

现在,数据被分割到多个注意头中,以便每个注意头能够独立地处理它。

需要注意:

1. “切分”只是逻辑上的分割。对于参数矩阵 Query, Key, Value 而言,并没有物理切分成对应于每个注意力头的独立矩阵,
2. 逻辑上,每个注意力头对应于 Query, Key, Value 的独立一部分。各注意力头没有单独的线性层,而是所有的注意力头共用线性层,只是不同的注意力头在独属于各自的逻辑部分上进行操作。

这种切分可以按照两个步骤理解:

第一步,线性层权重矩阵的切分

这种逻辑分割,是通过将输入数据以及线性层权重,均匀划分到各注意头中来完成的。我们可以通过选择下面的 Query Size大小来实现:
在这里插入图片描述
在我们的例子中,这就是为什么 Query_Size=6/2=3。尽管层权重(和输入数据)均为单一矩阵,我们可以认为它是“将每个头的独立层权重 ‘堆叠’在一起"成为一个矩阵
在这里插入图片描述
也因此,在代码中定义 W_Q、W_K、W_V 矩阵时可这样定义:

self.W_Q = nn.Linear(embedding_size, Query_size * n_heads, bias=False) 

self.W_K = nn.Linear(embedding_size, Query_size * n_heads, bias=False)

self.W_V = nn.Linear(embedding_size, Value_size * n_heads, bias=False)

基于此,所有 Heads 的计算可通过对一个的矩阵操作来实现,而不需要 N 个单独操作。这使得计算更加有效,同时保持模型的简单:所需线性层更少,同时获得了多头注意力的效果。

回顾前一小节的内容,input 的维度是:

(batch_size, seq_length, embedding_size)

而线性层的维度是:

(batch_size, embedding_size,  Query_size * n_heads)

由于 embedding_size = Query_size * n_heads,所以线性层的维度并未发生变化。

输入经过线性层映射后,得到 Q、K 和 V 矩阵形状是:

(batch_size, seq_length, Query_size * n_heads)

第二步,重塑 Q、K 和 V 矩阵形状

经由线性层输出得到的 Q、K 和 V 矩阵要经过 Reshape 操作,以产生一个 Head 维度。现在每个 "切片 "对应于代表每个头的一个矩阵。

通过交换 n_heads 和 seq_length 这两个维度,改变 Q、K 和 V 矩阵的形状。图示中虽然未表达出 Batch 维度,但对应于每一个注意力头的 ‘Q’ 的维度是:

	(batch_size, n_heads, seq_length, Query size)

在这里插入图片描述

在上图中,我们可以看到从线性层 (Wq) 出来后,分割 Q 矩阵的完整过程。(没看懂)

最后一个阶段只是为了形象化–实际上 Q 矩阵仍然是一个单一矩阵,但可以把它看作是每个注意力头的逻辑上独立的 Q 矩阵。

在这里插入图片描述

4.5 为每个头计算注意力分数(Compute the Attention Score for each head)

现在有了分属各头的 Q、K、V 3个矩阵,这些矩阵用来计算注意力分数。

为方便理解,只展示单个 head 的计算。使用最后两个维度(seq_length, Query size),跳过前两个维度(batch_size, n_heads)。

从本质上讲,可以想象,计算对于每个头和每个批次中的每个样本都是 "重复"进行的。(虽然实际上它们是作为一个单一矩阵操作进行的)

1.在 Q 和 K 的转置矩阵做一个矩阵乘法。

在这里插入图片描述 2. 将掩码值被添加到结果中。在 Encoder Self-attention 中,掩码用于掩盖填充值,这样它们就不会参与到注意分数中。
在这里插入图片描述

3.上一步的结果通过除以 Query size 的平方根进行缩放,然后对其应用Softmax。
在这里插入图片描述

4.在 Softmax 的输出和 V 矩阵之间进行另一个矩阵乘法。

在这里插入图片描述
在 Encoder Self-attention 中,一个注意力头的完整注意力计算如下图所示:
在这里插入图片描述

每个注意力头的输出形状为:

(batch_size,n_heads,seq_length,Query size)

注意,实际上此处最后一个维度的大小为 value_size,只是在 Transformer 中的 value_size=key_size=query_size

4.6 融合每个头的注意力分数(Merge each Head’s Attention Scores together)

我们现在对每个头都有单独的注意力分数,需要将其合并为一个分数。这个合并操作本质上是与分割操作相反,通过重塑结果矩阵以消除 n_heads 维度来完成的。其步骤如下:

1.交换头部和序列维度来重塑注意力分数矩阵。换句话说,矩阵的形状从

(batch_size,n_heads,seq_length,Query_size)

变为:

(batch_size,seq_length,n_heads,Query_size)

2.通过重塑为 (Batch, Sequence,Head * Query size)折叠头部维度。这就有效地将每个头的注意得分向量连接成一个合并的注意得分。。这就有效地将每个头的注意得分向量连接成一个合并的注意得分。

由于 embedding_size=n_heads * query_size ,合并后的分数是

(batch_size,seq_length,embedding_size

在下图中,我们可以看到分数矩阵的完整合并过程:
(在李沐的代码中,直接使用 permute 和 reshape 两次变换就可以)
在这里插入图片描述
整体上多头注意力的计算过程如下:在这里插入图片描述

4.7 多头分割可以捕捉到更丰富的表示(Multi-head split captures richer interpretations)

一个嵌入向量捕捉了一个词的含义。在 Multi-head Attention 的机制下,正如我们所见,输入(和目标)序列的嵌入向量在逻辑上被分割到多个头。这意味着,嵌入向量的不同部分可以表征每个词在不同方面的含义,而每个词不同的含义与序列中的其他词有关。这使得 Transformer 能够捕捉到对序列更丰富的表示。
在这里插入图片描述
例如,嵌入向量的某一部分可以捕获一个名词的词性,而另一个部分可以捕获一个名词的单复数。这在翻译中很重要,许多语言中,动词的使用与这些因素有关。

以上虽然不是一个现实的例子,但它可能有助于建立直觉。例如,一个部分可以捕捉名词的 “性别”(男、女、中性),而另一个部分则可以捕捉名词的 “词性”(单数与复数)。这在翻译过程中可能很重要,因为在许多语言中,需要使用的动词取决于这些因素。

4.8 解码器中的“编码器-解码器注意力”与“掩码操作”(Decoder Encoder-Decoder Attention and Masking)

“编码器-解码器注意力”从两个来源获得输入。因此,与计算输入中每个词与其他词之间相互作用的 Encoder Self-Attention 不同;也与计算每个目标词与其他目标词之间相互作用的 Decoder-Self-Attention 不同,“编码器-解码器注意力”计算的是每个 input word 与每个 target word之间的相互作用。

在这里插入图片描述
因此,所产生的注意分数中的每一个单元都对应于一个 Q(target sequence word)与所有其他 K(input sequence)词和所有 V(input sequence)词之间的相互作用。

我们现在明白了 Transformer 到底是做什么的。但是我们还没有完全回答为什么 Transformer 的 Attention 要进行这样的计算。为什么它要使用查询、键和值的概念,为什么它要执行我们刚才看到的矩阵乘法?

我们有一个模糊的直观想法,即它 “抓住了每个词与其他词之间的关系”,但这到底是什么意思?这到底是如何让转化器的注意力有能力理解序列中每个词的细微差别的?

继续

5、注意力计算原理(细节)

介绍注意力分数计算如何捕获序列中单词之间的关系
原文:https://towardsdatascience.com/transformers-explained-visually-not-just-how-but-why-they-work-so-well-d840bd61a9d3

5.1 输入序列如何到达Attention模块

注意力模块存在于编码器堆栈中的每个编码器中,以及解码器堆栈中的每个解码器中。我们将首先放大编码器的注意力。

在这里插入图片描述
举个例子,假设我们正在研究一个英语到西班牙语的翻译问题,其中一个示例源序列是“球是蓝色的”。目标序列是“La bola es azul”。

源序列首先通过嵌入(Embedding )位置编码层(Position Encoding),该层为序列中的每个单词生成嵌入向量。嵌入被传递到编码器,首先到达注意力模块( Attention module)

在注意力机制中,嵌入序列通过三个线性层(Linear layers),产生三个独立的矩阵——称为查询、键和值(Query、Key、Value)。这是用于计算注意力分数的三个矩阵。这些矩阵的每一 "行 "对应于源序列中的一个词。

在这里插入图片描述

5.2 每个输入行都是序列中的一个单词

一个理解Attention的方法是,从源序列中的单个词出发,观察其在 Transformer 中的路径。通过关注 Attention module 内部的情况,我们可以清楚地看到每个词是与其他词时如何互动的。

因此,需要特别关注的是 Attention module 对每个词进行的操作,以及每个向量如何映射到原始输入词,而不需要担心诸如矩阵形状、具体计算、多少个注意力头等其他细节,因为这些细节与每个词的去向没有直接关系。

为了简化解释和可视化,我们忽略嵌入维度并仅跟踪每个单词的行,将一个“行”作为一个整体进行理解。
在这里插入图片描述

5.3 每一行,都会经过一系列可学习的变换操作

每个这样的行都是通过一系列转换(嵌入、位置编码和线性层)从其相应的源字生成的。

所有这些转换都是可训练的操作。这意味着这些操作中使用的权重不是预先确定的,而是由模型学习的,从而产生所需的输出预测。

在这里插入图片描述

关键问题是,Transformer 如何计算出哪一组权重能够带来最佳结果?请记住这一点,因为我们稍后会再讨论它

5.4 计算注意力分数——查询和关键词之间的点积

注意力执行几个步骤,但在这里,我们将只关注线性层和注意力分数。
在这里插入图片描述
在这里插入图片描述
从公式中我们可以看出,Attention 的第一步是在 Query (Q) 矩阵和 Key (K) 矩阵的转置之间进行矩阵乘法(即点积)。观察每个单词发生了什么?

我们生成一个中间矩阵(我们称之为“因子”矩阵),其中每个单元格都是两个单词之间的矩阵乘法。

查询矩阵和关键矩阵之间的点积:

查询矩阵和关键矩阵之间的点积
例如,第四行中的每一列对应于第四个查询词与每个关键字之间的点积。
在这里插入图片描述

下一步是这个中间“因子”矩阵和值(V)矩阵之间的矩阵乘法,以产生由注意力模块输出的注意力分数。

第四行对应于第四个查询词矩阵乘以所有其他关键字和值词。
在这里插入图片描述
这会产生由注意力模块输出的注意力分数向量–Attention Score vector (Z) (Z)。

注意力分数理解成一个词的“编码值”。这个编码值是由“因子矩阵”对 Value 矩阵中的词加权而来。而“因子矩阵”中对应的权值则是该特定单词的 Query 向量与 Key 向量的点积。

1-一个词的注意力得分可以理解为该词的"编码值",它是注意力机制最终为每个词赋予的表示向量。
2-这个"编码值"是由"值矩阵"(Value矩阵)中每个词的值向量加权求和得到的。
3-加权的权重就是"因子矩阵"中对应的注意力权重。
4-"因子矩阵"中的注意力权重是通过该词的查询向量(Query)与所有词的键向量(Key)做点积计算得到的。

在这里插入图片描述

5.5 Query、Key、Value的作用

对某一个查询向量 Query,可以理解为正在计算注意力分数的词。而 Key 向量和 Value 向量是我们正在观察的词,即该词与查询词的相关程度。

在这里插入图片描述
例如,对于 "The ball is blue "这个句子,单词 "blue "这一行包含 "blue "与其他每个单词的注意力分数。在这里,"blue "是 Query word,其他的词是 “Key/Value”。

还有其他操作正在执行,例如除法和 softmax,但我们可以在本文中忽略它们。它们只是更改矩阵中的数值,但不影响矩阵中每个单词行的位置。它们也不涉及任何词间交互。

5.6 点积:衡量向量之间的相似度

Attention Score 是通过做点乘,然后把它们加起来,捕捉某个特定的词和句子中其他词之间的关系。但是,矩阵乘法如何帮助 Transformer 确定两个词之间的相关性?

要理解这一点,请记住查询、键和值行实际上是具有嵌入维度的向量。让我们仔细看看这些向量之间的矩阵乘法是如何计算的。

每个单元格都是两个词向量之间的点积:
在这里插入图片描述
当我们在两个向量之间进行点积时,我们将数字对相乘,然后将它们相加。

  • 如果两个配对数字(例如上面的“a”和“d”)均为正数或均为负数,则乘积将为正数。该乘积将增加最终的总和。
  • 如果一个数为正数,另一个数为负数,则乘积将为负数。该乘积将减少最终的总和。
  • 如果乘积为正,则两个数字越大,对最终求和的贡献就越大。

这意味着如果两个向量中对应数字的符号对齐,则最终的和会更大。

5.7 Transformer 如何学习单词之间的相关性?

点积的概念也适用于注意力分数。如果两个单词的向量更加对齐,则注意力分数会更高。

那么我们想要 Transformer 的行为是什么?

我们希望句子中彼此相关的两个单词的注意力分数较高。两个彼此不相关的单词的分数较低。

例如,对于 “The black cat drank the milk” 这个句子,"milk "这个词与 "drank "非常相关,与 "cat "的相关性可能稍差,而与 "black "无关。我们希望 "milk "和 "drank "产生一个高的注意力分数,"milk "和 "cat "产生一个稍低的分数,而 "milk"和 "black "则产生一个可以忽略的分数。这就是我们希望模型学习产生的输出。

要做到这一点,"milk "和 "drank "的词向量必须是一致的。“milk” 和 “cat” 的向量会有一些分歧。而对于 "milk "和 "black "来说,它们会有很大的不同。

让我们回到前述的问题—Transformer 是如何找出哪一组权重会给它带来最佳结果的?

词向量是根据词嵌入和线性层的权重生成的。因此,Transformer 可以学习这些嵌入向量、线性层权重等来产生上述要求的词向量。

换句话说,它将以这样的方式学习这些嵌入和权重:

如果一个句子中的两个词是相互关联的,那么它们的词向量将是一致的。从而产生一个更高的关注分数;对于那些彼此不相关的词,词向量将不会被对齐,并会产生较低的关注分数。

因此,"milk "和 "drank "的嵌入将非常一致,并产生较高的注意分数。对于 "milk "和 “cat”,它们会有一些分歧,产生一个稍低的分数,而对于 "milk "和 “black”,它们会有很大的不同,产生一个非常低的分数。

这就是注意力模块背后的原理。

5.8 是什么让 Transformer 运转起来?(总结)

Query 和 Key 之间的点积计算出每对词之间的相关性。然后,这种相关性被用作一个 "因子 "来计算所有 Value 向量的加权和。该加权和的输出为注意力分数

Transformer 通过对嵌入向量的学习,使彼此相关的词更加一致。

这就是引入三个线性层的原因之一:为 Attention module 提供更多的参数,使其能够通过学习调整词向量。

5.9 Transformer 中的几种 Attention module (回顾)

Transformer 中共有三处使用到了注意力机制:

  1. Encoder 中的自注意力机制:源序列与自身的注意力计算;

  2. Decoder 中的自注意力机制:目标序列与自身的注意力计算;

  3. Encoder-Decoder 中的注意力机制:目标序列对原序列的注意力计算。
    在这里插入图片描述
    在 "Encoder Self Attention "中,我们计算源序列中每个单词与源序列中其他单词的相关性。这发生在编码器堆栈中的所有 Encoder 中。

我们刚刚在编码器自注意力中看到的大部分内容也适用于解码器中的注意力,但有一些微小但显着的差异。

在这里插入图片描述
在 Decoder Self Attention 中,我们计算目标序列中每个单词与目标序列中其他单词的相关性。

在这里插入图片描述
在 "Encoder-Decoder Attention "中,Query 来自目标句,而Key/Value来自源句。这样,它就能计算出目标句中每个词与源句中每个词的相关性。
在这里插入图片描述

参考资料:

一文彻底搞懂 Transformer(图解+手撕)
适合你找个时间好好消化的文章,大模型核心技术——Transformer架构

2023 年初翻译的 Ketan Doshi 博客中关于 Transformer 的系列文章:(翻译版本)
图解 Transformer——功能概览
图解 transformer——逐层介绍
图解 transformer——多头注意力
图解 transformer——注意力计算原理

  • 40
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值