深度学习笔记36 注意力机制


这里其实有些跳课,最近看沐神看得比较少,在做毕业设计,但是因为要用到注意力机制了,所以抓紧时间过来先学这个,不按顺序学习是对我这个处女座最大的折磨(哭哭)

我在毕业设计中遇到了一个问题,对于HSV三个特征,我发现HS的贡献较小,V的贡献很大,所以我希望在模型训练中强化V所发挥的作用,因此我的第一个想法是:

在数据输入模型之前,要做归一化,比如HSV的范围为0-180,0-255,0-255,那我就全部除以最大值,将所有特征都局限到0-1,这样大家的影响都是一样的,而如果我想强化V的影响,我可以在归一化之后,对V额外乘以一个数字,比如乘以3,具体乘以多少,那就是调参的任务了。

然而,在和鑫哥交流之后,鑫哥告诉我这个类似注意力机制的思想,所以就过来先学习一下注意力机制。

早在60年代就有类似的概念:

拟合一组数据最简单的方法——平均。

K,在这里可以理解成一个核,就是一个计算距离的函数,用来计算x和xi之间的距离。这样的话,虽然仍然是对y求平均,但是有了一个权重(注意分母有求和,所以仍然是求平均),我们会重点考虑距离query更近的数据,而基本忽视其他距离此值很远的数据,从而达到“让注意力集中在某一个区域”的目的。

这个方案我非常喜欢,因为简单——不需要学习参数,直接就实现了注意力。

他用的核函数是高斯核:

当然这个是统计学上的注意力机制,没有可以学习的参数,所以我们可以加入一个w:

 随意线索里面的随意,可以翻译为随着意识的意思(翻译学家背锅)

代码实验

 1 n_train = 50  # 训练样本数
 2 x_train, _ = torch.sort(torch.rand(n_train) * 5)   # 排序后的训练样本
 3 
 4 def f(x):
 5     return 2 * torch.sin(x) + x**0.8
 6 
 7 y_train = f(x_train) + torch.normal(0.0, 0.5, (n_train,))  # 训练样本的输出
 8 x_test = torch.arange(0, 5, 0.1)  # 测试样本
 9 y_truth = f(x_test)  # 测试样本的真实输出
10 n_test = len(x_test)  # 测试样本数
11 n_test

定义了一个f(x),用来测试加入注意力机制的效果。

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

# 首先是平均池化层
y_hat = torch.repeat_interleave(y_train.mean(), n_test)
plot_kernel_reg(y_hat)

结果:

 自然是完全不拟合。

接下来我们加入前面提到的非参注意力机制:

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

 可以看到,拟合曲线还是有那么些意思的,但是因为这是统计学的模型,没有可学习的参数,当然没有机器学习的精度。

我们可以看一下注意力机制的权重:

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

 其实就是查看训练数据和测试数据的相关性,相关性高的地方权重值就高,因此可以更好地拟合曲线。

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

我们可以把这个模型改为带权重的小批量矩阵乘法的注意力池化层

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

 可以看到效果会更好一些。

我的问题

 但是正如我开始提到的问题,我想让模型对于HSV三个特征中更加注重V,这个和沐神讲的注意力机制我觉得是没什么关系的。

注意力机制,指的是给模型一种注意力机制,让模型根据test数据,更加注重于对某些训练数据的使用。我们对于模型的干预仅仅是加入了注意力机制,具体要他注意什么,我们不知道,这是模型要自己学习出来的问题。

而我的问题,在于我知道应该让模型注重什么数据,而不需要它自己学,所以不需要动网络结构,归一化之后乘以一个值来扩大权重,我觉得是最轻松的方法。