赞
踩
论文地址:https://arxiv.org/abs/1709.01507
代码地址:https://github.com/madao33/computer-vision-learning
SE-NET网络是一种基于卷积神经网络的模型,它引入了SE(Squeeze-and-Excitation)块来增强通道之间的相互关系。SE块通过学习每个通道的重要性权重,使得有用的特征被放大,没有用的特征被抑制。SE块的实现需要满足两个标准:灵活性和学习非互斥关系。SE-NET网络在图像分类、目标检测和语义分割等任务中都取得了很好的效果。在语义分割任务中,SE-NET网络可以与UNet和DenseNet等基准网络结合使用,提高分割精度。
SE结构设计的原因
对于图像,其输出都是由所有通道的求和产生,所以通道依赖关系隐含地嵌入到特征中,同时与滤波器捕获的局部空间相关性纠缠在一起。由卷积建模的通道关系本质上是隐式的和局部的(除了在最顶层的那些)。
作者希望通过明确地建模通道相互依赖来增强卷积特征的学习,从而使网络能够增加其对信息特征的敏感性,这些信息特征可被随后的转换所利用。
因此,作者希望为它提供获取全局信息的途径,并在它们被输入到下一个转换之前,通过挤压和激励两个步骤重新校准过滤器响应。
两个层的作用
1 挤压层:嵌入全局信息
为了解决通道依赖关系的问题,首先考虑信号到每个通道的输出特性。每个学习过的过滤器都使用一个局部接受域操作,因此转换输出 U 的每个单元无法利用该区域以外的上下文信息。
为了缓解这个问题,作者建议将全局空间信息压缩到一个通道描述符中。变换 U 的输出可以被解释为局部描述符的集合,这些描述符的统计信息表达了整个图像。
2 激励层:自适应重校
为了利用在挤压操作中聚合的信息,我们在它之后执行第二个操作,目的是完全捕获通道方面的依赖项。
为了实现这一目标,该操作必须符合两个标准:首先,它必须是灵活的(必须能够学习通道之间的非线性交互),其次,它必须学习一种非互斥关系,因为我们希望确保允许强调多个通道(而不是强制执行独热激活)。
为了满足这些标准,选择使用一个简单的带有sigmoid激活的门控机制。
为了限制模型的复杂性,通过在非线性周围形成两个全连接(FC)层的瓶颈来参数化门控机制,即降维层,降维率为 r,接一个ReLU,然后一个维度增加层返回到转换输出U的通道维度。块的最终输出是通过将U与激活sigmoid而获得的。
激励算子将特定输入的描述符映射为一组信道权值。
SE块结构如下图所示,其先将给定信息 X 经 F 转换映射到 U,然后经过挤压操作,即对每个通道的整个空间维度 (H×W) 进行特征聚合映射,最后经过激励层,其采用一种简单的自选门机制形式,将嵌入作为输入,并产生每通道调制权值的集合。这些权重被应用到特征映射U上,生成SE块的输出,该输出可以直接输入到网络的后续层
可以通过简单地堆叠SE块的集合来构建SE网络(SENet)。此外,这些SE块也可以作为一个插入式替换原始块。
虽然构建块的模板是通用的,但它在整个网络的不同深度上所扮演的角色是不同的。在早期的层中,它以一种与类无关的方式激发信息特性,加强共享的低级表示。在后面的层中,SE块变得越来越专门化,并以一种高度特定于类的方式响应不同的输入。因此,特征重新校准的好处得以实现。
SE三种变体:
- import torch
- import torch.nn as nn
- import torch.nn.functional as F
-
-
- def conv_block(in_channel, out_channel, relu_last=True, **kwargs):
- layers = [nn.Conv2d(in_channel, out_channel, bias=False, **kwargs),
- nn.BatchNorm2d(out_channel)]
- if relu_last:
- layers.append(nn.ReLU(inplace=True))
-
- return nn.Sequential(*layers)
-
-
- class ResidualSEBlock(nn.Module):
- expansion = 1
-
- def __init__(self, in_channel, out_channel, stride, r=16):
- super(ResidualSEBlock, self).__init__()
- self.residual = nn.Sequential(
- conv_block(in_channel, out_channel, kernel_size=3, stride=stride, padding=1),
- conv_block(out_channel, out_channel * self.expansion, kernel_size=3, padding=1)
- )
- self.shortcut = nn.Sequential()
- if stride != 1 or in_channel != out_channel * self.expansion:
- self.shortcut = conv_block(in_channel, out_channel * self.expansion, kernel_size=1, stride=stride,
- relu_last=False)
- self.squeeze = nn.AdaptiveAvgPool2d(1)
- self.excitation = nn.Sequential(
- nn.Linear(out_channel * self.expansion, out_channel * self.expansion // r),
- nn.ReLU(inplace=True),
- nn.Linear(out_channel * self.expansion // r, out_channel * self.expansion),
- nn.Sigmoid())
-
- def forward(self, x):
- r = self.residual(x)
- bs, c, _, _ = r.shape
- s = self.squeeze(r).view(bs, c)
- e = self.excitation(s).view(bs, c, 1, 1)
- return F.relu(self.shortcut(x) + r * e.expand_as(r))
-
-
- class SEResnet(nn.Module):
- def __init__(self, in_channel, n_classes, num_blocks, block):
- super(SEResnet, self).__init__()
- self.in_channels = 64
- self.feature = nn.Sequential(
- conv_block(in_channel, 64, kernel_size=3, padding=1),
- self._make_stage(64, 1, num_blocks[0], block),
- self._make_stage(128, 2, num_blocks[1], block),
- self._make_stage(256, 2, num_blocks[2], block),
- self._make_stage(512, 2, num_blocks[3], block)
- )
- self.classifier = nn.Sequential(
- nn.AdaptiveAvgPool2d((1, 1)),
- nn.Flatten(),
- nn.Linear(self.in_channels, n_classes),
- nn.LogSoftmax(dim=1)
- )
-
- def _make_stage(self, out_channel, stride, num_block, block):
- layers = []
- for i in range(num_block):
- stride = stride if i == 0 else 1
- layers.append(block(self.in_channels, out_channel, stride))
- self.in_channels = out_channel * block.expansion
- return nn.Sequential(*layers)
-
- def forward(self, x):
- return self.classifier(self.feature(x))
-
-
- def seresnet18():
- return SEResnet(3, 10, [2, 2, 2, 2], ResidualSEBlock)
-
-
- def seresnet34():
- return SEResnet(3, 10, [3, 4, 6, 3], ResidualSEBlock)
参考:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。