深度学习|ResNet

1.ResNet背景

在前面的模型中,研究人员不断增加网络层数,网络越深,获取的信息就越多,特征也越丰富,得到更高的精度,但在深度学习中,随着网络层数的增加,结果并没有向我们预想方向发展,优化效果反而越差,测试数据和训练数据的准确率反而降低了。
深度学习|ResNet
何凯明等人发现,当网络到达一定深度时,浅层网络能够达到比深层网络更好的训练效果,这时如果我们把浅层的特征传到深层,深层获取特征多于浅层,那么效果应该至少不比浅层的网络效果差。因此在前向传播中,采用直接映射的方式,将浅层特征传递给深层,残差网络由此诞生。

2.残差块

残差网络由若干个残差块组成。一个残差块X(l+1)=X(l)+F(x(l),W(l)),残差块分为两部分直接映射部分和残差部分,直接映射就是X(l),如下图左半部分,F(x(l),W(l))为残差部分,一般由两个卷积层构成,如下图右半部分。
深度学习|ResNet
上图中Weight是指卷积层中的卷积操作,addition是指将传入参数相加。
在卷积网络中,X(l)与X(l+1)的Feature Map的数量可能不一样,我们可以在直接映射部分加入1X1卷积层进行升维或者降维,这时残差块为:X(l+1)=H(X(l))+F(x(l),W(l)),如下图:
深度学习|ResNet
ResNet沿用了VGG全3×3卷积层的设计。残差块里首先有2个有相同输出通道数的3×3卷积层。每个卷积层后接BN层和ReLU激活函数,然后将输入直接加在最后的ReLU激活函数前,这种结构用于层数较少的神经网络中,比如ResNet34。若输入通道数比较多,就需要引入1×1卷积层来调整输入的通道数,这种结构也叫作瓶颈模块,通常用于网络层数较多的结构中。如下图所示:
深度学习|ResNet
下面代码实现:

# 导入相关的工具包
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
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])
上一篇:从零开始学keras之变分自编码器生成图像


下一篇:js获取页面元素的位置