https://mp.csdn.net/postedit
一个feature map经过一系列卷积池化得到的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 是怎么相乘的呢?看一个例子
相当于是每次乘以一列通道
注意:对于图片数组
一行里面一个像素点对应的一列通道一个像素
注意:
每个通道的权值都是网路学习出来的,那么怎么学习呢,记住学习,只要有参数和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的由来是因为不同通道的像素的重要性可能不一样,那么既然这样,同一个通道的不同位置像素重要性也可能不一样,所以就有了CBAM,既考虑不同通道像素的重要性,又考虑了同一通道不同位置像素的重要性
Convolutional Block Attention Module (CBAM) 表示卷积模块的注意力机制模块。是一种结合了空间(spatial)和通道(channel)的注意力机制模块。相比于senet只关注通道(channel)的注意力机制可以取得更好的效果。
它相对于SE多了一个空间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)
on 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
---------------------
作者:weixin_33602281
来源:CSDN
原文:https://blog.csdn.net/weixin_33602281/article/details/85223216
版权声明:本文为博主原创文章,转载请附上博文链接!