前言
- 一点感悟: 前几天简单看了下王者荣耀觉悟AI的论文,发现除了强化学习以外,也用到了熟悉的LSTM。之后我又想起了知乎上的一个问题:“Transformer会彻底取代RNN吗?”。我想,在觉悟AI这类严格依赖于时间(比如:每读一帧,就要立即进行相应的决策) 的情境中,就根本没法用Transformer这类基于self-attention的模型。因为self-attention的独特性使其必须在一开始就知道所有时间位置的信息,Transformer在NLP上的成功,我觉得还是因为自然语言并不算是严格依赖于时间的。因为我们在数据中看到的句子都是完整的一句话,这就方便了self-attention直接对每个位置进行建模。
- 所以,Transformer是不可能彻底取代RNN的。当然这只是我的一点思考,还有其他重要的原因:比如Transformer、Bert这种基于self-attention结构的预训练模型都需要海量的训练数据。在数据量不足的情景,只会带来巨大的偏差,但相同的数据在RNN甚至LSTM上已经可以达到足够好的效果。
- 所以,RNN及其变种是永恒的经典,有必要认真学习。遂推导了一下RNN的反向传播算法(BPTT),记录在此。
1 RNN模型结构及符号定义
1.1 模型结构
假设有一个时间序列 t = 1 , 2 , . . . , L t=1,2,...,L t=1,2,...,L,在每一时刻 t t t我们有:
z ( t ) = U x ( t ) + W h ( t − 1 ) + b h ( t ) = f ( z ( t ) ) s ( t ) = V h ( t ) + c y ( t ) = g ( s ( t ) ) \begin{aligned} \bm{z^{(t)}}&=\bm{Ux^{(t)}}+\bm{Wh^{(t-1)}}+\bm{b}\\ \bm{h^{(t)}}&=f(\bm{z^{(t)}})\\ \bm{s^{(t)}}&=\bm{Vh^{(t)}}+\bm{c}\\ \bm{y^{(t)}}&=g(\bm{s^{(t)}}) \end{aligned} z(t)h(t)s(t)y(t)=Ux(t)+Wh(t−1)+b=f(z(t))=Vh(t)+c=g(s(t))
这就是RNN的结构。可以看到,每一时刻 t t t的隐含状态 h ( t ) \bm{h^{(t)}} h(t) 都是由当前时刻的输入 x ( t ) \bm{x^{(t)}} x(t) 和上一时刻的隐含状态 h ( t − 1 ) \bm{h^{(t-1)}} h(t−1) 共同得到的。下面是详细的符号定义:
1.2 符号定义
符号 | 含义 | 维度 |
---|---|---|
x ( t ) \bm{x^{(t)}} x(t) | 第 t t t时刻的输入 | ( K × 1 ) (K\times 1) (K×1) |
z ( t ) \bm{z^{(t)}} z(t) | 第 t t t时刻隐层的带权输入 | ( N × 1 ) (N\times 1) (N×1) |
h ( t ) \bm{h^{(t)}} h(t) | 第 t t t时刻的隐含状态 | ( N × 1 ) (N\times 1) (N×1) |
s ( t ) \bm{s^{(t)}} s(t) | 第 t t t时刻输出层的带权输入 | ( M × 1 ) (M \times 1) (M×1) |
y ( t ) \bm{y^{(t)}} y(t) | 第 t t t时刻的输出 | ( M × 1 ) (M\times 1) (M×1) |
E ( t ) E^{(t)} E(t) | 第 t t t时刻的损失 | 标量 |
U \bm{U} U | 隐层对输入的参数,整个模型共享 | ( N × K ) (N\times K) (N×K) |
W \bm{W} W | 隐层对状态的参数,整个模型共享 | ( N × N ) (N\times N) (N×N) |
V \bm{V} V | 输出层参数,整个模型共享 | ( M × N ) (M\times N) (M×N) |
b \bm{b} b | 隐层的偏置,整个模型共享 | ( N × 1 ) (N\times 1) (N×1) |
c \bm{c} c | 输出层偏置,整个模型共享 | ( M × 1 ) (M\times 1) (M×1) |
g ( ) g() g() | 输出层激活函数 | \ |
f ( ) f() f() | 隐层的激活函数 | \ |
2 沿时间的反向传播算法
2.1 总体分析
首先快速总览一下RNN的全部流程。
- 首先令模型的隐含状态 h ( 0 ) = 0 \bm{h^{(0)}=0} h(0)=0。
- 每一时刻 t \bm{t} t的输入 x ( t ) \bm{x^{(t)}} x(t) 都是一个向量(比如:在NLP中,可以使用词向量),在经过模型后会得到这一时刻的状态 h ( t ) \bm{h^{(t)}} h(t) 和输出 y ( t ) \bm{y^{(t)}} y(t)。
- 在NLP中, y ( t ) \bm{y^{(t)}} y(t)是由 s ( t ) \bm{s^{(t)}} s(t)经过 g g g (通常为Softmax) 激活得到的,搭配Cross Entropy Loss (比如:在词表中挑选下一个单词,这是一个多分类问题) ,就能计算出此刻的损失 E ( t ) E^{(t)} E(t)。
- 计算出 E ( t ) E^{(t)} E(t)后,并不能立即对模型参数进行更新。需要沿着时间 t t t不断给出输入,计算出所有时刻的损失。模型总损失为 E = ∑ t E ( t ) E=\sum_tE^{(t)} E=∑tE(t)
- 我们需要根据总损失 E E E计算所有参数的梯度 ∂ E ∂ U , ∂ E ∂ W , ∂ E ∂ V , ∂ E ∂ b , ∂ E ∂ c \frac{\partial E}{\partial \bm{U}},\frac{\partial E}{\partial \bm{W}},\frac{\partial E}{\partial \bm{V}},\frac{\partial E}{\partial \bm{b}},\frac{\partial E}{\partial \bm{c}} ∂U∂E,∂W∂E,∂V∂E,∂b∂E,∂c∂E,再使用基于梯度的优化方法进行参数更新。
这就是一轮完整的流程,本文要讨论的就是:如何计算RNN模型参数的梯度。
2.2 求 ∂ E ∂ V \frac{\partial E}{\partial \bm{V}} ∂V∂E
∂ E ∂ V = ∑ t ∂ E ( t ) ∂ V \frac{\partial E}{\partial \bm{V}}=\sum_t\frac{\partial E^{(t)}}{\partial \bm{V}} ∂V∂E=t∑∂V∂E(t)
由公式 s ( t ) = V h ( t ) + c \bm{s^{(t)}}=\bm{Vh^{(t)}}+\bm{c} s(t)=Vh(t)+c 和 y ( t ) = g ( s ( t ) ) \bm{y^{(t)}}=g(\bm{s^{(t)}}) y(t)=g(s(t)),很容易有:
∂ E ( t ) ∂ V i j = ∂ E ( t ) ∂ s i ( t ) ∂ s i ( t ) ∂ V i j = ∂ E ( t ) ∂ y i ( t ) ∂ y i ( t ) ∂ s i ( t ) ∂ s i ( t ) ∂ V i j = ∂ E ( t ) ∂ y i ( t ) g ′ ( s i ( t ) ) h j ( t ) (a) \begin{aligned} \frac{\partial E^{(t)}}{\partial V_{ij}}&=\frac{\partial E^{(t)}}{\partial s_i^{(t)}}\frac{\partial s_i^{(t)}}{\partial V_{ij}}\tag{a}\\ &=\frac{\partial E^{(t)}}{\partial y_i^{(t)}}\frac{\partial y_i^{(t)}}{\partial s_i^{(t)}}\frac{\partial s_i^{(t)}}{\partial V_{ij}}\\ &=\frac{\partial E^{(t)}}{\partial y_i^{(t)}}g'(s_i^{(t)})h_j^{(t)} \end{aligned} ∂Vij∂E(t)=∂si(t)∂E(t)∂Vij∂si(t)=∂yi(t)∂E(t)∂si(t)∂yi(t)∂V