目录
一. ResNet网络模型
ResNet的提出
残差网络原理
ResNet 网络模型
二. 代码复现
ResNet残差网络主要是通过残差块组成的,在提出残差网络之前,网络结构无法很深,在VGG中,卷积网络达到了19层,在GoogLeNet中,网络达到了22层。随着网络层数的增加,网络发生了退化(degradation)的现象:随着网络层数的增多,训练集loss逐渐下降,然后趋于饱和,当你再增加网络深度的话,训练集loss反而会增大。而引入残差块后,网络可以达到很深,网络的效果也随之变好
与普通网络的串行结构相比,残差单元增加了跳跃映射,将输入与输出直接进行相加,补充卷积过程中损失的特征信息,这点与U-net的跳跃连接结构有点类似,不过Res中的跳跃连接做的是Add操作,而U-net的跳跃连接做的是Concatenate操作,还是有本质的不同。
Fig1. 故此网络块的输出为:
因为相加必须保证与是同维度的,因此可以写成通式如下式,用于匹配维度。
有两种维度匹配的方式(A)用zero-padding增加维度 (B)用1x1卷积增加维度
resnet18、resnet34、resnet50、resnet101、resnet152结构
Fig2.降采样由conv3 1、conv4 1和conv5 1以2的步幅执行
Fig3.ImageNet的网络架构示例。左:VGG-19模型[40](196亿次失败)作为参考。Mid-dle:具有34个参数层(36亿次浮点运算)的普通网络。右图:具有34个参数层(36亿次浮点运算)的剩余网络。虚线快捷方式增加了维度。表1显示了更多细节和其他变体
从上图Fig2, 可以看出(18layer\34layer)和(50\101\152layer) 的残差结构不一样,如下:
Fig4.ResNet的深度残差函数F。左图:称为BasicBlock结构 ,用于ResNet-18/34。右图:称为Bottleneck结构, 同于 ResNet-50/101/152的“瓶颈”构建块。
下面代码中所演示的是 BasicBlock, 若想改成ResNet-50/101/152, 改下网络结构即可
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential
class BasicBlock(layers.Layer):
def __init__(self, filter_num, strides=1):
super(BasicBlock, self).__init__()
# 这里解释下padding=same的规则,他是在输入上做padding的
# 如果stride不是1,那么他会通过补全输入的维度,使得输出是你预计的值
# 第一层,如果stride不是1,那么其实进行了一次采样,会导致输出的结果形状比输入小
self.conv1 = layers.Conv2D(filter_num, (3, 3), strides=strides, padding="same")
self.bn1 = layers.BatchNormalization()
self.relu = layers.Activation('relu')
# 第二层就不要下采样了,不然越来越小
self.conv2 = layers.Conv2D(filter_num, (3, 3), strides=1, padding="same")
self.bn2 = layers.BatchNormalization()
# 如果短接了,那么也要保证结果的shape是一样的
if strides != 1:
self.downsample = Sequential()
self.downsample.add(layers.Conv2D(filter_num, (1, 1), strides=strides))
else:
self.downsample = lambda x:x
def call(self, inputs, training=None):
# [b, h, w, c]
out = self.conv1(inputs)
out = self.bn1(out, training=training)
out = self.relu(out)
out = self.conv2(out, training=training)
out = self.bn2(out)
identity = self.downsample(inputs)
# layers.add 就是简单的把两个张量加到一起
output = layers.add([out, identity])
output = tf.nn.relu(output)
return output
class ResNet(keras.Model):
def __init__(self, layer_dims): # layer_dims: [2, 2, 2, 2], num_classes 就是输出结果数
super(ResNet, self).__init__()
# 根节点,对数据进行预处理,不是一个resblock
self.stem = Sequential([
layers.Conv2D(64, (3, 3), strides=(1, 1)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.MaxPool2D(pool_size=(2, 2), strides=(1, 1), padding='same')
])
# 四层 resblock
self.layer1 = self.build_resblock(64, layer_dims[0])
self.layer2 = self.build_resblock(128, layer_dims[1], strides=2)
self.layer3 = self.build_resblock(256, layer_dims[2], strides=2)
self.layer4 = self.build_resblock(512, layer_dims[3], strides=2)
# 输出层,到这一层处理前,形状是 [b, 512, h, w],
# 我们经过3次strides=2 处理,结果形状应该被缩小到 1/8,如果输入是 (32, 32) 那么到这里就是 (4,4),而我们需要h w变成 (1, 1),
# 因此加一个平均层,无论最后(h,w)值如何,都会被取一个平均值而缩减到(1,1),比如如果是 (4,4) 那么就取所有16个值的平均值
self.avgpool = layers.Flatten() #
self.fc = layers.Dense(1, activation=tf.nn.sigmoid) # 全连接层输出结果
def call(self, inputs, training=None):
x = self.stem(inputs, training=training)
x = self.layer1(x, training=training)
x = self.layer2(x, training=training)
x = self.layer3(x, training=training)
x = self.layer4(x, training=training)
x = self.avgpool(x)
x = self.fc(x)
return x
# 一个resblock 中间包含了n个basicblock
def build_resblock(self, filter_num, blocks, strides=1):
res_block = Sequential()
res_block.add(BasicBlock(filter_num, strides=strides))
for _ in range(1, blocks):
res_block.add(BasicBlock(filter_num, strides=1))
return res_block
def resnet10():
return ResNet([1, 1, 1, 1])
def resnet18():
return ResNet([2, 2, 2, 2])
def resnet34():
return ResNet([3, 4, 6, 3])
https://github.com/mcuwangzaiacm/ResNet_tensorflow2.0