龙空技术网

CV中的Attention和Self-Attention

AI约读社 806

前言:

现时朋友们对“senetcvpr2017”大概比较注重,小伙伴们都想要分析一些“senetcvpr2017”的相关文章。那么小编也在网摘上网罗了一些有关“senetcvpr2017””的相关文章,希望我们能喜欢,你们一起来学习一下吧!

1 Attention 和 Self-Attention

Attention的核心思想是:从关注全部到关注重点。

Attention 机制很像人类看图片的逻辑,当我们看一张图片的时候,我们并没有看清图片的全部内容,而是将注意力集中在了图片的焦点上。大家看下面这张图自行体会:

对于CV中早期的Attention,通常是在通道或者空间计算注意力分布,例如:SENet,CBAM。

而Self-attention(NLP中往往称为Scaled-Dot Attention)的结构有三个分支:query、key和value。计算时通常分为三步:

第一步是将query和每个key进行相似度计算得到权重,常用的相似度函数有点积,cos相似度,拼接,感知机等;第二步一般是使用一个softmax函数对这些权重进行归一化;第三步将权重和相应的键值value进行加权求和得到最后的attention。

假设输入的feature maps的大小Batch_size×Channels×Width×Height,那么通过三个1×1卷积(分别是query_conv , key_conv 和 value_conv)就可以得到query、key和value:

query:在query_conv卷积中,输入为B×C×W×H,输出为B×C/8×W×H;key:在key_conv卷积中,输入为B×C×W×H,输出为B×C/8×W×H;value:在value_conv卷积中,输入为B×C×W×H,输出为B×C×W×H。

后续的操作可以查看下面代码及注释:

class Self_Attn(nn.Module):    """ Self attention Layer"""    def __init__(self,in_dim,activation):        super(Self_Attn,self).__init__()        self.chanel_in = in_dim        self.activation = activation         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,x):        """            inputs :                x : input feature maps( B * C * W * H)            returns :                out : self attention value + input feature                attention: B * N * N (N is Width*Height)        """        m_batchsize,C,width ,height = x.size()        proj_query  = self.query_conv(x).view(m_batchsize,-1,width*height).permute(0,2,1) # B*N*C/8        proj_key =  self.key_conv(x).view(m_batchsize,-1,width*height) # B*C*N/8        energy =  torch.bmm(proj_query,proj_key) # batch的matmul B*N*N        attention = self.softmax(energy) # B * (N) * (N)        proj_value = self.value_conv(x).view(m_batchsize,-1, width*height) # B * C * N         out = torch.bmm(proj_value,attention.permute(0,2,1) ) # B*C*N        out = out.view(m_batchsize,C,width,height) # B*C*H*W         out = self.gamma*out + x        return out,attention
2 【CVPR 2017】SENet

论文:Squeeze-and-Excitation Networks

代码:hujie-frank/SENet

由Momenta研发的网路SENet,获得ImageNet 2017 Image Classification 冠军。将top-5 error从2.991%降到2.251%。

SENet是早期Attention,核心思想是学习 feature Channel 间的关系,以凸显feature Channel 不同的重要度(也就是注意力分布),进而提高模型表现。

上图是SE Module 的示意图。给定一个输入 x,其特征通道数为 c_1,通过一系列卷积等一般变换后得到一个特征通道数为 c_2 的特征。与传统的 CNN 不一样的是,接下来通过三个操作来重标定前面得到的特征。

首先是 Squeeze 操作,从空间维度来进行特征压缩,将h*w*c的特征变成一个1*1*c的特征,得到向量某种程度上具有全域性的感受野,并且输出的通道数和输入的特征通道数相匹配,它表示在特征通道上响应的全域性分布。公式非常简单,就是一个 global average pooling:

其次是 Excitation 操作,通过引入 w 参数来为每个特征通道生成权重,其中引数 w 是可学习的,并通过一个 Sigmoid 的门获得 0~1 之间归一化的权重,完成显式地建模特征通道间的相关性。公式如下:

最后是一个 Scale 的操作,将 Excitation 的输出的权重看做是经过选择后的每个特征通道的重要性,然后通过channel-wise multiplication 主通道加权到先前的特征上,完成在通道维度上的对原始特征的重标定。公式如下:

介绍完具体的公式实现,下面介绍下SE block如何运用到具体的网络中。

代码:

class SELayer(nn.Module):    def __init__(self, channel, reduction=16):        super(SELayer, self).__init__()        self.avg_pool = nn.AdaptiveAvgPool2d(1) # 压缩空间        self.fc = nn.Sequential(            nn.Linear(channel, channel // reduction, bias=False),            nn.ReLU(inplace=True),            nn.Linear(channel // reduction, channel, bias=False),            nn.Sigmoid()        )    def forward(self, x):        b, c, _, _ = x.size()        y = self.avg_pool(x).view(b, c)        y = self.fc(y).view(b, c, 1, 1)        return x * y.expand_as(x)

虽然没有看到query、key和value的影子,但是其体现了不同channel应有不同权重,是早期的attention。

3【ECCV 2018】CBAM

论文:CBAM: Convolutional Block Attention Module

代码:

这是2018年ECCV的一篇论文,引文超过1000篇。

CBAM可以无缝地集成到任何CNN架构中,开销不会很大,而且可以与基本CNN网络一起进行端到端的训练。与SENet类似,CBAM 也是早期的Attention,没有通过复杂的相似度计算得到注意力分布。

CBAM: General Architecture

CBAM依次推导出尺寸为C×1×1的一维通道注意图Mc和尺寸为1×H×W的二维空间注意图Ms:

其中⨂表示element-wise的乘法,F''是最终的优化输出。

实验表明,sequential arrangement 比parallel arrangement效果,并且channel-first 顺序略优于 spatial-first.。

ResBlock中的CBAM示例如下所示:

Channel Attention Module

Channel Attention集中在输入图像的“channel”上。

为了有效地计算channel attention,对输入特征映射的空间维度进行压缩。

对于空间信息的聚合,通常同时采用 average-pooling 和 max-pooling,以得到更精细的channel-wise attention。

Fcavg和Fcmax分别表示平均池特征和最大池特征,然后通过一个隐藏层的多层感知器(MLP),σ表示sigmoid函数。

Spatial Attention Module

Spatial attention关注“空间”的信息,是对Channel Attention的补充。

为了计算Spatial attention,在 Channel 轴上应用average-pooling 和 max-pooling,然后将它们连接起来生成一个有效的特征。

然后利用卷积层生成一个R×H×W的空间注意映射Ms(F),该映射对强调或抑制的位置进行编码。

具体地说,通过两次池生成两个映射:1×H×W的Fsavg和1×H×W的Fsmax。σ表示sigmoid函数,f7×7表示滤波器尺寸为7×7的卷积运算。

代码如下:

def conv3x3(in_planes, out_planes, stride=1):    "3x3 convolution with padding"    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,                     padding=1, bias=False)class ChannelAttention(nn.Module):    def __init__(self, in_planes, ratio=16):        super(ChannelAttention, self).__init__()        self.avg_pool = nn.AdaptiveAvgPool2d(1) # 压缩空间        self.max_pool = nn.AdaptiveMaxPool2d(1)        self.fc1   = nn.Conv2d(in_planes, in_planes // 16, 1, bias=False)        self.relu1 = nn.ReLU()        self.fc2   = nn.Conv2d(in_planes // 16, 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  # [b, C, 1, 1]        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)  # [b, 1, h, w]        x = self.conv1(x)        return self.sigmoid(x)class BasicBlock(nn.Module):    expansion = 1    def __init__(self, inplanes, planes, stride=1, downsample=None):        super(BasicBlock, self).__init__()        self.conv1 = conv3x3(inplanes, planes, stride)        self.bn1 = nn.BatchNorm2d(planes)        self.relu = nn.ReLU(inplace=True)        self.conv2 = conv3x3(planes, planes)        self.bn2 = nn.BatchNorm2d(planes)        self.ca = ChannelAttention(planes)        self.sa = SpatialAttention()        self.downsample = downsample        self.stride = stride    def forward(self, x):        residual = x        out = self.conv1(x)        out = self.bn1(out)        out = self.relu(out)        out = self.conv2(out)        out = self.bn2(out)        out = self.ca(out) * out        out = self.sa(out) * out        if self.downsample is not None:            residual = self.downsample(x)        out += residual        out = self.relu(out)        return outclass Bottleneck(nn.Module):    expansion = 4    def __init__(self, inplanes, planes, stride=1, downsample=None):        super(Bottleneck, self).__init__()        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)        self.bn1 = nn.BatchNorm2d(planes)        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,                               padding=1, bias=False)        self.bn2 = nn.BatchNorm2d(planes)        self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)        self.bn3 = nn.BatchNorm2d(planes * 4)        self.relu = nn.ReLU(inplace=True)        self.ca = ChannelAttention(planes * 4)        self.sa = SpatialAttention()        self.downsample = downsample        self.stride = stride    def forward(self, x):        residual = x        out = self.conv1(x)        out = self.bn1(out)        out = self.relu(out)        out = self.conv2(out)        out = self.bn2(out)        out = self.relu(out)        out = self.conv3(out)        out = self.bn3(out)        out = self.ca(out) * out        out = self.sa(out) * out        if self.downsample is not None:            residual = self.downsample(x)        out += residual        out = self.relu(out)        return out
4 【CVPR2018 Non-local】

论文地址:

再次回顾下Self-attention

Self-attention结构自上而下分为三个分支,分别是query、key和value。计算时通常分为三步:

第一步是将query和每个key进行相似度计算得到权重,常用的相似度函数有点积,cos相似度,拼接,感知机等;第二步一般是使用一个softmax函数对这些权重进行归一化;第三步将权重和相应的键值value进行加权求和得到最后的attention。

Non-local就是CV中的self-attetion。其计算公式如下:

x是输入信号,CV中使用的一般是 feature mapi 代表的是输出位置,如空间、时间或者时空的索引,j 代表全局响应f 函数式计算i和j的相似度g 函数计算feature map在j位置的表示最终的y是通过响应因子 C(x) 进行标准化处理以后得到的

non-local结构图如下,可以看到non-local的原理与self-attention运行原理一样,通过 3 个1*1的卷积构建了query,key 和 value。

5 【CVPR 2019】DANet

论文题目:Dual Attention Network for Scene Segmentation

论文地址:

DANet结构如上图,包含了Position Attention Module 和 Channel Attention Module,和CBAM相似,只是在spatial和channel维度利用self-attention思想建立全局上下文关系。如下所示:

6 总结

Self-attention能够捕捉全局的特征,因此,也在计算机视觉领域大放异彩,如 Detr,Sparse R-CNN等等,不过需要指出的是:Self-attention 也是有缺陷的,如:计算量大,并且这类Set Prediction检测器检测准确性还不能够超越之前的检测算法。

因此,如果是做研究,那么这是一个不错的主题;如果是要产品落地,那么直接拿来用可能就会被速度拖累。

公众号:AI约读社,欢迎关注

标签: #senetcvpr2017