之前的 文章 很详细的写了
bottleneck block的实现。
先回顾一下block的意义,网络的性能归根结底是对特征提取的优秀程度,
对特征提取越好,后面的分类,检测,分割等等,就会有更好的性能。
但是很深的网络会非常难训练,一方面是因为参数巨大,容易过拟合,
但是resnet实验中证明并不是因为过拟合导致的准确率下降,因为在训练阶段的准确率也下降了,
这并不是过拟合的结果。
另一方面,梯度的反向传播经过非常多次相乘会变得很小,这就导致了梯度消失现象,
因此性能饱和,无法进一步优化。
所以resnet不是一味的加深网络层数而提高性能的。
resnet的基本假设是,对于一个效果很好的网络,我在后面加上和前面层一模一样的层的时候,
模型的性能至少应该是和之前的浅层网络持平,
在下图中可以看到一个叫building block的基本结构,
block设计的初衷就是为了让x有两条路走,
第一条就是identify mapping,让新的层即使不学任何信息也能和上一层持平,
第二条就是resduial mapping,identify mapping确保了已经学到的信息被完整的保存下来,
如果需要有改进,那么就在resduial mapping中进行,也就是F(x)中进行,
F(x)学到的是block的输出相对于block输入的变化,如果F(x)什么都学不到,那么模型保持之前x状态的性能,即不会变差。
说了这么多,可以发现,F(x)对block之间的信息变化学的越好,那么模型的性能就越好。
因此如何设计F(x) 成了一个热门点。
在最初的resnet中,设计了两种F(x),也可以说是设计了两种block,左边叫build block,右边叫bottleneck block。一般有4个block和两个全连接层(早期用),可以构造为
resnet 18=2+(2+2+2+2)*2 ;
resnet 34=2+(3+4+6+3)*2;
resnet 50=2+(3+4+6+3)*3;
resnet 101=2+(3+4+23+3)*3;
resnet 152=2+(3+8+36+3)*3;
......
大多数简单任务,使用resnet 34或者50即可。
以上所说,统称为resent v1,发表在Deep Residual Learning for Image Recognition中,
在resnet v2,主要是引入了BN,如下图右边。
图中的resduial mapping都换到了右侧。
关于为什么使用full preactivation结构的解释,准备结合上一篇文章一起解释(两篇合一篇),以及不同channel的处理,在写完se block 和 gc block之后整理。
写了这么多前言,正题开始了,先讲Squeeze Excitation Net。
SE Net的核心思想是通过channel信息的attention机制(重新分布权重)来达到更好的学习效果。
从上面的图和下面的代码可以看出,在右侧的se blocke模块,我们将整个channel通道的重要信息都提取了出来,然后乘以原有信息,这就是通过注意力机制进行信息加强。
def se_block(bottom, ratio=16):
weight_initializer = tf.contrib.layers.variance_scaling_initializer()
bias_initializer = tf.constant_initializer(value=0.0)
# Bottom [N,H,W,C]
# Global average pooling
#with tf.variable_scope("se_block"):
channel = bottom.get_shape()[-1]
se = tf.reduce_mean(bottom, axis=[1,2], keep_dims=True)#[B,1,1,C]
#squeeze过程 Global average pooling
assert se.get_shape()[1:] == (1,1,channel)
se = tf.layers.dense(se, channel//ratio, activation=tf.nn.relu,
kernel_initializer=weight_initializer,
bias_initializer=bias_initializer)
#[B,1,1,C/ratio]
assert se.get_shape()[1:] == (1,1,channel//ratio)
se = tf.layers.dense(se, channel, activation=tf.nn.sigmoid,
kernel_initializer=weight_initializer,
bias_initializer=bias_initializer)
#[B,1,1,C]
assert se.get_shape()[1:] == (1,1,channel)
top = bottom * se
#[B,H,W,C] * [B,1,1,C]=[B,H,W,C]
return top
def res_block2(bottom, filters, training, use_bn, use_se_block, strides=1, downsample=False):
path_2=bottom
#conv 3x3
path_1 = conv_layer(bottom, filters[0], kernel_size=3,strides=strides)
path_1 = norm_layer(path_1, training, use_bn)
path_1 = relu(path_1)
# conv 3x3
path_1 = conv_layer(path_1, filters[1], kernel_size=3)
path_1 = norm_layer(path_1, training, use_bn)
path_1 = relu(path_1)
if use_se_block:
path_1=se_block(path_1) #将两个3x3卷积的block变成了se block
if downsample:
path_2 = conv_layer(path_2, filters[1], kernel_size=1, strides=strides)
path_2 = norm_layer(path_2, training, use_bn)
top = path_1 + path_2
top = relu(top)
return top
Spatial Attention Module
SAM是基于通道进行全局平均池化以及全局最大池化操作,产生两个代表不同信息的特征图,
合并后再通过一个感受野较大的7×7卷积进行特征融合,大的卷积核对获取全局特征有帮助。
最后再通过Sigmoid操作来生成权重图叠加回原始的输入特征图,从而使得目标区域得以增强。
import torch
from torch import nn
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) # 7,3 3,1
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avg_out = torch.mean(x, dim=1, keepdim=True) # 8x1x300x300 avg pooling
max_out, _ = torch.max(x, dim=1, keepdim=True) # 8x1x300x300 max pooling
x = torch.cat([avg_out, max_out], dim=1) # 8x2x300x300
x = self.conv1(x) # 8x1x300x300
return self.sigmoid(x) #太大变成1 太小变成0
if __name__ == '__main__':
SA = SpatialAttention(7)
data_in = torch.randn(8,32,300,300)
data_out = SA(data_in)
print(data_in.shape) # torch.Size([8, 32, 300, 300])
print(data_out.shape) # torch.Size([8, 1, 300, 300])
关于CAM和CBAM可以参考
博客地址