深度学习入门(六十七)循环神经网络——注意力机制

前言

核心内容来自博客链接1博客连接2希望大家多多支持作者
本文记录用,防止遗忘

循环神经网络——注意力机制

课件

心理学

  • 动物需要在复杂环境下有效关注值得注意的点
  • 心理学框架:人类根据随意线索和不随意线索选择注意点

注意力机制

卷积、全连接、池化层都只考虑不随意线索(没有明确的目标)

池化操作通常是将感受野范围中的最大值提取出来(最大池化)
卷积操作通常是对输入全部通过卷积核进行操作,然后提取出一些比较明显的特征

注意力机制是显式地考虑随意线索

随意线索被称之为查询(query)---- 所想要做的事情
每个输入是一个值(value)和不随意线索(key)的对 ---- 可以理解为环境,就是一些键值对,key 和 value 可以相同,也可以不同
通过注意力池化层来有偏向性地选择某些输入 ---- 根据 query 有偏向地选择输入,跟之前的池化层有所不同,这里显式地加入了 query,然后根据 query 查询所需要的东西

非参注意力池化层

在这里插入图片描述

非参:不需要学习参数
x – key
y – value
f(x)-- 对应所要查询的东西
(x,y) – key-value对(候选)
平均池化:之所以是最简单的方案,是因为不需要管所查询的东西(也就是f(x)中的 x ),而只需要无脑地对 y 求和取平均就可以了

Nadaraya-Watson 核回归:

核:K 函数,它可以认为是衡量 x 和 xi 之间距离的函数
数据就是给定的数据,对于新给定的值来讲,只需要在给定的数据中进行查询就可以了(选择和新给定的值比较相近的数据,然后将这些数据对应的 value 值然后进行加权求和,从而得到最终的 query),所以不需要学习参数

K 的选择:高斯核
在这里插入图片描述

u:代表 x 和 xi 之间的距离
exp:作用是将最终的结果变成大于 0 的数
softmax:得到 0 到 1 之间的数作为权重
在上式的基础上添加一个可以学习的 w :
在这里插入图片描述

总结

1、心理学认为人通过随意线索和不随意线索选择注意点

2、注意力机制中,通过query(随意线索)和 key(不随意线索)来有偏向性地选择输入,一般可以写作

在这里插入图片描述

f(x)的 key 和所有的不随意线索的 key 做距离上的计算(α(x,xi),通常称为注意力权重),分别作为所有的 value 的权重
这并不是一个新兴的概念,早在 60 年代就已经有非参数的注意力机制了

教材(注意力提示)

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

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

1 生物学中的注意力提示

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

非自主性提示是基于环境中物体的突出性和易见性。 想象一下,假如我们面前有五个物品: 一份报纸、一篇研究论文、一杯咖啡、一本笔记本和一本书, 就像下图。 所有纸制品都是黑白印刷的,但咖啡杯是红色的。 换句话说,这个咖啡杯在这种视觉环境中是突出和显眼的, 不由自主地引起人们的注意。 所以我们会把视力最敏锐的地方放到咖啡上, 如图所示。
在这里插入图片描述
喝咖啡后,我们会变得兴奋并想读书, 所以转过头,重新聚焦眼睛,然后看看书, 就像下图中描述那样。 与上图中由于突出性导致的选择不同, 此时选择书是受到了认知和意识的控制, 因此注意力在基于自主性提示去辅助选择时将更为谨慎。 受试者的主观意愿推动,选择的力量也就更强大。
在这里插入图片描述

2 查询、键和值

自主性的与非自主性的注意力提示解释了人类的注意力的方式, 下面来看看如何通过这两种注意力提示, 用神经网络来设计注意力机制的框架,

首先,考虑一个相对简单的状况, 即只使用非自主性提示。 要想将选择偏向于感官输入, 则可以简单地使用参数化的全连接层, 甚至是非参数化的最大汇聚层或平均汇聚层。

因此,“是否包含自主性提示”将注意力机制与全连接层或汇聚层区别开来。 在注意力机制的背景下,自主性提示被称为查询(query)。 给定任何查询,注意力机制通过注意力汇聚(attention pooling) 将选择引导至感官输入(sensory inputs,例如中间特征表示)。 在注意力机制中,这些感官输入被称为值(value)。 更通俗的解释,每个值都与一个键(key)配对, 这可以想象为感官输入的非自主提示。 如图所示,可以通过设计注意力汇聚的方式, 便于给定的查询(自主性提示)与键(非自主性提示)进行匹配, 这将引导得出最匹配的值(感官输入)。
在这里插入图片描述
鉴于上面所提框架在图中的主导地位, 因此这个框架下的模型将成为本章的中心。 然而,注意力机制的设计有许多替代方案。 例如可以设计一个不可微的注意力模型, 该模型可以使用强化学习方法 (Mnih et al., 2014)进行训练。

3 注意力的可视化

平均汇聚层可以被视为输入的加权平均值, 其中各输入的权重是一样的。 实际上,注意力汇聚得到的是加权平均的总和值, 其中权重是在给定的查询和不同的键之间计算得出的。

import torch
from d2l import torch as d2l

为了可视化注意力权重,需要定义一个show_heatmaps函数。 其输入matrices的形状是 (要显示的行数,要显示的列数,查询的数目,键的数目)。

#@save
def show_heatmaps(matrices, xlabel, ylabel, titles=None, figsize=(2.5, 2.5),
                  cmap='Reds'):
    """显示矩阵热图"""
    d2l.use_svg_display()
    num_rows, num_cols = matrices.shape[0], matrices.shape[1]
    fig, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize,
                                 sharex=True, sharey=True, squeeze=False)
    for i, (row_axes, row_matrices) in enumerate(zip(axes, matrices)):
        for j, (ax, matrix) in enumerate(zip(row_axes, row_matrices)):
            pcm = ax.imshow(matrix.detach().numpy(), cmap=cmap)
            if i == num_rows - 1:
                ax.set_xlabel(xlabel)
            if j == 0:
                ax.set_ylabel(ylabel)
            if titles:
                ax.set_title(titles[j])
    fig.colorbar(pcm, ax=axes, shrink=0.6);

下面使用一个简单的例子进行演示。 在本例子中,仅当查询和键相同时,注意力权重为1,否则为0。

attention_weights = torch.eye(10).reshape((1, 1, 10, 10))
show_heatmaps(attention_weights, xlabel='Keys', ylabel='Queries')

输出:
在这里插入图片描述

后面的章节内容将经常调用show_heatmaps函数来显示注意力权重。

4 小结

  • 人类的注意力是有限的、有价值和稀缺的资源。

  • 受试者使用非自主性和自主性提示有选择性地引导注意力。前者基于突出性,后者则依赖于意识。

  • 注意力机制与全连接层或者汇聚层的区别源于增加的自主提示。

  • 由于包含了自主性提示,注意力机制与全连接的层或汇聚层不同。

  • 注意力机制通过注意力汇聚使选择偏向于值(感官输入),其中包含查询(自主性提示)和键(非自主性提示)。键和值是成对的。

  • 可视化查询和键之间的注意力权重是可行的。

教材(注意力汇聚:Nadaraya-Watson 核回归)

上部分介绍了框架下的注意力机制的主要成分: 查询(自主提示)和键(非自主提示)之间的交互形成了注意力汇聚; 注意力汇聚有选择地聚合了值(感官输入)以生成最终的输出。 本节将介绍注意力汇聚的更多细节, 以便从宏观上了解注意力机制在实践中的运作方式。 具体来说,1964年提出的Nadaraya-Watson核回归模型 是一个简单但完整的例子,可以用于演示具有注意力机制的机器学习。

import torch
from torch import nn
from d2l import torch as d2l

1 生成数据集

n_train = 50  # 训练样本数
x_train, _ = torch.sort(torch.rand(n_train) * 5)   # 排序后的训练样本

def f(x):
    return 2 * torch.sin(x) + x**0.8

y_train = f(x_train) + torch.normal(0.0, 0.5, (n_train,))  # 训练样本的输出
x_test = torch.arange(0, 5, 0.1)  # 测试样本
y_truth = f(x_test)  # 测试样本的真实输出
n_test = len(x_test)  # 测试样本数
n_test

输出

50

下面的函数将绘制所有的训练样本(样本由圆圈表示), 不带噪声项的真实数据生成函数 f f f(标记为“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);

2 平均汇聚

先使用最简单的估计器来解决回归问题。 基于平均汇聚来计算所有训练样本输出值的平均值:
f ( x ) = 1 n ∑ i = 1 n y i , f(x) = \frac{1}{n}\sum_{i=1}^n y_i, f(x)=n1i=1nyi,
如下图所示,这个估计器确实不够聪明。 真实函数 f f f(“Truth”)和预测函数(“Pred”)相差很大。

y_hat = torch.repeat_interleave(y_train.mean(), n_test)
plot_kernel_reg(y_hat)

输出:
在这里插入图片描述

3 非参数注意力汇聚

显然,平均汇聚忽略了输入 x i x_i xi。 于是Nadaraya和 Watson提出了一个更好的想法, 根据输入的位置对输出 y i y_i yi进行加权:

f ( x ) = ∑ i = 1 n K ( x − x i ) ∑ j = 1 n K ( x − x j ) y i ( 10.2.3 ) f(x) = \sum_{i=1}^n \frac{K(x - x_i)}{\sum_{j=1}^n K(x - x_j)} y_i \qquad(10.2.3) f(x)=i=1nj=1nK(xxj)K(xxi)yi(10.2.3)

其中 K K K是核(kernel)。 公式 (10.2.3)所描述的估计器被称为 Nadaraya-Watson核回归(Nadaraya-Watson kernel regression)。 这里不会深入讨论核函数的细节, 但受此启发, 我们可以从注意力机制框架的角度重写 (10.2.3), 成为一个更加通用的注意力汇聚(attention pooling)公式:
f ( x ) = ∑ i = 1 n α ( x , x i ) y i ( 10.2.4 ) f(x) = \sum_{i=1}^n \alpha(x, x_i) y_i \qquad(10.2.4) f(x)=i=1nα(x,xi)yi(10.2.4)
其中 x x x是查询, ( x i , y i ) (x_i, y_i) (xi,yi)是键值对。 比较 (10.2.4)和 (10.2.2), 注意力汇聚是 y i y_i yi的加权平均。 将查询 x x x和键 x i x_i xi之间的关系建模为 注意力权重(attention weight), 如 (10.2.4)所示, 这个权重将被分配给每一个对应值 y i y_i yi。 对于任何查询,模型在所有键值对注意力权重都是一个有效的概率分布: 它们是非负的,并且总和为1。

为了更好地理解注意力汇聚, 下面考虑一个高斯核(Gaussian kernel),其定义为:

K ( u ) = 1 2 π exp ⁡ ( − u 2 2 ) ( 10.2.5 ) K(u) = \frac{1}{\sqrt{2\pi}} \exp(-\frac{u^2}{2})\qquad (10.2.5) K(u)=2π 1exp(2u2)(10.2.5)

将高斯核代入 (10.2.4)和 (10.2.3)可以得到:

f ( x ) = ∑ i = 1 n α ( x , x i ) y i = ∑ i = 1 n exp ⁡ ( − 1 2 ( x − x i ) 2 ) ∑ j = 1 n exp ⁡ ( − 1 2 ( x − x j ) 2 ) y i = ∑ i = 1 n s o f t m a x ( − 1 2 ( x − x i ) 2 ) y i . ( 10.2.6 ) \begin{split}\begin{aligned} f(x) &=\sum_{i=1}^n \alpha(x, x_i) y_i\\ &= \sum_{i=1}^n \frac{\exp\left(-\frac{1}{2}(x - x_i)^2\right)}{\sum_{j=1}^n \exp\left(-\frac{1}{2}(x - x_j)^2\right)} y_i \\&= \sum_{i=1}^n \mathrm{softmax}\left(-\frac{1}{2}(x - x_i)^2\right) y_i. \end{aligned}\end{split} \qquad (10.2.6) f(x)=i=1nα(x,xi)yi=i=1nj=1nexp(21(xxj)2)exp(21(xxi)2)yi=i=1nsoftmax(21(xxi)2)yi.(10.2.6)

在 (10.2.6)中, 如果一个键 x i x_i xi越是接近给定的查询 x x x, 那么分配给这个键对应值 y i y_i yi的注意力权重就会越大, 也就“获得了更多的注意力”。

值得注意的是,Nadaraya-Watson核回归是一个非参数模型。 因此, (10.2.6)是 非参数的注意力汇聚(nonparametric attention pooling)模型。 接下来,我们将基于这个非参数的注意力汇聚模型来绘制预测结果。 从绘制的结果会发现新的模型预测线是平滑的,并且比平均汇聚的预测更接近真实。

# X_repeat的形状:(n_test,n_train),
# 每一行都包含着相同的测试输入(例如:同样的查询)
X_repeat = x_test.repeat_interleave(n_train).reshape((-1, n_train))
# x_train包含着键。attention_weights的形状:(n_test,n_train),
# 每一行都包含着要在给定的每个查询的值(y_train)之间分配的注意力权重
attention_weights = nn.functional.softmax(-(X_repeat - x_train)**2 / 2, dim=1)
# y_hat的每个元素都是值的加权平均值,其中的权重是注意力权重
y_hat = torch.matmul(attention_weights, y_train)
plot_kernel_reg(y_hat)

输出:
在这里插入图片描述
现在来观察注意力的权重。 这里测试数据的输入相当于查询,而训练数据的输入相当于键。 因为两个输入都是经过排序的,因此由观察可知“查询-键”对越接近, 注意力汇聚的注意力权重就越高。

d2l.show_heatmaps(attention_weights.unsqueeze(0).unsqueeze(0),
                  xlabel='Sorted training inputs',
                  ylabel='Sorted testing inputs')

输出
在这里插入图片描述

4 带参数注意力汇聚

非参数的Nadaraya-Watson核回归具有一致性(consistency)的优点: 如果有足够的数据,此模型会收敛到最优结果。 尽管如此,我们还是可以轻松地将可学习的参数集成到注意力汇聚中。

例如,与 (10.2.6)略有不同, 在下面的查询 x x x和键 x i x_i xi之间的距离乘以可学习参数 w w w

f ( x ) = ∑ i = 1 n α ( x , x i ) y i = ∑ i = 1 n exp ⁡ ( − 1 2 ( ( x − x i ) w ) 2 ) ∑ j = 1 n exp ⁡ ( − 1 2 ( ( x − x j ) w ) 2 ) y i = ∑ i = 1 n s o f t m a x ( − 1 2 ( ( x − x i ) w ) 2 ) y i . ( 10.2.7 ) \begin{split}\begin{aligned}f(x) &= \sum_{i=1}^n \alpha(x, x_i) y_i \\&= \sum_{i=1}^n \frac{\exp\left(-\frac{1}{2}((x - x_i)w)^2\right)}{\sum_{j=1}^n \exp\left(-\frac{1}{2}((x - x_j)w)^2\right)} y_i \\&= \sum_{i=1}^n \mathrm{softmax}\left(-\frac{1}{2}((x - x_i)w)^2\right) y_i.\end{aligned}\end{split} \qquad (10.2.7) f(x)=i=1nα(x,xi)yi=i=1nj=1nexp(21((xxj)w)2)exp(21((xxi)w)2)yi=i=1nsoftmax(21((xxi)w)2)yi.(10.2.7)
本节的余下部分将通过训练这个模型 (10.2.7)来学习注意力汇聚的参数。

4.1 批量矩阵乘法

为了更有效地计算小批量数据的注意力, 我们可以利用深度学习开发框架中提供的批量矩阵乘法。

假设第一个小批量数据包含 n n n个矩阵 X 1 , … , X n \mathbf{X}_1,\ldots, \mathbf{X}_n X1,,Xn, 形状为 a × b a\times b a×b, 第二个小批量包含 n n n个矩阵 Y 1 , … , Y n \mathbf{Y}_1, \ldots, \mathbf{Y}_n Y1,,Yn, 形状为 b × c b\times c b×c。 它们的批量矩阵乘法得到 n n n个矩阵 X 1 Y 1 , … , X n Y n \mathbf{X}_1\mathbf{Y}_1, \ldots, \mathbf{X}_n\mathbf{Y}_n X1Y1,,XnYn , 形状为 a × c a\times c a×c。 因此,假定两个张量的形状分别是 ( n , a , b ) (n,a,b) (n,a,b) ( n , b , c ) (n,b,c) (n,b,c), 它们的批量矩阵乘法输出的形状为 ( n , a , c ) (n,a,c) (n,a,c)

X = torch.ones((2, 1, 4))
Y = torch.ones((2, 4, 6))
torch.bmm(X, Y).shape

输出

torch.Size([2, 1, 6])

在注意力机制的背景中,我们可以使用小批量矩阵乘法来计算小批量数据中的加权平均值。

weights = torch.ones((2, 10)) * 0.1
values = torch.arange(20.0).reshape((2, 10))
torch.bmm(weights.unsqueeze(1), values.unsqueeze(-1))

输出

tensor([[[ 4.5000]],

        [[14.5000]]])

4.2 定义模型

基于 (10.2.7)中的 带参数的注意力汇聚,使用小批量矩阵乘法, 定义Nadaraya-Watson核回归的带参数版本为:

class NWKernelRegression(nn.Module):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.w = nn.Parameter(torch.rand((1,), requires_grad=True))

    def forward(self, queries, keys, values):
        # queries和attention_weights的形状为(查询个数,“键-值”对个数)
        queries = queries.repeat_interleave(keys.shape[1]).reshape((-1, keys.shape[1]))
        self.attention_weights = nn.functional.softmax(
            -((queries - keys) * self.w)**2 / 2, dim=1)
        # values的形状为(查询个数,“键-值”对个数)
        return torch.bmm(self.attention_weights.unsqueeze(1),
                         values.unsqueeze(-1)).reshape(-1)

4.3 训练

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

# X_tile的形状:(n_train,n_train),每一行都包含着相同的训练输入
X_tile = x_train.repeat((n_train, 1))
# Y_tile的形状:(n_train,n_train),每一行都包含着相同的训练输出
Y_tile = y_train.repeat((n_train, 1))
# keys的形状:('n_train','n_train'-1)
keys = X_tile[(1 - torch.eye(n_train)).type(torch.bool)].reshape((n_train, -1))
# values的形状:('n_train','n_train'-1)
values = Y_tile[(1 - torch.eye(n_train)).type(torch.bool)].reshape((n_train, -1))

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

net = NWKernelRegression()
loss = nn.MSELoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=0.5)
animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[1, 5])

for epoch in range(5):
    trainer.zero_grad()
    l = loss(net(x_train, keys, values), y_train)
    l.sum().backward()
    trainer.step()
    print(f'epoch {
      
      epoch + 1}, loss {
      
      float(l.sum()):.6f}')
    animator.add(epoch + 1, float(l.sum()))

输出:
在这里插入图片描述
如下所示,训练完带参数的注意力汇聚模型后可以发现: 在尝试拟合带噪声的训练数据时, 预测结果绘制的线不如之前非参数模型的平滑。

# keys的形状:(n_test,n_train),每一行包含着相同的训练输入(例如,相同的键)
keys = x_train.repeat((n_test, 1))
# value的形状:(n_test,n_train)
values = y_train.repeat((n_test, 1))
y_hat = net(x_test, keys, values).unsqueeze(1).detach()
plot_kernel_reg(y_hat)

输出
在这里插入图片描述
为什么新的模型更不平滑了呢? 下面看一下输出结果的绘制图: 与非参数的注意力汇聚模型相比, 带参数的模型加入可学习的参数后, 曲线在注意力权重较大的区域变得更不平滑。

d2l.show_heatmaps(net.attention_weights.unsqueeze(0).unsqueeze(0),
                  xlabel='Sorted training inputs',
                  ylabel='Sorted testing inputs')

输出
在这里插入图片描述

5 小结

  • Nadaraya-Watson核回归是具有注意力机制的机器学习范例。

  • Nadaraya-Watson核回归的注意力汇聚是对训练数据中输出的加权平均。从注意力的角度来看,分配给每个值的注意力权重取决于将值所对应的键和查询作为输入的函数。

  • 注意力汇聚可以分为非参数型和带参数型。

猜你喜欢

转载自blog.csdn.net/qq_52358603/article/details/128485768