注:书中对代码的讲解并不详细,本文对很多细节做了详细注释。另外,书上的源代码是在Jupyter Notebook上运行的,较为分散,本文将代码集中起来,并加以完善,全部用vscode在python 3.9.18下测试通过,同时对于书上部分章节也做了整合。

Chapter8 Recurrent Neural Networks

8.7 Backpropagation Through Time

通过时间反向传播(backpropagation through time,BPTT)是循环神经网络中反向传播技术的一个特定应用,它要求我们将循环神经网络的计算图一次展开一个时间步,以获得模型变量和参数之间的依赖关系,然后,基于链式法则,应用反向传播来计算和存储梯度。由于序列可能相当长,因此依赖关系也可能相当长,在下文中,我们将阐明计算过程会发生什么以及如何在实践中解决它们。

8.7.1 RNN’s Gradient Analysis

我们从一个描述循环神经网络工作原理的简化模型开始,此模型忽略了隐状态的特性及其更新方式的细节,且其数学表示没有明确地区分标量、向量和矩阵。在这个简化模型中,我们将时间步ttt的隐状态表示为hth_tht,输入表示为xtx_txt,输出表示为oto_tot,分别使用whw_hwhwow_owo来表示隐藏层和输出层的权重。每个时间步的隐状态和输出可以写为:

ht=f(xt,ht−1,wh),ot=g(ht,wo),(2)\begin{aligned}h_t &= f(x_t, h_{t-1}, w_h),\\o_t &= g(h_t, w_o),\end{aligned}\tag{2}htot=f(xt,ht1,wh),=g(ht,wo),(2)

其中fffggg分别是隐藏层和输出层的变换。因此,我们有一个链{…,(xt−1,ht−1,ot−1),(xt,ht,ot),…}\{\ldots, (x_{t-1}, h_{t-1}, o_{t-1}), (x_{t}, h_{t}, o_t), \ldots\}{,(xt1,ht1,ot1),(xt,ht,ot),},它们通过循环计算彼此依赖。前向传播相当简单,一次一个时间步的遍历三元组(xt,ht,ot)(x_t, h_t, o_t)(xt,ht,ot),然后通过一个目标函数在所有TTT个时间步内评估输出oto_tot和对应的标签yty_tyt之间的差异:

L(x1,…,xT,y1,…,yT,wh,wo)=1T∑t=1Tl(yt,ot).L(x_1, \ldots, x_T, y_1, \ldots, y_T, w_h, w_o) = \frac{1}{T}\sum_{t=1}^T l(y_t, o_t).L(x1,,xT,y1,,yT,wh,wo)=T1t=1Tl(yt,ot).

对于反向传播,按照链式法则:

∂L∂wh=1T∑t=1T∂l(yt,ot)∂wh=1T∑t=1T∂l(yt,ot)∂ot∂g(ht,wo)∂ht∂ht∂wh.\begin{aligned}\frac{\partial L}{\partial w_h} & = \frac{1}{T}\sum_{t=1}^T \frac{\partial l(y_t, o_t)}{\partial w_h} \\& = \frac{1}{T}\sum_{t=1}^T \frac{\partial l(y_t, o_t)}{\partial o_t} \frac{\partial g(h_t, w_o)}{\partial h_t} \frac{\partial h_t}{\partial w_h}.\end{aligned}whL=T1t=1Twhl(yt,ot)=T1t=1Totl(yt,ot)htg(ht,wo)whht.

在上式乘积的第一项和第二项很容易计算,而第三项比较棘手,因为我们需要循环地计算参数whw_hwhhth_tht的影响。根据式(2),hth_tht既依赖于ht−1h_{t-1}ht1又依赖于whw_hwh,其中ht−1h_{t-1}ht1的计算也依赖于whw_hwh。因此,使用链式法则产生:

∂ht∂wh=∂f(xt,ht−1,wh)∂wh+∂f(xt,ht−1,wh)∂ht−1∂ht−1∂wh.(3)\frac{\partial h_t}{\partial w_h}= \frac{\partial f(x_{t},h_{t-1},w_h)}{\partial w_h} +\frac{\partial f(x_{t},h_{t-1},w_h)}{\partial h_{t-1}} \frac{\partial h_{t-1}}{\partial w_h}.\tag{3}whht=whf(xt,ht1,wh)+ht1f(xt,ht1,wh)whht1.(3)

为了导出上述梯度,假设我们有三个序列{at},{bt},{ct}\{a_{t}\},\{b_{t}\},\{c_{t}\}{at},{bt},{ct},当t=1,2,…t=1,2,\ldotst=1,2,时,序列满足a0=0a_{0}=0a0=0at=bt+ctat−1a_{t}=b_{t}+c_{t}a_{t-1}at=bt+ctat1。对于t≥1t\geq 1t1,就很容易得出:

at=bt+∑i=1t−1(∏j=i+1tcj)bi.(4)a_{t}=b_{t}+\sum_{i=1}^{t-1}\left(\prod_{j=i+1}^{t}c_{j}\right)b_{i}.\tag{4}at=bt+i=1t1(j=i+1tcj)bi.(4)

基于下列公式替换ata_tatbtb_tbtctc_tct

at=∂ht∂wh,bt=∂f(xt,ht−1,wh)∂wh,ct=∂f(xt,ht−1,wh)∂ht−1,\begin{aligned}a_t &= \frac{\partial h_t}{\partial w_h},\\ b_t &= \frac{\partial f(x_{t},h_{t-1},w_h)}{\partial w_h}, \\ c_t &= \frac{\partial f(x_{t},h_{t-1},w_h)}{\partial h_{t-1}},\end{aligned}atbtct=whht,=whf(xt,ht1,wh),=ht1f(xt,ht1,wh),

则:

∂ht∂wh=∂f(xt,ht−1,wh)∂wh+∑i=1t−1(∏j=i+1t∂f(xj,hj−1,wh)∂hj−1)∂f(xi,hi−1,wh)∂wh.(5)\frac{\partial h_t}{\partial w_h}=\frac{\partial f(x_{t},h_{t-1},w_h)}{\partial w_h}+\sum_{i=1}^{t-1}\left(\prod_{j=i+1}^{t} \frac{\partial f(x_{j},h_{j-1},w_h)}{\partial h_{j-1}} \right) \frac{\partial f(x_{i},h_{i-1},w_h)}{\partial w_h}.\tag{5}whht=whf(xt,ht1,wh)+i=1t1(j=i+1thj1f(xj,hj1,wh))whf(xi,hi1,wh).(5)

虽然我们可以使用链式法则递归地计算∂ht/∂wh\partial h_t/\partial w_hht/wh,但当ttt很大时这个链就会变得很长,在实践中是不可取的。

8.7.1.1 Cutting Off Time Steps

我们也可以在τ\tauτ步后截断式(5)中的求和计算,即将求和终止为∂ht−τ/∂wh\partial h_{t-\tau}/\partial w_hhtτ/wh,这种截断是通过在给定数量的时间步之后分离梯度来实现的。这样做导致该模型主要侧重于短期影响,而不是长期影响,在现实中是可取的。

8.7.1.2 Randomly Truncating

我们也可以用一个随机变量替换∂ht/∂wh\partial h_t/\partial w_hht/wh,这个随机变量通过序列ξt\xi_tξt实现。序列预定义了0≤πt≤10 \leq \pi_t \leq 10πt1,其中P(ξt=0)=1−πtP(\xi_t = 0) = 1-\pi_tP(ξt=0)=1πtP(ξt=πt−1)=πtP(\xi_t = \pi_t^{-1}) = \pi_tP(ξt=πt1)=πt,因此E[ξt]=1E[\xi_t] = 1E[ξt]=1。使用ztz_tzt来替换式(3)中的梯度∂ht/∂wh\partial h_t/\partial w_hht/wh得到:

zt=∂f(xt,ht−1,wh)∂wh+ξt∂f(xt,ht−1,wh)∂ht−1∂ht−1∂wh.z_t= \frac{\partial f(x_{t},h_{t-1},w_h)}{\partial w_h} +\xi_t \frac{\partial f(x_{t},h_{t-1},w_h)}{\partial h_{t-1}} \frac{\partial h_{t-1}}{\partial w_h}.zt=whf(xt,ht1,wh)+ξtht1f(xt,ht1,wh)whht1.

ξt\xi_tξt的定义中推导出来E[zt]=∂ht/∂whE[z_t] = \partial h_t/\partial w_hE[zt]=ht/wh,当ξt=0\xi_t = 0ξt=0时,递归计算终止在这个ttt时间步。这导致了不同长度序列的加权和,其中长序列出现的很少,所以需要适当地加大权重。

在这里插入图片描述

上图说明了当基于循环神经网络使用通过时间反向传播分析数据集的三种策略:

  • 第一行采用随机截断,方法是将文本划分为不同长度的片断;
  • 第二行采用常规截断,方法是将文本分解为相同长度的子序列;
  • 第三行采用通过时间的完全反向传播,结果是产生了在计算上不可行的表达式。

虽然随机截断在理论上具有吸引力,但由于多种因素在实践中并不总比常规截断更好。首先,在对过去若干个时间步经过反向传播后,观测结果足以捕获实际的依赖关系。其次,增加的方差抵消了时间步数越多梯度越精确的事实。第三,模型可能需要经过一定程度的正则化,以防止过拟合。通过常规截断方法,时间反向传播会引入一定程度的正则化效果,有助于控制模型的复杂度,并提高其泛化能力。

8.7.2 Details of BPTT

下面将展示如何计算目标函数相对于所有模型参数的梯度。简单起见,我们考虑一个没有偏置参数的RNN,其在隐藏层中的激活函数使用恒等映射(ϕ(x)=x\phi(x)=xϕ(x)=x)。对于时间步ttt,设单个样本的输入及其对应的标签分别为xt∈Rd\mathbf{x}_t \in \mathbb{R}^dxtRdyty_tyt。计算隐状态ht∈Rh\mathbf{h}_t \in \mathbb{R}^hhtRh和输出ot∈Rq\mathbf{o}_t \in \mathbb{R}^qotRq的方式为:

ht=Whxxt+Whhht−1,ot=Wqhht,\begin{aligned}\mathbf{h}_t &= \mathbf{W}_{hx} \mathbf{x}_t + \mathbf{W}_{hh} \mathbf{h}_{t-1},\\ \mathbf{o}_t &= \mathbf{W}_{qh} \mathbf{h}_{t},\end{aligned}htot=Whxxt+Whhht1,=Wqhht,

l(ot,yt)l(\mathbf{o}_t, y_t)l(ot,yt)表示时间步ttt处的损失函数,则目标函数的总体损失是:

L=1T∑t=1Tl(ot,yt).L = \frac{1}{T} \sum_{t=1}^T l(\mathbf{o}_t, y_t).L=T1t=1Tl(ot,yt).

模型绘制一个计算图如下所示。

在这里插入图片描述

上图中的模型参数是Whx\mathbf{W}_{hx}WhxWhh\mathbf{W}_{hh}WhhWqh\mathbf{W}_{qh}Wqh。通常,训练该模型需要分别计算:∂L/∂Whx\partial L/\partial \mathbf{W}_{hx}L/Whx∂L/∂Whh\partial L/\partial \mathbf{W}_{hh}L/Whh∂L/∂Wqh\partial L/\partial \mathbf{W}_{qh}L/Wqh。根据上图中的依赖关系,我们可以沿箭头的相反方向遍历计算图,依次计算和存储梯度。为了灵活地表示链式法则中不同形状的矩阵、向量和标量的乘法,我们继续使用4.7中所述的prod\text{prod}prod运算符。

首先有:

∂L∂ot=∂l(ot,yt)T⋅∂ot∈Rq.(6)\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.\tag{6}otL=Totl(ot,yt)Rq.(6)

接着得到:

∂L∂Wqh=∑t=1Tprod(∂L∂ot,∂ot∂Wqh)=∑t=1T∂L∂otht⊤∈Rq×h \frac{\partial L}{\partial \mathbf{W}_{qh}} = \sum_{t=1}^T \text{prod}\left(\frac{\partial L}{\partial \mathbf{o}_t}, \frac{\partial \mathbf{o}_t}{\partial \mathbf{W}_{qh}}\right) = \sum_{t=1}^T \frac{\partial L}{\partial \mathbf{o}_t} \mathbf{h}_t^\top\in \mathbb{R}^{q \times h} WqhL=t=1Tprod(otL,Wqhot)=t=1TotLhtRq×h

其中∂L/∂ot\partial L/\partial \mathbf{o}_tL/ot是由式(6)给出的。

接下来,如上图所示,在最后的时间步TTT,目标函数LLL仅通过oT\mathbf{o}_ToT依赖于隐状态hT\mathbf{h}_ThT。因此,我们通过使用链式法可以很容易地得到梯度∂L/∂hT\partial L/\partial \mathbf{h}_TL/hT

∂L∂hT=prod(∂L∂oT,∂oT∂hT)=Wqh⊤∂L∂oT∈Rh.(7)\frac{\partial L}{\partial \mathbf{h}_T} = \text{prod}\left(\frac{\partial L}{\partial \mathbf{o}_T}, \frac{\partial \mathbf{o}_T}{\partial \mathbf{h}_T} \right) = \mathbf{W}_{qh}^\top \frac{\partial L}{\partial \mathbf{o}_T}\in \mathbb{R}^h.\tag{7}hTL=prod(oTL,hToT)=WqhoTLRh.(7)

隐状态的梯度∂L/∂ht∈Rh\partial L/\partial \mathbf{h}_t \in \mathbb{R}^hL/htRh在任何t<Tt < Tt<T时都可以递归地计算为:

∂L∂ht=prod(∂L∂ht+1,∂ht+1∂ht)+prod(∂L∂ot,∂ot∂ht)=Whh⊤∂L∂ht+1+Wqh⊤∂L∂ot.(8)\frac{\partial L}{\partial \mathbf{h}_t} = \text{prod}\left(\frac{\partial L}{\partial \mathbf{h}_{t+1}}, \frac{\partial \mathbf{h}_{t+1}}{\partial \mathbf{h}_t} \right) + \text{prod}\left(\frac{\partial L}{\partial \mathbf{o}_t}, \frac{\partial \mathbf{o}_t}{\partial \mathbf{h}_t} \right) = \mathbf{W}_{hh}^\top \frac{\partial L}{\partial \mathbf{h}_{t+1}} + \mathbf{W}_{qh}^\top \frac{\partial L}{\partial \mathbf{o}_t}.\tag{8}htL=prod(ht+1L,htht+1)+prod(otL,htot)=Whhht+1L+WqhotL.(8)

对于任何时间步1≤t≤T1 \leq t \leq T1tT展开递归计算得:

∂L∂ht=∑i=tT(Whh⊤)T−iWqh⊤∂L∂oT+t−i.(9)\frac{\partial L}{\partial \mathbf{h}_t}= \sum_{i=t}^T {\left(\mathbf{W}_{hh}^\top\right)}^{T-i} \mathbf{W}_{qh}^\top \frac{\partial L}{\partial \mathbf{o}_{T+t-i}}.\tag{9}htL=i=tT(Whh)TiWqhoT+tiL.(9)

我们可以从式(9)中看到,这个简单的线性例子已经陷入到Whh⊤\mathbf{W}_{hh}^\topWhh的潜在的非常大的幂。在这个幂中,小于1的特征值将会消失,大于1的特征值将会发散。这在数值上是不稳定的,表现形式为梯度消失或梯度爆炸,解决此问题的一种方法如8.7.1中所述。

最后,应用链式规则得:

∂L∂Whx=∑t=1Tprod(∂L∂ht,∂ht∂Whx)=∑t=1T∂L∂htxt⊤∈Rh×d,∂L∂Whh=∑t=1Tprod(∂L∂ht,∂ht∂Whh)=∑t=1T∂L∂htht−1⊤∈Rh×d, \begin{aligned} \frac{\partial L}{\partial \mathbf{W}_{hx}} &= \sum_{t=1}^T \text{prod}\left(\frac{\partial L}{\partial \mathbf{h}_t}, \frac{\partial \mathbf{h}_t}{\partial \mathbf{W}_{hx}}\right) = \sum_{t=1}^T \frac{\partial L}{\partial \mathbf{h}_t} \mathbf{x}_t^\top\in \mathbb{R}^{h \times d},\\ \frac{\partial L}{\partial \mathbf{W}_{hh}} &= \sum_{t=1}^T \text{prod}\left(\frac{\partial L}{\partial \mathbf{h}_t}, \frac{\partial \mathbf{h}_t}{\partial \mathbf{W}_{hh}}\right) = \sum_{t=1}^T \frac{\partial L}{\partial \mathbf{h}_t} \mathbf{h}_{t-1}^\top\in \mathbb{R}^{h \times d}, \end{aligned} WhxLWhhL=t=1Tprod(htL,Whxht)=t=1ThtLxtRh×d,=t=1Tprod(htL,Whhht)=t=1ThtLht1Rh×d,

其中∂L/∂ht\partial L/\partial \mathbf{h}_tL/ht由式(7)和式(8)递归计算得到,是影响数值稳定性的关键量。在训练过程中一些中间值会被存储,以避免重复计算,例如存储∂L/∂ht\partial L/\partial \mathbf{h}_tL/ht,以便在计算∂L/∂Whx\partial L / \partial \mathbf{W}_{hx}L/Whx∂L/∂Whh\partial L / \partial \mathbf{W}_{hh}L/Whh时使用。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐