一、DnCNN
论文链接:https://arxiv.org/pdf/1608.03981.pdf
0.摘要
论文提出了一个前馈去噪卷积神经网络(DnCNN)用于图像的去噪,将非常深的结构、学习算法和正则化方法使用到图像去噪的过程中,使用残差学习和批量归一化来加速训练过程和提高去噪性能。DnCNN模型能够处理具有未知噪声水平的高斯去噪(即,盲高斯去噪)。
1.介绍
- 图像去噪:从一个有噪声的图像y=x+v中恢复干净的图像x
- 先验建模:
- 模型:非局部自相似(NSS)模型、稀疏模型、梯度模型、马尔可夫随机场(MRF)模型
- 缺点:
- 测试阶段涉及复杂的优化问题,在不牺牲计算效率的情况下很难获得高性能
- 模型是非凸的,涉及几个手动选择的参数
- 基于判别学习方法的先验模型:
- 模型:收缩级联方法(CSF)、非线性反应扩散(TNRD)、
- 评价:弥补了计算效率和去噪质量上的差距。但是本质上仅限于先前那种特定的形式。
- 缺点:
- 捕获图像结构整体特征上被限制
- 通过阶段式贪婪训练以及所有阶段之间的联合微调来学习参数,涉及许多手工参数
- 针对特定噪声水平的模型,在盲图像去噪上受限制
将图像去噪视为一种判别学习问题:(DnCNN)通过卷积神经网络将图像与噪声分离
- 优点:
- 第一:非常深的结构提高利用图像特征的容量和灵活方面是非常有效的
- 第二:训练CNN的正则化和学习方面取得了相当大的进展,ReLU、批量归一化、残差学习,提高去噪性能
- 第三:适合GPU的并行运算,提高运行时的性能
2.相关工作
A.用于图像去噪的深度神经网络
已经有人尝试过用深度神经网络来处理去噪问题,如:MLP和TNRD,但需要针对一定的噪声水平训练特定的模型。开发用于一般图像去噪的CNN仍有待研究。B.残差学习和批量归一化
残差学习和批量归一化的整合可以带来快速稳定的训练和更好的去噪性能。3.DnCnn模型的提出
DnCNN采用VGG网络结构并修改,使用残差学习,并结合了批量归一化。A.网络深度
对于一定噪声水平下的高斯去噪,我们将DnCNN的感受野大小设为35×35,深度设为17。对于其他一般图像去噪任务,我们采用较大的感受野,将深度设为20。B.网络架构

- 优点:
网络结构
- Conv+ReLU:64个大小为3 3 c的滤波器被用于生成64个特征图,然后使用ReLU进行非线性处理
- Conv+BN+ReLU:64个大小为3 3 64的过滤器,并且将批量归一化加在卷积和ReLU之间
- Conv:使用c个大小为3 3 64的滤波器重构输出。
- 边界伪影:采用0填充的方法解决了边界伪影
C.将残差学习与批处理归一化相结合用于图像去噪
上图,残差学习和批量归一化的整合不仅可以加快和稳定训练过程,还可以提高去噪性能。
D.与TNRD的联系
我们的DnCNN也可以解释为一阶段TNRD的推广。E.拓展到一般图像去噪
实验结果表明,学习后的单一DnCNN模型对于三种常规图像去噪任务:盲高斯去噪,SISR和JPEG去块中的任何一种都能产生良好的效果。四.实验结果
包括了实验设置、比较方法、定性和定量分析、运行程序的时间五.结论
本文提出了一种用于图像去噪的深度卷积神经网络,利用残差学习将噪声从有噪观测中分离出来。将批处理归一化和残差学习相结合,加快了训练过程,提高了去噪性能。与传统的判别模型针对特定的噪声水平训练特定的模型不同,我们的单一DnCNN模型具有处理未知噪声水平下的盲高斯去噪的能力。此外,我们还证明了训练一个单一的DnCNN模型来处理三种常用图像去噪任务的可行性,包括未知噪声水平的高斯去噪、多尺度放大因子的单图像超分辨率以及不同质量因子的JPEG图像去噪。大量的实验结果表明,该方法不仅在定量和定性上具有良好的图像去噪性能,而且通过GPU实现具有良好的运行时间。在未来,我们将研究合适的CNN模型,用于实际复杂噪声的图像去噪和其他一般的图像恢复任务。
二、SENet
论文链接:https://arxiv.org/abs/1709.01507
PyTorch代码:https://github.com/miraclewkf/SENet-PyTorch
0.摘要
CNN的核心是卷积,它使网络能够通过在每一层的局部感受野内融合空间和信道信息来构建信息特征。大量研究调查了这种关系的空间组成部分,视图通过提高整个特征层次的空间编码质量来增强CNN的表征能力。论文工作中关注的是通道关系,并提出了一个新的建筑单位,我们称为“压缩—激励(SE)”块,它通过明确地建模通道之间的相互依赖关系,自适应地重新校准通道相关的特征响应。我们表明,这些块可以堆叠在一起,形成SENet架构,在不同的数据集之间极其有效地泛化。我们进一步证明,SE块为现有的最先进的CNNs带来了显著的性能改善,只需要略微增加计算成本。
SENet是2017年ILSVRC分类提交的基础,该分类提交获得了第一名,并将前5名的错误减少到2.251%,比2016年获奖的分类提交的错误提高了约25%。
1.介绍
在这篇论文中,我们研究了网络设计的一个不同方面——信道之间的关系。我们引入了一个新的建筑单元,我们称之为压缩和激发(SE)块,目的是通过明确地建模网络卷积特性的通道之间的相互依赖关系来提高网络生成的表示的质量。为此,我们提出了一种机制,允许网络执行特征重新校准,通过这种机制,网络可以学会使用全局信息,有选择地强调信息特征,并抑制不太有用的特征。
SE模块的结构如图1所示。可以通过简单地堆叠SE块的集合来构建SE网络(SENet)。SE块的特征重新校准的好处可以通过网络累积。SE块的结构是简单的,可以直接使用在现有的最先进的体系结构,在计算上也是轻量级的,只增加模型复杂度和计算负担。
2.相关工作
更深的架构
大量研究表面更深的网络架构更利于学习,这些研究大多集中在降低模型和计算复杂度的目标上,反映了一个假设,即通道关系可以被表述为一个具有局部接受域的实例不确定函数的组合。相反,我们认为,为单元提供一种机制,使用全局信息显式地建模通道之间的动态非线性依赖关系,可以简化学习过程,并显著增强网络的表征能力。
搜索算法架构
除了上面描述的工作,还有一个丰富的研究历史,旨在放弃手工架构设计,而寻求自动学习网络结构。
注意和控制机制
SE块包含一个轻量级的控制机制,该机制侧重于通过在计算效率上建模基于通道的关系来增强网络的表征能力。
3.SE块
在我们提出的结构中,Squeeze 和 Excitation 是两个非常关键的操作。
- Squeeze 操作:我们顺着空间维度来进行特征压缩,将每个二维的特征通道变成一个实数,这个实数某种程度上具有全局的感受野,并且输出的维度和输入的特征通道数相匹配。它表征着在特征通道上响应的全局分布,而且使得靠近输入的层也可以获得全局的感受野,这一点在很多任务中都是非常有用的。
- Excitation 操作:它是一个类似于循环神经网络中门的机制。通过参数 w 来为每个特征通道生成权重,其中参数 w 被学习用来显式地建模特征通道间的相关性。
- Reweight 的操作:我们将 Excitation 的输出的权重看做是进过特征选择后的每个特征通道的重要性,然后通过乘法逐通道加权到先前的特征上,完成在通道维度上的对原始特征的重标定。

上左图是将 SE 模块嵌入到 Inception 结构的一个示例。方框旁边的维度信息代表该层的输出。
这里我们使用 global average pooling 作为 Squeeze 操作。紧接着两个 Fully Connected 层组成一个 Bottleneck 结构去建模通道间的相关性,并输出和输入特征同样数目的权重。我们首先将特征维度降低到输入的 1/16,然后经过 ReLu 激活后再通过一个 Fully Connected 层升回到原来的维度。这样做比直接用一个 Fully Connected 层的好处在于:1)具有更多的非线性,可以更好地拟合通道间复杂的相关性;2)极大地减少了参数量和计算量。然后通过一个 Sigmoid 的门获得 0~1 之间归一化的权重,最后通过一个 Scale 的操作来将归一化后的权重加权到每个通道的特征上。
除此之外,SE 模块还可以嵌入到含有 skip-connections 的模块中。上右图是将 SE 嵌入到 ResNet 模块中的一个例子,操作过程基本和 SE-Inception 一样,只不过是在 Addition 前对分支上 Residual 的特征进行了特征重标定。如果对 Addition 后主支上的特征进行重标定,由于在主干上存在 0~1 的 scale 操作,在网络较深 BP 优化时就会在靠近输入层容易出现梯度消散的情况,导致模型难以优化。
目前大多数的主流网络都是基于这两种类似的单元通过 repeat 方式叠加来构造的。由此可见,SE 模块可以嵌入到现在几乎所有的网络结构中。通过在原始网络结构的 building block 单元中嵌入 SE 模块,我们可以获得不同种类的 SENet。如 SE-BN-Inception、SE-ResNet、SE-ReNeXt、SE-Inception-ResNet-v2 等等。
4.模型和计算复杂度
SENet 构造非常简单,而且很容易被部署,不需要引入新的函数或者层。除此之外,它还在模型和计算复杂度上具有良好的特性。拿 ResNet-50 和 SE-ResNet-50 对比举例来说,SE-ResNet-50 相对于 ResNet-50 有着 10% 模型参数的增长。额外的模型参数都存在于 Bottleneck 设计的两个 Fully Connected 中,由于 ResNet 结构中最后一个 stage 的特征通道数目为 2048,导致模型参数有着较大的增长,实验发现移除掉最后一个 stage 中 3 个 build block 上的 SE 设定,可以将 10% 参数量的增长减少到 2%。此时模型的精度几乎无损失。
另外,由于在现有的 GPU 实现中,都没有对 global pooling 和较小计算量的 Fully Connected 进行优化,这导致了在 GPU 上的运行时间 SE-ResNet-50 相对于 ResNet-50 有着约 10% 的增长。尽管如此,其理论增长的额外计算量仅仅不到 1%,这与其在 CPU 运行时间上的增长相匹配(~2%)。可以看出,在现有网络架构中嵌入 SE 模块而导致额外的参数和计算量的增长微乎其微。
5.实验
进行了图像分类、场景分类、COCO目标检测、ILSVRC 2017级比赛。
6.消融实验
- 减速比
- 挤压操作符
- 激励操作符
- 不同阶段
-
7.SE块的作用
SE块产生特定于实例的响应,而这些响应仍然能够支持模型在体系结构的不同层上日益增长的类特定需求。
8.结论
在本文中,我们提出了SE块,这是一种建筑单元,旨在通过使网络执行动态通道特征重校准来提高网络的表示能力。大量的实验证明了SENets的有效性,它可以在多个数据集和任务之间实现最先进的性能。此外,SE块揭示了以前的体系结构无法充分地为渠道明智的特性依赖建立模型的缺陷。我们希望这一见解可以被证明对其他需要强鉴别特征的任务有用。最后,SE块产生的特征重要度值可以用于其他任务,如模型压缩的网络剪枝。
9.PyTorch代码
class SENet(nn.Module):def __init__(self, block, layers, num_classes=1000):self.inplanes = 64super(SENet, self).__init__()self.conv1 = nn.Conv2d(3, 64, kernel_size=7 , stride=2, padding=3,bias=False)self.bn1 = nn.BatchNorm2d(64)self.relu = nn.ReLU(inplace=True)self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)self.layer1 = self._make_layer(block, 64, layers[0])self.layer2 = self._make_layer(block, 128, layers[1], stride=2)self.layer3 = self._make_layer(block, 256, layers[2], stride=2)self.layer4 = self._make_layer(block, 512, layers[3], stride=2)self.avgpool = nn.AvgPool2d(7, stride=1)self.fc = nn.Linear(512 * block.expansion, num_classes)for m in self.modules():if isinstance(m, nn.Conv2d):n = m.kernel_size[0] * m.kernel_size[1] * m.out_channelsm.weight.data.normal_(0, math.sqrt(2. / n))elif isinstance(m, nn.BatchNorm2d):m.weight.data.fill_(1)m.bias.data.zero_()def _make_layer(self, block, planes, blocks, stride=1):downsample = Noneif stride != 1 or self.inplanes != planes * block.expansion:downsample = nn.Sequential(nn.Conv2d(self.inplanes, planes * block.expansion,kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(planes * block.expansion),)layers = []layers.append(block(self.inplanes, planes, stride, downsample))self.inplanes = planes * block.expansionfor i in range(1, blocks):layers.append(block(self.inplanes, planes))return nn.Sequential(*layers)def forward(self, x):x = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.maxpool(x)x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)x = self.avgpool(x)x = x.view(x.size(0), -1)x = self.fc(x)return x
forward中三部分分别进行了Squeeze、Excitation 、Reweight
三、CBAM
论文链接:https://arxiv.org/abs/1807.06521
PyTorch代码链接:https://github.com/Jongchan/attention-module
0.摘要
论文提出了卷积块注意模块(CBAM),一个简单而有效的用于前馈卷积神经网络的注意模块。给定中间特征图,模块依次推导出沿通道和空间两个独立维度的注意图,然后将注意图乘到输入特征图上进行自适应特征细化。因为CBAM是一个轻量级的通用模块,它可以无缝地集成到任何CNN架构中,开销可以忽略不计,并且可以与基础CNN一起进行端到端培训。通过ImageNet-1K、MS COCO检测和VOC 2007检测数据集的广泛实验来验证CBAM。实验表明,不同模型在分类和检测性能上都有了一致的改进,证明了CBAM的广泛适用性。代码和模型将公开提供。
1.介绍

CBAM有两个顺序子模块:通道和空间。在深度网络的每个卷积块上,通过CBAM模块自适应地细化中间特征图。
在本文中,我们提出了一个新的网络模块,名为“卷积块注意模块”。由于卷积操作通过混合跨通道和空间信息来提取信息特征,我们采用我们的模块来强调两个主要维度上的有意义的特征:通道轴和空间轴。我们的模块通过学习强调或抑制哪些信息,有效地帮助信息在网络中流动。我们精心设计了轻量级模块,所以在大多数情况下参数和计算的开销可以忽略不计。
贡献:
- 提出了一种简单而有效的注意力模块(CBAM),可广泛应用于增强CNN的表示能力。
- 通过广泛的消融研究验证了注意力模块的有效性。
- 验证了在多个基准测试(ImageNet-1K, MS COCO,和VOC 2007)上,通过插入本文的轻型模块,各种网络的性能都得到了极大改善。
2.相关工作
网络工程、注意力机制3.CBAM——卷积模块的注意力机制模块
Channel attention module
这部分的工作与SENet很相似,都是首先将feature map在spatial维度上进行压缩,得到一个一维矢量以后再进行操作。与SENet不同之处在于,对输入feature map进行spatial维度压缩时,作者不单单考虑了average pooling,额外引入max pooling作为补充,通过两个pooling函数以后总共可以得到两个一维矢量。global average pooling对feature map上的每一个像素点都有反馈,而global max pooling在进行梯度反向传播计算只有feature map中响应最大的地方有梯度的反馈,能作为GAP的一个补充。
具体做法如下,以FF表示输入feature map,FcavgFavgc和FcmaxFmaxc分别代表经过global average pooling和global max pooling计算后的feature,W0W0和W1W1代表的是多层感知机模型中的两层参数,在这部分的计算可以用如下公式表示:
值得注意的是,多层感知机模型中W0W0和W1W1之间的feature需要使用ReLU作为激活函数去处理。Spatial attention module
这部分工作是论文跟SENet区别开来的一个重要贡献,除了在channel上生成了attention模型,作者表示在spatial层面上也需要网络能明白feature map中哪些部分应该有更高的响应。首先,还是使用average pooling和max pooling对输入feature map进行压缩操作,只不过这里的压缩变成了通道层面上的压缩,对输入特征分别在通道维度上做了mean和max操作。最后得到了两个二维的feature,将其按通道维度拼接在一起得到一个通道数为2的feature map,之后使用一个包含单个卷积核的隐藏层对其进行卷积操作,要保证最后得到的feature在spatial维度上与输入的feature map一致,如同上图中下部分网络所示。
定义经过max pooling和average pooling操作后的feature map为,Fsavg∈R1∗H∗WFavgs∈R1∗H∗W和Fsmax∈R1∗H∗WFmaxs∈R1∗H∗W,该部分的数学处理可以用以下公式表示:
σσ表示的是sigmoid激活函数,该部分显示的卷积层使用了7x7的卷积核,其实后面的实验中作者也是用了3x3的卷积核进行实验,总体来说7x7的卷积会表现更好。Arrangement of attention modules
两个attention的module,以何种顺序去设置和组合同样很重要。作者发现顺序排列比并行排列的结果更好。对于序列过程的排列,实验结果表明通道一阶比空间一阶略好。4.实验
这部分展示了论文的实验,作者总共在三个平台上对CBAM模块进行测试。首先作者展示了CBAM的有效性,然后对CBAM中的组合结构进行了分离实验。下图中展示了将CBAM嵌入到ResNet结构中的示意图。
在ImageNet-1K的实验中,作者在测试时对验证集的处理为size为224x224的center crop,初始学习率为0.1,每隔30个epochs下降为之前的0.1倍,总共训练90个epochs。Channel attention
这部分作者对比了论文的channel attention方法,主要以ResNet50作为baseline,同时对比了SE-ResNet50和本文的两个实验,分别是ResNet50+MaxPool和ResNet50+MaxPool & AvgPool,实验结果如下所示
通过与baseline的对比可以发现,attention方法能提升网络的表征能力,在其中使用了AvgPool作为spatial压缩的方法对比MaxPool体现出了更好的优越性,当将两者结合在一起以后网络的表征能力.Spatial attention
这一部分在channel attention的基础上讨论了spatial attention的内部组成对全局的影响,实验结果如下:
k表示了最后用于得到feature map的卷积层中卷积核的大小,倒数四行中括号的第一个操作,分别表示作者使用avg&max或者1x1的卷积层去将输入feature map进行通道上的压缩。最终,使用了7x7的卷积核和avg&max的通道压缩方式可以得到最好的分类结果,表示了更大的卷积核在深层上能够组合更加全局的特征,这对分类任务是证明有效的。Arrangement of the channel and spatial attention
关于channel attention module和spatial attention module的组合顺序,论文也做了实验,实验结果如下:
在SE-ResNet50作为baseline的对比中,作者考虑了三种情况:channel-first、spatial-first和parall的方式,可以看到,channel-first能取得更好的分类结果。
论文还进行了ImageNet-1K图像分类、Grad-CAM网络可视化、MS COCO物体检测、VOC 2007目标检测5.结论
CBAM是一种改进CNN网络表示能力的新方法。将基于注意力的特征细化应用与通道和空间两个不同的模块,在保持较小的开销的情况下实现了可观的性能提升。6.PyTorch代码
```python import torch import math import torch.nn as nn import torch.nn.functional as F
class BasicConv(nn.Module): def init(self, inplanes, outplanes, kernel_size, stride=1, padding=0, dilation=1, groups=1, relu=True, bn=True, bias=False): super(BasicConv, self).__init() self.out_channels = out_planes self.conv = nn.Conv2d(in_planes, out_planes, kernel_size=kernel_size, stride=stride, padding=padding, dilation=dilation, groups=groups, bias=bias) self.bn = nn.BatchNorm2d(out_planes,eps=1e-5, momentum=0.01, affine=True) if bn else None self.relu = nn.ReLU() if relu else None
def forward(self, x):
x = self.conv(x)
if self.bn is not None:
x = self.bn(x)
if self.relu is not None:
x = self.relu(x)
return x
class Flatten(nn.Module): def forward(self, x): return x.view(x.size(0), -1)
class ChannelGate(nn.Module): def init(self, gatechannels, reductionratio=16, pool_types=[‘avg’, ‘max’]): super(ChannelGate, self).__init() self.gate_channels = gate_channels self.mlp = nn.Sequential( Flatten(), nn.Linear(gate_channels, gate_channels // reduction_ratio), nn.ReLU(), nn.Linear(gate_channels // reduction_ratio, gate_channels) ) self.pool_types = pool_types def forward(self, x): channel_att_sum = None for pool_type in self.pool_types: if pool_type==’avg’: avg_pool = F.avg_pool2d( x, (x.size(2), x.size(3)), stride=(x.size(2), x.size(3))) channel_att_raw = self.mlp( avg_pool ) elif pool_type==’max’: max_pool = F.max_pool2d( x, (x.size(2), x.size(3)), stride=(x.size(2), x.size(3))) channel_att_raw = self.mlp( max_pool ) elif pool_type==’lp’: lp_pool = F.lp_pool2d( x, 2, (x.size(2), x.size(3)), stride=(x.size(2), x.size(3))) channel_att_raw = self.mlp( lp_pool ) elif pool_type==’lse’:
# LSE pool only
lse_pool = logsumexp_2d(x)
channel_att_raw = self.mlp( lse_pool )
if channel_att_sum is None:
channel_att_sum = channel_att_raw
else:
channel_att_sum = channel_att_sum + channel_att_raw
scale = F.sigmoid( channel_att_sum ).unsqueeze(2).unsqueeze(3).expand_as(x)
return x * scale
def logsumexp2d(tensor): tensor_flatten = tensor.view(tensor.size(0), tensor.size(1), -1) s, = torch.max(tensor_flatten, dim=2, keepdim=True) outputs = s + (tensor_flatten - s).exp().sum(dim=2, keepdim=True).log() return outputs
class ChannelPool(nn.Module): def forward(self, x): return torch.cat( (torch.max(x,1)[0].unsqueeze(1), torch.mean(x,1).unsqueeze(1)), dim=1 )
class SpatialGate(nn.Module): def init(self): super(SpatialGate, self).init() kernel_size = 7 self.compress = ChannelPool() self.spatial = BasicConv(2, 1, kernel_size, stride=1, padding=(kernel_size-1) // 2, relu=False) def forward(self, x): x_compress = self.compress(x) x_out = self.spatial(x_compress) scale = F.sigmoid(x_out) # broadcasting return x * scale
class CBAM(nn.Module): def init(self, gatechannels, reductionratio=16, pool_types=[‘avg’, ‘max’], no_spatial=False): super(CBAM, self).__init() self.ChannelGate = ChannelGate(gate_channels, reduction_ratio, pool_types) self.no_spatial=no_spatial if not no_spatial: self.SpatialGate = SpatialGate() def forward(self, x): x_out = self.ChannelGate(x) if not self.no_spatial: x_out = self.SpatialGate(x_out) return x_out ``` 代码包括了两部分:ChannelGate和SpatialGate来提取特征
