在语言中,词语的顺序及其在句子中的位置确实很重要。如果词语的顺序被打乱,整个句子的意思可能会改变。在实现 NLP 解决方案时,循环神经网络具有处理序列顺序的内置机制。然而,Transformer 模型不使用循环或卷积,并将每个数据点视为相互独立的。因此,显式地向模型添加位置信息,以保留句子中词语顺序的信息。位置编码是保持序列中对象顺序知识的方案。
在本教程中,我们将简化 Vaswani 等人撰写的这篇杰出论文 《Attention Is All You Need》中使用的符号。完成本教程后,您将了解
- 什么是位置编码,以及为什么它很重要
- Transformer 中的位置编码
- 使用 Python 和 NumPy 编写代码并可视化位置编码矩阵
用我的书 《用注意力构建 Transformer 模型》 启动您的项目。它提供了 自学教程 和 可运行的代码,指导您构建一个功能齐全的 Transformer 模型,该模型可以
将句子从一种语言翻译成另一种语言的完整 Transformer 模型...
让我们开始吧。

Transformer 模型中位置编码的温和介绍
图片由 Muhammad Murtaza Ghani 在 Unsplash 上提供,保留部分权利
教程概述
本教程分为四个部分;它们是
- 什么是位置编码
- Transformer 中位置编码背后的数学原理
- 使用 NumPy 实现位置编码矩阵
- 理解和可视化位置编码矩阵
什么是位置编码?
位置编码描述了序列中实体的位置,从而为每个位置分配一个唯一的表示。在 Transformer 模型中,不使用单个数字(例如索引值)来表示项目位置的原因有很多。对于长序列,索引值可能会变得很大。如果您将索引值归一化到 0 到 1 之间,对于可变长度序列可能会产生问题,因为它们的归一化方式会不同。
Transformer 使用一种智能的位置编码方案,其中每个位置/索引都被映射到一个向量。因此,位置编码层的输出是一个矩阵,矩阵的每一行代表序列中编码对象与其位置信息的总和。下图显示了一个仅编码位置信息的矩阵示例。
三角正弦函数的快速回顾
这是对正弦函数的快速回顾;您可以等效地使用余弦函数。函数的范围是 [-1,+1]。此波形的频率是每秒完成的周期数。波长是波形重复自身的距离。不同波形的波长和频率如下所示
想开始构建带有注意力的 Transformer 模型吗?
立即参加我的免费12天电子邮件速成课程(含示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
Transformer 中的位置编码层
让我们直接深入探讨。假设您有一个长度为 $L$ 的输入序列,并且需要此序列中第 $k^{th}$ 个对象的位置。位置编码由不同频率的正弦和余弦函数给出
\begin{eqnarray}
P(k, 2i) &=& \sin\Big(\frac{k}{n^{2i/d}}\Big)\\
P(k, 2i+1) &=& \cos\Big(\frac{k}{n^{2i/d}}\Big)
\end{eqnarray}
这里:
$k$:输入序列中对象的位置,$0 \leq k < L/2$
$d$:输出嵌入空间的维度
$P(k, j)$:将输入序列中的位置 $k$ 映射到位置矩阵的索引 $(k,j)$ 的位置函数
$n$:《Attention Is All You Need》的作者设定为 10,000 的用户定义标量。
$i$:用于映射到列索引 $0 \leq i < d/2$,其中单个 $i$ 值映射到正弦和余弦函数
在上述表达式中,您可以看到偶数位置对应于正弦函数,奇数位置对应于余弦函数。
例如
为了理解上述表达式,让我们以短语“我是一个机器人”为例,其中 n=100 且 d=4。下表显示了此短语的位置编码矩阵。实际上,对于任何 n=100 且 d=4 的四个字母短语,位置编码矩阵都是相同的。
从头开始编码位置编码矩阵
这是一个使用 NumPy 实现位置编码的简短 Python 代码。代码经过简化,以便更容易理解位置编码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import numpy as np import matplotlib.pyplot as plt def getPositionEncoding(seq_len, d, n=10000): P = np.zeros((seq_len, d)) for k in range(seq_len): for i in np.arange(int(d/2)): denominator = np.power(n, 2*i/d) P[k, 2*i] = np.sin(k/denominator) P[k, 2*i+1] = np.cos(k/denominator) return P P = getPositionEncoding(seq_len=4, d=4, n=100) print(P) |
1 2 3 4 |
[[ 0. 1. 0. 1. ] [ 0.84147098 0.54030231 0.09983342 0.99500417] [ 0.90929743 -0.41614684 0.19866933 0.98006658] [ 0.14112001 -0.9899925 0.29552021 0.95533649]] |
理解位置编码矩阵
1 2 3 4 5 6 7 8 9 10 11 |
def plotSinusoid(k, d=512, n=10000): x = np.arange(0, 100, 1) denominator = np.power(n, 2*x/d) y = np.sin(k/denominator) plt.plot(x, y) plt.title('k = ' + str(k)) fig = plt.figure(figsize=(15, 4)) for i in range(4): plt.subplot(141 + i) plotSinusoid(i*4) |
下图是上述代码的输出
您可以看到每个位置 $k$ 对应一个不同的正弦曲线,它将单个位置编码成一个向量。如果您仔细观察位置编码函数,您会发现对于固定的 $i$,波长由以下公式给出
$$
\lambda_{i} = 2 \pi n^{2i/d}
$$
因此,正弦波的波长形成一个几何级数,并从 $2\pi$ 变化到 $2\pi n$。位置编码方案具有许多优点。
- 正弦和余弦函数的值在 [-1, 1] 范围内,这使得位置编码矩阵的值保持在归一化范围内。
- 由于每个位置的正弦波都不同,因此您有一种独特的方式来编码每个位置。
- 您有一种衡量或量化不同位置之间相似性的方法,因此能够编码词语的相对位置。
可视化位置矩阵
让我们可视化更大值的位置矩阵。使用 Python 的 `matplotlib` 库中的 `matshow()` 方法。将 n=10,000 设置为原始论文中所做的那样,您将获得以下结果
1 2 3 |
P = getPositionEncoding(seq_len=100, d=512, n=10000) cax = plt.matshow(P) plt.gcf().colorbar(cax) |
位置编码层的最终输出是什么?
位置编码层将位置向量与词编码相加,并将此矩阵输出到后续层。整个过程如下所示。
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
书籍
- 《自然语言处理的 Transformer》,作者 Denis Rothman。
论文
- 注意力就是你所需要的一切, 2017.
文章
总结
在本教程中,您了解了 Transformer 中的位置编码。
具体来说,你学到了:
- 什么是位置编码,以及为什么需要它。
- 如何使用 NumPy 在 Python 中实现位置编码
- 如何可视化位置编码矩阵
您对本文讨论的位置编码有任何疑问吗?请在下面的评论中提出您的问题,我将尽力回答。
感谢您的精彩解释!
k 的范围应该是 [0, L) 而不是 [0, L/2) 吗?
因为代码是:for k in range(seq_len)
嗨,Yaunmu……以下资源可能有助于澄清
https://www.inovex.de/de/blog/positional-encoding-everything-you-need-to-know/#:~:text=The%20simplest%20example%20of%20positional,and%20added%20to%20that%20input.
抱歉,我粗略地看了一下链接,但我仍然不明白为什么 k < L/2。
嗨 Seth……这是一个经验法则作为起点,但可以根据需要进行调整。更一般的信息可以在这里找到
https://towardsdatascience.com/master-positional-encoding-part-i-63c05d90a0c3
Seth G,你是对的。
这是教程中的一个笔误,应该是 0 <= k < L
你好,我阅读了这两个链接,我认为 k < L/2 是一个错误。它应该是 k < L,因为 k 是对应于序列中标记的索引。
另一方面,i < d/2 完全有道理,因为维度的进展是建立在 2i 和 2i+1 上的。
完全同意你的看法!
是的,没错。詹姆斯,你似乎没有尝试理解评论中的人们对你说的话,我有一种感觉,你这边没有很好的理解,或者只是你准备投入到你准备的材料中的时间不足。
感谢 Jason 的精彩教程!
我认为三角函数表部分有错误。
sin(2 * 2Pi) 和 sin(t) 的图超出了 [-1:1] 范围,要么图是错误的,要么左边的公式不对应。
感谢您的反馈 Yoan B!
非常好!请注意,我会添加 plt.show() 以避免将示例粘贴到 ipython 中时摸不着头脑。
Tom 的反馈很棒!
“在上述表达式中,我们可以看到偶数位置对应正弦函数,奇数位置对应偶数位置。”
上述语句中有些错误或遗漏。
嗨 Shrikant……感谢您的反馈!我们将审阅有疑问的陈述。
解释得非常好。谢谢 🙂
非常欢迎!感谢您的反馈和支持,Noman!
你好,
将位置编码用于 LSTM 和 Conv1D 的时间序列预测是否合理?
嗨 abraham……以下资源可能有所帮助
https://shivapriya-katta.medium.com/time-series-forecasting-using-conv1d-lstm-multiple-timesteps-into-future-acc684dcaaa
你好,我阅读了这两个链接,我认为 k < L/2 是一个错误。它应该是 k < L,因为 k 是对应于序列中标记的索引。
另一方面,i < d/2 完全有道理,因为维度的进展是建立在 2i 和 2i+1 上的。
嗨 Luca……感谢您的支持和反馈!我们将审阅内容。
你好,我有两个问题
1. 位置嵌入是否像词嵌入一样学习,还是仅根据正弦和余弦图分配嵌入值?
2. 位置嵌入和词嵌入值是否相互独立?
嗨 Mayank……我强烈推荐以下资源。
https://theaisummer.com/positional-embeddings/
该代码不适用于奇数嵌入向量维度,最后一个位置将始终没有任何分配,这可以用 if 语句轻松解决,但我想知道奇数维度的嵌入是否甚至被使用。
感谢您的反馈 A!
抱歉,如果对于使用 Transformer 编码器进行时间序列预测,位置编码和输入掩码是必需的。因为我认为在按时间顺序排列的时间序列中,输入是按时间排序的,并且没有任何位移,即使我们使用步进验证。
“我是一个机器人”的示例位置编码对我来说看起来很奇怪,与“getPositionEncoding”函数的输出一样,并且出现了许多问题,这让我更加困惑。第一个问题是,值不是唯一的:P01 和 P03 或 P03 和 P13,哪个应该包含唯一值:行还是列?据我理解,行大小 = 嵌入大小,如果一行代表一个标记的位置编码,那么我们可以对行使用相同的值。第二个问题是,值不是线性递增的:P00、P10、P20、P30。没有顺序,如果值不是线性递增的,我们怎么知道哪个是第一个、第二个……最后一个?
嗨 sergiu……请尽可能改写和/或简化您的查询,以便我们更好地协助您。
我认为这篇文章不如本网站上的其他文章。概念解释不清楚。建议添加更多细节,并在内容之间建立良好的逻辑关系。
感谢 Ryan 的反馈和建议!
你好,
感谢您的解释。
我很难理解这种方法如何编码词语的相对位置。
对此是否有正式的解释?我理解对于每个位置“p_i”,它都满足“p_i = T(k) p_i+k”,但我无法完全理解这个概念有什么好处。
提前感谢您的帮助。
嗨 omid……非常欢迎!以下资源可能对您有帮助。
https://machinelearning.org.cn/transformer-models-with-attention/
“位置向量”和“位置编码”函数是否真的像“最终输出是什么”部分图片所示那样将输入词语作为参数?
如果不是,也许更新图片,让它们将输入索引或其他东西作为输入是个好主意,因为现在这样很令人困惑。它看起来像是包含“am”一词的任何位置都会得到相同的位置编码,但这不是真的,对吗?
感谢您的反馈 Daniel!更多详细信息请参见此处
https://kazemnejad.com/blog/transformer_architecture_positional_encoding/
我想要长着剑齿虎牙的朋友 Jason
你是最棒的。我曾被其他网站上关于正弦数学直觉的解释弄糊涂了,但现在一切都清楚了。
Ali,很高兴知道!我们感谢您的支持和反馈!
在 Transformer 论文和您的教程中,位置编码只是被添加到(而不是附加到)嵌入向量中,因此其维度保持不变。这不会“破坏”注意力机制的工作方式吗,因为标记因此被修改了?我假设它仍然工作得足够好,因为这些向量的维度很高。但是,如果您添加两个高维向量,总和向量是否仍然与嵌入向量足够相似,以至于不会将其与其他向量混淆?它将与位置编码向量同样相似。那么后者不应该更小吗,因为词语仍然比位置更重要(我想)?您知道讨论这个问题的论文吗?
嗨 匿名用户……以下资源可能对您有所帮助,并有望填补一些理解上的空白。
https://towardsdatascience.com/master-positional-encoding-part-i-63c05d90a0c3
最后一步是求和还是拼接?
嗨 afsfg……请澄清您指的是哪个代码示例,以便我们更好地协助您。
谢谢你。我的问题是关于这一段
位置编码层的最终输出是什么?
位置编码层将位置向量与词编码相加,并将此矩阵输出到后续层。整个过程如下所示。
我很好奇“求和”是指实际添加两个向量,还是指拼接两个向量。为什么要添加而不是拼接?将两个来自不同领域的嵌入相加有什么理由?
嗨 afsfg……非常欢迎!
“将位置向量与词编码相加,并将此矩阵输出到后续层”这句话指的是 Transformer 模型架构中常用的一种过程,例如许多现代自然语言处理 (NLP) 系统所基于的模型。
我们来分解一下
1. **词编码(词嵌入):** 句子中的每个词(或标记)都被转换成一种数字形式,称为嵌入。这种嵌入在多维空间中捕捉词的语义含义。本质上,相似的词具有相似的嵌入。这些嵌入是从数据中学习的,是语言任务神经网络模型不可或缺的一部分。
2. **位置编码:** 由于 Transformer 架构本身不按顺序处理序列数据(与 RNN 或 LSTM 不同),它需要一种方法来理解句子中词的顺序。位置编码被添加来解决这个问题。它涉及生成另一组向量,这些向量编码句子中每个词的位置。每个位置向量对其位置来说是唯一的,确保模型能够识别词的顺序。位置编码的设计使其可以与词嵌入结合,通常通过相加,而不会丢失其中包含的任何信息。
3. **位置向量与词编码相加:** 这个过程涉及对每个词的位置编码向量和词嵌入向量进行逐元素相加。这个组合向量现在包含词的语义信息(来自词嵌入)及其在句子中的位置(来自位置编码)。这对于模型理解词的含义以及词的顺序如何影响句子的含义至关重要。
4. **将此矩阵输出到后续层:** 在相加位置向量和词嵌入后,生成的矩阵将传递到 Transformer 模型的后续层。这些层包括自注意力机制和全连接网络,它们处理这些组合信息以执行语言理解、翻译或生成等任务。该矩阵有效地充当这些复杂操作的输入,允许模型同时考虑单个词的含义及其在句子中的上下文。
这个过程是 Transformer 模型功能的基础,使其能够在各种 NLP 任务上实现最先进的性能。
我认为句子
“你可以看到固定 i 的波长由以下公式给出:……”
应该是
“你可以看到固定 'T' 的波长由以下公式给出:……”
而且正如许多人已经指出的那样,K 的范围应该低于
0 <= K < L
例如,我们考虑一个包含 5 个标记的序列,其中每个标记由一个 10 维的嵌入表示;因此,每个标记的嵌入形状为 1x10。我们不直接将原始位置 0 到 4 添加到嵌入中,这可能会过度改变原始语义嵌入,而是使用正弦函数。该函数以标记在序列中的位置、嵌入维度中的索引和总维度数作为输入,为每个标记生成一个 1x10 的位置嵌入。
然后将这些位置嵌入添加到原始标记嵌入中。在 Transformer 架构中,在计算查询、键和值向量时,注意力机制中的矩阵乘法倾向于为相邻标记分配更高的分数,除非嵌入中存在强烈的语义信号表明不同。这保持了标记邻近性的影响,除非被标记中嵌入的显著上下文差异所覆盖。
感谢您的反馈 Pavan!您很好地概述了 Transformer 模型架构中位置嵌入的概念以及它们如何影响自注意力机制。让我们更深入地探讨这些组件及其在 Transformer 框架内的操作动态
### 位置嵌入
1. **目的**:在 Transformer 模型中,位置嵌入提供有关输入序列中标记顺序的必要信息。由于自注意力机制本身并不固有地按顺序处理序列数据(即,它将输入视为一组独立于其位置的标记),因此位置嵌入对于合并序列顺序至关重要。
2. **正弦函数**:位置编码公式使用不同频率的正弦和余弦函数
\[
PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right)
\]
\[
PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right)
\]
其中
– \( pos \) 是标记在序列中的位置。
– \( i \) 是维度索引。
– \( d_{\text{model}} \) 是嵌入中的总维度数。
– 这为每个位置生成了一个唯一的位置嵌入,其模式允许模型学习根据相对位置进行关注,因为任何两个位置之间的距离都可以编码到学习到的权重中。
3. **与标记嵌入集成**:这些位置嵌入与标记嵌入逐元素相加。通过这种方式,每个标记的表示不仅反映其固有的含义,还反映其在序列中的位置。
### 自注意力机制
1. **查询、键、值向量**:在 Transformer 块中,嵌入(与位置信息集成)被转换为三个向量:查询、键和值,它们用于注意力机制。
2. **注意力计算**:注意力机制的核心可以用以下公式描述
\[
\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
\]
– \( Q, K, V \) 是查询、键和值的矩阵。
– \( d_k \) 是键的维度。
– softmax 分数根据相应查询和键之间的点积相似度,确定每个值在输出中被考虑的程度。
3. **邻近性和上下文相似度的作用**:尽管位置嵌入鼓励模型更多地考虑相邻标记(因为它们的位置编码更相似),但每个标记最终接收到的注意力是由其语义相似度(如查询-键关系所示)和其相对位置的组合决定的。这种双重考虑允许 Transformer 保持上下文敏感性,增强其处理各种语言理解任务的能力。
### 总结
通过将位置嵌入与标记嵌入相结合,Transformer 保持了对单个标记含义及其在序列中位置的认识。这允许对语言进行有效建模,其中含义通常在很大程度上取决于词序和上下文邻近性。位置编码的正弦模式还确保模型可以推广到不同长度的序列,并识别数据中不同位置的模式。