注:书中对代码的讲解并不详细,本文对很多细节做了详细注释。另外,书上的源代码是在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
我们从一个描述循环神经网络工作原理的简化模型开始,此模型忽略了隐状态的特性及其更新方式的细节,且其数学表示没有明确地区分标量、向量和矩阵。在这个简化模型中,我们将时间步 t t t的隐状态表示为 h t h_t ht,输入表示为 x t x_t xt,输出表示为 o t o_t ot,分别使用 w h w_h wh和 w o w_o wo来表示隐藏层和输出层的权重。每个时间步的隐状态和输出可以写为:
h t = f ( x t , h t − 1 , w h ) , o t = g ( h t , w o ) , (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,ht−1,wh),=g(ht,wo),(2)
其中 f f f和 g g g分别是隐藏层和输出层的变换。因此,我们有一个链 { … , ( x t − 1 , h t − 1 , o t − 1 ) , ( x t , h t , o t ) , … } \{\ldots, (x_{t-1}, h_{t-1}, o_{t-1}), (x_{t}, h_{t}, o_t), \ldots\} {…,(xt−1,ht−1,ot−1),(xt,ht,ot),…},它们通过循环计算彼此依赖。前向传播相当简单,一次一个时间步的遍历三元组 ( x t , h t , o t ) (x_t, h_t, o_t) (xt,ht,ot),然后通过一个目标函数在所有 T T T个时间步内评估输出 o t o_t ot和对应的标签 y t y_t yt之间的差异:
L ( x 1 , … , x T , y 1 , … , y T , w h , w o ) = 1 T ∑ t = 1 T l ( y t , o t ) . 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=1∑Tl(yt,ot).
对于反向传播,按照链式法则:
∂ L ∂ w h = 1 T ∑ t = 1 T ∂ l ( y t , o t ) ∂ w h = 1 T ∑ t = 1 T ∂ l ( y t , o t ) ∂ o t ∂ g ( h t , w o ) ∂ h t ∂ h t ∂ w h . \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} ∂wh∂L=T1t=1∑T∂wh∂l(yt,ot)=T1t=1∑T∂ot∂l(yt,ot)∂ht∂g(ht,wo)∂wh∂ht.
在上式乘积的第一项和第二项很容易计算,而第三项比较棘手,因为我们需要循环地计算参数 w h w_h wh对 h t h_t ht的影响。根据式(2), h t h_t ht既依赖于 h