Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
Laurence001
d2l-zh
提交
88c79a81
D
d2l-zh
项目概览
Laurence001
/
d2l-zh
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
D
d2l-zh
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
88c79a81
编写于
12月 18, 2020
作者:
R
Rachel Hu
提交者:
Aston Zhang
2月 17, 2021
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
first version done
上级
f797d85a
变更
1
隐藏空白更改
内联
并排
Showing
1 changed file
with
76 addition
and
29 deletion
+76
-29
chapter_convolutional-modern/resnet.md
chapter_convolutional-modern/resnet.md
+76
-29
未找到文件。
chapter_convolutional-modern/resnet.md
浏览文件 @
88c79a81
# 残差网络(ResNet)
:label:
`sec_resnet`
随着我们设计越来越深的网络,了解添加层如何增加网络的复杂性和表现力变得越来越重要。更重要的是设计网络的能力,在这种网络中,添加层会使网络更具表现力,而不仅仅是与众不同。为了取得一些进展,我们需要一些数学知识。
随着我们设计越来越深的网络,理解“添加层如何增加网络的复杂性和性能”变得至关重要。
为了取得质的突破,我们需要一些数学基础知识。
## 函数类
考虑$
\m
athcal{F}$,一种特定网络架构(连同学习速率和其他超参数设置)可以达到的功能类别。也就是说,对于所有$f
\i
n
\m
athcal{F}$,存在一些参数集(例如权重和偏差),这些参数可以通过在合适的数据集上进行训练而获得。让我们假设$f^
*$是我们真正想要找到的“真相”函数。如果是$\mathcal{F}$,我们的状态很好,但通常我们不会那么幸运。相反,我们将尝试找到一些$f^*
_
\m
athcal{F}$,这是我们在$
\m
athcal{F}$中的最佳选择。例如,给定一个具有$
\m
athbf{X}$特性和$
\m
athbf{y}$标签的数据集,我们可以尝试通过解决以下优化问题来找到它:
首先,假设有一类特定的神经网络架构 $
\m
athcal{F}$,它包括学习速率和其他超参数设置。
也就是说,对于所有 $f
\i
n
\m
athcal{F}$,存在一些参数集(例如权重和偏差),这些参数可以通过在合适的数据集上进行训练而获得。
现在假设 $f^
*$ 是我们真正想要找到的函数,如果是 $f^*
\i
n
\m
athcal{F}$,那我们的可以轻而易举的训练得到它,但通常我们不会那么幸运。
相反,我们将尝试找到一个函数 $f^
*
_
\m
athcal{F}$,这是我们在 $
\m
athcal{F}$ 中的最佳选择。
例如,给定一个具有 $
\m
athbf{X}$ 特性和 $
\m
athbf{y}$ 标签的数据集,我们可以尝试通过解决以下优化问题来找到它:
$$f^
*
_\mathcal{F} := \mathop{\mathrm{argmin}}_
f L(
\m
athbf{X},
\m
athbf{y}, f)
\t
ext{ subject to } f
\i
n
\m
athcal{F}.$$
只有合理的假设是,如果我们设计一个不同的、更强大的体系结构$
\m
athcal{F}'$,我们将获得更好的结果。换句话说,我们预计$f^
*_{\mathcal{F}'}$比$f^*_{\mathcal{F}}$“更好”。然而,如果$\mathcal{F} \not\subseteq \mathcal{F}'$,则无法保证这种情况会发生。事实上,$f^*_
{
\m
athcal{F}'}$可能更糟。如:numref:
`fig_functionclasses`
所示,对于非嵌套函数类,较大的函数类并不总是向“真”函数$f^
*$靠拢。例如,在:numref:`fig_functionclasses`的左边,虽然$\mathcal{F}_3$比$f^*
$更接近$f^
*$,但$\mathcal{F}_6$却离开了,并且不能保证进一步增加复杂性可以减少与$f^*
$的距离。对于嵌套函数类:numref:
`fig_functionclasses`
右侧的$
\m
athcal{F}_1
\s
ubseteq
\l
dots
\s
ubseteq
\m
athcal{F}_6$,我们可以从非嵌套函数类中避免上述问题。
那么,怎样得到“更近似”真正 $f^
*
$ 的函数呢?
唯一合理的可能性是,我们需要设计一个更强大的体系结构 $
\m
athcal{F}'$。
换句话说,我们预计 $f^
*_{\mathcal{F}'}$ 比 $f^*
_{
\m
athcal{F}}$ “更近似”。
然而,如果 $
\m
athcal{F}
\n
ot
\s
ubseteq
\m
athcal{F}'$,则无法保证新的体系“更近似”。
事实上, $f^
*
_{
\m
athcal{F}'}$ 可能更糟:
如 :numref:
`fig_functionclasses`
所示,对于非嵌套函数类,较复杂的函数类并不总是向“真”函数 $f^
*
$ 靠拢(复杂度由 $
\m
athcal{F}_1$ 向 $
\m
athcal{F}_6$ 递增)。
在 :numref:
`fig_functionclasses`
的左边,虽然 $
\m
athcal{F}_3$ 比 $f^
*$ 更接近 $f^*
$,但$
\m
athcal{F}_6$ 却离的更远了。
相反对于 :numref:
`fig_functionclasses`
右侧的嵌套函数类 $
\m
athcal{F}_1
\s
ubseteq
\l
dots
\s
ubseteq
\m
athcal{F}_6$,我们可以避免上述问题。
![
For non-nested function classes, a larger (indicated by area) function class does not guarantee to get closer to the "truth" function ($f^*$). This does not happen in nested function classes.
](
../img/functionclasses.svg
)
![
对于非嵌套函数类,较复杂(由较大区域表示)的函数类不能保证更接近“真”函数( $f^*$ )。这种现象在嵌套函数类中不会发生。
](
../img/functionclasses.svg
)
:label:
`fig_functionclasses`
因此,只有当较大的函数类包含较小的函数类时,我们才能保证增加它们严格地增加网络的表达能力。对于深度神经网络,如果我们能将新增加的层训练成一个识别函数$f(
\m
athbf{x}) =
\m
athbf{x}$,新模型将与原模型一样有效。由于新模型可能会得到更好的解决方案来适应训练数据集,因此增加的层可能会更容易减少训练误差。
因此,只有当较复杂的函数类包含较小的函数类时,我们才能确保提高它们的性能。
对于深度神经网络,如果我们能将新添加的层训练成恒等映射 $f(
\m
athbf{x}) =
\m
athbf{x}$ ,新模型和原模型将同样有效。
同时,由于新模型可能得出更优的解来拟合训练数据集,因此添加层似乎更容易降低训练误差。
针对这一问题,何恺明等人提出了
*残差网络*
(ResNet) :cite:
`He.Zhang.Ren.ea.2016`
。
它在2015年的ImageNet图像识别挑战赛夺魁,并深刻影响了后来的深度神经网络的设计。
残差网络的核心思想是:每个附加层都应该更容易地包含原始函数作为其元素之一。
于是,
*残差块*
(residual blocks) 便诞生了,这个设计对如何建立深层神经网络产生了深远的影响。
凭借它,ResNet 赢得了 2015 年 ImageNet 大规模视觉识别挑战赛。。
这是他等人提出的问题。当工作在非常深的计算机视觉模型:cite:
`He.Zhang.Ren.ea.2016`
。他们提出的“残差网络”(
*ResNet*
)的核心思想是,每个附加层都应该更容易地包含身份函数作为其元素之一。这些考虑是相当深刻的,但他们导致了一个惊人的简单的解决方案,一个
*剩余块*
。凭借它,ResNet赢得了2015年ImageNet大规模视觉识别挑战赛。这个设计对如何建立深层神经网络产生了深远的影响。
## 残差块
让我们关注神经网络的局部部分,如:numref:
`fig_residual_block`
所示。用$
\m
athbf{x}$表示输入。我们假设我们希望通过学习获得的所需底层映射是$f(
\m
athbf{x})$,用作顶部激活函数的输入。在:numref:
`fig_residual_block`
的左边,虚线框内的部分必须直接学习映射$f(
\m
athbf{x})$。在右边,虚线框内的部分需要学习
*残差映射*
$f(
\m
athbf{x}) -
\m
athbf{x}$,这就是残差块如何获得其名称的。如果身份映射$f(
\m
athbf{x}) =
\m
athbf{x}$是所需的底层映射,则残差映射更容易学习:我们只需将虚线框内上部权重层(例如,完全连接层和卷积层)的权重和偏差推到零。:numref:
`fig_residual_block`
中的右图说明了ResNet的
*剩余块*
,其中携带层输入$
\m
athbf{x}$到加法运算符的实线称为
*剩余连接*
(或
*快捷连接*
)。使用剩余块,输入可以通过层间的剩余连接更快地向前传播。
让我们聚焦于神经网络局部:如图 :numref:
`fig_residual_block`
所示,假设我们的原始输入为 $x$ ,而希望学出的理想映射为 $f(
\m
athbf{x})$ (作为 :numref:
`fig_residual_block`
上方激活函数的输入)。
:numref:
`fig_residual_block`
左图虚线框中的部分需要直接拟合出该映射 $f(
\m
athbf{x})$ ,而右图虚线框中的部分则需要拟合出有关恒等映射的残差映射 $f(
\m
athbf{x}) -
\m
athbf{x}$ 。
残差映射在现实中往往更容易优化。
以本节开头提到的恒等映射作为我们希望学出的理想映射 $f(
\m
athbf{x})$ ,我们只需将 :numref:
`fig_residual_block`
中右图虚线框内上方的加权运算(如仿射)的权重和偏差参数设成 0,那么 $f(
\m
athbf{x})$ 即为恒等映射。
实际中,当理想映射 $f(
\m
athbf{x})$ 极接近于恒等映射时,残差映射也易于捕捉恒等映射的细微波动。
:numref:
`fig_residual_block`
右图是 ResNet 的基础块,即
*残差块*
(residual block)。
在残差块中,输入可通过跨层的数据线路更快地向前传播。
![
A regular block (left) and a residual block (right).
](
../img/residual-block.svg
)
![
一个正常块(左图)和一个残差块(右图)。
](
../img/residual-block.svg
)
:label:
`fig_residual_block`
ResNet遵循VGG的完整$3
\t
imes 3$卷积层设计。剩余块具有两个$3
\t
imes 3$个卷积层,具有相同数量的输出信道。每个卷积层后面是一个批处理规范化层和一个ReLU激活函数。然后,我们跳过这两个卷积运算,直接在最终的ReLU激活函数之前添加输入。这种设计要求两个卷积层的输出必须与输入具有相同的形状,以便将它们相加。如果我们想要改变通道的数量,我们需要引入一个额外的$1
\t
imes 1$卷积层来将输入转换成加法运算所需的形状。让我们看看下面的代码。
ResNet 沿用了 VGG 完整的 $3
\t
imes 3$ 卷积层设计。
残差块里首先有 2 个有相同输出通道数的 $3
\t
imes 3$ 卷积层。
每个卷积层后接一个批量归一化层和 ReLU 激活函数。
然后我们将输入跳过这 2 个卷积运算后直接加在最后的 ReLU 激活函数前。
这样的设计要求 2 个卷积层的输出与输入形状一样,从而可以相加。
如果想改变通道数,就需要引入一个额外的 $1
\t
imes 1$ 卷积层来将输入变换成需要的形状后再做相加运算。
残差块的实现如下。
```
{.python .input}
from d2l import mxnet as d2l
...
...
@@ -116,12 +148,14 @@ class Residual(tf.keras.Model): #@save
return tf.keras.activations.relu(Y)
```
此代码生成两种类型的网络:一种是在
`use_1x1conv=False`
应用ReLU非线性之前将输入添加到输出,另一种是在添加之前通过$1
\t
imes 1$卷积调整信道和分辨率。:numref:
`fig_resnet_block`
说明了这一点:
如图 :numref:
`fig_resnet_block`
所示,此代码生成两种类型的网络:
一种是在
`use_1x1conv=False`
时、应用 ReLU 非线性之前,将输入添加到输出。
另一种是在
`use_1x1conv=True`
时,添加通过 $1
\t
imes 1$ 卷积调整通道和分辨率。
![
ResNet block with and without $1 \times 1$ convolution.
](
../img/resnet-block.svg
)
![
包含以及不包含 $1 \times 1$ 卷积层的残差块。
](
../img/resnet-block.svg
)
:label:
`fig_resnet_block`
现在让我们看看输入和输出是相同形状
的情况。
下面我们来查看输入和输出形状一致
的情况。
```
{.python .input}
blk = Residual(3)
...
...
@@ -146,7 +180,7 @@ Y = blk(X)
Y.shape
```
我们
还可以选择将输出高度和宽度减半,同时增加输出通道的数量
。
我们
也可以在增加输出通道数的同时减半输出的高和宽
。
```
{.python .input}
blk = Residual(6, use_1x1conv=True, strides=2)
...
...
@@ -168,7 +202,9 @@ blk(X).shape
## ResNet模型
ResNet的前两层与我们前面描述的GoogLeNet是相同的:$7
\t
imes 7$卷积层有64个输出通道,步长为2,后面是$3
\t
imes 3$最大池层,步长为2。区别是在ResNet中每个卷积层之后添加的批处理规范化层。
ResNet 的前两层跟之前介绍的 GoogLeNet 中的一样:
在输出通道数为 64、步幅为 2 的 $7
\t
imes 7$ 卷积层后接步幅为 2 的 $3
\t
imes 3$ 的最大池化层。
不同之处在于 ResNet 每个卷积层后增加了批量归一化层。
```
{.python .input}
net = nn.Sequential()
...
...
@@ -193,9 +229,13 @@ b1 = tf.keras.models.Sequential([
tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')])
```
GoogLeNet使用了由Inception块组成的四个模块。然而,ResNet使用由剩余块组成的四个模块,每个模块使用具有相同数量输出信道的几个剩余块。第一个模块中的通道数与输入通道数相同。由于已经使用了跨距为2的最大池层,因此不必减小高度和宽度。在每个后续模块的第一剩余块中,与前一模块相比,信道数量增加了一倍,并且高度和宽度减半。
GoogLeNet 在后面接了 4 个由 Inception 块组成的模块。
ResNet 则使用 4 个由残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。
第一个模块的通道数同输入通道数一致。
由于之前已经使用了步幅为 2 的最大池化层,所以无须减小高和宽。
之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半。
现在,我们实现这个模块。注意,对第一个模块执行了特殊
处理。
下面我们来实现这个模块。注意,我们对第一个模块做了特别
处理。
```
{.python .input}
def resnet_block(num_channels, num_residuals, first_block=False):
...
...
@@ -242,7 +282,7 @@ class ResnetBlock(tf.keras.layers.Layer):
return X
```
然后,我们将所有模块添加到ResNet中。这里,每个模块使用两个剩余
块。
接着我们 为ResNet 加入所有残差块。这里每个模块使用 2 个残差
块。
```
{.python .input}
net.add(resnet_block(64, 2, first_block=True),
...
...
@@ -267,7 +307,7 @@ b4 = ResnetBlock(256, 2)
b5 = ResnetBlock(512, 2)
```
最后,
就像GoogLeNet一样,我们添加了一个全局平均池层,然后是完全连接的
层输出。
最后,
与 GoogLeNet 一样,加入全局平均池化层后接上全连接
层输出。
```
{.python .input}
net.add(nn.GlobalAvgPool2D(), nn.Dense(10))
...
...
@@ -304,12 +344,18 @@ def net():
tf.keras.layers.Dense(units=10)])
```
每个模块有4个卷积层(不包括$1
\t
imes 1$卷积层)。加上第一个$7
\t
imes 7$卷积层和最后一个全连通层,共有18个层。因此,这种模型通常被称为ResNet-18。通过在模块中配置不同数量的信道和剩余块,我们可以创建不同的ResNet模型,例如更深的152层ResNet-152。虽然ResNet的主要架构与GoogLeNet相似,但是ResNet的结构更简单、更容易修改。所有这些因素导致ResNet的迅速和广泛的使用。:numref:
`fig_resnet18`
描述了完整的ResNet-18。
每个模块有 4 个卷积层(不包括 $1
\t
imes 1$ 卷积层)。
加上第一个 $7
\t
imes 7$ 卷积层和最后一个全连接层,共有 18 层。
因此,这种模型通常被称为 ResNet-18。
通过配置不同的通道数和模块里的残差块数可以得到不同的 ResNet 模型,例如更深的含 15 层的 ResNet-152。
虽然 ResNet 的主体架构跟 GoogLeNet 的类似,但 ResNet 结构更简单,修改也更方便。这些因素都导致了 ResNet 迅速被广泛使用。
:numref:
`fig_resnet18`
描述了完整的ResNet-18。
![
The ResNet-18 architecture.
](
../img/resnet18.svg
)
:label:
`fig_resnet18`
在训练ResNet之前,让我们观察一下ResNet中不同模块的输入形状是如何变化的。在所有以前的架构中,分辨率降低,而通道数量增加,直到全局平均池层聚集所有特性为止。
在训练 ResNet 之前,让我们观察一下 ResNet 中不同模块的输入形状是如何变化的。
在之前所有架构中,分辨率降低,通道数量增加,直到全局平均池化层聚集所有特性为止。
```
{.python .input}
X = np.random.uniform(size=(1, 1, 224, 224))
...
...
@@ -348,18 +394,19 @@ d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr)
## 小结
*
嵌套函数类是理想的。在深层神经网络中学习另一层作为身份函数(尽管这是一个极端情况)应该很容易。
*
残差映射可以更容易地学习同一函数,例如将权重层中的参数推到零。
*
利用残差块可以训练出一个有效的深层神经网络。输入可以通过层间的残余连接更快地向前传播。
*
ResNet对随后的深层神经网络的设计产生了重大影响,无论是卷积的还是序列的。
*
嵌套函数类是理想的。在深层神经网络中学习另一层作为“原始”函数较容易(尽管这是一个极端情况)。
*
残差映射可以更容易地学习同一函数,例如将权重层中的参数近似为零。
*
利用残差块(residual blocks)可以训练出一个有效的深层神经网络:输入可以通过层间的残余连接更快地向前传播。
*
ResNet 对随后的深层神经网络设计产生了深远影响,无论是卷积还是全连接层。
## 练习
1.
:numref:
`fig_inception`
中的起始块与剩余块之间的主要区别是什么?在删除了Inception
块中的一些路径之后,它们是如何相互关联的?
1.
参考
ResNet论文:cite:
`He.Zhang.Ren.ea.2016`
中的表
1,以实现不同的变体。
1.
对于更深层次的网络,ResNet
引入了“瓶颈
”架构来降低模型的复杂性。试着去实现它。
1.
在
ResNet的后续版本中,作者将“卷积、批处理规范化和激活”结构更改为“批处理规范化、激活和卷积”结构。你自己做这个改进。详见:cite:
`He.Zhang.Ren.ea.2016*1`
中的图
1。
1.
为什么即使函数类是嵌套的,我们
为什么不能不加限制地
增加函数的复杂性呢?
1.
:numref:
`fig_inception`
中的起始块与残差块之间的主要区别是什么?在删除了 Inception
块中的一些路径之后,它们是如何相互关联的?
1.
参考
ResNet 论文 :cite:
`He.Zhang.Ren.ea.2016`
中的表
1,以实现不同的变体。
1.
对于更深层次的网络,ResNet
引入了“bottleneck
”架构来降低模型的复杂性。试着去实现它。
1.
在
ResNet 的后续版本中,作者将“卷积、批处理规范化和激活”结构更改为“批处理规范化、激活和卷积”结构。请你做这个改进。详见 :cite:
`He.Zhang.Ren.ea.2016*1`
中的图
1。
1.
为什么即使函数类是嵌套的,我们
仍然要限制
增加函数的复杂性呢?
:begin_tab:
`mxnet`
[
Discussions
](
https://discuss.d2l.ai/t/85
)
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录