9.7. 通过时间反向传播
在 Colab 中打开 Notebook
在 Colab 中打开 Notebook
在 Colab 中打开 Notebook
在 Colab 中打开 Notebook
在 SageMaker Studio Lab 中打开 Notebook

如果你完成了 第 9.5 节中的练习,你就会发现梯度裁剪对于防止偶尔出现的巨大梯度破坏训练的稳定性至关重要。我们曾暗示,梯度爆炸源于长序列的反向传播。在介绍一系列现代循环神经网络架构之前,让我们先从数学细节上仔细看看*反向传播*在序列模型中是如何工作的。希望这次讨论能为*梯度消失*和*梯度爆炸*的概念带来一些精确的定义。如果你还记得在 第 5.3 节介绍多层感知机时我们对计算图中前向和反向传播的讨论,那么循环神经网络中的前向传播应该相对简单。在循环神经网络中应用反向传播称为*通过时间反向传播*(backpropagation through time,BPTT)(Werbos, 1990)。这个过程要求我们一次一个时间步地展开(或“展开”)循环神经网络的计算图。展开后的循环神经网络本质上是一个前馈神经网络,其特殊之处在于,相同的参数在整个展开的网络中重复出现,在每个时间步出现一次。然后,就像在任何前馈神经网络中一样,我们可以应用链式法则,通过展开的网络反向传播梯度。对于每个参数的梯度,必须在展开的网络中该参数出现的所有位置上进行求和。处理这种权重绑定应该对我们来说很熟悉,我们在卷积神经网络的章节中已经遇到过。

由于序列可能相当长,因此会产生一些复杂问题。处理由一千多个词元组成的文本序列并不罕见。请注意,这在计算(内存过大)和优化(数值不稳定性)方面都构成了问题。第一步的输入在到达输出之前要经过超过1000次矩阵乘积,计算梯度还需要另外1000次矩阵乘积。我们现在来分析一下可能会出现什么问题以及如何在实践中解决它。

9.7.1. 循环神经网络中的梯度分析

我们从一个简化的循环神经网络工作模型开始。这个模型忽略了有关隐藏状态的具体细节以及它是如何更新的。这里的数学符号没有明确区分标量、向量和矩阵。我们只是想建立一些直觉。在这个简化模型中,我们用 \(h_t\) 表示隐藏状态,\(x_t\) 表示输入,\(o_t\) 表示在时间步 \(t\) 的输出。回想一下我们在 第 9.4.2 节中的讨论,输入和隐藏状态在被隐藏层中的一个权重变量相乘之前可以被拼接起来。因此,我们用 \(w_\textrm{h}\)\(w_\textrm{o}\) 分别表示隐藏层和输出层的权重。结果,每个时间步的隐藏状态和输出为

(9.7.1)\[\begin{split}\begin{aligned}h_t &= f(x_t, h_{t-1}, w_\textrm{h}),\\o_t &= g(h_t, w_\textrm{o}),\end{aligned}\end{split}\]

其中 \(f\)\(g\) 分别是隐藏层和输出层的变换。因此,我们有一个值链 \(\{\ldots, (x_{t-1}, h_{t-1}, o_{t-1}), (x_{t}, h_{t}, o_t), \ldots\}\),它们通过循环计算相互依赖。前向传播相当直接。我们只需要一次一个时间步地遍历 \((x_t, h_t, o_t)\) 三元组。输出 \(o_t\) 和期望目标 \(y_t\) 之间的差异然后由一个目标函数在所有 \(T\) 个时间步上进行评估,如下所示:

(9.7.2)\[L(x_1, \ldots, x_T, y_1, \ldots, y_T, w_\textrm{h}, w_\textrm{o}) = \frac{1}{T}\sum_{t=1}^T l(y_t, o_t).\]

对于反向传播,事情要棘手一些,特别是当我们计算目标函数 \(L\) 关于参数 \(w_\textrm{h}\) 的梯度时。具体来说,根据链式法则,

(9.7.3)\[\begin{split}\begin{aligned}\frac{\partial L}{\partial w_\textrm{h}} & = \frac{1}{T}\sum_{t=1}^T \frac{\partial l(y_t, o_t)}{\partial w_\textrm{h}} \\& = \frac{1}{T}\sum_{t=1}^T \frac{\partial l(y_t, o_t)}{\partial o_t} \frac{\partial g(h_t, w_\textrm{o})}{\partial h_t} \frac{\partial h_t}{\partial w_\textrm{h}}.\end{aligned}\end{split}\]

(9.7.3) 中乘积的第一和第二个因子很容易计算。第三个因子 \(\partial h_t/\partial w_\textrm{h}\) 是棘手之处,因为我们需要循环地计算参数 \(w_\textrm{h}\)\(h_t\) 的影响。根据 (9.7.1) 中的循环计算,\(h_t\) 同时依赖于 \(h_{t-1}\)\(w_\textrm{h}\),而 \(h_{t-1}\) 的计算也依赖于 \(w_\textrm{h}\)。因此,使用链式法则评估 \(h_t\)\(w_\textrm{h}\) 的全导数得到

(9.7.4)\[\frac{\partial h_t}{\partial w_\textrm{h}}= \frac{\partial f(x_{t},h_{t-1},w_\textrm{h})}{\partial w_\textrm{h}} +\frac{\partial f(x_{t},h_{t-1},w_\textrm{h})}{\partial h_{t-1}} \frac{\partial h_{t-1}}{\partial w_\textrm{h}}.\]

为了推导上述梯度,假设我们有三个序列 \(\{a_{t}\},\{b_{t}\},\{c_{t}\}\) 满足 \(a_{0}=0\) 且当 \(t=1, 2,\ldots\)\(a_{t}=b_{t}+c_{t}a_{t-1}\)。那么对于 \(t\geq 1\),很容易证明

(9.7.5)\[a_{t}=b_{t}+\sum_{i=1}^{t-1}\left(\prod_{j=i+1}^{t}c_{j}\right)b_{i}.\]

通过如下替换 \(a_t\)\(b_t\)\(c_t\)

(9.7.6)\[\begin{split}\begin{aligned}a_t &= \frac{\partial h_t}{\partial w_\textrm{h}},\\ b_t &= \frac{\partial f(x_{t},h_{t-1},w_\textrm{h})}{\partial w_\textrm{h}}, \\ c_t &= \frac{\partial f(x_{t},h_{t-1},w_\textrm{h})}{\partial h_{t-1}},\end{aligned}\end{split}\]

(9.7.4) 中的梯度计算满足 \(a_{t}=b_{t}+c_{t}a_{t-1}\)。因此,根据 (9.7.5),我们可以用以下方式消除 (9.7.4) 中的循环计算:

(9.7.7)\[\frac{\partial h_t}{\partial w_\textrm{h}}=\frac{\partial f(x_{t},h_{t-1},w_\textrm{h})}{\partial w_\textrm{h}}+\sum_{i=1}^{t-1}\left(\prod_{j=i+1}^{t} \frac{\partial f(x_{j},h_{j-1},w_\textrm{h})}{\partial h_{j-1}} \right) \frac{\partial f(x_{i},h_{i-1},w_\textrm{h})}{\partial w_\textrm{h}}.\]

虽然我们可以使用链式法则递归地计算 \(\partial h_t/\partial w_\textrm{h}\),但只要 \(t\) 很大,这个链就会变得很长。让我们讨论一些处理这个问题的方法。

9.7.1.1. 完整计算

一个想法可能是计算 (9.7.7) 中的完整和。然而,这非常慢,并且梯度可能会爆炸,因为初始条件的微小变化可能会极大地影响结果。也就是说,我们可能会看到类似于蝴蝶效应的情况,初始条件的微小变化导致结果发生不成比例的变化。这通常是不可取的。毕竟,我们正在寻找能够很好泛化的稳健估计器。因此,这种策略在实践中几乎从不使用。

9.7.1.2. 截断时间步

或者,我们可以在 \(\tau\) 步之后截断 (9.7.7) 中的求和。这是我们到目前为止一直在讨论的。这导致了对真实梯度的*近似*,只需在 \(\partial h_{t-\tau}/\partial w_\textrm{h}\) 处终止求和。在实践中,这种方法效果很好。这就是通常所说的截断的通过时间反向传播 (Jaeger, 2002)。这种做法的一个后果是模型主要关注短期影响而不是长期后果。这实际上是*可取的*,因为它使估计偏向于更简单和更稳定的模型。

9.7.1.3. 随机截断

最后,我们可以用一个随机变量来替换 \(\partial h_t/\partial w_\textrm{h}\),这个随机变量在期望上是正确的,但会截断序列。这是通过使用一个序列 \(\xi_t\) 实现的,其中预定义了 \(0 \leq \pi_t \leq 1\),使得 \(P(\xi_t = 0) = 1-\pi_t\)\(P(\xi_t = \pi_t^{-1}) = \pi_t\),因此 \(E[\xi_t] = 1\)。我们用它来替换 (9.7.4) 中的梯度 \(\partial h_t/\partial w_\textrm{h}\)

(9.7.8)\[z_t= \frac{\partial f(x_{t},h_{t-1},w_\textrm{h})}{\partial w_\textrm{h}} +\xi_t \frac{\partial f(x_{t},h_{t-1},w_\textrm{h})}{\partial h_{t-1}} \frac{\partial h_{t-1}}{\partial w_\textrm{h}}.\]

\(\xi_t\) 的定义可以得出 \(E[z_t] = \partial h_t/\partial w_\textrm{h}\)。每当 \(\xi_t = 0\) 时,循环计算在该时间步 \(t\) 终止。这导致了一个不同长度序列的加权和,其中长序列很少见但被适当地加权。这个想法由 Tallec 和 Ollivier (2017) 提出。

9.7.1.4. 比较策略

../_images/truncated-bptt.svg

图 9.7.1 比较循环神经网络中计算梯度的策略。从上到下:随机截断、常规截断和完整计算。

图 9.7.1阐释了在使用循环神经网络的通过时间反向传播分析《时间机器》的前几个字符时的三种策略。

  • 第一行是随机截断,它将文本划分为不同长度的段落。

  • 第二行是常规截断,它将文本分解成相同长度的子序列。这是我们在循环神经网络实验中一直在做的事情。

  • 第三行是完整的通过时间反向传播,这会导致一个计算上不可行的表达式。

不幸的是,虽然理论上很有吸引力,但随机截断的效果并不比常规截断好多少,这很可能是由于多种因素造成的。首先,在实践中,一个观测经过几次反向传播步骤到过去的影响足以捕捉依赖关系。其次,增加的方差抵消了梯度在更多步骤后更准确的事实。第三,我们实际上*想要*只有短程交互的模型。因此,常规截断的通过时间反向传播具有轻微的正则化效果,这可能是可取的。

9.7.2. 通过时间反向传播详解

在讨论了一般原理之后,让我们详细讨论一下通过时间反向传播。与 第 9.7.1 节中的分析不同,下面我们将展示如何计算目标函数相对于所有分解的模型参数的梯度。为了简单起见,我们考虑一个没有偏置参数的循环神经网络,其隐藏层的激活函数使用恒等映射(\(\phi(x)=x\))。对于时间步 \(t\),设单个样本输入和目标分别为 \(\mathbf{x}_t \in \mathbb{R}^d\)\(y_t\)。隐藏状态 \(\mathbf{h}_t \in \mathbb{R}^h\) 和输出 \(\mathbf{o}_t \in \mathbb{R}^q\) 计算如下:

(9.7.9)\[\begin{split}\begin{aligned}\mathbf{h}_t &= \mathbf{W}_\textrm{hx} \mathbf{x}_t + \mathbf{W}_\textrm{hh} \mathbf{h}_{t-1},\\ \mathbf{o}_t &= \mathbf{W}_\textrm{qh} \mathbf{h}_{t},\end{aligned}\end{split}\]

其中 \(\mathbf{W}_\textrm{hx} \in \mathbb{R}^{h \times d}\)\(\mathbf{W}_\textrm{hh} \in \mathbb{R}^{h \times h}\)\(\mathbf{W}_\textrm{qh} \in \mathbb{R}^{q \times h}\) 是权重参数。用 \(l(\mathbf{o}_t, y_t)\) 表示时间步 \(t\) 的损失。我们的目标函数,即从序列开始的 \(T\) 个时间步的损失,因此是:

(9.7.10)\[L = \frac{1}{T} \sum_{t=1}^T l(\mathbf{o}_t, y_t).\]

为了可视化循环神经网络计算过程中模型变量和参数之间的依赖关系,我们可以为模型绘制一个计算图,如 图 9.7.2 所示。例如,时间步 3 的隐藏状态 \(\mathbf{h}_3\) 的计算依赖于模型参数 \(\mathbf{W}_\textrm{hx}\)\(\mathbf{W}_\textrm{hh}\)、前一个时间步的隐藏状态 \(\mathbf{h}_2\) 以及当前时间步的输入 \(\mathbf{x}_3\)

../_images/rnn-bptt.svg

图 9.7.2 展示一个具有三个时间步的循环神经网络模型依赖关系的计算图。方框表示变量(未着色)或参数(着色),圆圈表示运算符。

正如刚才提到的,图 9.7.2 中的模型参数是 \(\mathbf{W}_\textrm{hx}\)\(\mathbf{W}_\textrm{hh}\)\(\mathbf{W}_\textrm{qh}\)。通常,训练这个模型需要计算关于这些参数的梯度 \(\partial L/\partial \mathbf{W}_\textrm{hx}\)\(\partial L/\partial \mathbf{W}_\textrm{hh}\)\(\partial L/\partial \mathbf{W}_\textrm{qh}\)。根据 图 9.7.2 中的依赖关系,我们可以沿箭头的相反方向遍历来依次计算和存储梯度。为了在链式法则中灵活地表达不同形状的矩阵、向量和标量的乘法,我们继续使用在 第 5.3 节中描述的 \(\textrm{prod}\) 运算符。

首先,将目标函数对任何时间步 \(t\) 的模型输出求导是相当直接的:

(9.7.11)\[\frac{\partial L}{\partial \mathbf{o}_t} = \frac{\partial l (\mathbf{o}_t, y_t)}{T \cdot \partial \mathbf{o}_t} \in \mathbb{R}^q.\]

现在我们可以计算目标函数关于输出层参数 \(\mathbf{W}_\textrm{qh}\) 的梯度:\(\partial L/\partial \mathbf{W}_\textrm{qh} \in \mathbb{R}^{q \times h}\)。根据 图 9.7.2,目标 \(L\) 通过 \(\mathbf{o}_1, \ldots, \mathbf{o}_T\) 依赖于 \(\mathbf{W}_\textrm{qh}\)。使用链式法则得到

(9.7.12)\[\frac{\partial L}{\partial \mathbf{W}_\textrm{qh}} = \sum_{t=1}^T \textrm{prod}\left(\frac{\partial L}{\partial \mathbf{o}_t}, \frac{\partial \mathbf{o}_t}{\partial \mathbf{W}_\textrm{qh}}\right) = \sum_{t=1}^T \frac{\partial L}{\partial \mathbf{o}_t} \mathbf{h}_t^\top,\]

其中 \(\partial L/\partial \mathbf{o}_t\)(9.7.11) 给出。

接下来,如 图 9.7.2 所示,在最后一个时间步 \(T\),目标函数 \(L\) 仅通过 \(\mathbf{o}_T\) 依赖于隐藏状态 \(\mathbf{h}_T\)。因此,我们可以使用链式法则轻松找到梯度 \(\partial L/\partial \mathbf{h}_T \in \mathbb{R}^h\)

(9.7.13)\[\frac{\partial L}{\partial \mathbf{h}_T} = \textrm{prod}\left(\frac{\partial L}{\partial \mathbf{o}_T}, \frac{\partial \mathbf{o}_T}{\partial \mathbf{h}_T} \right) = \mathbf{W}_\textrm{qh}^\top \frac{\partial L}{\partial \mathbf{o}_T}.\]

对于任何时间步 \(t < T\),事情变得更棘手,因为目标函数 \(L\) 通过 \(\mathbf{h}_{t+1}\)\(\mathbf{o}_t\) 依赖于 \(\mathbf{h}_t\)。根据链式法则,在任何时间步 \(t < T\) 的隐藏状态梯度 \(\partial L/\partial \mathbf{h}_t \in \mathbb{R}^h\) 可以递归地计算为:

(9.7.14)\[\frac{\partial L}{\partial \mathbf{h}_t} = \textrm{prod}\left(\frac{\partial L}{\partial \mathbf{h}_{t+1}}, \frac{\partial \mathbf{h}_{t+1}}{\partial \mathbf{h}_t} \right) + \textrm{prod}\left(\frac{\partial L}{\partial \mathbf{o}_t}, \frac{\partial \mathbf{o}_t}{\partial \mathbf{h}_t} \right) = \mathbf{W}_\textrm{hh}^\top \frac{\partial L}{\partial \mathbf{h}_{t+1}} + \mathbf{W}_\textrm{qh}^\top \frac{\partial L}{\partial \mathbf{o}_t}.\]

为了分析,展开对于任何时间步 \(1 \leq t \leq T\) 的递归计算得到:

(9.7.15)\[\frac{\partial L}{\partial \mathbf{h}_t}= \sum_{i=t}^T {\left(\mathbf{W}_\textrm{hh}^\top\right)}^{T-i} \mathbf{W}_\textrm{qh}^\top \frac{\partial L}{\partial \mathbf{o}_{T+t-i}}.\]

我们可以从 (9.7.15) 中看到,这个简单的线性例子已经展示了长序列模型的一些关键问题:它涉及到可能非常大的 \(\mathbf{W}_\textrm{hh}^\top\) 的幂。在其中,小于 1 的特征值会消失,大于 1 的特征值会发散。这在数值上是不稳定的,表现为梯度消失和梯度爆炸的形式。解决这个问题的一种方法是如 第 9.7.1 节 中讨论的,将时间步截断到一个计算上方便的大小。在实践中,这种截断也可以通过在给定数量的时间步后分离梯度来实现。稍后,我们将看到更复杂的序列模型,如长短期记忆,如何进一步缓解这个问题。

最后,图 9.7.2 显示目标函数 \(L\) 通过隐藏状态 \(\mathbf{h}_1, \ldots, \mathbf{h}_T\) 依赖于隐藏层中的模型参数 \(\mathbf{W}_\textrm{hx}\)\(\mathbf{W}_\textrm{hh}\)。为了计算关于这些参数的梯度 \(\partial L / \partial \mathbf{W}_\textrm{hx} \in \mathbb{R}^{h \times d}\)\(\partial L / \partial \mathbf{W}_\textrm{hh} \in \mathbb{R}^{h \times h}\),我们应用链式法则得到:

(9.7.16)\[\begin{split}\begin{aligned} \frac{\partial L}{\partial \mathbf{W}_\textrm{hx}} &= \sum_{t=1}^T \textrm{prod}\left(\frac{\partial L}{\partial \mathbf{h}_t}, \frac{\partial \mathbf{h}_t}{\partial \mathbf{W}_\textrm{hx}}\right) = \sum_{t=1}^T \frac{\partial L}{\partial \mathbf{h}_t} \mathbf{x}_t^\top,\\ \frac{\partial L}{\partial \mathbf{W}_\textrm{hh}} &= \sum_{t=1}^T \textrm{prod}\left(\frac{\partial L}{\partial \mathbf{h}_t}, \frac{\partial \mathbf{h}_t}{\partial \mathbf{W}_\textrm{hh}}\right) = \sum_{t=1}^T \frac{\partial L}{\partial \mathbf{h}_t} \mathbf{h}_{t-1}^\top, \end{aligned}\end{split}\]

其中由 (9.7.13)(9.7.14) 递归计算的 \(\partial L/\partial \mathbf{h}_t\) 是影响数值稳定性的关键量。

由于通过时间反向传播是反向传播在循环神经网络中的应用,正如我们在 第 5.3 节 中解释的那样,训练循环神经网络交替进行前向传播和通过时间反向传播。此外,通过时间反向传播会依次计算并存储上述梯度。具体来说,存储的中间值被重用以避免重复计算,例如存储 \(\partial L/\partial \mathbf{h}_t\) 以用于计算 \(\partial L / \partial \mathbf{W}_\textrm{hx}\)\(\partial L / \partial \mathbf{W}_\textrm{hh}\)

9.7.3. 小结

通过时间反向传播仅仅是反向传播在带有隐藏状态的序列模型中的应用。为了计算方便和数值稳定,需要进行截断,如常规截断或随机截断。矩阵的高次幂可能导致特征值发散或消失。这表现为梯度爆炸或梯度消失的形式。为了高效计算,在通过时间反向传播期间会缓存中间值。

9.7.4. 练习

  1. 假设我们有一个对称矩阵 \(\mathbf{M} \in \mathbb{R}^{n \times n}\),其特征值为 \(\lambda_i\),对应的特征向量为 \(\mathbf{v}_i\) (\(i = 1, \ldots, n\))。不失一般性,假设它们按 \(|\lambda_i| \geq |\lambda_{i+1}|\) 的顺序排列。

    1. 证明 \(\mathbf{M}^k\) 的特征值为 \(\lambda_i^k\)

    2. 证明对于一个随机向量 \(\mathbf{x} \in \mathbb{R}^n\)\(\mathbf{M}^k \mathbf{x}\) 很有可能会与 \(\mathbf{M}\) 的特征向量 \(\mathbf{v}_1\) 非常对齐。请形式化这个陈述。

    3. 上述结果对循环神经网络中的梯度意味着什么?

  2. 除了梯度裁剪,你还能想到其他处理循环神经网络中梯度爆炸的方法吗?

讨论