注意力机制的引入是为了提升编码器-解码器模型在机器翻译任务中的性能。其背后的思想是,允许解码器通过对所有编码后的输入向量进行加权组合,以一种灵活的方式利用输入序列中最相关的部分,其中最相关的向量会被赋予最高的权重。
在本教程中,您将了解注意力机制及其实现。
完成本教程后,您将了解:
- 注意力机制如何使用所有编码器隐藏状态的加权和,来灵活地将解码器的注意力集中在输入序列最相关的部分。
- 如何将注意力机制推广到那些信息未必以序列方式关联的任务中。
- 如何使用 Python 的 NumPy 和 SciPy 库来实现通用的注意力机制。
通过我的《使用注意力机制构建 Transformer 模型》一书,**快速启动您的项目**。它提供**自学教程**和**可运行的代码**,指导您构建一个功能完备的 Transformer 模型,该模型可以
将句子从一种语言翻译成另一种语言的完整 Transformer 模型...
让我们开始吧。

从零开始实现注意力机制
照片由 Nitish Meena 拍摄,保留部分权利。
教程概述
本教程分为三个部分;它们是:
- 注意力机制
- 通用注意力机制
- 使用 NumPy 和 SciPy 实现通用注意力机制
注意力机制
注意力机制由 Bahdanau 等人 (2014) 提出,旨在解决使用固定长度编码向量时出现的瓶颈问题,即解码器对输入信息访问受限。对于长和/或复杂的序列,这个问题被认为尤为突出,因为它们的表示维度被迫与较短或较简单的序列相同。
请注意,Bahdanau 等人的*注意力机制*分为*对齐分数*、*权重*和*上下文向量*的逐步计算。
- 对齐分数:对齐模型接收编码器的隐藏状态 $\mathbf{h}_i$ 和前一个解码器的输出 $\mathbf{s}_{t-1}$,计算出一个分数 $e_{t,i}$,该分数表示输入序列的元素与位置 $t$ 的当前输出对齐得有多好。对齐模型由一个函数 $a(.)$ 表示,该函数可以通过前馈神经网络实现:
$$e_{t,i} = a(\mathbf{s}_{t-1}, \mathbf{h}_i)$$
- 权重:权重 $\alpha_{t,i}$ 是通过对先前计算的对齐分数应用 softmax 操作来计算的:
$$\alpha_{t,i} = \text{softmax}(e_{t,i})$$
- 上下文向量:在每个时间步,一个唯一的上下文向量 $\mathbf{c}_t$ 会被送入解码器。它是通过对所有 $T$ 个编码器隐藏状态进行加权求和计算得出的:
$$\mathbf{c}_t = \sum_{i=1}^T \alpha_{t,i} \mathbf{h}_i$$
Bahdanau 等人为编码器和解码器都实现了一个 RNN。
然而,注意力机制可以被重新表述为一种通用形式,可以应用于任何序列到序列(seq2seq)任务,其中信息不一定以序列方式关联。
换句话说,数据库不必由不同步骤的 RNN 隐藏状态组成,而是可以包含任何类型的信息。
—— 使用 Python 进行高级深度学习,2019年。
通用注意力机制
通用注意力机制使用三个主要组件,即*查询*(queries)$\mathbf{Q}$、*键*(keys)$\mathbf{K}$ 和*值*(values)$\mathbf{V}$。
如果将这三个组件与 Bahdanau 等人提出的注意力机制进行比较,那么查询就类似于前一个解码器的输出 $\mathbf{s}_{t-1}$,而值则类似于编码后的输入 $\mathbf{h}_i$。在 Bahdanau 的注意力机制中,键和值是同一个向量。
在这种情况下,我们可以将向量 $\mathbf{s}_{t-1}$ 看作是针对一个由键值对组成的数据库执行的查询,其中键是向量,而隐藏状态 $\mathbf{h}_i$ 是值。
—— 使用 Python 进行高级深度学习,2019年。
通用注意力机制随后执行以下计算:
- 每个查询向量 $\mathbf{q} = \mathbf{s}_{t-1}$ 与数据库中的键进行匹配,以计算一个分数值。这个匹配操作计算为当前特定查询与每个键向量 $\mathbf{k}_i$ 的点积:
$$e_{\mathbf{q},\mathbf{k}_i} = \mathbf{q} \cdot \mathbf{k}_i$$
- 分数通过一个 softmax 操作来生成权重:
$$\alpha_{\mathbf{q},\mathbf{k}_i} = \text{softmax}(e_{\mathbf{q},\mathbf{k}_i})$$
- 然后,通过对值向量 $\mathbf{v}_{\mathbf{k}_i}$ 进行加权求和来计算广义注意力,其中每个值向量与一个对应的键配对:
$$\text{attention}(\mathbf{q}, \mathbf{K}, \mathbf{V}) = \sum_i \alpha_{\mathbf{q},\mathbf{k}_i} \mathbf{v}_{\mathbf{k}_i}$$
在机器翻译的背景下,输入句子中的每个词都会被赋予其自身的查询、键和值向量。这些向量是通过将编码器对特定词的表示与三个不同的权重矩阵相乘生成的,这些权重矩阵会在训练过程中生成。
本质上,当通用注意力机制接收到一个词序列时,它会获取序列中某个特定词的查询向量,并将其与数据库中的每个键进行评分。通过这样做,它捕捉了所考虑的词与序列中其他词的关系。然后,它根据注意力权重(从分数计算得出)来缩放值,以保持对与查询相关的词的关注。这样,它就为所考虑的词生成了一个注意力输出。
想开始构建带有注意力的 Transformer 模型吗?
立即参加我的免费12天电子邮件速成课程(含示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
使用 NumPy 和 SciPy 实现通用注意力机制
本节将探讨如何使用 Python 中的 NumPy 和 SciPy 库来实现通用注意力机制。
为简单起见,您将首先为一个包含四个词的序列中的第一个词计算注意力。然后,您将把代码泛化,以矩阵形式为所有四个词计算注意力输出。
因此,让我们首先定义四个不同词的词嵌入来计算注意力。在实际应用中,这些词嵌入会由编码器生成;然而,在这个特定示例中,您将手动定义它们。
1 2 3 4 5 |
# 四个不同词的编码器表示 word_1 = array([1, 0, 0]) word_2 = array([0, 1, 0]) word_3 = array([1, 1, 0]) word_4 = array([0, 0, 1]) |
下一步是生成权重矩阵,您最终会将它们与词嵌入相乘以生成查询、键和值。在这里,您将随机生成这些权重矩阵;然而,在实际应用中,这些矩阵是通过训练学习得到的。
1 2 3 4 5 6 |
... # 生成权重矩阵 random.seed(42) # 允许我们复现相同的注意力值 W_Q = random.randint(3, size=(3, 3)) W_K = random.randint(3, size=(3, 3)) W_V = random.randint(3, size=(3, 3)) |
请注意,这些矩阵的行数都等于词嵌入的维度(在本例中为三),以便我们能执行矩阵乘法。
随后,通过将每个词嵌入与每个权重矩阵相乘,来为每个词生成查询、键和值向量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
... # 生成查询、键和值 query_1 = word_1 @ W_Q key_1 = word_1 @ W_K value_1 = word_1 @ W_V query_2 = word_2 @ W_Q key_2 = word_2 @ W_K value_2 = word_2 @ W_V query_3 = word_3 @ W_Q key_3 = word_3 @ W_K value_3 = word_3 @ W_V query_4 = word_4 @ W_Q key_4 = word_4 @ W_K value_4 = word_4 @ W_V |
暂时只考虑第一个词,下一步是使用点积操作将其查询向量与所有键向量进行评分。
1 2 3 |
... # 对第一个查询向量与所有键向量进行评分 scores = array([dot(query_1, key_1), dot(query_1, key_2), dot(query_1, key_3), dot(query_1, key_4)]) |
随后,分数值通过 softmax 操作来生成权重。在此之前,通常的做法是将分数值除以键向量维度(本例中为三)的平方根,以保持梯度稳定。
1 2 3 |
... # 通过 softmax 操作计算权重 weights = softmax(scores / key_1.shape[0] ** 0.5) |
最后,通过对所有四个值向量进行加权求和来计算注意力输出。
1 2 3 4 5 |
... # 通过对值向量进行加权求和来计算注意力 attention = (weights[0] * value_1) + (weights[1] * value_2) + (weights[2] * value_3) + (weights[3] * value_4) print(attention) |
1 |
[0.98522025 1.74174051 0.75652026] |
为了加快处理速度,同样的计算可以用矩阵形式实现,一次性为所有四个词生成注意力输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
from numpy import array from numpy import random 从 numpy 导入 dot from scipy.special import softmax # 四个不同词的编码器表示 word_1 = array([1, 0, 0]) word_2 = array([0, 1, 0]) word_3 = array([1, 1, 0]) word_4 = array([0, 0, 1]) # 将词嵌入堆叠成单个数组 words = array([word_1, word_2, word_3, word_4]) # 生成权重矩阵 random.seed(42) W_Q = random.randint(3, size=(3, 3)) W_K = random.randint(3, size=(3, 3)) W_V = random.randint(3, size=(3, 3)) # 生成查询、键和值 Q = words @ W_Q K = words @ W_K V = words @ W_V # 对查询向量与所有键向量进行评分 scores = Q @ K.transpose() # 通过 softmax 操作计算权重 weights = softmax(scores / K.shape[1] ** 0.5, axis=1) # 通过对值向量进行加权求和来计算注意力 attention = weights @ V print(attention) |
1 2 3 4 |
[[0.98522025 1.74174051 0.75652026] [0.90965265 1.40965265 0.5 ] [0.99851226 1.75849334 0.75998108] [0.99560386 1.90407309 0.90846923]] |
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
书籍
- 使用 Python 进行高级深度学习, 2019.
- 深度学习要点, 2018.
论文
- 通过联合学习对齐和翻译的神经机器翻译, 2014.
总结
在本教程中,您了解了注意力机制及其实现。
具体来说,你学到了:
- 注意力机制如何使用所有编码器隐藏状态的加权和,来灵活地将解码器的注意力集中在输入序列最相关的部分。
- 如何将注意力机制推广到那些信息未必以序列方式关联的任务中。
- 如何使用 NumPy 和 SciPy 实现通用注意力机制。
你有什么问题吗?
在下面的评论中提出您的问题,我将尽力回答。
你好 Stefania,
我有一个比较初级的问题,注意力机制可以用于表格数据(其中的值是浮点数单位,即类似于鸢尾花数据集)吗?如果可以,您能好心建议一下(或者如果可能的话)如何做吗?我正在处理高度不平衡的多维表格数据,并希望将注意力机制应用到我的 1D-CNN 中。我将非常感谢您的意见。
谢谢。
嗨 Shad…以下资源可能对表格数据和深度学习有所帮助:
https://medium.com/codon-consulting/tabular-data-and-deep-learning-where-do-we-stand-209b202e443a
先生,最后计算出的注意力矩阵向我们展示了什么?
它是在显示单词之间的相关性吗?
嗨 Deep Learner…你的理解是正确的!继续努力!
你好,我有个小问题,对这些都很陌生,请问下面代码中的‘@’运算符是什么意思?
query_1 = word_1 @ W_Q
谢谢,
嗨 Shuja…以下资源可能有助于澄清:
https://www.codingem.com/numpy-at-operator/
谢谢 James,非常感谢。
非常欢迎你,Shuja!
你好,教授,
关于注意力结果的形式和含义,我有一个初级的问题:列数是由编码器(维度为3)决定的,还是因为我们使用了4个词,然后每个词都与句子中的其他三个词相关联,就像 Deep Learner 评论的那样?
在后一种情况下,如果我得到,例如,第3个词,那么相应的注意力向量(行)的第一个元素与第一个词相关,第二个元素与第二个词相关,第三个元素与第四个词相关吗?
嗨 Diego…你可能会对以下内容感兴趣:
https://machinelearning.org.cn/a-tour-of-attention-based-architectures/
谢谢!
我也想弄清楚这一点,而且我认为 James Carmichael 的回答可以更直接一些…
在机器翻译的背景下,输入句子中的每个词 ….
难道不应该是 —- 在自注意力的背景下,…
因为在自注意力中,我们取每个词的 h,然后将其与权重相乘,再进行点积和其他操作。
太棒了…非常感谢
非常欢迎你,harshavardhana!
有人能详细解释一下注意力矩阵的含义吗?我不太明白为什么它能告诉我们单词之间的“相关性”。看看第一行和第二行,
[0.98522025 1.74174051 0.75652026]
[0.90965265 1.40965265 0.5 ]
这告诉我们第一个和第二个词之间有什么关系?
嗨 ginko…以下资源应该能让问题更清晰:
https://towardsdatascience.com/attention-and-its-different-forms-7fc3674d14dc
谢谢这个链接!我学到了很多,但这个链接没有明确解释这些注意力向量的具体含义。这是我的理解——对于这篇教程,这是一个准确的类比吗?
我们有一个包含4个单词(行)的英语短语。我们把它翻译成法语,翻译结果有4个单词(列)。
得分矩阵(另一个链接称之为“注意力”矩阵)
F1 F2 F3 F4
E1 [[2.36e-01, 7.39e-03, 7.49e-01, 7.39e-03],
E2 [4.55e-01, 4.52e-02, 4.55e-01, 4.52e-02],
E3 [2.39e-01, 7.44e-04, 7.59e-01, 7.44e-04],
E4 [9.00e-02, 2.82e-03, 9.06e-01, 1.58e-03]]
-- 表明所有4个英语单词(E1, E2, E3, E4)都可以由法语单词F3来概括。然而,法语单词F3在英语中最佳对应的单词是E4(得分最高)。
-- 表明对于英语单词E2,它为法语单词F1和F3提供了大致相等的重要性。
-- 理论上,对角线应该为1(如果是一对一翻译)。
注意力矩阵(链接中称为“上下文”矩阵)的解释有点模糊。如果有人能纠正这一点,和/或为这个4 x 3的注意力矩阵提供一个解释,我相信这将极大地改进这篇教程!感谢您整理这篇文章。
嗨 ginko,我也在想同样的问题“我应该如何解释注意力矩阵?”。我尽力回答了这个问题,这是我的想法:
-- 得分矩阵是4×4的,所以在这里我们仍然有一个关于单词之间得分关系的一对一关联。
-- 困惑来自于我们计算最终的注意力矩阵时,因为一个4×4矩阵与一个4×3矩阵相乘,最终的矩阵将是4×3的。
但是,如果我们把得分矩阵的第一行看作是:
[ 词1/词1 词1/词2 词1/词3 词1/词4 ]
那么注意力向量(注意力矩阵的一行)的元素可以被解释为得分矩阵行的某种编码版本,因为在两个矩阵的乘法中,我们做的是行乘以列的运算。
所以,这就像注意力向量中的每个元素都携带了以下信息:
词1/词1 词1/词2 词1/词3 词1/词4
在这种情况下是3次,因为键向量的维度是3。
我们如何在回归问题中实现它?这可能吗?与 xgboost 或 lightgbm 一起使用?
我犯了个错误,不是4×3矩阵,我们乘以权重的是3×3矩阵。
你好,
如果我想在自编码器神经网络中添加注意力机制。
我训练自编码器的主要目的是为了特征降维。
在注意力机制中使用它有用吗?如果可以,怎么用?(它看起来更适合 seq2seq 问题)
谢谢,
MAK
感谢您出色的解释和您所有的工作。
我只有一个问题,就是数学公式没有用 LaTeX 显示,而是仍然保持其 html 形式,例如我看到的所有公式都是这样的:
$$e_{\mathbf{q},\mathbf{k}_i} = \mathbf{q} \cdot \mathbf{k}_i$$
感谢您的反馈和建议!
我可能理解错了,但我们能将一个 4×4 的矩阵与一个 3×3 的矩阵相乘吗?
嗨 Ricardo…答案是不能。以下资源解释了矩阵乘法。
https://www.mathsisfun.com/algebra/matrix-multiplying.html
既然您已经提供了代码和描述,那么购买电子书有什么好处呢?