网络的完整结构,可通过下面代码显示在控制台上
import keras_resnet.models
import keras
shape, classes = (224, 224, 3), 1000
x = keras.layers.Input(shape)
model = keras_resnet.models.ResNet50(x)
model.summary()
网络源码实现:
下面节选自python3.6/site-packages/keras_resnet/models/_2d.py
刚开始,第一层,含有补边,卷积,BN,激活函数五个部分
备注:网络中,一般把含有可训练参数的模块称为一层,比如卷积层称为一层,而激活函数等不含可训练参数的模块一般不称为一层。
1. keras.layers.ZeroPadding2D(padding=3, name="padding_conv1")(inputs)
功能:对输入inputs的4个边进行补0,比如若输入图像尺寸为(224,224,3),则每个边多补充3行(列)0,
则宽就变成了224+3+3=230, 即经过这一层后,图片尺寸转变为(230,230,3)
2. keras.layers.Conv2D(64, (7, 7), strides=(2, 2), use_bias=False, name="conv1")(x)
功能:卷积层,卷积核(7,7,64), strid=2, 若输入图片x的尺寸为(230,230,3),则经过这个卷积层后的尺寸为:
(230-7+1)/2=112, 因此输入(230,230,3)的图片经过这个卷积层后,输出尺寸为(112,112,64)
3.keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn_conv1")(x)
BN层,对输入进行归一化,不改变输入的尺寸,输出尺寸为(112,112,64)
4.keras.layers.Activation("relu", name="conv1_relu")(x)
激活函数层,用的relu激活函数,不改变输入的尺寸,输出尺寸为(112,112,64)
5.keras.layers.MaxPooling2D((3, 3), strides=(2, 2), padding="same", name="pool1")(x)
池化层, 输出的计算公式同上述卷积层,若输入维度(112,112,64),则输出尺寸为(56,56,64)
(112-3+1)/2=????这里结论是56,怎么算出来的,不知道哦
6. 后面是block的构造
另外,对于resnet来讲,还有一个重要的bottleneck_2d模块,位于python3.6/site_packages/keras_resnet/blocks/_2d.py中
def bottleneck_2d(filters, stage=0, block=0, kernel_size=3, numerical_name=False, stride=None, freeze_bn=False):
if stride is None:
if block != 0 or stage == 0:
stride = 1
else:
stride = 2
def f(x):
y = keras.layers.Conv2D(filters, (1, 1), strides=stride, use_bias=False, name="res{}{}_branch2a".format(stage_char, block_char), **parameters)(x)
y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2a".format(stage_char, block_char))(y)
y = keras.layers.Activation("relu", name="res{}{}_branch2a_relu".format(stage_char, block_char))(y)
y = keras.layers.ZeroPadding2D(padding=1, name="padding{}{}_branch2b".format(stage_char, block_char))(y)
y = keras.layers.Conv2D(filters, kernel_size, use_bias=False, name="res{}{}_branch2b".format(stage_char, block_char), **parameters)(y)
y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2b".format(stage_char, block_char))(y)
y = keras.layers.Activation("relu", name="res{}{}_branch2b_relu".format(stage_char, block_char))(y)
y = keras.layers.Conv2D(filters * 4, (1, 1), use_bias=False, name="res{}{}_branch2c".format(stage_char, block_char), **parameters)(y)
y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2c".format(stage_char, block_char))(y)
if block == 0:
shortcut = keras.layers.Conv2D(filters * 4, (1, 1), strides=stride, use_bias=False, name="res{}{}_branch1".format(stage_char, block_char), **parameters)(x)
shortcut = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch1".format(stage_char, block_char))(shortcut)
else:
shortcut = x
y = keras.layers.Add(name="res{}{}".format(stage_char, block_char))([y, shortcut])
y = keras.layers.Activation("relu", name="res{}{}_relu".format(stage_char, block_char))(y)
return y
return f
把这个称为block2a,可以看出,这个块中,共包含了3层,
第一层:stride=1,filters=64卷积层keras.layers.Conv2D(filters, (1, 1), strides=stride),这里会哟stride的确定,可以看出stride会在1和2之间确定, 输出尺寸宽=(56-1+1)/1=56, 输出尺寸为(56,56,64)
BN模块,对输入进行归一化,不改变输入的尺寸,输出尺寸为(56,56,64)
激活函数,用的relu激活函数,不改变输入的尺寸输出尺寸为(56,56,64)
ZeroPadding2D(padding=1)层,输出尺寸为(58,58,64)
第二层:卷积模块tride=1,filters=64,kernel_size=(3,3)输出尺寸宽=(58-3+1)/1=56, 输出尺寸为(56,56,64)
BN模块,,对输入进行归一化,不改变输入的尺寸,输出尺寸为(56,56,64)
激活函数层,用的relu激活函数,不改变输入的尺寸输出尺寸为(56,56,64)
第三层::卷积层,stride=1,filters=64*4,kernel_size=(1,1)输出尺寸宽=(56-1+1)/1=56, 输出尺寸为(56,56,256)
BN模块,,对输入进行归一化,不改变输入的尺寸,输出尺寸为(56,56,256)
构造shortcut参差块
若block=0
第10层:卷积层,stride=1,filters=64*4,kernel_size=(1,1)输出尺寸宽=(56-1+1)/1=56, 输出尺寸为(56,56,256)
第11层:shortcut=BN层,对输入进行归一化,不改变输入的尺寸,输出尺寸为(56,56,256)
若block!=0
shortcut=block的输入
ADD层,将第9层输出与shortcut相加, 输出尺寸(56,56,256)
激活函数,用的relu激活函数,不改变输入的尺寸输出尺寸为(56,56,256)
block构造结束后,block2a的输出(56,56,256),直接添加进模型的outputs, 并是下一个block的输入,,第一个block结束,到此共有5+13=18层,
循环进入第2个block2b,,其基本结构同block2a,但少了2层,共11层
下面分析第2个个block2b,共11层,
1: 卷积层,2.BN层,3.激活层4.zeropadding层,5:卷积层,6.BN层,7.激活层8.卷积9.BN 10. ADD 11.relu ,
block2b的输出(56,56,256), 到此共有5+13+11=29层,
循环进入第3个block2c,,其基本结构同block2b,共11层
1: 卷积层,2.BN层,3.激活层4.zeropadding层,5:卷积层,6.BN层,7.激活层8.卷积9.BN 10. ADD 11.relu ,
block2c的输出(56,56,256), 到此共有5+13+11+11=40层,
循环进入第4个block3a,,其基本结构同block2a,共13层
1: 卷积层,2.BN层,3.激活层4.zeropadding层,5:卷积层,6.BN层,7.激活层8.卷积9.卷积,10.BN 11.BN12. ADD 13.relu ,
block3a的输出(28,28,512), 到此共有5+13+11+11+13=53层,
循环进入第5个block3b,,其基本结构同block2b,共11层
block3b的输出(28,28,512), 到此共有5+13+11+11+13+11=64层,
循环进入第6个block3c,其基本结构同block2c,共11层
block3b的输出(28,28,512), 到此共有5+13+11+11+13+11+11=75层,
循环进入第7个block3d,,其基本结构同block2c,共11层
block3d的输出(28,28,512), 到此共有5+13+11+11+13+11+11+11=86层,
循环进入第8个block4a,,其基本结构同block2c,共11层
block3d的输出(28,28,512), 到此共有5+13+11+11+13+11+11+11=86层,
循环进入第block4b(11层),block4c(11层),.block4d(11层),block4e(11层),block4f(11层)....
block4f的输出(14,14,1024), 到此共有5+13+11+11+13+11+11+11+13+11*5=152层,
循环进入block5a(13层),block5b(11层),block5c(11层)
block5c的输出(7,7,2048), 到此共有152+13+11+11=187层
由此看来,并不50层啊,一直以为其是50层,为什么叫resnet50呢?
对于骨干网络resnet50来说,其输出共包含4个block的输出,有block2c,输出(?, 56, 56, 256),有block3d,(?, 28, 28, 512)
有block4f,(?, 14, 14, 1024),有block5c,(?, 7, 7, 2048)
下面只取出了3,4,5block的输出, 用上述的resnet作为骨干,我们又继续向下构造网络
pyramid_layer_1:卷积层, feature_depth=256, kernerl_size=1, stride=1, 输入为block5c,(?, 7, 7, 2048),输出宽
(7-1+1)/1=7, 输出p1_out尺寸:(?, 7, 7, 256),
pyramid_layer_2:upsamplelike层, 输入为[p1_out,block4f],则输入尺寸:[7,7,256],[14,14,1024],输出p2_out?
pyramid_layer_3:卷积层,输入为p1_out, feature_depth=256, kernerl_size=3, stride=1,输出宽
(7-3+1)/1=5, 输出p3_out尺寸:(?,5, 5, 256)
pyramid_layer_4:卷积层,输入为block4f, feature_depth=256, kernerl_size=1, stride=1,输出宽
(14-1+1)/1=14, 输出p4_out尺寸:(?,14, 14, 256)
pyramid_layer_5:ADD层,输入为p2_out,p4_out(?,14, 14, 256), , 输出p5_out尺寸:?
pyramid_layer_2:upsamplelike层, 输入为[p1_out,block4f],则输入尺寸:[7,7,256],[14,14,1024],输出p2_out?
待续:retinanet/keras_retinanet/models/retinanet.py/_create_pyramid_features()