深度学习之注意力机制详解
- 前言
- 一、自注意力机制(self-Attention)
- 二、代码
前言
深度学习attention机制是对人类视觉注意力机制的仿生,本质上是一种资源分配机制。生理原理就是人类视觉注意力能够以高分辨率接收于图片上的某个区域,并且以低分辨率感知其周边区域,并且视点能够随着时间而改变。换而言之,就是人眼通过快速扫描全局图像,找到需要关注的目标区域,然后对这个区域分配更多注意,目的在于获取更多细节信息和抑制其他无用信息。提高 representation 的高效性。
在神经网络中,attention机制可以它认为是一种资源分配的机制,可以理解为对于原本平均分配的资源根据attention对象的重要程度重新分配资源,重要的单位就多分一点,不重要或者不好的单位就少分一点,在深度神经网络的结构设计中,attention所要分配的资源基本上就是权重了。
图 2 红色代表高注意力,蓝色代表低注意力
视觉注意力分为几种,核心思想是基于原有的数据找到其之间的关联性,然后突出其某些重要特征,例如处理检测任务时,我们可以让注意力集中在建筑物物体上,从而提高识别效率,有通道注意力,像素注意力,多阶注意力等
一、自注意力机制(self-Attention)
**那么如何实现注意力机制呢?**方差描述的是单个随机变量与其均值之间的偏差,而协方差描述的是两个随机变量之间的相似性。如果两个随机变量的分布相似,它们的协方差很大。否则,它们的协方差很小。如果我们将feature map中的每个像素作为一个随机变量,计算所有像素之间的配对协方差,我们可以根据每个预测像素在图像中与其他像素之间的相似性来增强或减弱每个预测像素的值在训练和预测时使用相似的像素,忽略不相似的像素。这种机制叫做自注意力
具体实施细节:
Self-Attention可以理解将队列和一组值与输入对应,即形成querry,key,value向output的映射,output可以看作是value的加权求和,加权值则是由Self-Attention来得出的。在self-attention中,有3个不同的向量,它们分别是Query向量,Key向量和Value向量,长度相同。它们是通过3个不同的权值矩阵由input X乘以三个不同的权值矩阵得到,其中三个矩阵的尺寸也是相同的,过程图如下:
首先输入高度为H、宽度为w的特征图x,然后将X reshape为三个一维向量A、B和c,将A和B相乘得到大小为HWxHW的协方差矩阵。最后,我们用协方差矩阵和C相乘,得到D并对它reshape,得到输出特征图Y,并从输入X进行残差连接。这里D中的每一项都是输入X的加权和,权重是像素和彼此之间的协方差。
上述为大致流程,具体实现过程如下:
- 将输入特征图经过三个不同的1x1卷积,得到q(query),k(key),v(value)三个向量;
- 将q,k,v三个向量reshape为二维矩阵;
- 将q向量与k的转置矩阵相乘,经过softmax得到权重系数A;
- 为了梯度的稳定,Transformer使用了score归一化,即除以根号c;
- 将矩阵A乘以v,得到加权的每个输入向量的评分y
- 将y向量reshape为三维特征向量;
- 相加之后得到最终的输出结果z。
其中
Z输出特征图
F(X)为残差映射
A为权重矩阵
二、代码
class BAM(nn.Module):
""" Basic self-attention module
"""
def __init__(self, in_dim, ds=8, activation=nn.ReLU):
super(BAM, self).__init__()
self.chanel_in = in_dim
self.key_channel = self.chanel_in //8
self.activation = activation
self.ds = ds #
self.pool = nn.AvgPool2d(self.ds)
#print('ds: ',ds)
self.query_conv = nn.Conv2d(in_channels=in_dim, out_channels=in_dim // 8, kernel_size=1)
self.key_conv = nn.Conv2d(in_channels=in_dim, out_channels=in_dim // 8, kernel_size=1)
self.value_conv = nn.Conv2d(in_channels=in_dim, out_channels=in_dim, kernel_size=1)
self.gamma = nn.Parameter(torch.zeros(1))
self.softmax = nn.Softmax(dim=-1) #
def forward(self, input):
"""
inputs :
x : input feature maps( B X C X W X H)
returns :
out : self attention value + input feature
attention: B X N X N (N is Width*Height)
"""
#1,256,8,8
x = self.pool(input)
#print("1",x.shape)
#1,256,8,8
m_batchsize, C, width, height = x.size()
#将1,256,8,8 变为 1,64,32
proj_query = self.query_conv(x).view(m_batchsize, -1, width * height).permute(0, 2, 1) # B X C X (N)/(ds*ds)
#print("2",proj_query.shape)
# 将1,256,8,8 变为 1,64,64
proj_key = self.key_conv(x).view(m_batchsize, -1, width * height) # B X C x (*W*H)/(ds*ds)
#print(proj_key.shape)
#1,64,32x1,32,64----1,64,64
energy = torch.bmm(proj_query, proj_key) # transpose check
#print(energy.shape)
#计算A
energy = (self.key_channel**-.5) * energy
#print("3",energy.shape)
#经过softmax得到相似矩阵A
attention = self.softmax(energy) # BX (N) X (N)/(ds*ds)/(ds*ds)
#print(attention.shape)
#reshape V 256x64
proj_value = self.value_conv(x).view(m_batchsize, -1, width * height) # B X C X N
#print(proj_value.shape)
#256x64 x 64x64 得到 256 64
out = torch.bmm(proj_value, attention.permute(0, 2, 1))
#reshape为256 x8x8
out = out.view(m_batchsize, C, width, height)
#经过双线性插值恢复为原图大小
out = F.interpolate(out, [width*self.ds,height*self.ds])
out = out + input
return out