1、算法流程
1.1 将序列按给定窗口大小和滑动步长进行划分;
1.2 对每个窗口计算自注意力,
首先是计算窗口内每个元素的查询Q、键K和值V,这一步是使用一个线性层实现的,底层是矩阵乘法,就是将特征向量分别与三个权重矩阵相乘,权重矩阵的值通过反向传播获得,输入是特征向量的维度,输出是注意力模块输出的元素的维度。
其次是Q乘以K的转置计算窗口内的注意力分数,还可以加入一个相对位置编码的偏置因子,用于捕获顺序信息,如果序列存在填充,还可以加入一个掩码矩阵,掩码矩阵的形状和注意力分数矩阵的形状一样,在窗口滑动到具有填充元素的位置时,将掩码矩阵中对应填充元素的位置置为一个很大的负值,并将这个掩码矩阵加到注意力分数矩阵上,经过softmax归一化计算后,对应填充元素的得分将变为0,即该填充元素就不会对其它元素产生影响。
然后,将注意力分数矩阵与V相乘,得到每个窗口的输出,
最后,如果为了保持输出序列的长度和输入序列的长度一致,可以将每个窗口的输出进行拼接,可以对相邻窗口重叠的元素采用池化或加权平均的方式。当然,通过使用不同的滑动步长,也可以输出不同的序列长度,类似于卷积。
以上就是真个滑动窗口注意力机制的计算过程。
2、例子
如果大小为3的窗口内最后一个元素是填充的,那么在计算自注意力时,我们希望模型忽略这个填充元素。在这种情况下,掩码矩阵应该被设计为屏蔽最后一个元素,即在掩码矩阵中将最后一个元素对应的位置设置为一个非常大的负数(例如,-1e9),而其他位置设置为0。
对于一个大小为3的窗口,如果最后一个元素是填充的,掩码矩阵应该是:
[[0, 0, -1e9],
[0, 0, -1e9],
[0, 0, -1e9]]
这个掩码矩阵表示窗口内的前两个元素可以相互关注,但它们都不关注最后一个填充元素,最后一个元素也不关注任何元素。
在实际的自注意力计算中,这个掩码矩阵会被加到注意力分数矩阵上,使得最后一个元素对应的分数变成一个非常大的负数,经过Softmax归一化后,这个元素的权重会接近于0,从而在加权聚合Value时被忽略。
代码示例
以下是一个简单的Python示例,展示如何在PyTorch中实现这个掩码矩阵:
import torch
import torch.nn.functional as F
# 假设窗口大小为3,最后一个元素是填充的
window_size = 3
window = torch.tensor([1, 2, 0]) # 假设0表示填充元素
# 生成Query、Key和Value
Q = window
K = window
V = window
# 计算注意力得分
attention_scores = torch.matmul(Q.view(-1, 1), K.view(1, -1))
# 生成掩码矩阵
attention_mask = torch.zeros((window_size, window_size))
attention_mask[:, -1] = -1e9 # 屏蔽最后一个元素
# 应用掩码矩阵
masked_attention_scores = attention_scores + attention_mask
# Softmax归一化
attention_weights = F.softmax(masked_attention_scores, dim=-1)
# 加权聚合Value
window_output = torch.matmul(attention_weights, V)
print(window_output)
在这个示例中,我们首先计算了窗口内元素的注意力得分,然后生成了一个掩码矩阵,将最后一个元素对应的位置设置为-1e9,最后将掩码矩阵加到注意力得分上,使得最后一个元素在自注意力计算中被忽略。这个例子没有涉及窗口滑动和窗口输出拼接,这两个都简单。
3、核心基于卷积的区别
transformer是先获得一个序列,这个序列是具有很强的上下文信息的,然后对这个序列采用窗口自注意力机制,能够很好的捕捉上下文信息,而卷积没有序列化这一步,所以捕捉的上下文信息没有注意力机制捕捉的准确,比如卷积窗口内的不同元素虽然属于同一个窗口,但是有可能位置相差很远(如:车辆的边界位置和后面的墙),但是如果先把数据序列化后,上下文的位置关系就很紧密了,我认为,transformer比卷积强大主要就在这个方面,卷积是学习卷积核参数和注意力机制是学习三个权重矩阵参数,这两个在效果上的区别我还没有找到。