注意力机制和 Transformer 模型简介

Transformer 是一种流行的自然语言处理(NLP)深度学习架构。它是一种旨在处理文本等序列数据的神经网络。在本文中,我们将探讨注意力机制和 Transformer 架构的概念。具体来说,您将了解:

  • Transformer 模型解决什么问题
  • 注意力机制与 Transformer 模型的关系
  • Transformer 模型有哪些不同的变体

让我们开始吧!

注意力机制和 Transformer 模型简介
照片作者:Andre Benz。部分权利保留。

概述

这篇博文分为三部分;它们是:

  • Transformer 模型起源
  • Transformer 架构
  • Transformer 架构的变体

Transformer 模型起源

Transformer 架构起源于 Vaswani 等人于 2017 年发表的论文“Attention Is All You Need”。它与传统神经网络的不同之处在于,它使用自注意力机制来处理输入数据。自注意力机制允许模型根据任务需求,关注输入数据的不同部分。

Transformer 架构解决了循环神经网络 (RNN) 的局限性。RNN 被期望处理输入序列,例如向量序列。相同的网络架构被反复用于处理序列的每个元素。在网络内部,使用了一些记忆机制。记忆会随着每一步的更新,代表迄今为止看到的序列。

RNN 在 NLP 任务中很有用,例如 seq2seq 架构,用于翻译自然语言。然而,由于 RNN 一次处理一个元素,当处理序列的最后一个元素时,网络很难记住第一个元素传递的信息,特别是当序列任意长时。

Transformer 架构中的解决方案是使用自注意力机制一次性处理整个序列。序列的每个元素都可以“看到”序列中的所有元素,模型可以从中提取上下文信息。因此,Transformer 架构可以表现得更好。此外,注意力机制的性质使得计算更具并行性,因为与 RNN 不同,序列的一个元素对应的输出不依赖于序列其他元素对应的输出。

Transformer 架构

原始的 Transformer 架构由编码器和解码器组成。其布局如下图所示。

回想一下,Transformer 模型是为了翻译任务而开发的,取代了通常与 RNN 一起使用的 seq2seq 架构。因此,它借鉴了编码器-解码器架构。

编码器的作用是编码输入数据,即源语言的句子。编码器输出一个上下文表示,该表示应能捕获句子的含义。

解码器的作用是产生输出,即生成目标语言的句子,捕获与源语言句子相同的含义。因此,解码器将来自编码器的上下文表示作为输入之一。解码器一次生成一个目标句子中的单词(技术上称为 token)。它需要知道到目前为止已生成的内容,才能确定下一步要生成什么。因此,到目前为止生成的局部序列被作为另一个输入馈送回解码器。

通常,Transformer 模型中的编码器和解码器由一系列相同的层组成。编码器和解码器中的每一层都是一个注意力子层,后面跟着一个前馈子层。

注意力子层通过“关注”序列中的每个其他元素来逐个元素地转换输入序列。它像一个查找表一样工作,以找到最合适的输出。它本质上是一种线性变换,输出的长度与输入相同。然而,前馈子层是一种非线性变换。它是一个具有激活函数的​​多层感知器,应用于序列的每个元素。

本质上,注意力层允许序列的每个元素从序列中的所有其他元素学习,然后前馈层进一步转换每个元素。

Transformer 架构中使用的现代注意力机制称为缩放点积注意力。它接收三个输入序列:查询 (query)、键 (key) 和值 (value)。查询和键计算注意力权重,然后使用这些权重计算值 (value) 的加权和作为输出。

通常,编码器层中使用的注意力是自注意力,这意味着相同的输入序列用于导出查询、键和值序列。然而,在解码器层中,同时使用自注意力和交叉注意力。解码器中的交叉注意力使用部分生成的序列作为查询,并使用来自编码器的上下文表示作为键和值。

Transformer 架构的变体

我们来看看 Transformer 架构中的编码器层是如何实现的。

这是一个简化的实现,其中省略了许多细节和错误处理。本质上,输入序列是形状为 (batch_size, seq_len, d_model) 的张量,其中 d_model 是模型的维度或序列中每个向量元素的大小。输出序列的形状相同,因此您可以再次用另一个编码器层对其进行处理。因此,您可以轻松地堆叠多个层来形成编码器。

MultiheadAttention 层会产生一个 Python 元组。第一个元素是注意力输出,第二个是注意力权重,在此实现中未对其进行使用。然后,将输出加回到原始输入,并应用层归一化。将输出加回到输入称为残差连接。这是深度学习中常见的做法,有助于模型更好地学习。

在注意力子层之后,输出序列被传递到前馈子层。前馈子层是一个具有 ReLU 激活函数的多层感知器,用于单独处理每个元素。输出的形状仍然与输入序列相同,尽管中间层通常会使用更大的维度。

来自前馈子层的输出将具有另一个残差连接和归一化。然后,这就是编码器层的输出。

上面的代码说明了后归一化架构。这是原始 Transformer 论文提出的架构,但后来发现前归一化架构更容易训练。前归一化版本如下所示:

您总是在注意力或前馈子层之后具有残差连接。但在前归一化架构中,层归一化是在子层开始时而不是结束时应用的。

在上面的两个示例代码中,输出始终是以下内容:

很容易识别出两个线性层和两个归一化层的权重。注意力层的权重分为两部分:输入投影和输出投影。输入投影的形状为 $48\times 16$,输出投影的形状为 $16\times 16$。48 的原因是输入到注意力层的查询、键和值序列的连接,每个序列的形状都是 batch_size, seq_len, d_model,其中 d_model=16。因此 $16\times 3=48$。

您无法在权重中看到 num_heads=4 的影响,因为当您设置 d_model 时,每个头的维度是 d_model/num_heads。因此,在这种情况下,每个注意力头只处理 4 个维度的切片。每个头处理投影输入的某个切片,然后沿着嵌入维度连接以形成最终输出。

这些示例中的前馈层使用 ReLU 作为激活函数。您可以使用其他激活函数,例如 GELU 或 SwiGLU。事实上,现代 Transformer 模型更不可能使用 ReLU。

这些示例中应用了层归一化。有些模型会改用 RMS 范数。

解码器层的实现类似。不同之处在于您需要添加一个交叉注意力层并以不同的方式调用它。

其中 y 是来自编码器输出的序列,它的长度可能不同。完整代码如下所示:

进一步阅读

以下是一些您可能会觉得有用的论文。

总结

在本文中,您了解了 Transformer 架构和注意力机制。您还看到了如何在 PyTorch 中实现编码器和解码器层。特别是,您了解了:

  • Transformer 架构是一种旨在处理文本等序列数据的神经网络。
  • Transformer 模型的一个标志是使用注意力机制来处理输入序列。
  • Transformer 架构由编码器和解码器组成。每个都由一系列相同的层组成。
  • 通过类似的架构,Transformer 模型可以在前归一化或后归一化、不同的归一化方法和不同的激活函数方面有所不同。

注意力机制和 Transformer 模型入门指南 的 4 条回复

  1. Mohamed Nedal 2025年3月31日晚上9:43 #

    您好,文章写得很好!您能否分享一个如何使用 Transformer 模型进行时间序列预测的例子,以及如何跟踪模型的训练性能,以及如何可视化模型的输出与实际测试集的对比?

  2. Abe 2025年4月3日晚上8:32 #

    这确实是一个入门介绍……当我读那本书时,我的眼睛会流血 😂

  3. Chad 2025年8月1日早上6:52 #

    您好,我想知道为什么在最后一个代码块的第 35 行,您输入的参数是 (x,y,y)?xattention 使用 Pytorch 的 MultiheadAttention 块,它接受的顺序是 Query、Key、Value,这意味着 Query=x、Key=y、Value=y。

    然而,在原始 Transformer 论文中,编码器的输出不是作为 Query 和 Key 输入的吗?而解码器自注意力的输出不是作为 Value 输入的吗?那么参数输入应该是 (y,y,x) 吗?

    我很想听听您的见解!

    • James Carmichael 2025年8月1日晚上11:41 #

      Chad,您对 MultiheadAttention 调用中的输入顺序提出疑问是完全正确的,尤其是在 Transformer 架构的交叉注意力方面。

      在 PyTorch 的 MultiheadAttention 模块中,forward 方法按此顺序接收三个参数:query、key、value。所以当你看到一行像


      attention_output = attention(x, y, y)

      这意味着:

      * Query 是 x
      * Key 是 y
      * Value 是 y

      这表明 x 在关注 y。换句话说,表示 x 正在尝试从 y 收集上下文。

      现在,根据原始 Transformer 论文,在解码器的交叉注意力阶段:

      * 解码器的隐藏状态(自注意力之后)用作查询
      * 编码器的输出同时用作键和值

      所以,标准的编码器-解码器交叉注意力的正确用法应该是:


      attention_output = attention(y, x, x)

      也就是说:

      * Query 是解码器的隐藏状态 (y)
      * Key 是编码器的输出 (x)
      * Value 也是编码器的输出 (x)

      如果您正在查看的代码使用了 (x, y, y),那么有几种可能性:

      1. 变量名 x 和 y 可能与标准的命名约定颠倒了。
      2. 计算的注意力可能不是交叉注意力;它可能是一些自定义或对称操作。
      3. 这可能是代码中的一个错误或简化。

      为了确认,您可以检查 x 和 y 的形状和含义。通常:

      * 解码器隐藏状态(查询)的形状为 \[目标序列长度, batch size, 嵌入大小]
      * 编码器输出(键和值)的形状为 \[源序列长度, batch size, 嵌入大小]

      如果 x 与解码器隐藏状态的形状匹配,y 与编码器输出的形状匹配,那么 (x, y, y) 的用法与 Transformer 设计一致。

发表回复

Machine Learning Mastery 是 Guiding Tech Media 的一部分,Guiding Tech Media 是一家领先的数字媒体出版商,专注于帮助人们了解技术。访问我们的公司网站以了解更多关于我们的使命和团队的信息。