ResNet 改进点
提出层间残差跳连
引入前方信息,缓解梯度消失,使得神经网络层数增加 成为可能。
前四个四个神经网络的层数
网络层数越多,效果越好。
但是效果并非如此。
RetNet作者 何凯明 发现 56层网络错误率 反而高于20层网络的错误率。
提出:单纯堆积神经网络层数 会使得 神经网络模型蜕化, 后面特征 会 丢失前面网络的学习的特征。
eg: 下面是在cifar 10上面做的发现。
于是 设计出 一根 跳连线, 直接连接 到 前面的特征。
这样 既包含了 堆叠卷积的非线性输出F(x)又包含了 跳过卷积 直接连接前面的 恒等映射x。
从而使得 神经网络的堆叠成为可能。
这种映射 的话,如果卷积改变了维度,就是需要虚线通过1x1改变一下维度,再相加。
如果没改变,就直接相加,就是实线。
代码实现:
将虚线和实线的两种连接方式,封转到一个类中
调用 ResNetBlock一次,会生成一个黄色块。
堆叠前后维度不同, self.residual_path就等于1,调用变换维度的红色部分代码,使用1x1卷积操作,
调整输入特征图input的尺寸or深度, 卷积和直接 相加,过激活函数,输出。
堆叠前后维度相同,卷积和直接 相加,过激活函数,输出。
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 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 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
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网络结构
for block_id in range(len(block_list)): # 第几个resnet block
for layer_id in range(block_list[block_id]): # 第几个卷积层
if block_id != 0 and layer_id == 0: # 对除第一个block以外的每个block的输入进行下采样
block = ResnetBlock(self.out_filters, strides=2, residual_path=True)
else:
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()
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
搭建ResNet网络
一共是 8 个 ResNet 块
最后是一个全连接
每个ResNet 块 两层卷积
一共是18层网络。
第一个 小紫色块 64个3x3卷积核 步长1 全零填充,BN操作,激活函数为relu
下面是 4个相连接的黄色块
第一个黄色块 两个实线跳连
第二三四都是 先虚线 再实线跳连
可以通过for循环嵌套实现
循环次数:参数列表元素个数 决定
列表赋值 为 2,2,2,2 一共是四个元素
最外层for循环执行4次
进入循环,根据 属于的循环次数,选择residual_path=True还是False,判断虚线还是实线连接。
最后平均池化
全连接,输出结果。
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
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网络结构
for block_id in range(len(block_list)): # 第几个resnet block
for layer_id in range(block_list[block_id]): # 第几个卷积层
if block_id != 0 and layer_id == 0: # 对除第一个block以外的每个block的输入进行下采样
block = ResnetBlock(self.out_filters, strides=2, residual_path=True)
else:
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()
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])