深度学习|ResNet
Posted 朱宏中
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度学习|ResNet相关的知识,希望对你有一定的参考价值。
1.ResNet背景
在前面的模型中,研究人员不断增加网络层数,网络越深,获取的信息就越多,特征也越丰富,得到更高的精度,但在深度学习中,随着网络层数的增加,结果并没有向我们预想方向发展,优化效果反而越差,测试数据和训练数据的准确率反而降低了。
何凯明等人发现,当网络到达一定深度时,浅层网络能够达到比深层网络更好的训练效果,这时如果我们把浅层的特征传到深层,深层获取特征多于浅层,那么效果应该至少不比浅层的网络效果差。因此在前向传播中,采用直接映射的方式,将浅层特征传递给深层,残差网络由此诞生。
2.残差块
残差网络由若干个残差块组成。一个残差块X(l+1)=X(l)+F(x(l),W(l)),残差块分为两部分直接映射部分和残差部分,直接映射就是X(l),如下图左半部分,F(x(l),W(l))为残差部分,一般由两个卷积层构成,如下图右半部分。
上图中Weight是指卷积层中的卷积操作,addition是指将传入参数相加。
在卷积网络中,X(l)与X(l+1)的Feature Map的数量可能不一样,我们可以在直接映射部分加入1X1卷积层进行升维或者降维,这时残差块为:X(l+1)=H(X(l))+F(x(l),W(l)),如下图:
ResNet沿用了VGG全3×3卷积层的设计。残差块里首先有2个有相同输出通道数的3×3卷积层。每个卷积层后接BN层和ReLU激活函数,然后将输入直接加在最后的ReLU激活函数前,这种结构用于层数较少的神经网络中,比如ResNet34。若输入通道数比较多,就需要引入1×1卷积层来调整输入的通道数,这种结构也叫作瓶颈模块,通常用于网络层数较多的结构中。如下图所示:
下面代码实现:
# 导入相关的工具包
import tensorflow as tf
from tensorflow.keras import layers,activations
# 定义ResNet的残差块
class Residual(tf.keras.Model):
# 告知残差块的通道数以及是否使用1x1卷积,步长
def __init__(self,num_channels,use_1x1conv=False,strides=1):
super(Residual,self).__init__()
#卷积层
self.conv1 = layers.Conv2D(num_channels,padding=\'same\',kernei_size=3,strides=strides)
self.conv2 = layers.Conv2D(num_channels,padding=\'same\',kernei_size=3,strides=strides)
# BN层
self.bn1 = layers.BatchNormalization()
self.bn2 = layers.BatchNormalization()
if use_1x1conv:
self.conv3 = layers.Conv2D(num_channels,kernei_size=1,strides=strides)
else:
self.conv3 = None
def call(self,x):
# 卷积 BN 激活
y = activations.relu(self.bn1(self.conv1(x)))
# 卷积 BN
y = self.bn2(self.conv2(y))
# 映射层
if self.conv3(x):
x =self.conv3(x)
return activations.relu(x+y)
3.ResNet模型
ResNet模型的构成如下图所示:
ResNet网络中按照残差块的通道数分为不同的模块。第一个模块前使用了步幅为2的最大池化层,所以无须减小高和宽。之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半。
下面代码实现:
# 网络层的定义:输出通道数(卷积核个数),模块中包含的残差块个数,是否为第一个模块
def __init__(self,num_channels,num_residuals,frist_block=False):
super(ResnetBlock,self).__init__()
# 模块中的网络层
self.listLayers = []
# 遍历模块中的所有层
for i in range(num_residuals):
# 若为第一个残差块并且不是第一个模块,则使用1*1卷积,步长为2
if i == 0 and not frist_block:
self.listLayers.append(Residual(num_channels,use_1x1conv=True,strides=2))
else:
self.listLayers.append(Residual(num_channels))
# 前向传播
def call(self,x):
# 所有层次
for layer in self.listLayers.layers:
x = layer(x)
return x
ResNet的前两层跟之前介绍的GoogLeNet中的一样:在输出通道数为64、步幅为2的7×7卷积层后接步幅为2的3×3的最大池化层。不同之处在于ResNet每个卷积层后增加了BN层,接着是所有残差模块,最后,与GoogLeNet一样,加入全局平均池化层(GAP)后接上全连接层输出。
# 构建ResNet网络
class ResNet(tf.keras.Model):
# 初始化:指定每个模块中的残差快的个数
def __init__(self,num_blocks):
super(ResNet, self).__init__()
# 输入层:7*7卷积,步长为2
self.conv=layers.Conv2D(64, kernel_size=7, strides=2, padding=\'same\')
# BN层
self.bn=layers.BatchNormalization()
# 激活层
self.relu=layers.Activation(\'relu\')
# 最大池化层
self.mp=layers.MaxPool2D(pool_size=3, strides=2, padding=\'same\')
# 第一个block,通道数为64
self.resnet_block1=ResnetBlock(64,num_blocks[0], first_block=True)
# 第二个block,通道数为128
self.resnet_block2=ResnetBlock(128,num_blocks[1])
# 第三个block,通道数为256
self.resnet_block3=ResnetBlock(256,num_blocks[2])
# 第四个block,通道数为512
self.resnet_block4=ResnetBlock(512,num_blocks[3])
# 全局平均池化
self.gap=layers.GlobalAvgPool2D()
# 全连接层:分类
self.fc=layers.Dense(units=10,activation=tf.keras.activations.softmax)
# 前向传播过程
def call(self, x):
# 卷积
x=self.conv(x)
# BN
x=self.bn(x)
# 激活
x=self.relu(x)
# 最大池化
x=self.mp(x)
# 残差模块
x=self.resnet_block1(x)
x=self.resnet_block2(x)
x=self.resnet_block3(x)
x=self.resnet_block4(x)
# 全局平均池化
x=self.gap(x)
# 全链接层
x=self.fc(x)
return x
# 模型实例化:指定每个block中的残差块个数
mynet=ResNet([2,2,2,2])
以上是关于深度学习|ResNet的主要内容,如果未能解决你的问题,请参考以下文章
「深度学习一遍过」必修23:基于ResNet18的MNIST手写数字识别
深度学习100例 | 第29天-ResNet50模型:船型识别