目录
- 摘要
- Abstract
- 图像识别的深度残差学习
- 研究背景
- 研究动机
- 解决办法
- Residual Learning
- Shortcut Connections
- 网络结构
- 实验结果
- 代码实践
- 论文原文
- 总结
摘要
在之前对神经网络的基础学习中,师兄推荐了我去了解一下 ResNet。因此本周对 ResNet 的开山之作—Deep Residual Learning for Image Recognition 进行了详细的阅读并进行了简单的代码实践。
Abstract
In the previous foundational learning of neural networks, my senior recommended that I look into ResNet. Therefore, this week I have read in detail the pioneering work of ResNet — Deep Residual Learning for Image Recognition — and conducted some simple code practices.
图像识别的深度残差学习
Title: Deep Residual Learning for Image Recognition
Author: He, KM (He, Kaiming); Zhang, XY (Zhang, Xiangyu); Ren, SQ (Ren, Shaoqing); Sun, J (Sun, Jian)
Source: 2016 IEEE CONFERENCE ON COMPUTER VISION AND PATTERN RECOGNITION (CVPR)
DOI: 10.1109/CVPR.2016.90
研究背景
深度卷积神经网络在图像识别方面取得了巨大的突破。CNN中不同深度的层可以捕捉到不同的特征,通过堆叠大量的层可以提高网络的性能。作者也认为网络深度至关重要,他提到了最近的很多成果都用到了非常深的网络结构。
Recent evidence [40, 43] reveals that network depth is of crucial importance, and the leading results [40, 43, 12, 16] on the challenging ImageNet dataset [35] all exploit “very deep” [40] models, with a depth of sixteen [40] to thirty [16]. Many other nontrivial visual recognition tasks [7, 11, 6, 32, 27] have also greatly benefited from very deep models.
研究动机
随着网络层数的加深,一个问题也显现出来了:神经网络越深性能一定越好吗?
答案显然不是,一昧地增加网络深度可能会导致梯度消失或者梯度爆炸,使得整个神经网络难以收敛。
然而,这并不是阻碍我们疯狂堆叠网络深度的唯一问题,作者也给出了目前解决这个问题的办法。
This problem, however, has been largely addressed by normalized initialization [23, 8, 36, 12] and intermediate normalization layers [16], which enable networks with tens of layers to start converging for stochastic gradient descent (SGD) with backpropagation [22].
解决梯度消失和梯度爆炸可以通过初始归一化和中间归一化来解决,也就是说我们可以在初始化的时候做好一点,比如权重参数初始化的时候不要太大也不要太小,或者在中间加入归一化层。
即使解决了收敛问题,一个更大的问题暴露出来——网络退化。
作者给出了这个图,通过该图我们可以看到,56层的网络无论是训练误差还是测试误差都比20层还高。从图中我们可知这并不是由于过拟合引起的。
正是这个吊诡的现象引起了作者的关注,作者也正是在对这个现象进行研究的过程中解决了网络退化的问题,从而发了这篇 paper。
接下来作者提出了一个解决方案:假设有一浅一深两个神经网络,深层网络前面的层直接复制浅层网络,而新加入的层为恒等映射,也就是说深层网络和浅层网络最后的输出是相同的。按照这个假设,那深层网络的精度就算不会变得更优也至少不会变差。
但是在实践中来看,让神经网络拟合一个恒等映射——也就是说让神经网络什么都不做,这是非常有挑战性的。
But experiments show that our current solvers on hand are unable to find solutions that are comparably good or better than the constructed solution (or unable to do so in feasible time).
有时候什么都不做就是最优解。
解决办法
Residual Learning
作者提出了这样一个结构,称之为残差块。首先将上一层的输出通过一个权重层然后激活,然后再通过一个权重层不激活,最后将结果于上一层的输出相加再进行一次激活。假设我们真实需要学习的函数是
H
(
x
)
H(x)
H(x),由于最后输出的是
F
(
X
)
+
x
F(X)+x
F(X)+x 的值,实际上在残差块的内部学习到的就是
H
(
x
)
−
x
H(x)-x
H(x)−x 的值,即预测值和真实值的残差。
在这种情况下,如果上一层输出的值已经是最佳了,那么机器就会将残差块拟合成一个恒等映射,也就是上文提到的“什么都不做”。对神经网络来说,将权重层中的训练权重拟合为0比直接去拟合一个恒等映射要简单,因此如果上一层输出的值已经是最佳,那么残差块中的各个权重就会趋向于0。
Shortcut Connections
采用了上图的残差块结构之后,我们可以把它的函数表达式写成这样:
y
=
F
(
x
,
W
i
)
+
x
y=F(x,{W_i})+x
y=F(x,Wi)+x
其中,x和y分别为这个块中输入和输出向量,
F
(
x
,
W
i
)
F(x,{W_i})
F(x,Wi)表示残差块中学习的函数。
在上图的结构中,函数F可以写成下式:
F
=
W
2
σ
(
W
1
x
)
F=W_2σ(W_1x)
F=W2σ(W1x)
其中,σ代表激活函数 ReLU。
接下来作者提到,采用了跳连接之后神经网络既不会引入额外的参数也不会提高计算复杂度,这是采用跳连接的一个显著优点。
The shortcut connections in Eqn.(1) introduce neither ex- tra
parameter nor computation complexity.
由于跳连接采用了直接将x于F(x)相加的方法,所以我们必须保证x的维度和F(x)一致,函数表达式如下所示:
y
=
F
(
x
,
W
i
)
+
W
s
x
y=F(x,{W_i})+W_sx
y=F(x,Wi)+Wsx
其中,
W
s
W_s
Ws表示一个将 x 进行维度变换的投影矩阵。
作者在他的另一篇论文——Identity Mapping in Deep Residual Networks 给出了不同的恒等映射方法,在本文中他指出了恒等映射是有效且经济地解决网络退化问题的方法。
But we will show by experiments that the identity mapping is
sufficient for addressing the degradation problem and is economical
对于残差块的结构其实我们一直还有一个疑问——为什么残差块中要经过两层的权重层呢?作者在本段的最后一部分说明了这个问题。他解释道,残差块如果只有一层就会类似一个普通线性层,残差结构就不会再表现出来优势了。
网络结构
网络结构如上图所示,其中左边的是VGG-19的网络结构,中间的是不带残差的34层普通神经网络,右边是带残差的34层神经网络。
我们可以通过观察看到,残差网络中有许多跳连接使用虚线表示,这代表着输入输出维度不一致,因此我们在进行处理的时候需要对残差块的输入进行升维操作。
作者在此处给出了两个方案:
- (A)用0去对残差块的输入x进行padding(填充)
- (B)用1×1的卷积核对 x 进行升维
实验结果
图中细线代表训练误差,粗线代表测试误差。从左图可以看出34层的普通神经网络无论是训练误差还是测试误差都比18层的要高,可知网络退化问题并不是由于过拟合引起的。再观察右图加了残差之后的结果可以看到,34层的网络无论是训练误差还是测试误差都比18层的要低,由此可知作者解决了网络退化的问题。
作者在这个部分继续探讨了为什么会发生网络退化的现象,并且认为这个问题也不是由于梯度消失引起的。
作者在实现这个普通神经网络的时候采用了批量归一化技术(BN),这使得反向传播的梯度有着一个比较健康的范数,也就是说梯度不会极端地向0靠近。通过上图各个网络有着相似的错误率也可以验证梯度是可以正常的进行传播的。
随后作者推测,深层神经网络难以训练的原因有可能是因为其收敛速度以指数级的速度减小。
We conjecture that the deep plain nets may have exponentially low
convergence rates, which impact the reducing of the training error.
针对于这个问题,作者还在题注里说明,即使增加迭代次数也依然不能解决网络退化问题。
We have experimented with more training iterations (3×) and still ob-served the degradation problem, suggesting that this problem cannot be feasibly addressed by simply using more iterations.
通过上面的图表和之前的 Figure 4,作者观察到了三个重要的结论:
第一,34层的残差网络比18层的要好;
第二,残差学习可用来搭建比较深的网络;
第三,带残差的网络收敛速度更快。
在这段的后半部分,作者还给出一个新的残差模块结构。上图左边为普通的残差模块,右边称之为 bottleneck 残差模块。其中 1×1 的卷积就是为了将输入张量进行降维,这个原理和上一周学习 Inception 时的 1×1 卷积的作用是一样的。先降维再升维可以减少大量的计算量。
上图可以看到各种网络的误差变化,其中中间的突变是由于学习率改变而导致的。
We start with a learning rate of 0.1, divide it by 10 at 32k and 48k
iterations, and terminate training at 64k iterations, which is
determined on a 45k/5k train/val split.
从上图中我们也可以看到残差神经网络解决了网络退化问题,即使是1202层的超深度网络也能正常的进行训练。但是当深度达到一定的大小之后又会产生过拟合。因此神经网络的深度依然不是越大越好。
代码实践
由上图可以看到,深层网络的误差比浅层网络还高有可能是因为发生了梯度消失现象。随着网络深度的加深,越来越多的梯度相乘可能会导致梯度无限趋近于0从而使得参数无法进行有效的更新。
而对于经过残差块的数据来说,令:
H
(
x
)
=
F
(
x
)
+
x
H(x)=F(x)+x
H(x)=F(x)+x
假设不考虑激活函数,对
x
x
x 求偏导有:
∂
H
∂
x
=
∂
F
∂
x
+
1
\frac{\partial H}{\partial x}=\frac{\partial F}{\partial x}+1
∂x∂H=∂x∂F+1
由上式可知,即使 ∂ F ∂ x \frac{\partial F}{\partial x} ∂x∂F趋向于0,整体的梯度都会在1的附近,从而使得梯度可以有效地传递。
注意,由于经过残差块之后的输出要加上最开始的输入,也就是说最后输出的结果是F(x)+x,所以在残差块的内部学习到的是H(x)-x。
并且由于最后要与x做加法,所以残差块内部的输出张量维度必须要和输入张量(x)维度是一致的。
下面以图中这个简单的残差网络为例子来进行手动实现:
残差块的实现:
class ResidualBlock(nn.Module):
def __init__(self, in_channels):
super(ResidualBlock, self).__init__()
self.channels = in_channels
# 3x3的卷积核会使得图片宽高减2,padding为1正好加回去使得图片尺寸不变
self.conv1 = nn.Conv2d(in_channels, in_channels, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(in_channels, in_channels, kernel_size=3, padding=1)
def forward(self, x):
y = F.relu(self.conv1(x))
y = self.conv2(y)
return F.relu(y + x)
网络设计:
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 16, kernel_size=5)
self.conv2 = nn.Conv2d(16, 32, kernel_size=5)
self.maxpool = nn.MaxPool2d(2)
self.rblock1 = ResidualBlock(16)
self.rblock2 = ResidualBlock(32)
self.fc1 = nn.Linear(32 * 4 * 4, 10)
def forward(self, x):
in_size = x.size(0) # 获取batch_size
x = self.maxpool(F.relu(self.conv1(x)))
x = self.rblock1(x)
x = self.maxpool(F.relu(self.conv2(x)))
x = self.rblock2(x)
x = x.view(in_size, -1) # 除batch_size之外的维度展平
x = self.fc1(x)
return x
训练结果:
我们可以把训练结果和上一期使用 inception 模块的训练结果做对比。
下图为采用了 inception 模块的卷积神经网络的训练结果:
而下图为采用了 residual 模块的训练结果:
对比后可以发现采用残差模块的 CNN 网络的精度显著提高,最高的时候甚至能达到99%。
论文原文
总结
ResNet 主要解决了训练非常深的神经网络时出现的梯度消失或梯度爆炸问题,通过引入“残差块”或“跳连接”(skip connections)来解决这个问题。在传统的深度神经网络中,随着网络层数的增加,模型可能会遭遇网络退化问题,即随着网络深度的增加,模型的准确率反而会下降。而ResNet通过在层之间添加直接的连接,允许信息跨越多层传递,从而使得深层网络的训练变得更加容易。这种结构也有助于梯度在整个网络中的流动,从而改善了深层网络的训练效果。每个残差块通常包含两个或多个卷积层,并且输入可以直接跳过这些层到达输出。如果输入X通过几个卷积层后得到F(X),那么最终的输出可以表示为F(X) + X。这样做的好处是,在反向传播过程中,梯度可以通过这两个路径进行传递,即可以通过残差路径直接传回给前面的层,减少了梯度消失的风险。由于其出色的性能和简便的设计,ResNet已经成为许多计算机视觉任务的标准模型之一,包括但不限于图像分类、目标检测和语义分割等