目录
1. ResNet介绍
ResNet(残差网络)是一种深度卷积神经网络模型,由Kaiming He等人于2015年提出。它的提出解决了深度神经网络的梯度消失和梯度爆炸问题,使得深层网络的训练变得更加容易和有效。
在深度神经网络中,随着网络层数的增加,梯度在反向传播过程中逐渐变小,导致网络的训练变得困难。这是因为在传统的网络结构中,每个网络层都是通过直接逐层堆叠来进行信息的传递。当网络层数增加时,信息的传递路径变得更长,导致梯度逐渐消失。为了解决这个问题,ResNet提出了“残差学习”的概念。
ResNet引入了“残差块”(residual block)的概念,其中每个残差块包含一个跳跃连接(skip connection),将输入直接添加到输出中。这个跳跃连接允许梯度直接通过残差块传递,避免了梯度的消失问题。通过残差块的堆叠,ResNet可以构建非常深的网络,如ResNet-50、ResNet-101等。
ResNet的提出极大地促进了深度神经网络的发展。它在多个视觉任务上取得了非常好的性能,成为了目标检测、图像分类、图像分割等领域的重要基准模型。同时,ResNet的思想也影响了后续的深度神经网络架构设计,被广泛应用于各种深度学习任务中。
Resnet 太经典了,以至于后面基本上都采用了‘残差块’的结构,关于resnet网络的详细介绍,可以百度,或者参考本人之前的 博文
2. CBAM 模块
CBAM代表卷积块注意力模块。它是一种神经网络架构,增强了卷积神经网络(CNN)中的注意力机制。CBAM模块由两个注意力机制组成,即通道注意力模块(CAM)和空间注意力模块(SAM)。
通道注意力模块侧重于通过计算通道统计数据并将这些统计数据的加权和应用于每个通道来捕获通道之间的全局依赖关系。这使得网络能够强调重要渠道,抑制无关渠道。
另一方面,空间注意力模块捕捉图像中空间位置之间的依赖关系。它通过考虑每个空间位置相对于其相邻位置的重要性来计算空间注意力图。这有助于网络关注重要的空间区域,忽略信息量较小的区域。
通过结合信道注意力模块和空间注意力模块,CBAM模块使网络能够动态调整CNN内的注意力。这提高了图像分类、对象检测和语义分割等任务的性能。
python的代码实现如下:
class ChannelAttention(nn.Module):
def __init__(self, in_planes, reduction=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 // reduction, 1, bias=False)
self.relu1 = nn.ReLU()
self.fc2 = nn.Conv2d(in_planes // reduction, 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
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)
x = self.conv1(x)
return self.sigmoid(x)
class CBAM(nn.Module):
def __init__(self, in_planes, reduction=16, kernel_size=7):
super(CBAM, self).__init__()
self.ca = ChannelAttention(in_planes, reduction)
self.sa = SpatialAttention(kernel_size)
def forward(self, x):
out = x * self.ca(x)
out = out * self.sa(out)
return out
3. resnet + cbam
resnet不同版本的结构如下
其实都是5层的结构,不过每层的相同模块重复多少次的区别
对于浅层(18,34)的残差块,可以看到没有1*1的卷积,因为网络并不大。而后面的50、101、152因为通道数很多,导致网络参数量很大,所以使用了1*1进行降维
添加模块很简单,因为CBAM模块的输入维度和输出维度是一样的,这样就可以在resnet网络的任意位置进行添加。
就比如vgg都是3*3连续两次的卷积,就是因为通道一样,那我想要连续卷积3次,甚至4次五次也只是重复叠加就行了,就类似于这个CMAB模块
事实上,通道不一样也可以添加,不过要更改维度稍微麻烦一点罢了
3.1 添加在每个layer层后
关键代码如下
net.layer1.add_module('cbam',CBAM(net.layer1[-1].conv2.out_channels))
net.layer2.add_module('cbam',CBAM(net.layer2[-1].conv2.out_channels))
net.layer3.add_module('cbam',CBAM(net.layer3[-1].conv2.out_channels))
net.layer4.add_module('cbam',CBAM(net.layer4[-1].conv2.out_channels))
这里的net就是resnet18 ~ resnet152网络,如果想要在单独某个层后添加,将对应的layerx注释即可,网络仍然可以正常运行
因为CBAM模块需要知道上一层传入的通道数是多少,这里经过测试发现,不同版本resnet的不同layer的最后模块都是下面这样,那么将它取出,传给resnet就行了
(1): BasicBlock(
(conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
添加后的网络结构如下: