一文看懂,注意力机制到底是什么

目录

前言

引入

基本实现

平均池化

生成数据集

平均池化方法

代码实现

运行结果 

非参数注意力池化 

介绍

注意力机制中的非参注意力池化

说明

代码实现

 运行结果

带参数注意力池化

介绍

代码实现

定义模型

训练

运行结果 

损失函数

拟合曲线

注意力热图 


前言

学习本节知识后,相信你可以对注意力机制有基本的了解和实现的能力。

本节学习时间大概需要一整个下午加上半个晚上,请劳逸结合,合理规划时间,如果你对d2l内置库不够了解,或对深度学习的基础知识不够了解,你可能需要更多的时间。

引入

在深度学习中,我们常常使用“注意力机制”进行学习,那么注意力机制到底是什么呢?在生物学上,注意力是指我们在观察图像或视频时将观察重点放在什么地方。在视觉中,人类基于非自主性提示自主性提示 有选择地引导注意力的焦点。非自主性提示是基于环境中物体的突出性和易见性。 自主性提示则是受到了认知和意识的主观上的控制产生的。在深度学习中,也可以使用类似的方法来使机器将注意力集中到某处。

非自主性提示
自主性提示

基本实现

首先,要实现非自主性提示,可以简单地使用参数化的全连接层, 甚至是非参数化的最大汇聚层或平均汇聚层。而要使用自主性提示,则是要给定一个“查询”(Query),通过注意力池化(Attention Pooling),与键(Key)进行匹配,从而对应相应的值(Value)。

理解基本原理后,我们开始实现注意力机制。注意力池化本质上就是平均池化层加上权重进行池化的过程。 下面首先建立一个10x10对角矩阵,对角线的值为1,其他为0。具体实现较为复杂,使用imshow()来绘制,画出矩阵对应热图,只需要知道输入matrices的形状是 (要显示的行数,要显示的列数,查询的数目,键的数目)即可。

from mxnet import np, npx
from d2l import mxnet as d2l

npx.set_np()
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.asnumpy(), 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 = np.eye(10).reshape((1, 1, 10, 10))
show_heatmaps(attention_weights, xlabel='Keys', ylabel='Queries')

平均池化

生成数据集

下面代码采用平均汇聚的方法,其数据计算方式为y=2sinx+x^{0.8}+\epsilon,生成五十个数据作为训练数据集,训练后进行测试

平均池化方法

平均池化使用f(x)=\frac{1}{n}\sum_{i=1}^{n}y_i进行训练。我们当然知道,这样的“训练”其实是十分无效的,但是通过平均池化,可以模拟一种,将注意力放在全局(平均注意力)的方法,产生的状况。

代码实现

from mxnet import autograd, gluon, np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l

npx.set_np()
n_train = 50 
x_train = np.sort(np.random.rand(n_train) * 5) 
def f(x):
    return 2 * np.sin(x) + x**0.8

y_train = f(x_train) + np.random.normal(0.0, 0.5, (n_train,))  # 训练样本的输出
x_test = np.arange(0, 5, 0.1) 
y_truth = f(x_test) 
n_test = len(x_test) 
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);
y_hat = y_train.mean().repeat(n_test)
plot_kernel_reg(y_hat)

运行结果 

产生这样的一种预测显然也是在我们预料之内的。

非参数注意力池化 

介绍

非参数注意力池化,也称之为Nadaraya-Watson核回归,它提出根据输入的位置对输出yi进行加权。Nadaraya-Watson核回归的定义是这样的:

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

注意力机制中的非参注意力池化

其中K是(kernel)。这里不会深入讨论核函数的细节, 但受此启发, 我们可以从注意力机制框架的角度重写本式,使之成为一个更加通用的注意力池化(attention pooling)公式:f(x)= \sum_{i=1}^{n} \alpha (x,x_i)y_i

其中x是查询,(x_i,y_i)是键值对,注意力汇聚是y_i的加权平均。将查询x和键xi之间的关系建模为注意力权重(attention weight)\alpha (x,x_i),这个权重将被分配给每一个对应值y_i。对于任何查询,模型在所有键值对注意力权重都是一个有效的概率分布: 它们是非负的,并且总和为1。

相信对深度学习有一些了解的人此刻就会想到Softmax回归。我们考虑一个高斯核(Gaussian kernel),定义为:K(u)=\frac{1}{\sqrt{2 \pi}}e^{-\frac{u^2}{2}}

代入注意力计算公式可以得出,f(x)=\Sigma _{i=1}^{n}softmax(-\frac{1}{2}(x-x_i)^2)y_i。由此,我们得到了我们所熟悉的softmax运算。此时,如果一个键xi越是接近给定的查询x, 那么分配给这个键对应值yi的注意力权重就会越大, 也就“获得了更多的注意力”。

说明

可以发现,在上述公式中已经有了“注意力”的存在,但并不需要进行任何参数的学习,而是直接进行计算,因此这种注意力机制称之为非参数注意力池化。

代码实现

分为两个片段,分别为预测结果展示和注意力热图展示。

X_repeat = x_test.repeat(n_train).reshape((-1, n_train))
attention_weights = npx.softmax(-(X_repeat - x_train)**2 / 2)
y_hat = np.dot(attention_weights, y_train)
plot_kernel_reg(y_hat)
d2l.show_heatmaps(np.expand_dims(np.expand_dims(attention_weights, 0), 0),xlabel='Sorted training inputs',ylabel='Sorted testing inputs')

 运行结果

显然,结果有了一定效果,而根据热图可以看出,注意力的位置基本与y=x一致,这也是符合我们的预期的。

带参数注意力池化

介绍

非参数的Nadaraya-Watson核回归具有一致性(consistency)的优点: 如果有足够的数据,此模型会收敛到最优结果。 尽管如此,我们还是可以轻松地将可学习的参数集成到注意力池化中。相对于Nadaraya-Watson核回归,这只是多一个权重参数而已。它的计算是这样的:

f(x)= \Sigma _{i=1}^{n}softmax(-\frac{1}{2}(x-x_i)^2w)y_i

代码实现

定义模型

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

class NWKernelRegression(nn.Block):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.w = self.params.get('w', shape=(1,))
    def forward(self, queries, keys, values):
        queries = queries.repeat(keys.shape[1]).reshape((-1, keys.shape[1]))
        self.attention_weights = npx.softmax(
            -((queries - keys) * self.w.data())**2 / 2)
        return npx.batch_dot(np.expand_dims(self.attention_weights, 1),
                             np.expand_dims(values, -1)).reshape(-1)

训练

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

# X_tile的形状:(n_train,n_train),每一行都包含着相同的训练输入
X_tile = np.tile(x_train, (n_train, 1))
# Y_tile的形状:(n_train,n_train),每一行都包含着相同的训练输出
Y_tile = np.tile(y_train, (n_train, 1))
# keys的形状:('n_train','n_train'-1)
keys = X_tile[(1 - np.eye(n_train)).astype('bool')].reshape((n_train, -1))
# values的形状:('n_train','n_train'-1)
values = Y_tile[(1 - np.eye(n_train)).astype('bool')].reshape((n_train, -1))
net = NWKernelRegression()
net.initialize()
loss = gluon.loss.L2Loss()
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.5})
animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[1, 5])

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

运行结果 

损失函数

拟合曲线

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

注意力热图 

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值