提交 c2abaf09 编写于 作者: R Rachel Hu 提交者: Aston Zhang

second version done

上级 88c79a81
# 残差网络(ResNet)
:label:`sec_resnet`
随着我们设计越来越深的网络,理解“添加层如何增加网络的复杂性和性能”变得至关重要。
随着我们设计越来越深的网络,深刻理解“新添加的层如何提升神经网络的性能”变得至关重要。
为了取得质的突破,我们需要一些数学基础知识。
## 函数类
首先,假设有一类特定的神经网络构 $\mathcal{F}$,它包括学习速率和其他超参数设置。
也就是说,对于所有 $f \in \mathcal{F}$,存在一些参数集(例如权重和偏差),这些参数可以通过在合适的数据集上进行训练而获得。
首先,假设有一类特定的神经网络构 $\mathcal{F}$,它包括学习速率和其他超参数设置。
对于所有 $f \in \mathcal{F}$,存在一些参数集(例如权重和偏差),这些参数可以通过在合适的数据集上进行训练而获得。
现在假设 $f^*$ 是我们真正想要找到的函数,如果是 $f^* \in \mathcal{F}$,那我们的可以轻而易举的训练得到它,但通常我们不会那么幸运。
相反,我们将尝试找到一个函数 $f^*_\mathcal{F}$,这是我们在 $\mathcal{F}$ 中的最佳选择。
例如,给定一个具有 $\mathbf{X}$ 特性和 $\mathbf{y}$ 标签的数据集,我们可以尝试通过解决以下优化问题来找到它:
......@@ -20,15 +20,15 @@ $$f^*_\mathcal{F} := \mathop{\mathrm{argmin}}_f L(\mathbf{X}, \mathbf{y}, f) \te
换句话说,我们预计 $f^*_{\mathcal{F}'}$ 比 $f^*_{\mathcal{F}}$ “更近似”。
然而,如果 $\mathcal{F} \not\subseteq \mathcal{F}'$,则无法保证新的体系“更近似”。
事实上, $f^*_{\mathcal{F}'}$ 可能更糟:
如 :numref:`fig_functionclasses` 所示,对于非嵌套函数类,较复杂的函数类并不总是向“真”函数 $f^*$ 靠拢(复杂度由 $\mathcal{F}_1$ 向 $\mathcal{F}_6$ 递增)。
如 :numref:`fig_functionclasses` 所示,对于非嵌套函数(non-nested function)类,较复杂的函数类并不总是向“真”函数 $f^*$ 靠拢(复杂度由 $\mathcal{F}_1$ 向 $\mathcal{F}_6$ 递增)。
在 :numref:`fig_functionclasses` 的左边,虽然 $\mathcal{F}_3$ 比 $f^*$ 更接近 $f^*$,但$\mathcal{F}_6$ 却离的更远了。
相反对于 :numref:`fig_functionclasses` 右侧的嵌套函数类 $\mathcal{F}_1 \subseteq \ldots \subseteq \mathcal{F}_6$,我们可以避免上述问题。
相反对于 :numref:`fig_functionclasses` 右侧的嵌套函数(nested function)类 $\mathcal{F}_1 \subseteq \ldots \subseteq \mathcal{F}_6$,我们可以避免上述问题。
![对于非嵌套函数类,较复杂(由较大区域表示)的函数类不能保证更接近“真”函数( $f^*$ )。这种现象在嵌套函数类中不会发生。](../img/functionclasses.svg)
:label:`fig_functionclasses`
因此,只有当较复杂的函数类包含较小的函数类时,我们才能确保提高它们的性能。
对于深度神经网络,如果我们能将新添加的层训练成恒等映射 $f(\mathbf{x}) = \mathbf{x}$ ,新模型和原模型将同样有效。
对于深度神经网络,如果我们能将新添加的层训练成 *恒等映射*(identity function) $f(\mathbf{x}) = \mathbf{x}$ ,新模型和原模型将同样有效。
同时,由于新模型可能得出更优的解来拟合训练数据集,因此添加层似乎更容易降低训练误差。
针对这一问题,何恺明等人提出了*残差网络*(ResNet) :cite:`He.Zhang.Ren.ea.2016`
......@@ -41,12 +41,12 @@ $$f^*_\mathcal{F} := \mathop{\mathrm{argmin}}_f L(\mathbf{X}, \mathbf{y}, f) \te
## 残差块
让我们聚焦于神经网络局部:如图 :numref:`fig_residual_block` 所示,假设我们的原始输入为 $x$ ,而希望学出的理想映射为 $f(\mathbf{x})$ (作为 :numref:`fig_residual_block` 上方激活函数的输入)。
:numref:`fig_residual_block` 左图虚线框中的部分需要直接拟合出该映射 $f(\mathbf{x})$ ,而右图虚线框中的部分则需要拟合出有关恒等映射的残差映射 $f(\mathbf{x}) - \mathbf{x}$ 。
:numref:`fig_residual_block` 左图虚线框中的部分需要直接拟合出该映射 $f(\mathbf{x})$ ,而右图虚线框中的部分则需要拟合出残差映射 $f(\mathbf{x}) - \mathbf{x}$ 。
残差映射在现实中往往更容易优化。
以本节开头提到的恒等映射作为我们希望学出的理想映射 $f(\mathbf{x})$ ,我们只需将 :numref:`fig_residual_block` 中右图虚线框内上方的加权运算(如仿射)的权重和偏差参数设成 0,那么 $f(\mathbf{x})$ 即为恒等映射。
实际中,当理想映射 $f(\mathbf{x})$ 极接近于恒等映射时,残差映射也易于捕捉恒等映射的细微波动。
:numref:`fig_residual_block` 右图是 ResNet 的基础块,即 *残差块*(residual block)。
在残差块中,输入可通过跨层数据线路更快地向前传播。
:numref:`fig_residual_block` 右图是 ResNet 的基础结构-- *残差块*(residual block)。
在残差块中,输入可通过跨层数据线路更快地向前传播。
![一个正常块(左图)和一个残差块(右图)。](../img/residual-block.svg)
:label:`fig_residual_block`
......@@ -54,10 +54,10 @@ $$f^*_\mathcal{F} := \mathop{\mathrm{argmin}}_f L(\mathbf{X}, \mathbf{y}, f) \te
ResNet 沿用了 VGG 完整的 $3\times 3$ 卷积层设计。
残差块里首先有 2 个有相同输出通道数的 $3\times 3$ 卷积层。
每个卷积层后接一个批量归一化层和 ReLU 激活函数。
然后我们将输入跳过这 2 个卷积运算后直接加在最后的 ReLU 激活函数前。
然后我们通过跨层数据线路,跳过这 2 个卷积运算,将输入直接加在最后的 ReLU 激活函数前。
这样的设计要求 2 个卷积层的输出与输入形状一样,从而可以相加。
如果想改变通道数,就需要引入一个额外的 $1\times 1$ 卷积层来将输入变换成需要的形状后再做相加运算。
残差块的实现如下
残差块的实现如下
```{.python .input}
from d2l import mxnet as d2l
......@@ -149,7 +149,7 @@ class Residual(tf.keras.Model): #@save
```
如图 :numref:`fig_resnet_block` 所示,此代码生成两种类型的网络:
一种是在 `use_1x1conv=False` 、应用 ReLU 非线性之前,将输入添加到输出。
一种是在 `use_1x1conv=False` 、应用 ReLU 非线性之前,将输入添加到输出。
另一种是在 `use_1x1conv=True` 时,添加通过 $1 \times 1$ 卷积调整通道和分辨率。
![包含以及不包含 $1 \times 1$ 卷积层的残差块。](../img/resnet-block.svg)
......@@ -203,7 +203,7 @@ blk(X).shape
## ResNet模型
ResNet 的前两层跟之前介绍的 GoogLeNet 中的一样:
在输出通道数为 64、步幅为 2 的 $7 \times 7$ 卷积层后接步幅为 2 的 $3 \times 3$ 的最大池化层。
在输出通道数为 64、步幅为 2 的 $7 \times 7$ 卷积层后接步幅为 2 的 $3 \times 3$ 的最大池化层。
不同之处在于 ResNet 每个卷积层后增加了批量归一化层。
```{.python .input}
......@@ -282,7 +282,7 @@ class ResnetBlock(tf.keras.layers.Layer):
return X
```
接着我们 为ResNet 加入所有残差块。这里每个模块使用 2 个残差块。
接着在 ResNet 加入所有残差块,这里每个模块使用 2 个残差块。
```{.python .input}
net.add(resnet_block(64, 2, first_block=True),
......@@ -307,7 +307,7 @@ b4 = ResnetBlock(256, 2)
b5 = ResnetBlock(512, 2)
```
最后,与 GoogLeNet 一样,加入全局平均池化层后接上全连接层输出。
最后,与 GoogLeNet 一样,在 ResNet 中加入全局平均池化层,以及全连接层输出。
```{.python .input}
net.add(nn.GlobalAvgPool2D(), nn.Dense(10))
......@@ -322,11 +322,9 @@ net = nn.Sequential(b1, b2, b3, b4, b5,
```{.python .input}
#@tab tensorflow
# Recall that we define this as a function so we can reuse later and run it
# within `tf.distribute.MirroredStrategy`'s scope to utilize various
# computational resources, e.g. GPUs. Also note that even though we have
# created b1, b2, b3, b4, b5 but we will recreate them inside this function's
# scope instead
# 回想之前我们定义一个函数,以便用它在 `tf.distribute.MirroredStrategy` 的范围,
# 来利用各种计算资源,例如gpu。另外,尽管我们已经创建了b1、b2、b3、b4、b5,
# 但是我们将在这个函数的作用域内重新创建它们
def net():
return tf.keras.Sequential([
# The following layers are the same as b1 that we created earlier
......@@ -344,18 +342,18 @@ def net():
tf.keras.layers.Dense(units=10)])
```
每个模块有 4 个卷积层(不包括 $1\times 1$ 卷积层)。
每个模块有 4 个卷积层(不包括恒等映射的 $1\times 1$ 卷积层)。
加上第一个 $7\times 7$ 卷积层和最后一个全连接层,共有 18 层。
因此,这种模型通常被称为 ResNet-18。
通过配置不同的通道数和模块里的残差块数可以得到不同的 ResNet 模型,例如更深的含 15 层的 ResNet-152。
通过配置不同的通道数和模块里的残差块数可以得到不同的 ResNet 模型,例如更深的含 152 层的 ResNet-152。
虽然 ResNet 的主体架构跟 GoogLeNet 的类似,但 ResNet 结构更简单,修改也更方便。这些因素都导致了 ResNet 迅速被广泛使用。
:numref:`fig_resnet18`描述了完整的ResNet-18。
:numref:`fig_resnet18` 描述了完整的 ResNet-18。
![The ResNet-18 architecture.](../img/resnet18.svg)
![ResNet-18 架构](../img/resnet18.svg)
:label:`fig_resnet18`
在训练 ResNet 之前,让我们观察一下 ResNet 中不同模块的输入形状是如何变化的。
在之前所有架构中,分辨率降低,通道数量增加,直到全局平均池化层聚集所有特性为止
在之前所有架构中,分辨率降低,通道数量增加,直到全局平均池化层聚集所有特
```{.python .input}
X = np.random.uniform(size=(1, 1, 224, 224))
......@@ -394,20 +392,20 @@ d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr)
## 小结
* 嵌套函数类是理想的。在深层神经网络中学习另一层作为“原始”函数较容易(尽管这是一个极端情况)。
* 学习嵌套函数(nested function)是训练神经网络的理想情况。在深层神经网络中,学习另一层作为恒等映射(identity function)较容易(尽管这是一个极端情况)。
* 残差映射可以更容易地学习同一函数,例如将权重层中的参数近似为零。
* 利用残差块(residual blocks)可以训练出一个有效的深层神经网络:输入可以通过层间的残余连接更快地向前传播。
* ResNet 对随后的深层神经网络设计产生了深远影响,无论是卷积还是全连接层
* ResNet 对随后的深层神经网络设计产生了深远影响,无论是卷积类还是全连接类
## 练习
1. :numref:`fig_inception` 中的起始块与残差块之间的主要区别是什么?在删除了 Inception 块中的一些路径之后,它们是如何相互关联的?
1. :numref:`fig_inception` 中的 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. 对于更深层次的网络,ResNet 引入了“bottleneck”架构来降低模型复杂性。请你试着去实现它。
1. 在 ResNet 的后续版本中,作者将“卷积层、批量归一化层和激活层”结构更改为“批量归一化层、激活层和卷积层”结构。请你做这个改进。详见 :cite:`He.Zhang.Ren.ea.2016*1` 中的图 1。
1. 为什么即使函数类是嵌套的,我们仍然要限制增加函数的复杂性呢?
:begin_tab:`mxnet`
[Discussions](https://discuss.d2l.ai/t/85)
:end_tab:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册