ResNet网络搭建
网络结构及分析:
上述四种CNN,通过加深网络层数,结果会越来越好,但是增大到一定程度,再增加层数,会使神经网络模型退化,因为后面的特征会丢失前面特征的原本模样
因此,将前面的特征x直接跳过两个卷积层,与两个卷积层的输出F(x)相加得到H(x)。这种操作可以缓解神经网络模型层数堆叠导致的模型退化,使我们能够增加更多的层数
(注:ResNet中的+是特征图对应元素相加,矩阵值的每个对应元素相加,
InceptionNet中的+是沿深度方向叠加,增加特征图的层数)
ResNet块有两种情况:
一种是下图的实线所示,两层卷积之后没有改变特征图的维度,可以直接H(x)=F(x)+x
另一种是虚线所示,两层卷积之后改变了特征图的维度,需要借助1*1的卷积来调整x的维度,使W(x)与F(x)维度一致
下图即是ResNet的结构:
RetNet由一个卷积层+8个RetNet块+所有通道进行平均池化的池化层+Dense层组成
一共18层网络
每个ResNet块有两种情况:虚线和实线
如上图结构,可将ResNet块封装到类ResnetBlock中:
class ResnetBlock(Model):
def __init__(self, filters, strides=1, residual_path=False):
super(ResnetBlock, self).__init__()
self.filters = filters
self.strides = strides
self.residual_path = residual_path
self.c1 = Conv2D(filters, (3, 3), strides=strides, padding='same', use_bias=False)
self.b1 = BatchNormalization()
self.a1 = Activation('relu')
self.c2 = Conv2D(filters, (3, 3), strides=1, padding='same', use_bias=False)
self.b2 = BatchNormalization()
# residual_path为True时,对输入进行下采样,即用1x1的卷积核做卷积操作,保证x能和F(x)维度相同,顺利相加
# 如果是实线,不执行以下if语句,如果是虚线,则执行以下if语句,将W(x)核F(x)维度一致
if residual_path:
self.down_c1 = Conv2D(filters, (1, 1), strides=strides, padding='same', use_bias=False)
self.down_b1 = BatchNormalization()
self.a2 = Activation('relu')
def call(self, inputs):
residual = inputs # residual等于输入值本身,即residual=x
# 将输入通过卷积、BN层、激活层,计算F(x)
x = self.c1(inputs)
x = self.b1(x)
x = self.a1(x)
x = self.c2(x)
y = self.b2(x)
# 如果是实线,不执行以下if语句,如果是虚线,则执行以下if语句,将W(x)核F(x)维度一致
if self.residual_path:
residual = self.down_c1(inputs)
residual = self.down_b1(residual)
out = self.a2(y + residual) # 最后输出的是两部分的和,即F(x)+x或F(x)+Wx,再过激活函数
return out
进而可设计ResNet网络结构:
class ResNet18(Model):
def __init__(self, block_list, initial_filters=64): # block_list表示每个block有几个卷积层
super(ResNet18, self).__init__()
self.num_blocks = len(block_list) # 共有几个block
self.block_list = block_list
self.out_filters = initial_filters
# 第一个卷积层:CBA
self.c1 = Conv2D(self.out_filters, (3, 3), strides=1, padding='same', use_bias=False)
self.b1 = BatchNormalization()
self.a1 = Activation('relu')
self.blocks = tf.keras.models.Sequential()
# 构建ResNet网络结构
# 外层循环层数由参数列表的循环个数决定,如model = ResNet18([2, 2, 2, 2]),则循环4次,有4个橙色块,即8个ResNet块
for block_id in range(len(block_list)): # 第几个橙色块
for layer_id in range(block_list[block_id]): # 第几个ResNet块
if block_id != 0 and layer_id == 0: # 除第一个橙色块,以下三个橙色块的第一个ResNet块外,都是虚线,定义residual_path=True
block = ResnetBlock(self.out_filters, strides=2, residual_path=True)
else: # 第一个橙色块,以下三个橙色块的第一个ResNet块是实线,定义residual_path=False
block = ResnetBlock(self.out_filters, residual_path=False)
self.blocks.add(block) # 将构建好的block加入resnet
self.out_filters *= 2 # 下一个block的卷积核数是上一个block的2倍
# 全局池化
self.p1 = tf.keras.layers.GlobalAveragePooling2D()
# Dense层
self.f1 = tf.keras.layers.Dense(10, activation='softmax', kernel_regularizer=tf.keras.regularizers.l2())
def call(self, inputs):
x = self.c1(inputs)
x = self.b1(x)
x = self.a1(x)
x = self.blocks(x)
x = self.p1(x)
y = self.f1(x)
return y
model = ResNet18([2, 2, 2, 2])
整体代码:
以下以cifar10数据集为例进行演示
(cifar10数据集有5万张32*32像素点的彩色图片,用于训练
有1万张32*32像素点的彩色图片,用于测试)
cifar10 = tf.keras.datasets.cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
class ResnetBlock(Model):
def __init__(self, filters, strides=1, residual_path=False):
super(ResnetBlock, self).__init__()
self.filters = filters
self.strides = strides
self.residual_path = residual_path
self.c1 = Conv2D(filters, (3, 3), strides=strides, padding='same', use_bias=False)
self.b1 = BatchNormalization()
self.a1 = Activation('relu')
self.c2 = Conv2D(filters, (3, 3), strides=1, padding='same', use_bias=False)
self.b2 = BatchNormalization()
# residual_path为True时,对输入进行下采样,即用1x1的卷积核做卷积操作,保证x能和F(x)维度相同,顺利相加
# 如果是实线,不执行以下if语句,如果是虚线,则执行以下if语句,将W(x)核F(x)维度一致
if residual_path:
self.down_c1 = Conv2D(filters, (1, 1), strides=strides, padding='same', use_bias=False)
self.down_b1 = BatchNormalization()
self.a2 = Activation('relu')
def call(self, inputs):
residual = inputs # residual等于输入值本身,即residual=x
# 将输入通过卷积、BN层、激活层,计算F(x)
x = self.c1(inputs)
x = self.b1(x)
x = self.a1(x)
x = self.c2(x)
y = self.b2(x)
# 如果是实线,不执行以下if语句,如果是虚线,则执行以下if语句,将W(x)核F(x)维度一致
if self.residual_path:
residual = self.down_c1(inputs)
residual = self.down_b1(residual)
out = self.a2(y + residual) # 最后输出的是两部分的和,即F(x)+x或F(x)+Wx,再过激活函数
return out
class ResNet18(Model):
def __init__(self, block_list, initial_filters=64): # block_list表示每个block有几个卷积层
super(ResNet18, self).__init__()
self.num_blocks = len(block_list) # 共有几个block
self.block_list = block_list
self.out_filters = initial_filters
# 第一个卷积层:CBA
self.c1 = Conv2D(self.out_filters, (3, 3), strides=1, padding='same', use_bias=False)
self.b1 = BatchNormalization()
self.a1 = Activation('relu')
self.blocks = tf.keras.models.Sequential()
# 构建ResNet网络结构
# 外层循环层数由参数列表的循环个数决定,如model = ResNet18([2, 2, 2, 2]),则循环4次,有4个橙色块,即8个ResNet块
for block_id in range(len(block_list)): # 第几个橙色块
for layer_id in range(block_list[block_id]): # 第几个ResNet块
if block_id != 0 and layer_id == 0: # 除第一个橙色块,以下三个橙色块的第一个ResNet块外,都是虚线,定义residual_path=True
block = ResnetBlock(self.out_filters, strides=2, residual_path=True)
else: # 第一个橙色块,以下三个橙色块的第一个ResNet块是实线,定义residual_path=False
block = ResnetBlock(self.out_filters, residual_path=False)
self.blocks.add(block) # 将构建好的block加入resnet
self.out_filters *= 2 # 下一个block的卷积核数是上一个block的2倍
# 全局池化
self.p1 = tf.keras.layers.GlobalAveragePooling2D()
# Dense层
self.f1 = tf.keras.layers.Dense(10, activation='softmax', kernel_regularizer=tf.keras.regularizers.l2())
def call(self, inputs):
x = self.c1(inputs)
x = self.b1(x)
x = self.a1(x)
x = self.blocks(x)
x = self.p1(x)
y = self.f1(x)
return y
model = ResNet18([2, 2, 2, 2])
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
metrics=['sparse_categorical_accuracy'])
############################################### show ###############################################
# 显示训练集和验证集的acc和loss曲线
plt.subplot(1, 2, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.title('Training and Validation Loss')
plt.legend()
plt.show()
注:本文来自于中国大学mooc中北京大学的人工智能实践:Tensorflow笔记,在此感谢北大的曹健老师