动手学深度学习 6.6 通过时间反向传播

前言

从零开始学习ai文章系列计划是个人在《动手学深度学习》和《磨菇书》两本书的学习中的个人笔记,文章也会以课本中的章节分开,即每个章节一片笔记。我会尽量的把主要内容以及遇到的难点进行记录与解决,如果哪里有错误的欢迎指正。或者不清晰的可以直接查看原文部分。

《动手学深度学习》原文(课本):https://tangshusen.me/Dive-into-DL-PyTorch/#/

《动手学深度学习》代码:https://github.com/ShusenTang/Dive-into-DL-PyTorch

(由于有时候公式太多,可能会直接贴图片)


在前面两节中,如果不裁剪梯度,模型将无法正常训练。为了深刻理解这一现象,本节将介绍循环神经网络中梯度的计算和存储方法,即通过时间反向传播(back-propagation through time)。

1.定义模型

简单起见,我们考虑一个无偏差项的循环神经网络,且激活函数为恒等映射(\(ϕ(x)=x\) )。

设时间步 t 的输入为单样本 \(x_t\) ,标签为 \(y_t\)

那么隐藏状态 \(h_t\) 的计算表达式为 \[ h_t=W_{hx}x_t+W_{hh}h_{t-1} \] 其中 \(W_{hx}\)\(W_{hh}\) 是隐藏层权重参数。

设输出层权重参数 \(W_{qh}\) ,时间步 t 的输出层变量 \(o_t\) 计算为 \[ o_t=W_{qh}h_t \] 设时间步 t 的损失为 \(ℓ(o_t,y_t)\)。时间步数为T的损失函数L定义为 \[ L=\frac {1} {T}\sum ^{T}_{t=1} {ℓ(o_t,y_t)} \] 我们将 L 称为有关给定时间步的数据样本的目标函数,并在本节后续讨论中简称为目标函数。

2.模型计算图

为了可视化循环神经网络中模型变量和参数在计算中的依赖关系,我们可以绘制模型计算图,如图6.3所示。

例如,时间步3的隐藏状态 \(h_3\) 的计算依赖模型参数 \(W_{hx}、W_{hh}\) 、上一时间步隐藏状态 \(h_2\) 以及当前时间步输入 \(x_3\)

3.方法

刚刚提到,图6.3中的模型的参数是 \(W_{hx}, W_{hh}和 W_{qh}\)

与3.14节(正向传播、反向传播和计算图)中的类似,训练模型通常需要模型参数的梯度 \(∂L/∂W_{hx}、∂L/∂W_{hh}、∂L/∂W_{qh}\)

根据图6.3中的依赖关系,我们可以按照其中箭头所指的反方向依次计算并存储梯度。为了表述方便,我们依然采用3.14节中表达链式法则的运算符prod。

首先,目标函数有关各时间步输出层变量的梯度 \(∂L/∂o_t\) 很容易计算:

原式:\(L=\frac {1} {T}\sum ^{T}_{t=1} {ℓ(o_t,y_t)}\)

求导:\(\frac{∂L}{∂o_t} = \frac{1}{T}\frac{ℓ(o_t,y_t)}{∂o_t}\)

下面,我们可以计算目标函数有关模型参数 \(W_{qh}\) 的梯度 \(∂L/∂W_{qh}\) 。根据图6.3,L通过 \(o_1,…,o_T\) 依赖\(W_{qh}\)

依据链式法则,

原式:\(L=\frac {1} {T}\sum ^{T}_{t=1} {ℓ(o_t,y_t)} \quad,o_t=W_{qh}h_t\)

求导:\(\frac{∂L}{∂W_{qh}} = \sum^T_{t=1}{prod(\frac{∂L}{∂o_t},\frac{∂o_t}{∂W_{qh}})}=\sum^T_{t=1}{\frac{∂L}{∂o_t}h_t^\intercal}\)

在解决 L 对 \(W_{hx}, W_{hh}\) 的导数前,我们我们注意到隐藏状态之间也存在依赖关系。

在图6.3中,L只通过 \(o_T\) 依赖最终时间步T的隐藏状态\(h_T\)。因此,我们先计算目标函数有关最终时间步隐藏状态的梯度\(∂L/∂h_{T}\) (本例中指的是图中的\(h_3\) )

原式:\(L=\frac {1} {T}\sum ^{T}_{t=1} {ℓ(o_t,y_t)} \quad,o_t=W_{qh}h_t\)

仅第T时间步求导:\(\frac{∂L}{∂h_{T}} = prod(\frac{∂L}{∂o_t},\frac{∂o_t}{∂h_{T}})=W_{qh}^\intercal\frac{∂L}{∂o_T}\)

接下来对于时间步 t<T , 在图6.3中,L通过\(h_{t+1} 和 o_t\)依赖 \(h_t\) 。依据链式法则, 目标函数有关时间步t<T 的隐藏状态的梯度\(∂L/∂h_t\) 需要按照时间步从大到小依次计算:

我们前面求了最近时间步t=3的导数 \(∂L/∂h_3\) 后,我们求上一时间步t=2,从图中我们看到 \(h_2\) 参与了2个计算过程(\(o_3与o_2\)),这两与L的关系是加和的,\(L=\frac {1} {T}\sum ^{T}_{t=1} {ℓ(o_t,y_t)}\),因此求\(h_2\)的偏导时,我们得到 \[ \frac{∂L}{∂h_{2}} =\frac{∂L}{∂h_{3}}\frac{∂h_3}{∂h_{2}} + \frac{∂L}{∂o_{2}}\frac{∂o_2}{∂h_{2}}=W_{hh}^\intercal\frac{∂L}{∂h_{3}} + W_{qh}^\intercal\frac{∂L}{∂o_{2}} \] 相关原式:\(h_t=W_{hx}x_t+W_{hh}h_{t-1}\) ,$ o_t=W_{qh}h_t $

同理: \[ \frac{∂L}{∂h_{1}} =W_{hh}^\intercal\frac{∂L}{∂h_{2}} + W_{qh}^\intercal\frac{∂L}{∂o_{1}} \] 因此对于时间步 t<T,存在 \[ \frac{∂L}{∂h_{t}} =\frac{∂L}{∂h_{t+1}}\frac{∂h_{t+1}}{∂h_{t}} + \frac{∂L}{∂o_{t}}\frac{∂o_t}{∂h_{t}}=W_{hh}^\intercal\frac{∂L}{∂h_{t+1}} + W_{qh}^\intercal\frac{∂L}{∂o_{t}} \] 我们根据之前已知:\(\frac{∂L}{∂h_{T}} = prod(\frac{∂L}{∂o_t},\frac{∂o_t}{∂h_{T}})=W_{qh}^\intercal\frac{∂L}{∂o_t}\)

带入公式得到 \[ \frac{∂L}{∂h_{t}} =W_{hh}^\intercal W_{qh}^\intercal\frac{∂L}{∂o_{t+1}} + W_{qh}^\intercal\frac{∂L}{∂o_{t}} \] 那么L对于\(h_2和h_1\) 的求导如下 \[ \frac{∂L}{∂h_{2}} = W_{hh}^\intercal W_{qh}^\intercal\frac{∂L}{∂o_{3}} + W_{qh}^\intercal\frac{∂L}{∂o_{2}} \]

\[ \begin{align*} \frac{∂L}{∂h_{1}} &=W_{hh}^\intercal\frac{∂L}{∂h_{2}} + W_{qh}^\intercal\frac{∂L}{∂o_{1}} \\ &= W_{hh}^\intercal W_{hh}^\intercal W_{qh}^\intercal\frac{∂L}{∂o_3}+ W_{hh}^\intercal W_{qh}^\intercal\frac{∂L}{∂o_{2}}+ W_{qh}^\intercal\frac{∂L}{∂o_{1}} \end{align*} \]

将上面的递归公式展开,对任意时间步1≤t≤T,我们可以得到目标函数有关隐藏状态梯度的通项公式 \[ \frac{∂L}{∂h_{t}} = \sum^T_{i=t}{((W_{hh}^\intercal)^{T-i} (W_{qh}^\intercal)\frac{∂L}{∂o_{T+t-i}})} \] 由上式中的指数项可见,当时间步数 T 较大或者时间步 t 较小时,目标函数有关隐藏状态的梯度较容易出现衰减和爆炸。

这也会影响其他包含 \(∂L/∂h_t\) 项的梯度,例如隐藏层中模型参数的梯度 \(∂L/∂W_{hx}\)\(∂L/∂W_{hh}\)

我们已在3.14节里解释过,每次迭代中,我们在依次计算完以上各个梯度后,会将它们存储起来,从而避免重复计算。例如,由于隐藏状态梯度 \(∂L/∂h_t\) 被计算和存储,之后的模型参数梯度 \(∂L/∂W_{hx}\)\(∂L/∂W_{hh}\) 的计算可以直接读取 \(∂L/∂h_t\) 的值,而无须重复计算它们。

此外,反向传播中的梯度计算可能会依赖变量的当前值。它们正是通过正向传播计算出来的。 举例来说,参数梯度 \(∂L/∂W_{hh}\) 的计算需要依赖隐藏状态在时间步\(t=0,…,T−1\)的当前值 \(h_t\)\(h_0\)是初始化得到的)。这些值是通过从输入层到输出层的正向传播计算并存储得到的。