基于paddlepaddle的Attention模块理解与构建

1. 注意力机制的简介

简而言之,注意力机制就是让神经网络更多关注到更需要关注的地方。


以计算机视觉为例,当使用卷积神经网络去处理图片的时候,我们会更希望卷积神经网络去注意应该注意的地方,而不是什么都关注。比如识别一只鸟,网络就需要更关注鸟喙、鸟爪、羽毛等部分。然而,我们不可能手动调节权重去使网络注意某些部分,因此就需要让卷积神经网络去自适应的注意重要的物体。


注意力机制就是实现网络自适应注意的一种方式。一般而言,注意力机制主要包括通道注意力机制,空间注意力机制,以及二者的结合。


下面就选取三种典型的注意力模块(SENet、CBAM、ECANet)进行理解与构建。

2. SENet


SENet是一种典型的通道注意力机制。


SENet是ILSVRC 2017中的冠军方案,并中了2018年的CVPR Oral Paper《Squeeze-and-Excitation Networks》,论文的arxiv链接:https://arxiv.org/abs/1709.01507


2017年提出的SENet是最后一届ImageNet竞赛的冠军,其实现示意图如下所示,通过对每个通道乘以可学习的权重参数,让网络关注需要关注的通道:

SENet.png


其具体实现方式就是:

  1. 对输入进来的特征层进行全局平均池化。
  2. 对全局平均池化的一维向量进行两次全连接,第一次全连接神经元个数较少,第二次全连接神经元个数和输入特征层相同。
  3. 在完成两次全连接后,再取一次Sigmoid将值固定到0-1之间,即获得输入特征层每一个通道的权值(0-1之间)。
  4. 在获得这个权值后,将这个权值乘上原输入特征层即可。

2. SENet(Code Part)

# Construction
import paddle
from paddle import nn

class SENet(nn.Layer):
    def __init__(self, in_channel, ratio=16):
        super(SENet, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2D(1)
        self.fc = nn.Sequential(
                nn.Linear(in_channel, in_channel // ratio),
                nn.ReLU(),
                nn.Linear(in_channel // ratio, in_channel),
                nn.Sigmoid()
        )

    def forward(self, x):
        b, c, _, _ = x.shape
        y = self.avg_pool(x).reshape([b, c])
        # print(y.shape)
        y = self.fc(y).reshape([b, c, 1, 1])
        # print(y.shape)
        return x * y
# Test
x = paddle.ones([1, 32, 512, 512])
snet = SENet(32)
out = snet(x)
print(out.shape)
[1, 32, 512, 512]

3. CBAM


CBAM是一种典型的道注意力机制和空间注意力机制结合。


CBAM在ECCV 2018会议上《CBAM: Convolutional Block Attention Module》论文中提出,论文的arxiv链接:https://arxiv.org/abs/1807.06521


CBAM相比于SENet只关注通道的注意力机制可以取得更好的效果。其实现示意图如下所示,CBAM会对输入进来的特征层,分别进行通道注意力机制的处理和空间注意力机制的处理:

CBAM.png


其具体实现方式就是:

  • 通道注意力机制部分

    通道注意力机制的实现可以分为两个部分,对输入进来的单个特征层,分别进行全局平均池化和全局最大池化。之后对平均池化和最大池化的结果,利用共享的全连接层进行处理,对处理后的两个结果进行相加,然后取一个sigmoid,此时获得了输入特征层每一个通道的权值(0-1之间)。在获得这个权值后,将这个权值乘上原输入特征层即可。
  • 空间注意力机制部分

    对输入进来的特征层,在每一个特征点的通道上取最大值和平均值。之后将这两个结果进行一个堆叠,利用一次通道数为1的卷积调整通道数,然后取一个sigmoid,此时获得了输入特征层每一个特征点的权值(0-1之间)。在获得这个权值后,将这个权值乘上原输入特征层即可。

3. CBAM(Code Part)

# Construction
import paddle
from paddle import nn

class CBAM(nn.Layer):
    def __init__(self, in_channel, ratio=8, kernel_size=7):
        super(CBAM, self).__init__()
        self.channelAttention = ChannelAttention(in_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

class ChannelAttention(nn.Layer):
    def __init__(self, in_channel, ratio=8):
        super(ChannelAttention, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2D(1)
        self.max_pool = nn.AdaptiveMaxPool2D(1)

        # 利用1x1卷积代替全连接
        self.fc1   = nn.Conv2D(in_channel, in_channel // ratio, 1)
        self.relu1 = nn.ReLU()
        self.fc2   = nn.Conv2D(in_channel // ratio, in_channel, 1)

        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.Layer):
    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)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        avg_out = paddle.mean(x, axis=1, keepdim=True)
        max_out = paddle.max(x, axis=1, keepdim=True)
        x = paddle.concat([avg_out, max_out], axis=1)
        x = self.conv1(x)
        return self.sigmoid(x)
# Test
x = paddle.ones([1, 32, 512, 512])
cbam = CBAM(32)
out = cbam(x)
print(out.shape)
[1, 32, 512, 512]

4. ECANet


ECANet也是通道注意力机制的一种实现形式,可以看作是SENet的改进版。


ECANet在CVPR 2020《ECA-Net: Efficient Channel Attention for Deep Convolutional Neural Networks》论文中提出,论文的arxiv链接:https://arxiv.org/abs/1910.03151


ECANet的作者认为SENet对通道注意力机制的预测存在副作用,捕获所有通道的依赖关系是低效并且是不必要的。提出去除了原来SENet中的全连接层,直接在全局平均池化之后的特征上通过一个1D卷积进行学习,以利用卷积良好的跨通道信息获取能力。其具体的实现如图所示:

ecanet.png


由于采用了1D卷积,核心关键在于确定合适的卷积核尺寸,1D卷积的卷积核大小会影响注意力机制每个权重的计算要考虑的通道数量,即跨通道交互的覆盖率。


对于卷积核尺寸的选择,作者认为C,k,r之间存在关系 C = 2 2 ( r ∗ k − b ) C=2^{2(r*k-b)} C=22(rkb),并选取r=2,b=1,所以 k = ∣ l o g 2 C r + b r ∣ k = |\frac{log_{2}C}{r}+\frac{b}{r}| k=rlog2C+rb

4. ECANet(Code Part)

import paddle
from paddle import nn
import math
class ECANet(nn.Layer):
    def __init__(self, in_channel, b=1, gamma=2):
        super(ECANet, self).__init__()
        kernel_size = int(abs((math.log(in_channel, 2) + b) / gamma))
        kernel_size = kernel_size if kernel_size % 2 else kernel_size + 1
        
        self.avg_pool = nn.AdaptiveAvgPool2D(1)
        self.conv = nn.Conv1D(1, 1, kernel_size=kernel_size, padding=(kernel_size - 1) // 2) 
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        # print(x.shape) # b,c,h,w
        y = self.avg_pool(x).squeeze(-1).transpose([0,2,1])
        # print(y.shape) # b,1,c
        y = self.conv(y)
        # print(y.shape) # b,1,c
        y = self.sigmoid(y).transpose([0,2,1])
        y = y.reshape([*y.shape, 1])
        # print(y.shape) # b,c,1,1
        return x * y.expand_as(x)

# Test
x = paddle.ones([2, 32, 512, 512])
eca_net = ECANet(32)
out = eca_net(x)
print(out.shape)
[2, 32, 512, 512]
Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐