深度学习第19讲:CNN经典论文研读之残差网络ResNet及其keras实现

简介:

在 VGG 网络论文研读中,我们了解到卷积神经网络也可以进行到很深层,VGG16 和 VGG19 就是证明。但卷积网络变得更深呢?当然是可以的。深度神经网络能够从提取图像各个层级的特征,使得图像识别的准确率越来越高。但在2014年和15年那会儿,将卷积网络变深且取得不错的训练效果并不是一件容易的事。

深度卷积网络一开始面临的最主要的问题是梯度消失和梯度爆炸。那什么是梯度消失和梯度爆炸呢?所谓梯度消失,就是在深层神经网络的训练过程中,计算得到的梯度越来越小,使得权值得不到更新的情形,这样算法也就失效了。而梯度爆炸则是相反的情况,是指在神经网络训练过程中梯度变得越来越大,权值得到疯狂更新的情形,这样算法得不到收敛,模型也就失效了。当然,其间通过设置 relu 和归一化激活函数层等手段使得我们很好的解决这些问题。但当我们将网络层数加到更深时却发现训练的准确率在逐渐降低。这种并不是由过拟合造成的神经网络训练数据识别准确率降低的现象我们称之为退化(degradation)。

1353079b5027a8ff52d6d14332fd5bb03b8f49fe

由上图我们可以看到 56 层的普通卷积网络不管是在训练集还是测试集上的训练误差都要高于 20 层的卷积网络。是个典型的退化现象。

这退化问题不解决,咱们的深度学习就无法 go deeper. 于是何凯明等一干大佬就发明了今天我们要研读的论文主题——残差网络 ResNet.

残差块与残差网络

要理解残差网络,就必须理解残差块(residual block)这个结构,因为残差块是残差网络的基本组成部分。回忆一下我们之前学到的各种卷积网络结构(LeNet-5/AlexNet/VGG),通常结构就是卷积池化再卷积池化,中间的卷积池化操作可以很多层。类似这样的网络结构何凯明在论文中将其称为普通网络(Plain Network),何凯明认为普通网络解决不了退化问题,我们需要在网络结构上作出创新。

何凯明给出的创新在于给网络之间添加一个捷径(shortcuts)或者也叫跳跃连接(skip connection),这使得捷径之间之间的网络能够学习一个恒等函数,使得在加深网络的情形下训练效果至少不会变差。残差块的基本结构如下:

c90e0c934ee31924f65cb40b9f124b437eab7088

以上残差块是一个两层的网络结构,输入 X 经过两层的加权和激活得到 F(X) 的输出,这是典型的普通卷积网络结构。但残差块的区别在于添加了一个从输入 X 到两层网络输出单元的 shortcut,这使得输入节点的信息单元直接获得了与输出节点的信息单元通信的能力 ,这时候在进行 relu 激活之前的输出就不再是 F(X) 了,而是 F(X)+X。当很多个具备类似结构的这样的残差块组建到一起时,残差网络就顺利形成了。残差网络能够顺利训练很深层的卷积网络,其中能够很好的解决网络的退化问题。

或许你可能会问凭什么加了一条从输入到输出的捷径网络就能防止退化训练更深层的卷积网络?或是是说残差网络为什么能有效?我们将上述残差块的两层输入输出符号改为 和 ,相应的就有:

0cd281fb213b9891173e615e8f951a7991670f43

加入的跳跃连接后就有: 148fd99c8c17d608f277603ed3a367171d0621a6

在网络中加入 L2 正则化进行权值衰减或者其他情形下,l+2 层的权值 W 是很容易衰减为零的,假设偏置同样为零的情形下就有 = 。深度学习的试验表明学习这个恒等式并不困难,这就意味着,在拥有跳跃连接的普通网络即使多加几层,其效果也并不逊色于加深之前的网络效果。当然,我们的目标不是保持网络不退化,而是需要提升网络表现,当隐藏层能够学到一些有用的信息时,残差网络的效果就会提升。所以,残差网络之所以有效是在于它能够很好的学习上述那个恒等式,而普通网络学习恒等式都很困难,残差网络在两者相较中自然胜出。

由很多个残差块组成的残差网络如下图右图所示:

9030205da0f462b752ab601c7b82c97a62a9c040

残差块的 keras 实现

要实现一个残差块,关键在于实现一个跳跃连接。实际处理中跳跃连接会随着残差块输入输出大小的不同而分为两种。一种是输入输出一致情况下的 Identity Block,另一种则是输入输出不一致情形下的 Convolutional Block,顾名思义,就是跳跃连接中包含卷积操作,用来使得输入输出一致。且看二者的 keras 实现方法。

Identity Block 的图示如下:

ee8cf8de071e6a57199d8ddef9c09c86e133bf5e

编写实现代码如下:


def identity_block(X, f, filters, stage, block):
"""
Implementation of the identity block as defined in Figure 3
Arguments:
X -- input tensor of shape (m, n_H_prev, n_W_prev, n_C_prev)
f -- integer, specifying the shape of the middle CONV's window for the main path
filters -- python list of integers, defining the number of filters in the CONV layers of the main path
stage -- integer, used to name the layers, depending on their position in the network
Returns:
block -- string/character, used to name the layers, depending on their position in the network X -- output of the identity block, tensor of shape (n_H, n_W, n_C)
""" # defining name basis
conv_name_base = 'res' + str(stage) + block + '_branch'
bn_name_base = 'bn' + str(stage) + block + '_branch' # Retrieve Filters
F1, F2, F3 = filters
# Save the input value. You'll need this later to add back to the main path.
X_shortcut = X
# First component of main path
X = Conv2D(filters = F1, kernel_size = (1, 1), strides = (1,1), padding = 'valid', name = conv_name_base + '2a', kernel_initializer = glorot_uniform(seed=0))(X)
X = BatchNormalization(axis = 3, name = bn_name_base + '2a')(X)
X = Activation('relu')(X)
# Second component of main path
X = Conv2D(filters = F2, kernel_size = (f, f), strides= (1, 1), padding = 'same', name = conv_name_base + '2b', kernel_initializer = glorot_uniform(seed=0))(X)
X = BatchNormalization(axis = 3, name = bn_name_base + '2b')(X)
X = Activation('relu')(X)
# Third component of main path
X = Conv2D(filters = F3, kernel_size = (1, 1), strides = (1, 1), padding = 'valid', name = conv_name_base + '2c', kernel_initializer = glorot_uniform(seed=0))(X)
X = BatchNormalization(axis = 3, name = bn_name_base + '2c')(X)
# Final step: Add shortcut value to main path, and pass it through a RELU activation
X = Add()([X, X_shortcut])
X = Activation('relu')(X)
return X

可见残差块的实现特殊之处就在于添加一条跳跃连接。

Convolutional Block 的图示如下:

3128681d852260ee284a9712a196b3c6dfd64750

编写实现代码如下:


def convolutional_block(X, f, filters, stage, block, s = 2):
"""
Implementation of the convolutional block as defined in Figure 4
Arguments:
f -- integer, specifying the shape of the middle CONV's window for the main path
X -- input tensor of shape (m, n_H_prev, n_W_prev, n_C_prev)
filters -- python list of integers, defining the number of filters in the CONV layers of the main path
stage -- integer, used to name the layers, depending on their position in the network
X -- output of the convolutional block, tensor of shape (n_H, n_W, n_C)
block -- string/character, used to name the layers, depending on their position in the network s -- Integer, specifying the stride to be used Returns:
""" # defining name basis
conv_name_base = 'res' + str(stage) + block + '_branch'
bn_name_base = 'bn' + str(stage) + block + '_branch' # Retrieve Filters
F1, F2, F3 = filters
# Save the input value
X_shortcut = X
##### MAIN PATH ##### # First component of main path
X = Conv2D(filters = F1, kernel_size = (1, 1), strides = (s,s), padding = 'valid', name = conv_name_base + '2a', kernel_initializer = glorot_uniform(seed=0))(X)
X = BatchNormalization(axis = 3, name = bn_name_base + '2a')(X)
X = Activation('relu')(X)
# Second component of main path
X = Conv2D(filters = F2, kernel_size = (f, f), strides = (1,1), padding = 'same', name = conv_name_base + '2b', kernel_initializer = glorot_uniform(seed=0))(X)
X = BatchNormalization(axis = 3, name = bn_name_base + '2b')(X)
X = Activation('relu')(X)
# Third component of main path
X = Conv2D(filters = F3, kernel_size = (1, 1), strides = (1,1), padding = 'valid', name = conv_name_base + '2c', kernel_initializer = glorot_uniform(seed=0))(X)
X = BatchNormalization(axis = 3, name = bn_name_base + '2c')(X)
##### SHORTCUT PATH ####
X_shortcut = Conv2D(filters = F3, kernel_size = (1, 1), strides = (s, s), padding = 'valid', name = conv_name_base + '1', kernel_initializer = glorot_uniform(seed=0))(X_shortcut)
X_shortcut = BatchNormalization(axis = 3, name = bn_name_base + '1')(X_shortcut)
# Final step: Add shortcut value to main path, and pass it through a RELU activation
X = Add()([X, X_shortcut])
X = Activation('relu')(X)
return X

残差网络 resnet50 的 keras 实现

搭建好组件残差块之后就是确定网络结构,将一个个残差块组成残差网络。下面搭建一个 resnet50 的残差网络,其基本结构如下:

CONV2D -> BATCHNORM -> RELU -> MAXPOOL -> CONVBLOCK -> IDBLOCK2 -> CONVBLOCK -> IDBLOCK3 -> CONVBLOCK -> IDBLOCK5 -> CONVBLOCK -> IDBLOCK2 -> AVGPOOL -> TOPLAYER

45a852c5562e59d93e9f79f4315863ae20bf6e2e

编写实现代码如下:


def ResNet50(input_shape = (64, 64, 3), classes = 6):

# Define the input as a tensor with shape input_shape
X_input = Input(input_shape)
# Zero-Padding
X = ZeroPadding2D((3, 3))(X_input)
# Stage 1
X = Conv2D(64, (7, 7), strides = (2, 2), name = 'conv1', kernel_initializer = glorot_uniform(seed=0))(X)
X = BatchNormalization(axis = 3, name = 'bn_conv1')(X)
X = Activation('relu')(X)
X = MaxPooling2D((3, 3), strides=(2, 2))(X)
# Stage 2
X = convolutional_block(X, f = 3, filters = [64, 64, 256], stage = 2, block='a', s = 1)
X = identity_block(X, 3, [64, 64, 256], stage=2, block='b')
X = identity_block(X, 3, [64, 64, 256], stage=2, block='c')
# Stage 3
X = convolutional_block(X, f = 3, filters = [128, 128, 512], stage = 3, block='a', s = 2)
X = identity_block(X, 3, [128, 128, 512], stage=3, block='b')
X = identity_block(X, 3, [128, 128, 512], stage=3, block='c')
X = identity_block(X, 3, [128, 128, 512], stage=3, block='d')
# Stage 4
X = convolutional_block(X, f = 3, filters = [256, 256, 1024], stage = 4, block='a', s = 2)
X = identity_block(X, 3, [256, 256, 1024], stage=4, block='b')
X = identity_block(X, 3, [256, 256, 1024], stage=4, block='c')
X = identity_block(X, 3, [256, 256, 1024], stage=4, block='d')
X = identity_block(X, 3, [256, 256, 1024], stage=4, block='e')
X = identity_block(X, 3, [256, 256, 1024], stage=4, block='f')
# Stage 5
X = convolutional_block(X, f = 3, filters = [512, 512, 2048], stage = 5, block='a', s = 2)
X = identity_block(X, 3, [512, 512, 2048], stage=5, block='b')
X = identity_block(X, 3, [512, 512, 2048], stage=5, block='c')
# AVGPOOL (≈1 line). Use "X = AveragePooling2D(...)(X)"
X = AveragePooling2D((2, 2), strides=(2, 2))(X)
# output layer
X = Flatten()(X)
X = Dense(classes, activation='softmax', name='fc' + str(classes), kernel_initializer = glorot_uniform(seed=0))(X)
# Create model
model = Model(inputs = X_input, outputs = X, name='ResNet50')
return model

这样一个 resnet50 的残差网络就搭建好了,其关键还是在于搭建残差块,残差块搭建好之后只需根据网络结构构建残差网络即可。当然,其间也可以看到 keras 作为一个优秀的深度学习框架的便利之处。


原文发布时间为:2018-10-13

本文作者:louwill

本文来自云栖社区合作伙伴“Python爱好者社区”,了解相关信息可以关注“Python爱好者社区”。

相关文章
|
11天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习的奥秘:探索神经网络的核心原理
本文将深入浅出地介绍深度学习的基本概念,包括神经网络的结构、工作原理以及训练过程。我们将从最初的感知机模型出发,逐步深入到现代复杂的深度网络架构,并探讨如何通过反向传播算法优化网络权重。文章旨在为初学者提供一个清晰的深度学习入门指南,同时为有经验的研究者回顾和巩固基础知识。
34 11
|
12天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习的魔法:如何用神经网络解锁数据的奥秘
在人工智能的璀璨星空中,深度学习犹如一颗最亮的星,它以其强大的数据处理能力,改变了我们对世界的认知方式。本文将深入浅出地介绍深度学习的核心概念、工作原理及其在不同领域的应用实例,让读者能够理解并欣赏到深度学习技术背后的奇妙和强大之处。
26 3
|
1天前
|
机器学习/深度学习 算法 自动驾驶
深度学习的奥秘:探索神经网络的魔法
【9月更文挑战第18天】本文将带领读者深入理解深度学习背后的科学原理,通过直观的例子和简单的语言解释复杂的技术概念。我们将一起探讨如何通过构建神经网络模型来模拟人脑的工作方式,并了解这一领域如何影响我们的日常生活。
|
5天前
|
机器学习/深度学习 人工智能 TensorFlow
深度学习入门:理解卷积神经网络(CNN)
【9月更文挑战第14天】本文旨在为初学者提供一个关于卷积神经网络(CNN)的直观理解,通过简单的语言和比喻来揭示这一深度学习模型如何识别图像。我们将一起探索CNN的基本组成,包括卷积层、激活函数、池化层和全连接层,并了解它们如何协同工作以实现图像分类任务。文章末尾将给出一个简单的代码示例,帮助读者更好地理解CNN的工作原理。
23 7
|
3天前
|
机器学习/深度学习 算法 算法框架/工具
深度学习中的卷积神经网络(CNN)及其在图像识别中的应用
【9月更文挑战第16天】本文将深入浅出地介绍卷积神经网络(CNN)的基本概念、结构和工作原理,同时通过一个实际的代码示例来展示如何在Python中使用Keras库构建一个简单的CNN模型进行图像识别。我们将看到,即使是初学者也能够通过简单的步骤实现深度学习的强大功能,进而探索其在复杂数据集上的应用潜力。
|
4天前
|
机器学习/深度学习 自动驾驶 搜索推荐
深度学习之探索神经网络、感知器与损失函数
在当今的数字化时代,深度学习作为一种强大的机器学习技术,正在迅速改变着我们的生活方式。无论是智能推荐系统、自动驾驶车辆还是语音识别应用,深度学习都在背后默默地发挥作用。
13 1
|
8天前
|
机器学习/深度学习 监控 并行计算
深度学习之生物网络推理
基于深度学习的生物网络推理利用深度学习技术来解析和理解生物网络(如基因调控网络、代谢网络、蛋白质-蛋白质相互作用网络等)的复杂关系和动态行为。
22 5
|
7天前
|
机器学习/深度学习 编解码 自然语言处理
深度学习中的对抗生成网络
本文深入探讨了深度学习中的一种重要模型——对抗生成网络(GAN)。通过详细介绍其基本原理、结构组成以及训练过程,揭示了GAN在数据生成方面的独特优势。同时,文章还讨论了GAN在图像处理、自然语言处理等领域的广泛应用,并指出了其面临的挑战及未来发展方向。
13 2
|
8天前
|
机器学习/深度学习 人工智能 自然语言处理
探索AI的奥秘:深度学习与神经网络
【9月更文挑战第11天】本文将深入探讨人工智能的核心领域——深度学习,以及其背后的神经网络技术。我们将从基础理论出发,逐步深入到实践应用,揭示这一领域的神秘面纱。无论你是AI领域的初学者,还是有一定基础的开发者,都能在这篇文章中获得新的启示和理解。让我们一起踏上这场探索之旅,揭开AI的神秘面纱,体验深度学习的魅力。
|
11天前
|
机器学习/深度学习 数据采集 数据可视化
深度学习实践:构建并训练卷积神经网络(CNN)对CIFAR-10数据集进行分类
本文详细介绍如何使用PyTorch构建并训练卷积神经网络(CNN)对CIFAR-10数据集进行图像分类。从数据预处理、模型定义到训练过程及结果可视化,文章全面展示了深度学习项目的全流程。通过实际操作,读者可以深入了解CNN在图像分类任务中的应用,并掌握PyTorch的基本使用方法。希望本文为您的深度学习项目提供有价值的参考与启示。

热门文章

最新文章