原文链接:https://blog.csdn.net/xu380393916/article/details/109304082
一、SENET SENET是2017年的世界冠军,SE全称Squeeze-and-Excitation是一个模块,将现有的网络嵌入SE模块的话,那么该网络就是SENet,它几乎可以嵌入当前流行的任何网络,那么为什么会引出这个东西呢,来看下图:
SE结构 一个SEblock的过程分为 Squeeze(压缩) 和 Excitation(激发) 两个步骤: Squeeze(压缩) 通过在Feature Map层上执行Global Average Pooling,得到当前Feature Map的全局压缩特征量; Excitation(激发) 通过两层全连接的bottleneck结构得到Feature Map中每个通道的权值,并将加权后的Feature Map作为下一层网络的输入 一个feature map经过一系列卷积池化得到的feature map,通常我们认为这个得到的feature map的每个通道都是同样重要的,我们并没有分那个通道重要,那个通道不怎么重要,那么实际上会不会有这种情况呢,就是得到的featurmap 的每个通道的重要性都不一样,比如确实有的图片三个通道,我只有一个通道有用,其它通道没什么用,比如一张包含动物的图片,那么背景肯定不怎么重要,它转化为灰度图后,只有一个通道比较重要,其它的不怎么重要,那么其实实际上我们得到的feature map每个通道的重要程度还是不一样的,也就是说每个通道其实还应该有一个重要性权值才行,然后每个通道的重要性权值乘以每个通道原来的值,就是我们求的真正feature map,这个feature map不同的通道重要性不一样(可能权值大的乘以原来的数要大些)如上面得到最终图,每个通道颜色不一样,也就代表着不同的重要性 那么每个通道的权值或者重要性怎么来呢,就是上面这块,做法如下:
其实很简单,假入原来feature map 是 h * w * c的,给它做一个global池化(池化窗口就是h * w 得到的就是1 * 1窗口,通道数不变)得到 1 * 1 * c的feature map,然后它再接两个全连接层(第一个全连接层神经元个数是c/16,相当于对c进行了降维,输入是c个特征,第二个全连接层神经元个数为c,相当于又增维回到了c个特征,这样做比直接用一个 Fully Connected 层的好处在于具有更多的非线性,可以更好地拟合通道间复杂的相关性,极大地减少了参数量和计算量),然后再接一个sigmod层(这里采用sigmod应该是通道之间是具有相关性的,所以不能用softmax,softmax的话,最终加起来必须为1),输出1 * 1 * c, 原来的feature map维度h * w * c,得到的是通道的权值维度1 * 1 * c,然后它们进行相乘,这里是全乘,不是矩阵相乘,然后得到的feature map对应的每个通道重要性就不一样了(可能更重要的它的值要大些) 那么h * w * c 和1 * 1 * c 是怎么相乘的呢?看一个例子
像这样相乘
这个是一个通道的,看到没它乘的倍率是1,
这个通道的倍率是乘2
它就是这么实现不同通道乘以不同的倍率,从而得到不同的重要性的 相当于是每次乘以一列通道
注意:对于图片数组,我们来熟悉下:
一行
一行里面一个像素点对应的一列通道
一个像素 注意: 每个通道的权值都是网路学习出来的,那么怎么学习呢,记住学习,只要有参数和loss就可以,如下
所以它主要学习的是SE模块这两个全连接层的参数,它们学到了,自然最终结果就有了,所用用最终的分类损失去更新这两个全连接层的参数就可以了 #注意上面采用两层全连接,第一层全连接单元个数为c/16,主要是为了压缩参数的如下, c. * c/r + c/r * c = 2c2/r = 2/r * c2 只用一层全连接参数的话参数个数: c * c = c * 2
实际中可能会用一个比例reduction_ratio reduction_ratio = 0.5的话 那么就是c * 0.5c + 0.5c * c = c2 reduction_ratio如果 = 0.3的话那么就是c * 0.3c + 0.3c * c = 0.6c2 如果只用一层全连接层的话那么就是 c * c = c * 2个,显然参数减少了0.4c2个
SE的代码实现 SE模块实现的 Pytorch版:
class SELayer(nn.Module):
def __init__(self, channel, reduction=16):
super(SELayer, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Sequential(
nn.Linear(channel, channel // reduction, bias=False),
nn.ReLU(inplace=True),
nn.Linear(channel // reduction, channel, bias=False),
nn.Sigmoid()
)
def forward(self, x):
b, c, _, _ = x.size()
y = self.avg_pool(x).view(b, c)
y = self.fc(y).view(b, c, 1, 1)
return x * y.expand_as(x)
12345678910111213141516
SE-ResNet模型的 Pytorch版:
class SEBottleneck(nn.Module):
expansion = 4
def __init__(self, inplanes, planes, stride=1, downsample=None, reduction=16):
super(SEBottleneck, self).__init__()
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes)
self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(planes * 4)
self.relu = nn.ReLU(inplace=True)
self.se = SELayer(planes * 4, reduction)
self.downsample = downsample
self.stride = stride
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
out = self.se(out)
if self.downsample is not None:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
123456789101112131415161718192021222324252627282930313233343536373839
二、CBAM模块 CBAM 它是2018年的分类冠军,它和SE一样也是一个模型,现在任何流行网络都可以嵌入这个模块,那么它的由来是什么呢? SE的由来是因为不同通道的像素的重要性可能不一样,那么既然这样,同一个通道的不同位置像素重要性也可能不一样,所以就有了CBAM,既考虑不同通道像素的重要性,又考虑了同一通道不同位置像素的重要性
组成 该注意力模块( CBAM ),可以在通道和空间维度上进行 Attention 。其包含两个子模块 Channel Attention Module(CAM) 和 Spartial Attention Module(SAM) Convolutional Block Attention Module (CBAM) 表示卷积模块的注意力机制模块。是一种结合了空间(spatial)和通道(channel)的注意力机制模块。相比于senet只关注通道(channel)的注意力机制可以取得更好的效果。 它相对于SE多了一个空间attension,这个空间其实就是宽高对应的方形或者说是一个通道对应的feature map,SE只关注通道,它既关注通道,也关注宽高, 它的大概流程如下:
CAM的结构与SE有何区别? 其结构如上图所示,相比SE,只是多了一个并行的Max Pooling层。那为什么加个并行的呢?结果导向,作者通过实验说明这样的效果好一些,我感觉其好一些的原因应该是多一种信息编码方式,使得获得的信息更加全面了吧,可能再加一些其他并行操作效果会更好? 先来看通道上的attension:
它这里和SE模块有点区别就是,SE只用了一个池化globalpool(一般是maxpool),而它这里用了两个池化maxpool,avgpool(池化本身是提取高层次特征,不同的池化意味着提取的高层次特征更加丰富),既然是两个那么输出肯定也是两个都是11c,然后将输出两个相加,再进行sigmod,结果也是11c,然后再和原来的feature map相乘 然后再来看空间上的attension
它的过程也很简单: 它是原来的feature map先做完通道attension ,然后它在这个基础之上进一步做空间attension的,它先将feature map 进行基于通道的池化,一般的池化都是在长宽的维度,它这个其实就是在列通道的维度池化(取一列通道的最大值,平均值),这就意味着一次池化一列通道变成了一个值就是一个通道了,长宽不变,假如输入feature map是 h * w * c,它这个一次池化后就变成了h * w * 1的feature map了,它进行了两次池化,那就是两个h * w * 1的feature map,然后将它们进行基于通道的拼接,那就变成了 h * w * 2的feature map了,然后对这个feature map用一个7*7的卷积核进行卷积,将通道数压缩成了1(因为只用了一个卷积核),得到一个新的feature map,然后对这个feature map进行sigmod的,得到一个attension feature map,然后将它和最开始的feature map进行相乘,比如下面: 原先的经过channel attension后的feature map是 h * w * c,那么经过空间attension后,它得到的attension feature map就是 h * w * 1了,那么它们怎么相乘呢,如下: b.shape = (2,2,3)
这一列就是一个通道上的像素,显然,他每次乘都是乘以一个通道上的像素,对一个通道上的像素进行增大或者缩小,下一次再乘以另外一个通道的
最终它这个空间attension feature map肯定也是学习出来的,那么具体是学习什么呢?肯定是参数,它这个过程有哪些参数,其实就是再到数第二步,用一个77的卷积核对输入的2层feature map卷积,那么它学习的就是这个77的卷积核参数
代码实现
def cbam_module(inputs,reduction_ratio=0.5,name=""):
with tf.variable_scope("cbam_"+name, reuse=tf.AUTO_REUSE):
#假如输入是[batsize,h,w,channel],
#channel attension 因为要得到batsize * 1 * 1 * channel,它的全连接层第一层
#隐藏层单元个数是channel / r, 第二层是channel,所以这里把channel赋值给hidden_num
batch_size,hidden_num=inputs.get_shape().as_list()[0],inputs.get_shape().as_list()[3]
#通道attension
#全局最大池化,窗口大小为h * w,所以对于这个数据[batsize,h,w,channel],他其实是求每个h * w面积的最大值
#这里实现是先对h这个维度求最大值,然后对w这个维度求最大值,平均池化也一样
maxpool_channel=tf.reduce_max(tf.reduce_max(inputs,axis=1,keepdims=True),axis=2,keepdims=True)
avgpool_channel=tf.reduce_mean(tf.reduce_mean(inputs,axis=1,keepdims=True),axis=2,keepdims=True)
#上面全局池化结果为batsize * 1 * 1 * channel,它这个拉平输入到全连接层
#这个拉平,它会保留batsize,所以结果是[batsize,channel]
maxpool_channel = tf.layers.Flatten()(maxpool_channel)
avgpool_channel = tf.layers.Flatten()(avgpool_channel)
#将上面拉平后结果输入到全连接层,第一个全连接层hiddensize = channel/r = channel * reduction_ratio,
#第二哥全连接层hiddensize = channel
mlp_1_max=tf.layers.dense(inputs=maxpool_channel,units=int(hidden_num*reduction_ratio),name="mlp_1",reuse=None,activation=tf.nn.relu)
mlp_2_max=tf.layers.dense(inputs=mlp_1_max,units=hidden_num,name="mlp_2",reuse=None)
#全连接层输出结果为[batsize,channel],这里又降它转回到原来维度batsize * 1 * 1 * channel,
mlp_2_max=tf.reshape(mlp_2_max,[batch_size,1,1,hidden_num])
mlp_1_avg=tf.layers.dense(inputs=avgpool_channel,units=int(hidden_num*reduction_ratio),name="mlp_1",reuse=True,activation=tf.nn.relu)
mlp_2_avg=tf.layers.dense(inputs=mlp_1_avg,units=hidden_num,name="mlp_2",reuse=True)
mlp_2_avg=tf.reshape(mlp_2_avg,[batch_size,1,1,hidden_num])
#将平均和最大池化的结果维度都是[batch_size,1,1,channel]相加,然后进行sigmod,维度不变
channel_attention=tf.nn.sigmoid(mlp_2_max+mlp_2_avg)
#和最开始的inputs相乘,相当于[batch_size,1,1,channel] * [batch_size,h,w,channel]
#只有维度一样才能相乘,这里相乘相当于给每个通道作用了不同的权重
channel_refined_feature=inputs*channel_attention
#空间attension
#上面得到的结果维度依然是[batch_size,h,w,channel],
#下面要进行全局通道池化,其实就是一条通道里面那个通道的值最大,其实就是对channel这个维度求最大值
#每个通道池化相当于将通道压缩到了1维,有两个池化,结果为两个[batch_size,h,w,1]feature map
maxpool_spatial=tf.reduce_max(inputs,axis=3,keepdims=True)
avgpool_spatial=tf.reduce_mean(inputs,axis=3,keepdims=True)
#将两个[batch_size,h,w,1]的feature map进行通道合并得到[batch_size,h,w,2]的feature map
max_avg_pool_spatial=tf.concat([maxpool_spatial,avgpool_spatial],axis=3)
#然后对上面的feature map用1个7*7的卷积核进行卷积得到[batch_size,h,w,1]的feature map,因为是用一个卷积核卷的
#所以将2个输入通道压缩到了1个输出通道
conv_layer=tf.layers.conv2d(inputs=max_avg_pool_spatial, filters=1, kernel_size=(7, 7), padding="same", activation=None)
#然后再对上面得到的[batch_size,h,w,1]feature map进行sigmod,这里为什么要用一个卷积核压缩到1个通道,相当于只得到了一个面积的值
#然后进行sigmod,因为我们要求的就是feature map面积上不同位置像素的中重要性,所以它压缩到了一个通道,然后求sigmod
spatial_attention=tf.nn.sigmoid(conv_layer)
#上面得到了空间attension feature map [batch_size,h,w,1],然后再用这个和经过空间attension作用的结果相乘得到最终的结果
#这个结果就是经过通道和空间attension共同作用的结果
refined_feature=channel_refined_feature*spatial_attention
return refined_feature
参考文献 CV领域常用的注意力机制模块(SE、CBAM)_weixin_42907473的博客-CSDN博客 https://blog.csdn.net/weixin_42907473/article/details/106525668 深度学习之卷积网络attention机制SENET、CBAM模块原理总结_weixin_33602281的博客-CSDN博客 https://blog.csdn.net/weixin_33602281/article/details/85223216 ———————————————— 版权声明:本文为CSDN博主「JY丫丫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/xu380393916/article/details/109304082
|