注意力机制(一)SE模块(Squeeze-and-Excitation Networks)论文总结和代码实现

Squeeze-and-Excitation Networks(压缩和激励网络)

论文地址:Squeeze-and-Excitation Networks

论文中文版:Squeeze-and-Excitation Networks_中文版

代码地址:GitHub - hujie-frank/SENet: Squeeze-and-Excitation Networks

目录

一、论文出发点

二、论文的主要工作

三、Squeeze-and-Excitation模块

(1)Transformation(Ftr): 转型

(2)Squeeze:全局信息嵌入

(3)Excitation:自适应重新校正

(4)Scale:重新加权

四、模型:SE-Inception和SE-ResNet

五、实验

六、结论

七、源码分析

 (1)SE模块

 (2)SE-ResNet完整代码


一、论文出发点

为了提高网络的表示能力,许多现有的工作已经显示出增强空间编码的好处。而作者专注于通道,希望能够提出了一种新的架构单元,通过显式地建模出卷积特征通道之间的相互依赖性来提高网络的表示能力。

这里引用“博文:Squeeze-and-Excitation Networks解读”中的总结:核心思想是不同通道的权重应该自适应分配,由网络自己学习出来的,而不是像Inception net一样留下过多人工干预的痕迹。

二、论文的主要工作

1.提出了一种新的架构单元Squeeze-and-Excitation模块,该模块可以显式地建模卷积特征通道之间的相互依赖性来提高网络的表示能力。

2.提出了一种机制,使网络能够执行特征重新校准,通过这种机制可以学习使用全局信息来选择性地强调信息特征并抑制不太有用的特征。

三、Squeeze-and-Excitation模块

注意力机制(一)SE模块(Squeeze-and-Excitation Networks)论文总结和代码实现_第1张图片

(1)Transformation(Ftr): 转型

注意力机制(一)SE模块(Squeeze-and-Excitation Networks)论文总结和代码实现_第2张图片

F_{tr}:X\rightarrow U,经过F_{tr}特征图X变为特征图U。
F_{tr}可以看作一个标准的卷积算子。该卷积算子公式为U_{c}=V_{c}*X=\sum_{s=1}^{C'}V_{c}^{s}*X^{s}

其中:

1.   U=[U_{1},U_{2}...U_{c}],这里U_c指输出特征图的一个单通道2D特征层。

2.   V=[V_{1},V_{2}...V_{c}]表示学习到的一组滤波器核,Vc指的是第c个滤波器的参数,此外V_{c}=[V_{c}^{1},V_{c}^{2}...V_{c}^{c'}]这里 V_{c}^{s}是指一个通道数为1的2D空间核

3.  X=[X^{1},X^{2}...X^{c'}]这里X^{s}是指输入特征图的一个单通道2D特征层

该卷积算子公式表示,输入特征图X的每一层都经过一个2D空间核的卷积最终得到C个输出的feature map,组成特征图U。

原文内容如下:

注意力机制(一)SE模块(Squeeze-and-Excitation Networks)论文总结和代码实现_第3张图片

  • X∈R^(H′×W′×C′):输入特征图
  • U∈R^(H×W×C):输出特征图
  • V:表示学习到的一组滤波器核
  • Vc:指的是第c个滤波器的参数
  • V_{c}^{s}​:表示一个2D的空间核
  • *:卷积操作

(2)Squeeze:全局信息嵌入

注意力机制(一)SE模块(Squeeze-and-Excitation Networks)论文总结和代码实现_第4张图片

Fsq就是使用通道的全局平均池化。
原文中为了解决利用通道依赖性的问题,选择将全局空间信息压缩到一个信道描述符中,即使用通道的全局平均池化,将包含全局信息的W×H×C 的特征图直接压缩成一个1×1×C的特征向量Z,C个feature map的通道特征都被压缩成了一个数值,这样使得生成的通道级统计数据Z就包含了上下文信息,缓解了通道依赖性的问题。
算子公式如下:

Zc为Z的第c个元素。

(3)Excitation:自适应重新校正

注意力机制(一)SE模块(Squeeze-and-Excitation Networks)论文总结和代码实现_第5张图片

目的为了利用压缩操作中汇聚的信息,我们接下来通过Excitation操作来全面捕获通道依赖性。
实现方法
为了实现这个目标,这个功能必须符合两个标准
第一,它必须是灵活的 (它必须能够学习通道之间的非线性交互)
第二,它必须学习一个非互斥的关系,因为独热激活相反,这里允许强调多个通道。
为了满足这些标准,作者采用了两层全连接构成的门机制,第一个全连接层把C个通道压缩成了C/r个通道来降低计算量,再通过一个RELU非线性激活层,第二个全连接层将通道数恢复回为C个通道,再通过Sigmoid激活得到权重s,最后得到的这个s的维度是1×1×C,它是用来刻画特征图U中C个feature map的权重。r是指压缩的比例。

为什么这里要有两个FC,并且通道先缩小,再放大?

因为一个全连接层无法同时应用relu和sigmoid两个非线性函数,但是两者又缺一不可。为了减少参数,所以设置了r比率。

(4)Scale:重新加权

注意力机制(一)SE模块(Squeeze-and-Excitation Networks)论文总结和代码实现_第6张图片

目的:最后是Scale操作,将前面得到的注意力权重加权到每个通道的特征上

实现方法:
特征图U中的每个feature map乘以对应的权重,得到SE模块的最终输出\widetilde{X}

四、模型:SE-Inception和SE-ResNet

注意力机制(一)SE模块(Squeeze-and-Excitation Networks)论文总结和代码实现_第7张图片

通过将一个整体的Inception模块看作SE模块中F_{tr},为Inception网络构建SE模块。

同理, 将一个整体的Residual模块看作SE模块中F_{tr},为ResNet网络构建SE模块。

五、实验

注意力机制(一)SE模块(Squeeze-and-Excitation Networks)论文总结和代码实现_第8张图片

注意力机制(一)SE模块(Squeeze-and-Excitation Networks)论文总结和代码实现_第9张图片

六、结论

本文提出的SE模块,这是一种新颖的架构单元,旨在通过使网络能够执行动态通道特征重新校准来提高网络的表示能力。大量实验证明了SENets的有效性,其在多个数据集上取得了最先进的性能。

七、源码分析

将SEblock嵌入ResNet的残差模块中

 (1)SE模块

注意力机制(一)SE模块(Squeeze-and-Excitation Networks)论文总结和代码实现_第10张图片

'''-------------一、SE模块-----------------------------'''
#全局平均池化+1*1卷积核+ReLu+1*1卷积核+Sigmoid
class SE_Block(nn.Module):
    def __init__(self, inchannel, ratio=16):
        super(SE_Block, self).__init__()
        # 全局平均池化(Fsq操作)
        self.gap = nn.AdaptiveAvgPool2d((1, 1))
        # 两个全连接层(Fex操作)
        self.fc = nn.Sequential(
            nn.Linear(inchannel, inchannel // ratio, bias=False),  # 从 c -> c/r
            nn.ReLU(),
            nn.Linear(inchannel // ratio, inchannel, bias=False),  # 从 c/r -> c
            nn.Sigmoid()
        )

    def forward(self, x):
            # 读取批数据图片数量及通道数
            b, c, h, w = x.size()
            # Fsq操作:经池化后输出b*c的矩阵
            y = self.gap(x).view(b, c)
            # Fex操作:经全连接层输出(b,c,1,1)矩阵
            y = self.fc(y).view(b, c, 1, 1)
            # Fscale操作:将得到的权重乘以原来的特征图x
            return x * y.expand_as(x)

 (2SE-ResNet完整代码

不同版本的ResNet各层主要是由BasicBlock模块(18-layer、34-layer)或Bottleneck模块(50-layer、101-layer、152-layer)构成的,因此只要在BasicBlock模块或Bottleneck模块尾部添加SE模块即可,但是要注意放在shortcut之前,因为shortcut仅是为了保存梯度,把SE模块加在作为提取信息的主干上即可。

注意力机制(一)SE模块(Squeeze-and-Excitation Networks)论文总结和代码实现_第11张图片

import torch
import torch.nn as nn
import torch.nn.functional as F
from torchsummary import summary

'''-------------一、SE模块-----------------------------'''
#全局平均池化+1*1卷积核+ReLu+1*1卷积核+Sigmoid
class SE_Block(nn.Module):
    def __init__(self, inchannel, ratio=16):
        super(SE_Block, self).__init__()
        # 全局平均池化(Fsq操作)
        self.gap = nn.AdaptiveAvgPool2d((1, 1))
        # 两个全连接层(Fex操作)
        self.fc = nn.Sequential(
            nn.Linear(inchannel, inchannel // ratio, bias=False),  # 从 c -> c/r
            nn.ReLU(),
            nn.Linear(inchannel // ratio, inchannel, bias=False),  # 从 c/r -> c
            nn.Sigmoid()
        )

    def forward(self, x):
            # 读取批数据图片数量及通道数
            b, c, h, w = x.size()
            # Fsq操作:经池化后输出b*c的矩阵
            y = self.gap(x).view(b, c)
            # Fex操作:经全连接层输出(b,c,1,1)矩阵
            y = self.fc(y).view(b, c, 1, 1)
            # Fscale操作:将得到的权重乘以原来的特征图x
            return x * y.expand_as(x)

'''-------------二、BasicBlock模块-----------------------------'''
# 左侧的 residual block 结构(18-layer、34-layer)
class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, inchannel, outchannel, stride=1):
        super(BasicBlock, self).__init__()
        self.conv1 = nn.Conv2d(inchannel, outchannel, kernel_size=3,
                               stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(outchannel)
        self.conv2 = nn.Conv2d(outchannel, outchannel, kernel_size=3,
                               stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(outchannel)
        # SE_Block放在BN之后,shortcut之前
        self.SE = SE_Block(outchannel)

        self.shortcut = nn.Sequential()
        if stride != 1 or inchannel != self.expansion*outchannel:
            self.shortcut = nn.Sequential(
                nn.Conv2d(inchannel, self.expansion*outchannel,
                          kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(self.expansion*outchannel)
            )

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        SE_out = self.SE(out)
        out = out * SE_out
        out += self.shortcut(x)
        out = F.relu(out)
        return out

'''-------------三、Bottleneck模块-----------------------------'''
# 右侧的 residual block 结构(50-layer、101-layer、152-layer)
class Bottleneck(nn.Module):
    expansion = 4

    def __init__(self, inchannel, outchannel, stride=1):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(inchannel, outchannel, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(outchannel)
        self.conv2 = nn.Conv2d(outchannel, outchannel, kernel_size=3,
                               stride=stride, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(outchannel)
        self.conv3 = nn.Conv2d(outchannel, self.expansion*outchannel,
                               kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(self.expansion*outchannel)
        # SE_Block放在BN之后,shortcut之前
        self.SE = SE_Block(self.expansion*outchannel)

        self.shortcut = nn.Sequential()
        if stride != 1 or inchannel != self.expansion*outchannel:
            self.shortcut = nn.Sequential(
                nn.Conv2d(inchannel, self.expansion*outchannel,
                          kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(self.expansion*outchannel)
            )

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = F.relu(self.bn2(self.conv2(out)))
        out = self.bn3(self.conv3(out))
        SE_out = self.SE(out)
        out = out * SE_out
        out += self.shortcut(x)
        out = F.relu(out)
        return out

'''-------------四、搭建SE_ResNet结构-----------------------------'''
class SE_ResNet(nn.Module):
    def __init__(self, block, num_blocks, num_classes=10):
        super(SE_ResNet, self).__init__()
        self.in_planes = 64

        self.conv1 = nn.Conv2d(3, 64, kernel_size=3,
                               stride=1, padding=1, bias=False)                  # conv1
        self.bn1 = nn.BatchNorm2d(64)
        self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)       # conv2_x
        self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)      # conv3_x
        self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)      # conv4_x
        self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)      # conv5_x
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.linear = nn.Linear(512 * block.expansion, num_classes)

    def _make_layer(self, block, planes, num_blocks, stride):
        strides = [stride] + [1]*(num_blocks-1)
        layers = []
        for stride in strides:
            layers.append(block(self.in_planes, planes, stride))
            self.in_planes = planes * block.expansion
        return nn.Sequential(*layers)

    def forward(self, x):
        x = F.relu(self.bn1(self.conv1(x)))
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        out = self.linear(x)
        return out


def SE_ResNet18():
    return SE_ResNet(BasicBlock, [2, 2, 2, 2])


def SE_ResNet34():
    return SE_ResNet(BasicBlock, [3, 4, 6, 3])


def SE_ResNet50():
    return SE_ResNet(Bottleneck, [3, 4, 6, 3])


def SE_ResNet101():
    return SE_ResNet(Bottleneck, [3, 4, 23, 3])


def SE_ResNet152():
    return SE_ResNet(Bottleneck, [3, 8, 36, 3])


'''
if __name__ == '__main__':
    model = SE_ResNet50()
    print(model)
    input = torch.randn(1, 3, 224, 224)
    out = model(input)
    print(out.shape)
# test()
'''
if __name__ == '__main__':
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    net = SE_ResNet50().to(device)
    # 打印网络结构和参数
    summary(net, (3, 224, 224))

你可能感兴趣的:(注意力机制,SE模块,注意力机制)