神经网络中的注意力机制(Attention Mechanism)是在计算能力有限的情况下,将计算资源分配给更重要的任务,同时解决信息超载问题的一种资源分配方案。在神经网络学习中,一般而言模型的参数越多则模型的表达能力越强,模型所存储的信息量也越大,但这会带来信息过载的问题。那么通过引入注意力机制,在众多的输入信息中聚焦于对当前任务更为关键的信息,降低对其他信息的关注度,甚至过滤掉无关信息,就可以解决信息过载问题,并提高任务处理的效率和准确性。
这就类似于人类的视觉注意力机制,通过扫描全局图像,获取需要重点关注的目标区域,而后对这一区域投入更多的注意力资源,获取更多与目标有关的细节信息,而忽视其他无关信息。通过这种机制可以利用有限的注意力资源从大量信息中快速筛选出高价值的信息。
注意力机制的核心:使网络关注到更为重要的信息
注意力机制能使卷积神经网络自适应的注意更为重要的信息,因此注意力机制是实现网络自适应注意的重要方式。
一般而言,注意力机制可以分为:通道注意力机制、空间注意力机制、二者的结合
空间注意力机制(关注每个通道的比重):
通道注意力机制(关注每个像素点的比重):SENet
二者的结合:CBAM
CBAM:Convolutional Block Attention Module,论文地址
CBAM的核心:应用了 Channel Attention Module(通道注意模块)和Spatial Attention Module(空间注意模块)的结合,对输入进来的特征层分别进行通道注意力模块和空间注意力模块的处理。
总流程(概括):
输入特征图 , 。利用通道注意力模块生成一维通道注意图 , ,利用空间注意力模块生成二维空间注意图 ,。
上图即为通道注意力模块,对输入进来的特征层分别进行全局平均池化(AvgPool)和全局最大池化(MaxPool)(两个池化都针对于输入特征层的高宽),再将平均池化和最大池化的结果利用共享的全连接层(Shared MLP)进行处理,然后将共享的全连接层所得到的结果进行相加再使用Sigmoid激活函数,进而获得通道注意图即获得输入特征层每一个通道的权重(0~1之间)。最后,将权重通过乘法逐通道加权到输入特征层上即可。
由于Feature Map的每个channel都提取某种级别的特征信息,因此Channel Attention Module的注意力集中在图像中什么级别的特征信息是更重要的。为了有效地计算通道注意力,Channel Attention Module采用了压缩输入特征映射的空间维度方法,相较于使用单一池化的方法Channel Attention Module使用AvgPool(平均池化)和MaxPool(最大池化)的方法并证明双池化的方法具有更强的表征力。具体计算过程如下所示,式中 表示Sigmoid函数,,。MLP的权重由 和 共享。 前有ReLU激活函数。
利用特征间的空间关系生成空间注意图
与通道注意模块不同的是,空间注意模块关注的是输入图像的哪部分信息是更重要的,是通道注意模块的补充。为了计算空间注意力,首先沿着每一个特征点的通道方向应用平均池化和最大池化(两个池化都针对于输入特征层的通道)并将其堆叠起来生成一个有效的特征描述符(使用两个池化聚合一个Feature Map的通道信息,生成两个2D Map: 和 ,分别为通道的平均池化特性和最大池化特性),再利用一个标准的卷积层(通道数为1的卷积)进行连接和卷积(调整了通道数),然后使用Sigmoid激活函数,进而得到二维空间注意力图即获得输入特征图每个特征点的权重值(0~1间),最后,将权重通过乘法逐通道加权到输入特征层上即可,具体计算过程如下所示,式中 表示Sigmoid函数,表示7×7大小的卷积核。
代码实现
import torch
import torch.nn as nn
class ChannelAttention(nn.Module):
def __init__(self, in_planes, ratio=8):
"""
第一层全连接层神经元个数较少,因此需要一个比例系数ratio进行缩放
"""
super(ChannelAttention, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.max_pool = nn.AdaptiveMaxPool2d(1)
"""
self.fc = nn.Sequential(
nn.Linear(channel, channel // ratio, False),
nn.ReLU(),
nn.Linear(channel // ratio, channel, False)
)
"""
# 利用1x1卷积代替全连接
self.fc1 = nn.Conv2d(in_planes, in_planes // ratio, 1, bias=False)
self.relu1 = nn.ReLU()
self.fc2 = nn.Conv2d(in_planes // ratio, in_planes, 1, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avg_out = self.fc2(self.relu1(self.fc1(self.avg_pool(x))))
max_out = self.fc2(self.relu1(self.fc1(self.max_pool(x))))
out = avg_out + max_out
return self.sigmoid(out)
class SpatialAttention(nn.Module):
def __init__(self, kernel_size=7):
super(SpatialAttention, self).__init__()
assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
padding = 3 if kernel_size == 7 else 1
self.conv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avg_out = torch.mean(x, dim=1, keepdim=True)
max_out, _ = torch.max(x, dim=1, keepdim=True)
x = torch.cat([avg_out, max_out], dim=1)
x = self.conv1(x)
return self.sigmoid(x)
class cbam_block(nn.Module):
def __init__(self, channel, ratio=8, kernel_size=7):
super(cbam_block, self).__init__()
self.channelattention = ChannelAttention(channel, ratio=ratio)
self.spatialattention = SpatialAttention(kernel_size=kernel_size)
def forward(self, x):
x = x * self.channelattention(x)
x = x * self.spatialattention(x)
return x