深度残差网络(deep residual network)是2015年微软何凯明团队发表的一篇名为:《Deep Residual Learning for Image Recognition》的论文中提出的一种全新的网络结构,其核心模块是残差块residual block。正是由于残差块结构的出现使得深度神经网络模型的层数可以不断加深到100层、1000层甚至更深,从而使得该团队在当年的ILSVRC 2015分类竞赛中取得卓越成绩,也深刻地影响了以后的很多深度神经网络的结构设计。
残差网络的成功不仅表现在其在ILSVRC 2015竞赛中的卓越效果,更是因为残差块skip connection/shorcut这样优秀的思想和设计,使得卷积网络随着层数加深而导致的模型退化问题能被够大幅解决,使模型深度提高一个数量级,到达上百、上千层。下面来学习一下该论文的技术原理。
在残差块这样的结构引入之前,如果一个神经网络模型的深度太深,可能会带来梯度消失和梯度爆炸的问题(如下图),随着一些正则化方法的应用可以缓解此问题,但是随着layer深度的继续加深,又带来了模型退化这样的问题。而添加了带shortcut的残差块结构之后,使得整个深度神经网络的层数可以大幅增加,变得更【深】,从而有时会带来更好的训练效果。
那么,这里就有一个问题,为什么加深网络会带来退化问题?即使新增的这些layer什么都没学习,保持恒等输出(所有的weight都设为1),那么按理说网络的精度也应该 = 原有未加深时的水平,如果新增的layer学习到了有用特征,那么必然加深过后的模型精度会 > 未加深的原网络。看起来对于网络的精度加深后都应该 >= 加深前才对啊 ?
实际上,让新增的layer保持什么都不学习的恒等状态,恰恰很困难,因为在训练过程中每一层layer都通过线性修正单元relu处理,而这样的处理方式必然带来特征的信息损失(不论是有效特征or冗余特征)。所以上述假设的前提是不成立的,简单的堆叠layer必然会带来退化问题。
到此,何凯明团队创新地提出了残差块的构想,通过shortcut/skip connection这样的方式(最初出现在highway network中),绕过这些新增的layer,既然保持新增layer的identity恒等性很困难,那就直接绕过它们,通过shortcut通路来保持恒等。如图2所示:
【2015】【ResNet】1512.03385v1.pdf
训练代码(官方实现):https://github.com/facebookarchive/fb.resnet.torch
Abstract
Deeper neural networks are more difficult to train. We present a residual learning framework to ease the training of networks that are substantially deeper than those used previously. We explicitly reformulate the layers as learning residual functions with reference to the layer inputs, instead of learning unreferenced functions. We provide comprehensive empirical evidence showing that these residual networks are easier to optimize, and can gain accuracy from considerably increased depth. On the ImageNet dataset we evaluate residual nets with a depth of up to 152 layers—8×deeper than VGG nets [41] but still having lower complexity. An ensemble of these residual nets achieves 3.57% erroron the ImageNet testset. This result won the 1st place on theILSVRC 2015 classification task. We also present analysison CIFAR-10 with 100 and 1000 layers.
The depth of representations is of central importancefor many visual recognition tasks. Solely due to our extremely deep representations, we obtain a 28% relative improvement on the COCO object detection dataset. Deepresidual nets are foundations of our submissions to ILSVRC& COCO 2015 competitions1, where we also won the 1stplaces on the tasks of ImageNet detection, ImageNet localization, COCO detection, and COCO segmentation
翻译
更深的神经网络更难训练。 我们提出了一种残差的学习框架,以简化比之前使用的网络还要深入的网络结构的训练。 我们通过改变一些层的输入来调整了这些层的结构以构造成残差函数来进行学习。我们提供了全面的经验证据,表明这些残差网络更易于优化,并且可以通过深度的增加而提升准确性。 在ImageNet数据集上,我们评估深度最大为152层的残差网络-比VGG网络[41]深8倍,但复杂度仍然较低。 这些残差网络整体在ImageNet测试集上实现了3.57%的误差。 该结果在ILSVRC 2015分类任务中获得第一名。 我们还将介绍具有100和1000层的基于CIFAR-10的分析。
表征深度对于许多视觉识别任务至关重要。 仅由于我们更深的网络结构,我们在COCO对象检测数据集上获得了28%的相对改进。 深度残差网络是我们提交ILSVRC&COCO 2015竞赛夺冠的基础,在该竞赛中,我们还获得了ImageNet检测,ImageNet本地化,COCO检测和COCO分割等任务的第一名
论文中Introduction部分的内容及思想,主要内容为残差学习和残差块提出的原因,解决的问题,在竞赛中取得的佳绩等。(以下大多数内容和1.简介中的类似)
深度神经网络模型随着深度加深,精度和表现都会提高。但是随着深度逐渐加深也会带来其他问题,首当其冲的是梯度消失和梯度爆炸,但是随着标准初始化和中间的正则化层解决了此问题,使得数十层的网络可以在随机梯度下降SGD过程中进行反向传播和收敛。然而,随着网络深度的进一步加深,模型的准确性达到饱和然后迅速下降,而且这种退化并不是由于过拟合所导致。如图所示:
在本文中,我们通过引入深度残差学习框架解决降级问题,而不是希望每个堆叠的层都直接适合所需的基础映射,而是明确让这些层适合残差映射。 形式上,将所需的基础映射表示为H(x),我们让堆叠的非线性层(图中的weight layer)适合F(x) := H(x) - x的另一个映射。 原始映射将重铸为F(x) + x。 我们假设与优化原始,未引用的映射相比,更容易优化残留映射。 极端地,如果identity特征映射是最佳的,则将残差推为零比通过用一叠非线性层拟合特征映射要容易。
针对上述构想,作者团队在ImageNet上做了全面实验并证明:
作者团队在在CIFAR-10数据集成功训练了深度超过100层以上的模型,也得到了同样的结论。并实验了1000层以上模型的训练。在ImageNet的分类数据集上,残差网络在测试集上取得了3.57%的top-5错误率从而获得ILSVRC2015分类挑战赛的冠军,同时152层的网络深度刷新了记录且其参数量比VGGNet还少。
除此之外,通过极深的残差网络,其团队在ILSVRC和COCO 2015中的各项细分比赛中都获得了冠军,如:ImageNet目标检测、定位;COCO目标检测、分割。这些证据表明残差学习的原则是通用的,在多项任务中都有着出色的泛化能力。
主要内容是从图像检索和分类的角度,残差矢量计算的对比和优化,从理论上给出证据表面残差方案使得训练时的模型收敛速度加快。以下是原论文翻译:
在图像识别中,VLAD [18]是通过相对于字典的残差矢量进行编码的表示,Fisher Vector [30]可以表示为VLAD的概率版本[18]。 它们都是用于图像检索和分类的有效的浅层表示[4,48]。 对于向量量化,编码残差向量[17]比编码原始向量更有效。在低级视觉和计算机图形学中,为求解偏微分方程(PDE),广泛使用的Multigrid方法[3]将系统引申出多个规模的子问题,其中每个子问题负责在较粗和较精scale之间的残差解决方案。 Multigrid的替代方法是以分层为基础的预处理[45,46],它依赖于代表两种scale之间的残差向量的变量。 已经证明[3、45、46],这些方案的收敛速度比那些对残差本质尚未涉及的标准方案快得多。 这些方法表明,良好的重构或预处理可以简化优化过程。
这一小节主要是进行了shortcut理论和实践的溯源,还有同highway network中shortcut功能区别的对比。以下部分是原论文翻译:
关于shortcut连接的实践和理论[2,34,49]已有很长时间的研究。 训练多层感知器(MLP)的早期实践是添加从网络输入连接到输出的线性层(如:Pattern recognition and neural networks. Cambridgeuniversity press, 1996)。 在GoogLeNet等网络中,一些中间层直接连接到辅助分类器,以解决消失/爆炸梯度。 文献[39,38,31,47]提出了通过shortcut连接实现对层响应,梯度和传播误差进行居中的方法。 在GoogLeNet中,“Inception”层由shortcut分支和一些更深的分支组成。
与我们同期的工作,“highway networks” [42、43]中,shortcut连接作为门功能[15]而呈现。这些门(gates)是带参的且依赖数据的,正与我们shortcut带参且有恒等性相反。当shortcut门“关闭”时(接近0),highway networks中的层表现出非残差功能;相反我们的表达式却永远在进行残差的学习。我们的shortcut永远不会关闭,所有信息始终通过其传递,同时残差函数也始终在进行学习。此外,highway networks并没有证明,随着深度增加,准确性有了明显提升。
在残差网络中,对每一个堆叠层都采用残差学习,一个堆叠层形如图2所示的一个残差块。
残差块的公式定义为:x和F(x)计算时尺寸必须相等,为此我们可以在必要时通过矩阵Ws来改变输入x的维度:
在残差块中堆叠的layer数量至少为2,因为如果只增加了1层,整个表达式便退化成了线性方程,而这就失去作用了。为了简单起见,表达式看上去只适合全连接层,其实可也以表示卷积层,F(x,{Wi})可以表示多个卷积层。在两个feature maps之间,元素之间的相加是在通道维上逐个进行的。
上图中间的是34层的基准普通网络,包含34个带权层(conv+fc)。其设计参考了VGG-19,卷积层的filter尺寸多为3×3,并遵循两个设计原则:1.如果输出的特征图尺寸减半,则filter数量加倍 2.如果输出的特征图尺寸不变,filter数量也不变。stride保持为2,网络结束之前会经过全局平均池化层,再连接一个1000路的softmax分类。
论文指出残差网络比VGG网络有着更少的过滤器和更低的复杂度,34层的基准网络有36亿个FLOP(乘加),而VGG-19有196亿个FLOP,仅占其18%。
上图最右边是在中间普通网络基础上,增加了shortcut,使其变为了残差网络。这里有两种shortcut,实线shortcut和虚线shortcut(投影projection)。当输入和输出维度相同时,使用实线shortcut,当维度增加时使用虚线shortcut。当维度改变使用虚线shortcut时,我们为了匹配维度有2种做法:
中的方式,通过1×1卷积来改变维度
对ImageNet的残差网络实现参照了VGGNet及其在ImageNet中的实现。
图像resize,并水平翻转,随机剪裁减去RGB均值像素,颜色增强等处理;
BN批量正则化;
batch size设为256;
随机梯度下降SGD;
初始学习率为0.1当loss稳定后除以10;
weight decay设为0.0001;
动量0.9;
值得注意的是,在该实现中并没有使用Dropout方法。
作者在ImageNet2012分类数据集评估了残差网络,128万张训练图,5万张验证图,1000个分类,在测试服务器上通过10万张测试图评估得到了top-1和top-5错误率。
上图左边可看出,在普通网络的对比中,34层的比18层误差大,存在模型退化现象。
通过对比左右两幅图+table2可以发现:当模型深度不是很深时(18)层,普通网络和ResNet网络的准确率差不多,表示通过一些初始化和正则化方法,可以降低普通卷积神经网络的过拟合;但当层数不断加深,就不可避免地出现模型退化现象,而此时ResNet可以在此情况下很好地解决退化问题。
作者团队不仅实验了34层的ResNet更实验了多种残差块,多种深度的残差网络,结构如下:
其中,以50层layer中的残差块为例,由于其输入输出尺寸不同,需要1×1卷积进行维度转换,这种残差block表现出上窄下宽的形状,被称为“瓶颈”残差块。具体如下图:
可以看见,在ResNet网络中,并没有看到模型退化的现象,反而随着layer深度加深,网络的精度越高。
这里使用tensorflow2.0实现ResNet-18网络结构,完整训练可参考:残差网络(ResNet)。我们先看一下ResNet-18的网络结构:
ResNet的前两层跟之前介绍的GoogLeNet中的一样:在输出通道数为64、步幅为2的7×7卷积层后接步幅为2的3×3的最大池化层。不同之处在于ResNet每个卷积层后增加的批量归一化层。然后接了4种类型的共计8个残差块,每种类型的2个残差块形成一个堆叠。最后加了全局平均池化和1000路的softmax输出。
残差块沿用了VGG的3×3的卷积核尺寸,且有两种结构:初入 = 输出;输入!=输出。针对输入和输出shape不一样的情况,需要增加一个1×1卷积变换维度。残差块内的卷积层经过卷积后接BN批量归一化层,然后经过ReLU激活。
定义残差块实现类Residual:
import tensorflow as tf
from tensorflow.keras import layers,activations
class Residual(tf.keras.Model):
def __init__(self, num_channels, use_1x1conv=False, strides=1, **kwargs):
super(Residual, self).__init__(**kwargs)
self.conv1 = layers.Conv2D(num_channels,
padding='same',
kernel_size=3,
strides=strides)
self.conv2 = layers.Conv2D(num_channels, kernel_size=3,padding='same')
# 如果需要变换维度,则增加1×1卷积层
if use_1x1conv:
self.conv3 = layers.Conv2D(num_channels,
kernel_size=1,
strides=strides)
else:
self.conv3 = None
self.bn1 = layers.BatchNormalization()
self.bn2 = layers.BatchNormalization()
def call(self, X):
Y = activations.relu(self.bn1(self.conv1(X)))
Y = self.bn2(self.conv2(Y))
if self.conv3:
X = self.conv3(X)
return activations.relu(Y + X)
ResNet-18总计4个残差块stack,每个stack包含两个残差块。下面定义残差块stack
class ResnetBlock(tf.keras.layers.Layer):
def __init__(self,num_channels, num_residuals, first_block=False,**kwargs):
super(ResnetBlock, self).__init__(**kwargs)
self.listLayers=[]
for i in range(num_residuals):
if i == 0 and not first_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
class ResNet(tf.keras.Model):
def __init__(self,num_blocks,**kwargs):
super(ResNet, self).__init__(**kwargs)
self.conv=layers.Conv2D(64, kernel_size=7, strides=2, padding='same')
self.bn=layers.BatchNormalization()
self.relu=layers.Activation('relu')
self.mp=layers.MaxPool2D(pool_size=3, strides=2, padding='same')
self.resnet_block1=ResnetBlock(64,num_blocks[0], first_block=True)
self.resnet_block2=ResnetBlock(128,num_blocks[1])
self.resnet_block3=ResnetBlock(256,num_blocks[2])
self.resnet_block4=ResnetBlock(512,num_blocks[3])
self.gap=layers.GlobalAvgPool2D()
self.fc=layers.Dense(units=1000,activation=tf.keras.activations.softmax)
def call(self, x):
x=self.conv(x)
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
mynet=ResNet([2,2,2,2])
# 观察ResNet网络的输出
X = tf.random.uniform(shape=(1, 224, 224 , 3))
for layer in mynet.layers:
X = layer(X)
print(layer.name, 'output shape:\t', X.shape)
输出:
conv2d output shape: (1, 112, 112, 64)
batch_normalization output shape: (1, 112, 112, 64)
activation output shape: (1, 112, 112, 64)
max_pooling2d output shape: (1, 56, 56, 64)
resnet_block output shape: (1, 56, 56, 64)
resnet_block_1 output shape: (1, 28, 28, 128)
resnet_block_2 output shape: (1, 14, 14, 256)
resnet_block_3 output shape: (1, 7, 7, 512)
global_average_pooling2d output shape: (1, 512)
dense output shape: (1, 1000)
残差网络的出现使人们摆脱了【深度】的束缚,大幅改善了深度神经网络中的模型退化问题,使网络层数从数十层跃升至几百上千层,大幅提高了模型精度,通用性强适合各种类型的数据集和任务。残差块和shortcut这种优秀的设计也极大影响了后面的网络结构发展。