0


注意力机制(含pytorch代码及各函数详解)

目录

注意力机制


不随意线索:不需要有想法,一眼就看到的东西

随意线索:想看书,所以去找了一本书

1.卷积、全连接、池化层都只考虑不随意线索

2.注意力机制则显示的考虑随意线索

  • 随意线索被称之为查询(query)
  • 每个输入是一个值(value)和不随意线索(key)的对
  • 通过注意力池化层来有偏向性的选择选择某些输入

与之前学习的所有层的区别在于加入了查询(query),根据查询,寻找自己表较感兴趣的东西

非参注意力汇聚概述(不需要学习参数)

在这里插入图片描述

通过K函数(核回归)计算x和xi的距离
在这里插入图片描述

参数化注意力机制概述

在之前的基础上引入可以学习的w
在这里插入图片描述

正式系统学习

1.平均汇聚(池化)

我们先使用最简单的估计器来解决回归问题: 基于平均汇聚来计算所有训练样本输出值的平均值:
在这里插入图片描述

import matplotlib.pyplot as plt
import torch
from torch import nn
from d2l import torch as d2l

n_train =50#torch.rand()产生一个服从均匀分布的张量,张量内的数据包含从区间[0,1)的随机数。参数size是一个整数序列,用于定义张量大小#torch.sort()返回两个值 第一个为排序后的张量,第二个为原来的索引
x_train, _ = torch.sort(torch.rand(n_train)*5)# *5表示将[0,1)扩展到[0,5)deff(x):#真实的f(x)  需要被拟合return2* torch.sin(x)+ x **0.8

y_train = f(x_train)+ torch.normal(0,0.5,(n_train,))#测试集
x_test = torch.arange(0,5,0.1)
y_truth = f(x_test)
n_test =len(x_test)defplot_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)
    plt.show()

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

运行结果如下图所示,这个估计器确效果一般: 真实函数f(“Truth”)和预测函数(“Pred”)相差很大。

在这里插入图片描述

2.非参数注意力汇聚(池化)

平均汇聚忽略了输入xi,我们可以根据输入的位置对输出yi进行加权

在这里插入图片描述

其中K是核,上述公式所描述的估计器被称为Nadaraya-Watson核回归,我们可以从注意力机制框架的角度重写,使之成为一个更加通用的注意力汇聚(attention pooling)公式:
在这里插入图片描述

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

高斯核:
在这里插入图片描述

将高斯核带入上面两个式子中得到:

在这里插入图片描述

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

torch.repeat_interleave()对tensor的特定维度进行复制

torch.repeat_interleave(self: Tensor, repeats:int, dim: Optional[int]=None)

参数说明:

self: 传入的数据为tensor

repeats: 复制的份数

dim: 要复制的维度,可设定为0/1/2…

例:

此处定义了一个4维tensor,要对第2个维度复制,由原来的1变为3,即将设定dim=1

    data1 = torch.rand([2,1,3,3])print("data1_shape: ", data1.shape)print("data1: ", data1)

    data2 = torch.repeat_interleave(data1, repeats=3, dim=1)print("data2_shape: ", data2.shape)print("data2: ", data2)

torch.matmul() 支持不同维度tensor进行矩阵乘积(特殊情况会转化为点乘)

torch.matmul(input, other, out=None) → Tensor

torch.matmul()也是一种类似于矩阵相乘操作的tensor联乘操作。但是它可以利用python 中的广播机制,处理一些维度不同的tensor结构进行相乘操作。这也是该函数与torch.bmm()区别所在。

参数:

input,other:两个要进行操作的tensor结构

(1)若两个都是1D(向量)的,则返回两个向量的点积

(2)若两个都是2D(矩阵)的,则按照(矩阵相乘)规则返回2D

(3)若input维度1D,other维度2D,则先将1D的维度扩充到2D(1D的维数前面+1),然后得到结果后再将此维度去掉,得到的与input的维度相同。即使作扩充(广播)处理,input的维度也要和other维度做对应关系。

(4)若input是2D,other是1D,则返回两者的点积结果(可以理解为先对other进行广播,或者other与input的每一行进行点积)。

a = torch.tensor([[1,2,3],[4,5,6],[7,8,9]])
b = torch.tensor([1,1,1])
c = torch.matmul(a, b)#tensor([ 6, 15, 24])

(5)如果一个维度至少是1D,另外一个大于2D,则返回的是一个批矩阵乘法( a batched matrix multiply)。

代码实现:

#X_repeat的形状为(n_test, n_train) 同一行的所有元素(测试输入,查询)都相同 每一行的结果为一个点
X_repeat = x_test.repeat_interleave(n_train).reshape((-1, n_train))#重复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_train = y_train.reshape(-1, 1)  #这一步可以不用做 matmul可以实现不同维度的矩阵成绩 但如果使用mm函数进行矩阵乘积必须转化为一列
y_hat = torch.matmul(attention_weights, y_train)
plot_kernel_reg(y_hat)

在这里插入图片描述

3.带参数注意力汇聚

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

与上一节的例子不同,下式的查询x和键xi之间的距离乘以可学习参数W:

在这里插入图片描述

torch.bmm():批量中进行矩阵乘法

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

在这里插入图片描述

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

torch.squeeze()

这个函数主要对数据的维度进行压缩,去掉维数为1的的维度,比如是一行或者一列(只有维度为1时才会去掉)

torch.unsqueeze()

这个函数主要是对数据维度进行扩充。给指定位置加上维数为一的维度

a = torch.arange(0,6).view(2,3)#(2, 3)print(a.shape)#在第二个维度增加一个维度变为(2, 1, 3)
b = a.unsqueeze(1)#a保持不变,将改变赋予bprint(b.shape)#将b的第二个维度删去变为(2, 3)
c = b.squeeze()print(c.shape)#torch.Size([2, 3])#torch.Size([2, 1, 3])#torch.Size([2, 3])

模型构建:

classNWKernelRegression(nn.Module):def__init__(self,**kwargs):super(NWKernelRegression, self).__init__()
        self.w = nn.Parameter(torch.rand((1,), requires_grad=True))defforward(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#dim=1表示按行计算)return torch.bmm(self.attention_weights.unsqueeze(1),
                         values.unsqueeze(-1)).reshape(-1)#并将结果变成一维

torch.nn.parameter.Parameter(data=None, requires_grad=True)

参数:

  • data (Tensor) – parameter tensor.
  • requires_grad (bool*,* optional) – if the parameter requires gradient. See Locally disabling gradient computation for more details. Default: True

torch.nn.Parameter是继承自torch.Tensor的子类,其主要作用是作为nn.Module中的可训练参数使用。它与torch.Tensor的区别就是nn.Parameter会自动被认为是module的可训练参数,即加入到parameter()这个迭代器中去;而module中非nn.Parameter()的普通tensor是不在parameter中的。
注意到,nn.Parameter的对象的requires_grad属性的默认值是True,即是可被训练的,这与torth.Tensor对象的默认值相反。
在nn.Module类中,pytorch也是使用nn.Parameter来对每一个module的参数进行初始化的

训练:

net = NWKernelRegression()
loss = nn.MSELoss(reduction='none')
optimizer = torch.optim.SGD(net.parameters(), lr=0.5)
animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[1,5])for epoch inrange(5):
    optimizer.zero_grad()#梯度清零
    l = loss(net(x_train, keys, values), y_train)#计算损失
    l.sum().backward()#将损失之和进行反向传播
    optimizer.step()#更新print(f'epoch {epoch +1}, loss {float(l.sum()):.6f}')
    animator.add(epoch +1,float(l.sum()))

plt.show()# 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)

在这里插入图片描述

在尝试拟合带噪声的训练数据时, 预测结果绘制的线不如之前非参数模型的平滑。

在这里插入图片描述

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


本文转载自: https://blog.csdn.net/gary101818/article/details/122729545
版权归原作者 Unstoppable~~~ 所有, 如有侵权,请联系我们删除。

“注意力机制(含pytorch代码及各函数详解)”的评论:

还没有评论