11-注意力机制

注意力

        由人类的注意力得到启发,将更多的关注放在更重要的地方,而忽视其他的无关信息。在神经网络中,注意力可以认为是权重,权重越大,代表需要投入更多的关注。最开始attention在CV领域中被提出,通过对全局的扫描,获取需要重点关注的区域,然后对这一区域投入更多的资源,获取更多与目标有关的细节信息,而忽视其他无关信息。通过这种机制可以利用有限的注意力资源从大量信息中快速筛选出高价值的信息。

查询、键、值

        如下图所示,在注意力机制下,将自主性提示称为查询(Query),给定任何查询,注意力机制通过注意力汇聚(attention pooling) 将选择引导至感官输入,在注意力机制中,这些感官输入被称为值(value)。即在给定一个Q的情况下,选择一个key并输出其对应的value,这里的key和value是一一对应的关系(K,V)。

注意力可视化 

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

import torch
from d2l import torch as d2l
#matrices的形状是 (要显示的行数,要显示的列数,Q的数量,K的数量)。
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)

attention_weights = torch.eye(10).reshape((1, 1, 10, 10))#torch.eye返回一个2维张量,对角线位置全1,其它位置全0
show_heatmaps(attention_weights, xlabel='Keys', ylabel='Queries')

在本例子中,仅当查询和键相同时,注意力权重为1,否则为0。可视化查询和键之间的注意力权重结果如下:

 

Nadaraya-Watson 核回归 

         给定的成对的“输入-输出”数据集{(x1,y1),…,(xn,yn)},如何学习f来预测任意新输入x的输出y^=f(x)?根据下面的非线性函数生成一个人工数据集, 其中加入的噪声项为ϵ(服从均值为0和标准差为0.5的正态分布):

y_i = 2\sin(x_i) + x_i^{0.8} + \epsilon

        即真实的f(x)函数为:2\sin(x_i) + x_i^{0.8}

        使用最简单的估计器来解决回归问题: 基于平均汇聚来计算所有训练样本输出值的平均值,即

f(x) = \frac{1}{n}\sum_{i=1}^n y_i,

        结果如下图所示,可以看出简单的使用训练样本输出值的平均值来进行预测的效果很差,这是因为没有对输入的Xi进行考虑。 

         Nadaraya和Watson提出,根据输入的位置对输出yi进行加权,其中K是(kernel):

f(x) = \sum_{i=1}^n \frac{K(x - x_i)}{\sum_{j=1}^n K(x - x_j)} y_i, 

        该公式所描述的估计器被称为Nadaraya-Watson核回归,这里对该公式进行拓展,得到更加通用的注意力汇聚(attention pooling)公式:

f(x) = \sum_{i=1}^n \alpha(x, x_i) y_i,

        其中x是查询,(xi,yi)是键值对,可以看出之前的注意力汇聚是yi的加权平均。将查询x和键xi之间的关系建模为注意力权重(attention weight) \alpha(x, x_i)。对于任何查询,模型在所有键值对注意力权重都是一个有效的概率分布: 它们是非负的,并且总和为1。这里使用高斯核,并带入上述公式有:

        如果一个键xi越是接近给定的查询x, 那么分配给这个键对应值yi的注意力权重就会越大, 也就“获得了更多的注意力”。(其实就是通过训练中的y来对现在输入的xi的yi进行预测,预测的方式就是看现在输入的xi与之前训练过的xj之间的相似度,这个相似度就是yj对yi值影响的一个权重。)

        值得注意的是,Nadaraya-Watson核回归是一个非参数模型。接下来,将基于这个非参数的注意力汇聚模型来绘制预测结果如下。

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

带参数注意力汇聚 

         非参数的Nadaraya-Watson核回归具有一致性(consistency)的优点: 如果有足够的数据,此模型会收敛到最优结果。这里在查询x和键xi之间的距离加上一个可学习参数w:

        基于小批量矩阵乘法来实现带参数的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)

# 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')

运行结果如下,可以看出新模型的更加不光滑,是因为带参数的模型加入可学习的参数后,在注意力权重较大的区域变得更不平滑

 

        这里将学习到的w进行输出查看,发现其是个标量。所以我们将非参数注意力汇聚添加一个23的权重,可以发现这样的话拟合效果就更好了,但是也变成了和参数注意力汇聚一样尖锐的加权区域。观察上述公式发现,w​加在(x-x_i)​外,平方之内,乘以一个-1/2​​​之后就相当于绝对值扩大了230倍左右,我们知道softmax函数在x趋向负无穷的时候值无限趋近于0,所以这样一来就保留了键和查询之间的差距足够小的pair,过滤掉了键和值差距较大的pair,从而达到注意力效果,使得预测结果更加准确。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值