2105.神经网络-注意力机制

我们的视觉系统每时每刻都在接收大量的感官输入, 这些感官输入远远超过了大脑能够完全处理的程度。 然而,并非所有刺激的影响都是相等的。 意识的聚集和专注使灵长类动物能够在复杂的视觉环境中将注意力引向感兴趣的物体。 只关注一小部分信息的能力对进化更加有意义,使人类得以生存和成功。

注意力提示

此刻读者正在阅读本书(而忽略了其他的书), 因此读者的注意力是用机会成本(与金钱类似)来支付的。 为了确保读者现在投入的注意力是值得的, 作者们尽全力(全部的注意力)创作一本好书。

自经济学研究稀缺资源分配以来,人们正处在“注意力经济”时代, 即人类的注意力被视为可以交换的、有限的、有价值的且稀缺的商品。 许多商业模式也被开发出来去利用这一点: 在音乐或视频流媒体服务上,人们要么消耗注意力在广告上,要么付钱来隐藏广告; 为了在网络游戏世界的成长,人们要么消耗注意力在游戏战斗中, 从而帮助吸引新的玩家,要么付钱立即变得强大。 总之,注意力不是免费的。

注意力是稀缺的,而环境中的干扰注意力的信息却并不少。 比如人类的视觉神经系统大约每秒收到 $10^8$ 位的信息, 这远远超过了大脑能够完全处理的水平。 幸运的是,人类的祖先已经从经验(也称为数据)中认识到 “并非感官的所有输入都是一样的”。 在整个人类历史中,这种只将注意力引向感兴趣的一小部分信息的能力, 使人类的大脑能够更明智地分配资源来生存、成长和社交, 例如发现天敌、找寻食物和伴侣。

注意力提示的方式

生物学中的注意力提示

注意力是如何应用于视觉世界中的呢? 这要从当今十分普及的双组件(two-component)的框架开始讲起: 这个框架的出现可以追溯到19世纪90年代的威廉·詹姆斯, 他被认为是“美国心理学之父” (James, 2007)。 在这个框架中,受试者基于非自主性提示自主性提示 有选择地引导注意力的焦点。

  • 非自主性提示是基于环境中物体的突出性和易见性。 想象一下,假如我们面前有五个物品: 一份报纸、一篇研究论文、一杯咖啡、一本笔记本和一本书。 所有纸制品都是黑白印刷的,但咖啡杯是红色的。 换句话说,这个咖啡杯在这种视觉环境中是突出和显眼的, 不由自主地引起人们的注意。 所以我们会把视力最敏锐的地方放到咖啡上, 如图所示。
    image

  • 喝咖啡后,我们会变得兴奋并想读书, 所以转过头,重新聚焦眼睛,然后看看书, 就像下图描述那样。 与上图中的由于突出性导致的选择不同, 此时选择书是受到了认知和意识的控制, 因此注意力在基于自主性提示去辅助选择时将更为谨慎。 受试者的主观意愿推动,选择的力量也就更强大。
    image

查询、键和值

下面来看看如何通过这两种注意力提示, 用神经网络来设计注意力机制的框架,

首先,考虑一个相对简单的状况, 即只使用非自主性提示。 要想将选择偏向于感官输入, 则可以简单地使用参数化的全连接层, 甚至是非参数化的最大汇聚层平均汇聚层
非自主性提示,我们可以理解为所有输入的权重是一样的,输入会无差别流入下一层。

自主性提示就是将注意力机制与全连接层或汇聚层区别开来。在注意力机制的背景下,自主性提示被称为查询(query)。 给定任何查询,注意力机制通过注意力汇聚(attention pooling) 将选择引导至感官输入(sensory inputs,例如中间特征表示)。 在注意力机制中,这些感官输入被称为值(value)。 更通俗的解释,每个值都与一个键(key)配对, 这可以想象为感官输入的非自主提示。 如下图所示,可以通过设计注意力汇聚的方式, 便于给定的查询(自主性提示)与键(非自主性提示)进行匹配, 这将引导得出最匹配的值(感官输入)。
image
Keys(键):在非自主提示下,进入视觉系统的的所有元素的线索,称为 Keys。 Query(查询):在自主提示下,自主提示的内容或元素的线索,称为 Query。 Values(值):在由自主提示 Query 限制或者强制下改变注意力的焦点,也就是经过从 Keys 中进行匹配 Query,所得到的进入视觉系统的内容,称为 Values。

注意力机制通过注意力汇聚将查询(自主性提示)和键(非自主性提示)结合在一起,实现对值(感官输入)的选择倾向。
自主性提示,相当于我们会通过提供的查询 ,根据输入信息的 和提供的查询 基于特定的规则进行相关性的计算,从而实现对所有输入的信息实现加权(注意力汇聚),最终只是选择权重重要的信息流入下游。

自主性的与非自主性的注意力提示解释了人类的注意力的方式,使用神经网络来设计注意力机制的框架,就是基于上述非自主性提示和自主性提示的原理,来选择要聚焦的观察对象。具体抽象出来的元素就是最基本的 Key、Query、Value,如下所示:

注意力汇聚

Nadaraya-Watson 核回归

查询(自主提示)和(非自主提示)之间的交互形成了注意力汇聚; 注意力汇聚有选择地聚合了值(感官输入)以生成最终的输出。接下来我们来了解下注意力汇聚的细节。
1964年提出的Nadaraya-Watson核回归模型是一个简单但完整的例子,这里使用它演示具有注意力机制的机器学习。

生成一组测试数据

根据下面的非线性函数生成一个人工数据集, 其中 $\epsilon$ 数据模拟过程中引入的噪音:
$$y_i=2\sin(x_i)+x_i^{0.8}+\epsilon$$
其中 $\epsilon$ 服从均值为 0 和标准差为 0.5 的正态分布。 在这里生成了 50 个训练样本和 50 个测试样本。 为了更好地可视化之后的注意力模式,需要将训练样本进行排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
n_train = 50
x_train = tf.sort(tf.random.uniform(shape=(n_train,), maxval=5))
def f(x):
return 2 * tf.sin(x) + x**0.8
y_train = f(x_train) + tf.random.normal((n_train,), 0.0, 0.5) # 训练样本的输出
x_test = tf.range(0, 5, 0.1) # 测试样本
y_truth = f(x_test) # 测试样本的真实输出
n_test = len(x_test) # 测试样本数

# 下面的函数将绘制所有的训练样本(样本由圆圈表示), 不带噪声项的真实数据生成函数(标记为“Truth”), 以及学习得到的预测函数(标记为“Pred”)。
def plot_kernel_reg(y_hat):
d2l.plot(x_test, [y_truth, y_hat], 'x', 'y', legend=['Truth', 'Pred'],
xlim=[0, 5], ylim=[-1, 5])
d2l.plt.plot(x_train, y_train, 'o', alpha=0.5);

上面这部分,我们准备好了进行测试的模拟数据,同时准备好预测结果的可视化函数。

平均汇聚

在我们不使用注意力汇聚模型的时候,那么很容易理解,我们模型的所有输入都是等权重的。所以我们会很容易的得到一个简单的估计器:
$$f(x)=\frac{1}{n}\sum_{i=1}^n y_i$$
因为没有使用注意力汇集,所有输入是等权重的,所以我们得到的是一个直线$f(x)=\overline{y}$,预测结果和$x_i$ 直接无关了,显然这个模型表现并不好。
image

非参数注意力汇聚

为了让我们能获得一个更好的模型,显然,我们必须考虑输入的 $x$ 值(因为我们知道,$y$ 是基于 $x$ 值计算得到的)。
于是Nadaraya (Nadaraya, 1964)和 Watson (Watson, 1964)提出了一个更好的想法, 根据输入的位置对输出 $y_i$ 进行加权:
$$f(x)=\sum_{i=1}^n \frac{K(x-x_i)}{\sum_{j=1}^n K(x-x_j)}y_i $$
其中$K$是核函数(kernel),为了便于理解,当我们假设 $y_i$ 恒等于1 时,那么针对x的权重进行积分,可以得到权重综合不管我们的核函数 $K$ 是如何定义的,最终所有位点的权重积分始终为1。所以我们可以根据我们的需要独立的进行核函数的定义,而不会影响整体的权重分配。
$$f(x)= \frac{\sum_{i=1}^n K(x-x_i)}{\sum_{j=1}^n K(x-x_j)} = 1$$
所以在我们定义好 $K$ 后,基于上述方案,当我们的输入(要查询的值) $x$ 确定后,就可以计算获得每个输入$y_i$所占的的权重(一般越接近待查询值$x$的键$x_i$ 获得的权重越大),然后对所有输入$y_i$进行加权求和(注意力汇聚),得到一个更好的模型。

值得注意的是,Nadaraya-Watson核回归是一个非参数模型。 因此上述该方法也是 非参数的注意力汇聚(nonparametric attention pooling)模型。我们基于这个非参数的注意力汇聚模型来绘制预测结果如下图,可以看到新的模型预测线是平滑的,并且比平均汇聚的预测更接近真实。
image
同时我们观察注意力的权重。 这里测试数据的输入相当于查询,而训练数据的输入相当于键。 因为两个输入都是经过排序的,因此由观察可知“查询-键”对越接近, 注意力汇聚的注意力权重就越高。
image

带参数注意力汇聚

非参数的Nadaraya-Watson核回归具有一致性(consistency)的优点: 如果有足够的数据,此模型会收敛到最优结果。 尽管如此,我们还是可以轻松地将可学习的参数集成到注意力汇聚中,和之前的区别,主要是我们定义好核函数$K$ 计算$x$到每个$x_i$的距离后,我们再引入一个参数 $w$ 对距离进行修正后计算权重
$$f(x)=\sum_{i=1}^n \frac{K(x-x_i)w}{\sum_{j=1}^n K(x-x_j)w}y_i $$

代码实现

定义Nadaraya-Watson核回归的带参数版本:

1
2
3
4
5
6
7
8
9
10
11
12
class NWKernelRegression(tf.keras.layers.Layer):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.w = tf.Variable(initial_value=tf.random.uniform(shape=(1,)))

def call(self, queries, keys, values, **kwargs):
# 对于训练,“查询”是x_train。“键”是每个点的训练数据的距离。“值”为'y_train'。
# queries和attention_weights的形状为(查询个数,“键-值”对个数)
queries = tf.repeat(tf.expand_dims(queries, axis=1), repeats=keys.shape[1], axis=1)
self.attention_weights = tf.nn.softmax(-((queries - keys) * self.w)**2 /2, axis =1)
# values的形状为(查询个数,“键-值”对个数)
return tf.squeeze(tf.matmul(tf.expand_dims(self.attention_weights, axis=1), tf.expand_dims(values, axis=-1)))

将训练数据集变换为键和值用于训练注意力模型。 在带参数的注意力汇聚模型中, 任何一个训练样本的输入都会和除自己以外的所有训练样本的“键-值”对进行计算, 从而得到其对应的预测输出。

1
2
3
4
5
6
7
8
# X_tile的形状:(n_train,n_train),每一行都包含着相同的训练输入
X_tile = tf.repeat(tf.expand_dims(x_train, axis=0), repeats=n_train, axis=0)
# Y_tile的形状:(n_train,n_train),每一行都包含着相同的训练输出
Y_tile = tf.repeat(tf.expand_dims(y_train, axis=0), repeats=n_train, axis=0)
# keys的形状:('n_train','n_train'-1)
keys = tf.reshape(X_tile[tf.cast(1 - tf.eye(n_train), dtype=tf.bool)], shape=(n_train, -1))
# values的形状:('n_train','n_train'-1)
values = tf.reshape(Y_tile[tf.cast(1 - tf.eye(n_train), dtype=tf.bool)], shape=(n_train, -1))

训练带参数的注意力汇聚模型时,使用平方损失函数和随机梯度下降。

1
2
3
4
5
6
7
8
9
10
11
12
13
net = NWKernelRegression()
loss_object = tf.keras.losses.MeanSquaredError()
optimizer = tf.keras.optimizers.SGD(learning_rate=0.5)
animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[1, 5])


for epoch in range(5):
with tf.GradientTape() as t:
loss = loss_object(y_train, net(x_train, keys, values)) * len(y_train)
grads = t.gradient(loss, net.trainable_variables)
optimizer.apply_gradients(zip(grads, net.trainable_variables))
print(f'epoch {epoch + 1}, loss {float(loss):.6f}')
animator.add(epoch + 1, float(loss))

损失函数下降曲线如下图:
image

1
2
3
4
5
6
7
8
9
10
11
# keys的形状:(n_test,n_train),每一行包含着相同的训练输入(例如,相同的键)
keys = tf.repeat(tf.expand_dims(x_train, axis=0), repeats=n_test, axis=0)
# value的形状:(n_test,n_train)
values = tf.repeat(tf.expand_dims(y_train, axis=0), repeats=n_test, axis=0)
y_hat = net(x_test, keys, values)
plot_kernel_reg(y_hat)
# 注意力权重绘图
d2l.show_heatmaps(tf.expand_dims(
tf.expand_dims(net.attention_weights, axis=0), axis=0),
xlabel='Sorted training inputs',
ylabel='Sorted testing inputs')

带参数注意力汇聚的拟合结果和注意力权重热图如下:
image
image
与非参数的注意力汇聚模型相比, 带参数的模型加入可学习的参数后, 曲线在注意力权重较大的区域变得更不平滑。

注意力评分

在我们进行注意力汇聚介绍时,提到一个重要的函数 $\alpha(x-x_i) = \frac{K(x-x_i)w}{\sum_{j=1}^n K(x-x_j)w}$ ,它可以帮我们衡量不同查询和键之间的距离,从而确定给定查询下,各个键的权重分布。$K(x-x_i)$ 又称为 注意力评分函数 可以帮助我们衡量查询$q$和键$x_i$之间的相似性/距离,进而确定每个 $y_i$的权重,最后得到的注意力汇聚的输出就是基于这些注意力权重的值的加权和。
image
所以显然,不同的注意力评分函数的选择,会给我们带来不同的权重计算方式,这里记录几个流行的评分函数。

masked softmax operation 掩蔽softmax操作

在某些情况下,并非所有的值都应该被纳入到注意力汇聚中。为了仅将有意义的词元作为值来获取注意力汇聚, 可以指定一个有效序列长度(即词元的个数), 以便在计算softmax时过滤掉超出指定范围的位置。 下面的masked_softmax函数 实现了这样的掩蔽softmax操作(masked softmax operation), 其中任何超出有效长度的位置都被掩蔽并置为0。

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
36
37
#@save
def masked_softmax(X, valid_lens):
"""通过在最后一个轴上掩蔽元素来执行softmax操作"""
# X:3D张量,valid_lens:1D或2D张量
if valid_lens is None:
return tf.nn.softmax(X, axis=-1)
else:
shape = X.shape
if len(valid_lens.shape) == 1:
valid_lens = tf.repeat(valid_lens, repeats=shape[1])

else:
valid_lens = tf.reshape(valid_lens, shape=-1)
# 最后一轴上被掩蔽的元素使用一个非常大的负值替换,从而其softmax输出为0
X = d2l.sequence_mask(tf.reshape(X, shape=(-1, shape[-1])),
valid_lens, value=-1e6)
return tf.nn.softmax(tf.reshape(X, shape=shape), axis=-1)

# 考虑由两个 2*4 矩阵表示的样本, 这两个样本的有效长度分别为2和3。 经过掩蔽softmax操作,超出有效长度的值都被掩蔽为0。
masked_softmax(tf.random.uniform(shape=(2, 2, 4)), tf.constant([2, 3]))

<tf.Tensor: shape=(2, 2, 4), dtype=float32, numpy=
array([[[0.42428792, 0.575712 , 0. , 0. ],
[0.5350254 , 0.46497452, 0. , 0. ]],

[[0.32676727, 0.47748092, 0.19575192, 0. ],
[0.43579608, 0.30782804, 0.2563759 , 0. ]]], dtype=float32)>

# 也可以使用二维张量,为矩阵样本中的每一行指定有效长度。
masked_softmax(tf.random.uniform(shape=(2, 2, 4)), tf.constant([[1, 3], [2, 4]]))

<tf.Tensor: shape=(2, 2, 4), dtype=float32, numpy=
array([[[1. , 0. , 0. , 0. ],
[0.40658087, 0.38102806, 0.21239112, 0. ]],

[[0.3735564 , 0.6264436 , 0. , 0. ],
[0.3183936 , 0.22352162, 0.18998112, 0.26810366]]], dtype=float32)>

additive attention 加性注意力

缩放点积注意力

多头注意力

当给定相同的查询、键和值的集合时, 我们希望模型可以基于相同的注意力机制学习到不同的行为, 然后将不同的行为作为知识组合起来, 捕获序列内各种范围的依赖关系 (例如,短距离依赖和长距离依赖关系)。 因此,允许注意力机制组合使用查询、键和值的不同 子空间表示(representation subspaces)可能是有益的。

为此,与其只使用单独一个注意力汇聚, 我们可以用独立学习得到的 $h$ 组不同的线性投影(linear projections)来变换查询、键和值。 然后,这组变换后的查询、键和值将并行地送到注意力汇聚中。 最后,将这个注意力汇聚的输出拼接在一起, 并且通过另一个可以学习的线性投影进行变换, 以产生最终输出。 这种设计被称为多头注意力(multihead attention) (Vaswani et al., 2017)。下图展示了使用全连接层来实现可学习的线性变换的多头注意力。
image

自主机制和位置编码

自注意力、卷积神经网络和循环神经网络

目标都是将由个词元组成的序列映射到另一个长度相等的序列,其中的每个输入词元或输出词元都由维向量表示。具体来说,将比较的是卷积神经网络、循环神经网络和自注意力这几个架构的计算复杂性、顺序操作和最大路径长度。请注意,顺序操作会妨碍并行计算,而任意的序列位置组合之间的路径越短,则能更轻松地学习序列中的远距离依赖关系 (Hochreiter et al., 2001)[https://www.bioinf.jku.at/publications/older/ch7.pdf]
image

考虑一个卷积核大小为 $3$ 的卷积层。 在后面的章节将提供关于使用卷积神经网络处理序列的更多详细信息。 目前只需要知道的是,由于序列长度是,输入和输出的通道数量都是, 所以卷积层的计算复杂度为 $O(knd^2) $。 如 上图所示, 卷积神经网络是分层的,因此为有 $O(1)$ 个顺序操作, 最大路径长度为 $O(k/n)$。 例如,$x_1$和$x_5$ 处于 图中卷积核大小为 3 的双层卷积神经网络的感受野内。

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

在自注意力中,查询、键和值都是 $nd$ 矩阵。 考虑其中缩放的”点-积“注意力, 其中 $nd$ 矩阵乘以 $dn$ 矩阵。 之后输出的 $nn$ 矩阵乘以矩阵 $n*d$。 因此,自注意力具有计算复杂性 $O(n^2d)$。每个词元都通过自注意力直接连接到任何其他词元。 因此,有$O(1)$个顺序操作可以并行计算, 最大路径长度也是$O(1)$。

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

位置编码

在处理词元序列时,循环神经网络是逐个的重复地处理词元的, 而自注意力则因为并行计算而放弃了顺序操作。 为了使用序列的顺序信息,通过在输入表示中添加 位置编码(positional encoding)来注入绝对的或相对的位置信息。 位置编码可以通过学习得到也可以直接固定得到。 接下来描述的是基于正弦函数和余弦函数的固定位置编码 (Vaswani et al., 2017)。
$$p_{i,2j}=\sin({i \over {10000^{2j/d}}})$$
$$p_{i,2j+1}=\cos({i \over {10000^{2j/d}}})$$
分别定义了 $2j$ 和 $2j+1$ 的位置编码(越靠前的列三角函数波动频率越高)。 其中 $i$ 是词元索引(每一行对应输入的一个词元位置), $d$ 是词元表示的维数。
image
同时可以看到越靠前的编码位置频率越高

绝对位置信息

为了明白沿着编码维度单调降低的频率与绝对位置信息的关系, 让我们打印出的二进制表示形式。 正如所看到的,每个数字、每两个数字和每四个数字上的比特值 在第一个最低位、第二个最低位和第三个最低位上分别交替。

1
2
3
4
5
6
7
8
0的二进制是:000
1的二进制是:001
2的二进制是:010
3的二进制是:011
4的二进制是:100
5的二进制是:101
6的二进制是:110
7的二进制是:111

在二进制表示中,较高比特位的交替频率低于较低比特位, 与下面的热图所示相似,只是位置编码通过使用三角函数在编码维度上降低频率。 由于输出是浮点数,因此此类连续表示比二进制表示法更节省空间。

所以可以理解为基于三角函数的固定位置编码和基于二进制的编码类型,只是二进制中使用的是0和1作为频率波动的幅度,而三角函数中使用的是正弦和余弦函数自身的波动性。二进制中的位置关系,在位置编码矩阵中对应矩阵的不同列。基于三角函数编码位置其本质和基于二进制并无区别,只是利用了三角函数输出是浮点数,连续的表示比二进制表示能更好的节省空间。

-------------本文结束感谢您的阅读-------------