【pytorch】注意力机制-1

1 注意力提示

1.1 自主性的与非自主性的注意力提示

非自主性提示:

可以简单地使用参数化的全连接层,甚至是非参数化的最大汇聚层或平均汇聚层。
自主性提示

注意力机制与全连接层或汇聚层区别开来。在注意力机制的背景下,自主性提示被称为查询(query)。给定任何查询,注意力机制通过注意力汇聚(attention pooling)将选择引导至感官输入(sensory inputs,例如中间特征表示)。在注意力机制中,这些感官输入被称为值(value)。

注意力机制通过注意力汇聚将查询(自主性提示)和键(非自主性提示)结合在一起,实现对值(感官输入)的选择倾向

1.2 注意力的可视化

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

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

python 复制代码
#@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。

python 复制代码
attention_weights = torch.eye(10).reshape((1, 1, 10, 10))
show_heatmaps(attention_weights, xlabel='Keys', ylabel='Queries')

2 注意力汇聚:Nadaraya-Watson 核回归

注意力机制的主要成分:查询(自主提示)和键(非自主提示)之间的交互形成了注意力汇聚;注意力汇聚有选择地聚合了值(感官输入)以生成最终的输出。

考虑下面这个回归问题:给定的成对的"输入-输出"数据集 {(x1, y1), . . . ,(xn, yn)},如何学习f来预测任意新输入x的输出yˆ = f(x)?

根据下面的非线性函数生成一个人工数据集,其中加入的噪声项为ϵ:

其中ϵ服从均值为0和标准差为0.5的正态分布。在这里生成了50个训练样本和50个测试样本。为了更好地可视化之后的注意力模式,需要将训练样本进行排序。

python 复制代码
n_train = 50  # 训练样本数
x_train, _ = torch.sort(torch.rand(n_train) * 5)   # 排序后的训练样本
python 复制代码
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

下面的函数将绘制所有的训练样本(样本由圆圈表示),不带噪声项的真实数据生成函数f(标记为"Truth"),以及学习得到的预测函数(标记为"Pred")。

python 复制代码
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.1 平均汇聚

基于平均汇聚来计算所有训练样本输出值的平均值:

python 复制代码
y_hat = torch.repeat_interleave(y_train.mean(), n_test)
plot_kernel_reg(y_hat)

显然, 真实函数𝑓("Truth")和预测函数("Pred")相差很大。

2.2 非参数注意力汇聚

平均汇聚忽略了输入xi,于是根据输入的位置对输出yi进行加权:

K是核(kernel)。所描述的估计器被称为 Nadaraya-Watson核回归。

受此启发,我们可以从注意力机制框架的角度重写,成为一个更加通用的注意力汇聚(attention pooling)公式:

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

考虑一个高斯核(Gaussian kernel),其定义为:

将高斯核代入可以得到:

在上面的表达式中,如果一个键xi越是接近给定的查询x,那么分配给这个键对应值yi的注意力权重就会越大,也就"获得了更多的注意力"。

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

python 复制代码
# 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)

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

python 复制代码
d2l.show_heatmaps(attention_weights.unsqueeze(0).unsqueeze(0),
                  xlabel='Sorted training inputs',
                  ylabel='Sorted testing inputs')

2.3 带参数注意力汇聚

在下面的查询x和键xi之间的距离乘以可学习参数w:

接下来训练上面这个模型来学习注意力汇聚的参数w。

批量矩阵乘法

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

假设第一个小批量数据包含n个矩阵X1, . . . , Xn,形状为a × b,第二个小批量包含n个矩阵Y1, . . . , Yn,形状为b × c。它们的批量矩阵乘法得到n个矩阵 X1Y1, . . . , XnYn,形状为a × c。因此,假定两个张量的形状分别是(n, a, b)和(n, b, c),它们的批量矩阵乘法输出的形状为(n, a, c)。

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

定义模型

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

python 复制代码
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)
训练

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

python 复制代码
# 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))

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

python 复制代码
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()))

训练完带参数的注意力汇聚模型后可以发现: 尝试拟合带噪声的训练数据,

python 复制代码
# 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)

预测结果绘制的线不如之前非参数模型的平滑。为什么新的模型更不平滑了呢? 下面看一下输出结果的绘制图:

python 复制代码
d2l.show_heatmaps(net.attention_weights.unsqueeze(0).unsqueeze(0),
                  xlabel='Sorted training inputs',
                  ylabel='Sorted testing inputs')

与非参数的注意力汇聚模型相比, 带参数的模型加入可学习的参数后, 曲线在注意力权重较大的区域变得更不平滑。

相关推荐
美狐美颜sdk2 小时前
直播美颜工具架构设计与性能优化实战:美颜SDK集成与实时处理
深度学习·美颜sdk·第三方美颜sdk·视频美颜sdk·美颜api
Fansv5873 小时前
深度学习-6.用于计算机视觉的深度学习
人工智能·深度学习·计算机视觉
deephub4 小时前
LLM高效推理:KV缓存与分页注意力机制深度解析
人工智能·深度学习·语言模型
奋斗的袍子0074 小时前
Spring AI + Ollama 实现调用DeepSeek-R1模型API
人工智能·spring boot·深度学习·spring·springai·deepseek
青衫弦语4 小时前
【论文精读】VLM-AD:通过视觉-语言模型监督实现端到端自动驾驶
人工智能·深度学习·语言模型·自然语言处理·自动驾驶
美狐美颜sdk5 小时前
直播美颜SDK的底层技术解析:图像处理与深度学习的结合
图像处理·人工智能·深度学习·直播美颜sdk·视频美颜sdk·美颜api·滤镜sdk
WHATEVER_LEO5 小时前
【每日论文】Text-guided Sparse Voxel Pruning for Efficient 3D Visual Grounding
人工智能·深度学习·神经网络·算法·机器学习·自然语言处理
Binary Oracle5 小时前
RNN中远距离时间步梯度消失问题及解决办法
人工智能·rnn·深度学习
阿_旭5 小时前
基于YOLO11深度学习的糖尿病视网膜病变检测与诊断系统【python源码+Pyqt5界面+数据集+训练代码】
人工智能·python·深度学习·视网膜病变检测
小宇爱6 小时前
38、深度学习-自学之路-自己搭建深度学习框架-3、自动梯度计算改进
人工智能·深度学习·自然语言处理