在深度学习中,我们经常使用卷积神经网络(CNN)或循环神经网络(RNN)对序列进行编码。 想象一下,有了注意力机制之后,我们将词元序列输入注意力池化中, 以便同一组词元同时充当查询、键和值。 具体来说,每个查询都会关注所有的键-值对并生成一个注意力输出。 由于查询、键和值来自同一组输入,因此被称为 自注意力(self-attention)Lin.Feng.Santos.ea.2017,Vaswani.Shazeer.Parmar.ea.2017, 也被称为内部注意力(intra-attention)Cheng.Dong.Lapata.2016,Parikh.Tackstrom.Das.ea.2016,Paulus.Xiong.Socher.2017。 在本节中,我们将使用自注意力进行序列编码,以及如何使用序列的顺序作为补充信息。

In [ ]
import math
import paddle
from paddle import nn
from d2l import paddle as d2l
[自注意力]
给定一个由词元组成的输入序列x1,…,xn\mathbf{x}_1, \ldots, \mathbf{x}_nx
1

,…,x
n

, 其中任意xi∈Rd\mathbf{x}_i \in \mathbb{R}^dx
i

∈R
d
(1≤i≤n1 \leq i \leq n1≤i≤n)。 该序列的自注意力输出为一个长度相同的序列 y1,…,yn\mathbf{y}_1, \ldots, \mathbf{y}_ny
1

,…,y
n

,其中:

yi=f(xi,(x1,x1),…,(xn,xn))∈Rd (10.6.1)\mathbf{y}_i = f(\mathbf{x}_i, (\mathbf{x}_1, \mathbf{x}_1), \ldots, (\mathbf{x}_n, \mathbf{x}_n)) \in \mathbb{R}^d~~~~~~~~~~(10.6.1)
y
i

=f(x
i

,(x
1

,x
1

),…,(x
n

,x
n

))∈R
d
(10.6.1)

根据公式(10.2.4)中定义的注意力池化函数fff。 下面的代码片段是基于多头注意力对一个张量完成自注意力的计算, 张量的形状为(批量大小,时间步的数目或词元序列的长度,ddd)。 输出与输入的张量形状相同。

In [ ]
num_hiddens, num_heads = 100, 5
attention = d2l.MultiHeadAttention(num_hiddens, num_hiddens, num_hiddens,
num_hiddens, num_heads, 0.5)
attention.eval()
In [ ]
batch_size, num_queries, valid_lens = 2, 4, paddle.to_tensor([3, 2])
X = paddle.ones((batch_size, num_queries, num_hiddens))
attention(X, X, X, valid_lens).shape
比较卷积神经网络、循环神经网络和自注意力
让我们比较下面几个架构,目标都是将由nnn个词元组成的序列映射到另一个长度相等的序列,其中的每个输入词元或输出词元都由ddd维向量表示。具体来说,我们将比较的是卷积神经网络、循环神经网络和自注意力这几个架构的计算复杂性、顺序操作和最大路径长度。请注意,顺序操作会妨碍并行计算,而任意的序列位置组合之间的路径越短,则能更轻松地学习序列中的远距离依赖关系Hochreiter.Bengio.Frasconi.ea.2001。

图10.6.1 比较卷积神经网络(填充词元被忽略)、循环神经网络和自注意力三种架构
考虑一个卷积核大小为kkk的卷积层。 我们将在后面的章节中提供关于使用卷积神经网络处理序列的更多详细信息。 目前,我们只需要知道,由于序列长度是nnn,输入和输出的通道数量都是ddd, 所以卷积层的计算复杂度为O(knd2)\mathcal{O}(knd^2)O(knd
2
)。 如图10.6.1所示, 卷积神经网络是分层的,因此为有O(1)\mathcal{O}(1)O(1)个顺序操作, 最大路径长度为O(n/k)\mathcal{O}(n/k)O(n/k)。 例如,x1\mathbf{x}_1x
1

和x5\mathbf{x}_5x
5

处于 图10.6.1中卷积核大小为3的双层卷积神经网络的感受野内。

当更新循环神经网络的隐状态时, d×dd \times dd×d权重矩阵和ddd维隐状态的乘法计算复杂度为O(d2)\mathcal{O}(d^2)O(d
2
)。 由于序列长度为nnn,因此循环神经网络层的计算复杂度为O(nd2)\mathcal{O}(nd^2)O(nd
2
)。 根据图10.6.1, 有O(n)\mathcal{O}(n)O(n)个顺序操作无法并行化,最大路径长度也是O(n)\mathcal{O}(n)O(n)。

在自注意力中,查询、键和值都是n×dn \times dn×d矩阵。 考虑公式(10.3.5)中缩放的”点-积“注意力, 其中n×dn \times dn×d矩阵乘以d×nd \times nd×n矩阵。 之后输出的n×nn \times nn×n矩阵乘以n×dn \times dn×d矩阵。 因此,自注意力具有O(n2d)\mathcal{O}(n^2d)O(n
2
d)计算复杂性。 正如我们在图10.6.1中看到的那样, 每个词元都通过自注意力直接连接到任何其他词元。 因此,有O(1)\mathcal{O}(1)O(1)个顺序操作可以并行计算, 最大路径长度也是O(1)\mathcal{O}(1)O(1)。

总而言之,卷积神经网络和自注意力都拥有并行计算的优势, 而且自注意力的最大路径长度最短。 但是因为其计算复杂度是关于序列长度的二次方,所以在很长的序列中计算会非常慢。

[位置编码]
在处理词元序列时,循环神经网络是逐个的重复地处理词元的, 而自注意力则因为并行计算而放弃了顺序操作。 为了使用序列的顺序信息,我们通过在输入表示中添加 位置编码(positional encoding)来注入绝对的或相对的位置信息。 位置编码可以通过学习得到也可以直接固定得到。 接下来,我们描述的是基于正弦函数和余弦函数的固定位置编码 Vaswani.Shazeer.Parmar.ea.2017。

假设输入表示X∈Rn×d\mathbf{X} \in \mathbb{R}^{n \times d}X∈R
n×d
包含一个序列中nnn个词元的ddd维嵌入表示。 位置编码使用相同形状的位置嵌入矩阵 P∈Rn×d\mathbf{P} \in \mathbb{R}^{n \times d}P∈R
n×d
输出X+P\mathbf{X} + \mathbf{P}X+P, 矩阵第iii行、第2j2j2j列和2j2j2j列上的元素为:

pi,2j=sin⁡(i100002j/d),pi,2j+1=cos⁡(i100002j/d). (10.6.2)\begin{aligned} p_{i, 2j} &= \sin\left(\frac{i}{10000^{2j/d}}\right),\p_{i, 2j+1} &= \cos\left(\frac{i}{10000^{2j/d}}\right).\end{aligned}~~~~~~~~~~(10.6.2)
p
i,2j

p
i,2j+1

=sin(
10000
2j/d

i

),
=cos(
10000
2j/d

i

).

(10.6.2)

乍一看,这种基于三角函数的设计看起来很奇怪。 在解释这个设计之前,让我们先在下面的PositionalEncoding类中实现它。

In [ ]
#@save
class PositionalEncoding(nn.Layer):
“”“位置编码”“”
def init(self, num_hiddens, dropout, max_len=1000):
super(PositionalEncoding, self).init()
self.dropout = nn.Dropout(dropout)
# 创建一个足够长的P
self.P = paddle.zeros((1, max_len, num_hiddens))
X = paddle.arange(max_len, dtype=paddle.float32).reshape(
(-1, 1)) / paddle.pow(paddle.to_tensor([10000.0]), paddle.arange(
0, num_hiddens, 2, dtype=paddle.float32) / num_hiddens)
self.P[:, :, 0::2] = paddle.sin(X)
self.P[:, :, 1::2] = paddle.cos(X)

def forward(self, X):
    X = X + self.P[:, :X.shape[1], :]
    return self.dropout(X)

在位置嵌入矩阵P\mathbf{P}P中, [行代表词元在序列中的位置,列代表位置编码的不同维度]。 在下面的例子中,我们可以看到位置嵌入矩阵的第666列和第777列的频率高于第888列和第999列。 第666列和第777列之间的偏移量(第888列和第999列相同)是由于正弦函数和余弦函数的交替。

In [ ]
encoding_dim, num_steps = 32, 60
pos_encoding = PositionalEncoding(encoding_dim, 0)
pos_encoding.eval()
X = pos_encoding(paddle.zeros((1, num_steps, encoding_dim)))
P = pos_encoding.P[:, :X.shape[1], :]
d2l.plot(paddle.arange(num_steps), P[0, :, 6:10].T, xlabel=‘Row (position)’,
figsize=(6, 2.5), legend=[“Col %d” % d for d in paddle.arange(6, 10)])
绝对位置信息
为了明白沿着编码维度单调降低的频率与绝对位置信息的关系, 让我们打印出0,1,…,70, 1, \ldots, 70,1,…,7的[二进制表示]形式。 正如我们所看到的,每个数字、每两个数字和每四个数字上的比特值 在第一个最低位、第二个最低位和第三个最低位上分别交替。

In [ ]
for i in range(8):
print(f’{i}的二进制是:{i:>03b}')
在二进制表示中,较高比特位的交替频率低于较低比特位, 与下面的热图所示相似,只是位置编码通过使用三角函数[在编码维度上降低频率]。 由于输出是浮点数,因此此类连续表示比二进制表示法更节省空间。

In [ ]
P = P[0, :, :].unsqueeze(0).unsqueeze(0)
d2l.show_heatmaps(P, xlabel=‘Column (encoding dimension)’,
ylabel=‘Row (position)’, figsize=(3.5, 4), cmap=‘Blues’)
相对位置信息
除了捕获绝对位置信息之外,上述的位置编码还允许模型学习得到输入序列中相对位置信息。 这是因为对于任何确定的位置偏移δ\deltaδ,位置i+δi + \deltai+δ处 的位置编码可以线性投影位置iii处的位置编码来表示。

这种投影的数学解释是,令ωj=1/100002j/d\omega_j = 1/10000^{2j/d}ω
j

=1/10000
2j/d
, 对于任何确定的位置偏移δ\deltaδ, 公式(10.6.2)中的任何一对 (pi,2j,pi,2j+1)(p_{i, 2j}, p_{i, 2j+1})(p
i,2j

,p
i,2j+1

)都可以线性投影到 (pi+δ,2j,pi+δ,2j+1)(p_{i+\delta, 2j}, p_{i+\delta, 2j+1})(p
i+δ,2j

,p
i+δ,2j+1

):

[cos⁡(δωj)sin⁡(δωj)−sin⁡(δωj)cos⁡(δωj)][pi,2jpi,2j+1]=[cos⁡(δωj)sin⁡(iωj)+sin⁡(δωj)cos⁡(iωj)−sin⁡(δωj)sin⁡(iωj)+cos⁡(δωj)cos⁡(iωj)]=[sin⁡((i+δ)ωj)cos⁡((i+δ)ωj)]=[pi+δ,2jpi+δ,2j+1], (10.6.3)\begin{aligned} &\begin{bmatrix} \cos(\delta \omega_j) & \sin(\delta \omega_j) \ -\sin(\delta \omega_j) & \cos(\delta \omega_j) \ \end{bmatrix} \begin{bmatrix} p_{i, 2j} \ p_{i, 2j+1} \ \end{bmatrix}\ =&\begin{bmatrix} \cos(\delta \omega_j) \sin(i \omega_j) + \sin(\delta \omega_j) \cos(i \omega_j) \ -\sin(\delta \omega_j) \sin(i \omega_j) + \cos(\delta \omega_j) \cos(i \omega_j) \ \end{bmatrix}\ =&\begin{bmatrix} \sin\left((i+\delta) \omega_j\right) \ \cos\left((i+\delta) \omega_j\right) \ \end{bmatrix}\ =& \begin{bmatrix} p_{i+\delta, 2j} \ p_{i+\delta, 2j+1} \ \end{bmatrix}, \end{aligned}~~~~~~~~~~(10.6.3)

=

[
cos(δω
j

)
−sin(δω
j

)

sin(δω
j

)
cos(δω
j

)

][
p
i,2j

p
i,2j+1


]
[
cos(δω
j

)sin(iω
j

)+sin(δω
j

)cos(iω
j

)
−sin(δω
j

)sin(iω
j

)+cos(δω
j

)cos(iω
j

)

]
[
sin((i+δ)ω
j

)
cos((i+δ)ω
j

)

]
[
p
i+δ,2j

p
i+δ,2j+1


],

(10.6.3)

2×22\times 22×2投影矩阵不依赖于任何位置的索引iii。

小结
在自注意力中,查询、键和值都来自同一组输入。
卷积神经网络和自注意力都拥有并行计算的优势,而且自注意力的最大路径长度最短。但是因为其计算复杂度是关于序列长度的二次方,所以在很长的序列中计算会非常慢。
为了使用序列的顺序信息,我们可以通过在输入表示中添加位置编码,来注入绝对的或相对的位置信息。
练习
假设我们设计一个深度架构,通过堆叠基于位置编码的自注意力层来表示序列。可能会存在什么问题?
你能设计一种可学习的位置编码方法吗?
如果想系统性学习该项目,可前往“动手学AI”课程(https://aistudio.baidu.com/aistudio/course/introduce/25851)查看完整章节

Discussions

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐