欢迎光临散文网 会员登陆 & 注册

[Quant 1.7] 从数理的角度理解一下RNN (2)

2022-08-08 11:08 作者:安平生一人好_  | 我要投稿

我先放资源

第一个是MIT的一本深度学习教材的RNN章节,作者是Ian Goodfellow & Yoshua Bengio & Aaron Courville

https://www.deeplearningbook.org/contents/rnn.html#pf7

第二个是吴恩达 (Andrew Ng)博士的网课



吴博士的网课主要从NLP的角度出发介绍RNN,里面有帮助的理解的例子。但是它的notation我看不太习惯。然后那本教材我感觉是给更厉害的quant或者数据科学/AI专业的学生读的,细节非常之多,我个人啃起来感觉吃力。这篇想把零零散散的知识点总结一下。(以下内容可能有很多理解错误的地方,后续我发现问题的话会编辑。然后我会有很多中英文穿插着,毕竟面试可能会被问到,我也借这个专栏强化一下记忆。)

4. RNN和它的两个弱点:梯度消失和梯度爆炸

这一步基本上我见过的所有视频都会一笔带过。他们只会讲RNN的梯度消失和梯度爆炸会影响SGD optimizer对参数的更新,从而引出不存在这两种问题的GRU和LSTM。但我认为这部分的推导是整个RNN最高难最有趣的地方。仔细研究一下不仅能让你复习很多忘掉的数学,还能对RNN的运行过程有更深刻的理解。


这里我会用上面介绍的第一种RNN来作为例子求梯度。

回忆一下,这是最基本切具有代表性的RNN


我们要求的梯度,就是当前的总损失对参数b%2CU%2CW%2Cc%2CV的梯度。求导的过程要用到我之前两个专栏的内容

所谓backward propogation,就是逆着unfolded computational graph箭头方向求导的过程。从图中我们能看出来,想对参数b%2CU%2CW%2Cc%2CV求梯度,o_t是必经之路,所以我们先尝试求一下总损失Lo_t的梯度,进而对h_t的梯度。下一步再求解o_th_t对这5个参数的梯度。


假设RNN已经训练到了时间%5Ctau,那么当前的总损失就是

L%20%3D%20%5Csum%5Climits_%7Bt%7D%5E%7B%5Ctau%7DL_t

Lo_t的梯度可以写成

%5Cnabla_%7Bo_t%7D%20L%20%3D%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20L_t%7D%20%5Ccdot%20%5Cnabla_%7B%5Chat%7By%7D_t%7D%20L_t%20%5Ccdot%20%5Cnabla_%7Bo_t%7D%5Chat%7By%7D_t

这个用到了之前说过的链式法则。只要我们有关于梯度输入和输出维度的规定,梯度的链式法则和求导的链式法则是一致。在等号右边的三个梯度中,第一个根据上面的总损失公式就等于1;第二个需要求一个Vector-to-scalar函数的梯度,因为%5Chat%7By%7D_tL_t的函数关系不涉及矩阵运算,我们只能用梯度的定义来求。之前我们假设我们的RNN用的是交叉熵损失函数cross entropy loss function,再假设RNN的输出是n维列向量,因此;

%5Cnabla_%7B%5Chat%7By%7D_t%7D%20L_t%20%3D%20(-%5Cfrac%7By_t%5E1%7D%7B%5Chat%7By%7D_t%5E1%7D%20%5Cldots%20-%5Cfrac%7By_t%5En%7D%7B%5Chat%7By%7D_t%5En%7D)

第三个是求一个Vector-to-vector函数的梯度,我们需要求的是Jacobian矩阵。o_t%5Chat%7By%7D_t的关系是softmax函数,即

%5Chat%7By%7D_t%5Ei%20%3D%20%5Cfrac%7Be%5E%7Bo_t%5Ei%7D%7D%7B%5Csum%5Climits_%7Bj%3D1%7D%5Ene%5E%7Bo_t%5Ej%7D%7D%20

Jacobian矩阵J%20%5Cin%20%5Cmathbb%7BR%7D%5E%7Bn%5Ctimes%20n%7D的第i行j列可以表示为

J_%7Bij%7D%20%3D%20%5Cleft%5C%7B%0A%5Cbegin%7Barray%7D%7Bll%7D%0A%5Chat%7By%7D_t%5Ei%20-%20(%5Chat%7By%7D_t%5Ei)%5E2%5C%5C%0A-%5Chat%7By%7D_t%5Ei%20%5Chat%7By%7D_t%5Ej%5C%5C%0A%5Cend%7Barray%7D%0A%5Cright.

所以%5Cnabla_%7Bo_t%7D%20L的计算实质上是一个1%5Ctimes%20n的梯度向量乘以一个n%20%5Ctimes%20n的Jacobian矩阵

%5Cbegin%7Balign%7D%0A%5Cnabla_%7Bo_t%7D%20L%20%26%3D%20%0A%0A%5Cbegin%7Bpmatrix%7D%0A-%5Cfrac%7By_t%5E1%7D%7B%5Chat%7By%7D_t%5E1%7D%20%5Cldots%20-%5Cfrac%7By_t%5En%7D%7B%5Chat%7By%7D_t%5En%7D%0A%5Cend%7Bpmatrix%7D%0A%0A%5Cbegin%7Bpmatrix%7D%0A%5Chat%7By%7D_t%5E1%20-%20(%5Chat%7By%7D_t%5E1)%5E2%20%26-%5Chat%7By%7D%5E1_t%5Chat%7By%7D%5E2_t%20%26%20%5Cldots%20%26%20-%5Chat%7By%7D%5E1_t%5Chat%7By%7D%5En_t%20%5C%5C%0A-%5Chat%7By%7D%5E2_t%5Chat%7By%7D%5E1_t%20%26%20%5Cddots%20%20%26%20%26%20%5Cvdots%20%5C%5C%0A%5Cvdots%20%26%20%26%20%5Cddots%20%26%20%5Cvdots%5C%5C%0A-%5Chat%7By%7D%5En_t%5Chat%7By%7D%5E1_t%20%26%20%5Cldots%20%26%20%5Cldots%20%26%20%5Chat%7By%7D_t%5En%20-%20(%5Chat%7By%7D_t%5En)%5E2%0A%5Cend%7Bpmatrix%7D%5C%5C%0A%0A%26%3D%0A%5Chat%7By%7D_t%5ET%20-%20y_t%5ET%0A%5Cend%7Balign%7D


我们现在成功获得了Lo_t上面的梯度,下一步就可以求o_t在别的参数上的梯度了。


我们先来看h_%5Ctauh_%5Ctau只从o_%5Ctau上面遗传下来。因为RNN中有明确的o_%5Ctau关于h_%5Ctau的矩阵表达式,所以我们可以用之前学过的Vector-to-vector求梯度的方法来计算:

%5Cnabla_%7Bh_%5Ctau%7Do_%5Ctau%20%3D%20V

所以

%5Cnabla_%7Bh_%5Ctau%7DL%20%3D%20(%5Chat%7By%7D_t%5ET%20-%20y_t%5ET)%20%5Ccdot%20V

但是问题来了,h_%5Ctau是只从o_%5Ctau上面遗传下来,但是h_t不是。从图中可以看出来,h_t的传播方向不仅有o_t,还有h_%7Bt%2B1%7D。所以为了链式法则能够连接到h_t,我们需要找到h_%7Bt%2B1%7Dh_t上的梯度

%5Cbegin%7Balign%7D%0A%5Cnabla_%7Bh_t%7Dh_%7Bt%2B1%7D%20%0A%26%20%3D%20diag(tanh.'(b%2BUx_%7Bt%2B1%7D%2BWh_t))%20%5Ccdot%20W%5C%5C%0A%26%20%3D%20diag(1-(h_%7Bt%2B1%7D)%5E2)%20%5Ccdot%20W%20%5Cquad%20%5Ctext%7B%E6%A0%B9%E6%8D%AE%E5%8F%8C%E6%9B%B2%E6%AD%A3%E5%88%87%E5%87%BD%E6%95%B0%E7%9A%84%E5%AF%BC%E5%87%BD%E6%95%B0%7D%5C%2C%20tanh'%20%3D%201%20-%20tanh%5E2%0A%5Cend%7Balign%7D

这个梯度的求解过程可以参考Quant 1.5里面的最后一个例子


因此我们能够得到关于h_t梯度的迭代公式

%5Cbegin%7Balign%7D%0A%5Cnabla_%7Bh_t%7D%20L%20%26%3D%20%5Cnabla_%7Bo_t%7D%20L%20%5Ccdot%20%20%5Cnabla_%7Bh_t%7D%20o_t%20%2B%20%20%5Cnabla_%7Bh_%7Bt%2B1%7D%7D%20L%20%5Ccdot%20%20%5Cnabla_%7Bh_t%7D%20h_%7Bt%2B1%7D%5C%5C%0A%26%3D%20(%5Chat%7By%7D_t%5ET%20-%20y_t%5ET)%5Ccdot%20V%20%2B%20%5Cnabla_%7Bh_%7Bt%2B1%7D%7D%20L%20%5Ccdot%20diag(1-(h_%7Bt%2B1%7D)%5E2)%20%5Ccdot%20W%0A%0A%5Cend%7Balign%7D

我们逐步迭代,直到t%2B1%20%3D%20%5Ctau,我们就能获得L关于h_t梯度的确定的值。


所有的准备工作都做完了,我们现在获得了关于o_th_t的全部梯度,需要继续利用链式法则求b%2CU%2CW%2Cc%2CV的梯度:

%5Cbegin%7Balign%7D%0A%5Cnabla_b%20L%20%26%3D%20%20%5Csum%5Climits_%7Bt%7D%5E%5Ctau%20%5Cnabla_%7Bh_t%7DL%20%5Ccdot%20%5Cnabla_b%20h_t%5C%5C%0A%26%20%3D%20%5Csum%5Climits_%7Bt%7D%5E%5Ctau%20%5Cnabla_%7Bh_t%7DL%20%5Ccdot%20diag(1-(h_t)%5E2)%5C%5C%0A%5C%5C%0A%5Cnabla_c%20L%20%26%3D%20%5Csum%5Climits_t%5E%5Ctau%20%5Cnabla_%7Bo_t%7DL%20%5Ccdot%20%5Cnabla_%7Bc%7Do_t%5C%5C%0A%26%3D%20%5Csum%5Climits_t%5E%5Ctau%20%5Cnabla_%7Bo_t%7DL%0A%5Cend%7Balign%7D

多说一句以防大家忘记,这里的链式法则的情形比前面的要复杂一些。之前例子中的链式法则都是单链的,也就是说从复合函数的自变量变换到因变量的过程只有一个路径,但是在RNN中时间t的存在使得b%2CU%2CW%2Cc%2CV到损失L的变换在每一个时间t都有一条平行的路径。多链的链式法则需要我们把每个平行路径的梯度加和来求总梯度。


然后上面我们已经求的了五个梯度中最简单的两个。但是在求另外三个的时候就会遇到一个问题...按照正常的链式法则,求关于U的梯度的时候应该有

%5Cnabla_U%20L%20%3D%20%5Csum%5Climits_t%5E%5Ctau%20%5Cnabla_%7Bh_t%7D%20L%20%5Ccdot%20%5Cnabla_U%20h_t

但是根据我目前学到的知识,这个我是算不出来的,因为%20%5Cnabla_U%20h_t是一个Matrix-to-vector函数的梯度,首先它的梯度求出来应该是一个三维tensor的形式,其次即使它的梯度有矩阵的表达形式,但是我认为这种表达形式放入chain rule进行计算是错误的。因此,上面的梯度无法这么求。


那我们应该怎么办呢?我们虽然不确定Matrix-to-vector函数的梯度能不能插入链式法则里面,但是我们能确定Matrix-to-scalar函数肯定是可以的!那我们可以不可以修改一下路径,使得由h_tU的梯度变成Matrix-to-vector函数的梯度呢?可以!我们只要把h_t的每一项拿出来作为中间项过度就可以了。大致变化差不多下面这张图,需要把h_t展开:

最后按照右边的图片,关于U的梯度就变成了

%5Cbegin%7Balign%7D%0A%5Cnabla_UL%20%3D%20%5Csum%5Climits_t%5E%5Ctau%20%5Csum%5Climits_%7Bi%3D1%7D%5En%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20h_t%5Ei%7D%20%20%5Cnabla_U%20h_t%5Ei%0A%0A%5Cend%7Balign%7D

接下来是全篇最蹩脚的地方:如何化简上面的等式?首先我们要求一个Matrix-to-scalar函数的梯度

%20%5Cnabla_U%20h_t%5Ei%0A

这个就不是很好理解。我们先看一下矩阵U变换到h_t%5Ei的过程:第一步对于矩阵U进行线性变换b%2BUx_t%2BWh_%7Bt-1%7D,对得到的向量只保留第i项a%5Ei_t,第二部是h_t%5Ei%20%3D%20tanh(a%5Ei_t)。所以根据链式法则:

%5Cbegin%7Balign%7D%20%0A%5Cnabla_U%20h_t%5Ei%20%26%3D%20%5Cfrac%7B%5Cpartial%20h_t%5Ei%7D%7B%5Cpartial%20a_t%5Ei%7D%20%5Ctimes%20%5Cnabla_U%20a_t%5Ei%5C%5C%0A%26%3D%20(1%20-%20(h%5Ei_t)%5E2)%20%5Ctimes%20%0A%5Cbegin%7Bpmatrix%7D%0A%5Cmathbf%7B0%7D%5C%5C%0A%5Cvdots%5C%5C%0Ax_t%5ET%5C%5C%0A%5Cvdots%5C%5C%0A%5Cmathbf%7B0%7D%0A%5Cend%7Bpmatrix%7D%20%5C%2C%5C%2C%5C%2C%20%5Ctext%7Bwith%20the%20i-th%20row%7D%20%5C%2C%20%5C%2C%20x_t%5ET%0A%5Cend%7Balign%7D

关于后面这个Matrix-to-scalar函数的梯度,因为这个函数里面的没有trace,所以我们只能通过Matrix-to-scalar函数梯度的定义来理解了。因为我们最后只取向量的第i项,所以矩阵U只有第i行和a_t%5Ei有关系。将a_t%5Ei对矩阵U的第i行各项求导,得到的就是向量x_t每一项。因此,得到的梯度矩阵除了第i行是x_t%5ET以外,其余项都是0。


下一步依然是难理解的一步,我们想要取消内部的求和符号就需要把所有%20%5Cnabla_U%20h_t%5Ei关于i都加起来:

%5Cbegin%7Balign%7D%0A%26%5Csum%5Climits_%7Bi%3D1%7D%5En%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20h_t%5Ei%7D%20%20%5Cnabla_U%20h_t%5Ei%5C%5C%20%0A%3D%26%20%5Csum%5Climits_%7Bi%3D1%7D%5En%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20h_t%5Ei%7D%20(1%20-%20(h%5Ei_t)%5E2)%20%5Ctimes%20%0A%5Cbegin%7Bpmatrix%7D%0A%5Cmathbf%7B0%7D%5C%5C%0A%5Cvdots%5C%5C%0Ax_t%5ET%5C%5C%0A%5Cvdots%5C%5C%0A%5Cmathbf%7B0%7D%0A%5Cend%7Bpmatrix%7D%20%5C%2C%5C%2C%5C%2C%20%5Ctext%7Bwith%20the%20i-th%20row%7D%20%5C%2C%20%5C%2C%20x_t%5ET%5C%5C%0A%3D%26%20%0A%5Cbegin%7Bpmatrix%7D%0A%5B1%20-%20(h_t%5E1)%5E2%5D%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20h_t%5E1%7D%20%5C%5C%0A%5Cvdots%5C%5C%0A%5B1%20-%20(h_t%5En)%5E2%5D%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20h_t%5En%7D%0A%5Cend%7Bpmatrix%7D%0A%5Cbegin%7Bpmatrix%7D%0Ax_1%20%26%20%5Cldots%20%26%20x_n%0A%5Cend%7Bpmatrix%7D%5C%5C%0A%0A%3D%20%26%0A%5Cbegin%7Bpmatrix%7D%0A1%20-%20(h_t%5E1)%5E2%20%5C%5C%0A%5Cvdots%5C%5C%0A1%20-%20(h_t%5En)%5E2%0A%5Cend%7Bpmatrix%7D%0A%5Ctimes%0A%5Cbegin%7Bpmatrix%7D%0A%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20h_t%5E1%7D%20%5C%5C%0A%5Cvdots%5C%5C%0A%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20h_t%5En%7D%0A%5Cend%7Bpmatrix%7D%0A%5Ccdot%0A%5Cbegin%7Bpmatrix%7D%0Ax_1%20%26%20%5Cldots%20%26%20x_n%0A%5Cend%7Bpmatrix%7D%5C%5C%0A%3D%26%20diag(1-(h_t)%5E2)%20%5Ccdot%20(%5Cnabla_%7Bh_t%7D%20L)%5ET%20x%5ET_t%0A%5Cend%7Balign%7D

所以

%5Cnabla_UL%20%3D%20%5Csum%5Climits_t%5E%5Ctau%20%0Adiag(1-(h_t)%5E2)%20%5Ccdot%20(%5Cnabla_%7Bh_t%7D%20L)%5ET%20x%5ET_t%0A

如果线代学的不是特别好的话,从求和符号到矩阵乘法之间的转换可能需要比较长的时间消化,这里大家可以多总结一下。


然后关于矩阵W%2C%20V的梯度和U十分类似,我就不再推一遍了

%5Cbegin%7Balign%7D%0A%5Cnabla_WL%20%26%3D%20%5Csum%5Climits_t%5E%5Ctau%20%0Adiag(1-(h_t)%5E2)%20%5Ccdot%20(%5Cnabla_%7Bh_t%7D%20L)%5ET%20h%5ET_t%5C%5C%0A%5Cnabla_VL%20%26%3D%20%5Csum%5Climits_t%5E%5Ctau%20(%5Cnabla_%7Bo_t%7DL)%5ET%20h_t%5ET%0A%5Cend%7Balign%7D


梯度求完了,我们现在要讨论的是梯度为什么会消失 (gradient vanishing),梯度又为什么会爆炸 (gradient exploding)。我在网上能找到的所有视频关于这两点都是泛泛而谈,包括上面的教材和视频,没有一个解释的很详细的,我感觉很无语。所以这里试着解释一下:


我们从上面一系列的梯度的推导中可以看出,每一个time step对参数的梯度都是有贡献的。但是如果我们的神经网络深度足够大的话,早期的time step对梯度的贡献会过小或者过大。这就是梯度消失/爆炸。


难道早期的time step对梯度的贡献小不是应该的吗?不一定!吴教授的视频中举了一个例子,假如我们现在想要预测一句不完整的话的下一个词是什么:

The cats, which already ate, were full.

The cat, which already ate, was full.

我们在预测这个was/were的时候,最重要的参考信息不是中间的非限制性定语从句,而是前面的cat/cats。因此,模型参数应该在cat/cats这个词对应time step的hidden state上面的梯度很大,从而可以显著的修改神经网络中的矩阵参数。然而,如果这个cat/cats离was/were太远了,这个梯度就会产生爆炸或者消失,导致我们最后训练出来的神经网络参数没有预测价值。毕竟语法上非限制性定语从句可以无限长。


那么如何从梯度的公式上理解梯度消失和梯度爆炸呢?根据我们之前推导过的梯度公式,可以知道损失函数关于各个矩阵/向量参数的梯度大多与%5Cnabla_%7Bh_t%7D%20L有关系,而%5Cnabla_%7Bh_t%7D%20L还有递推公式:

%0A%5Cnabla_%7Bh_t%7D%20L%20%0A%3D%20(%5Chat%7By%7D_t%5ET%20-%20y_t%5ET)%5Ccdot%20V%20%2B%20%5Cnabla_%7Bh_%7Bt%2B1%7D%7D%20L%20%5Ccdot%20diag(1-(h_%7Bt%2B1%7D)%5E2)%20%5Ccdot%20W

我们多写出来一步

%5Cbegin%7Balign%7D%0A%5Cnabla_%7Bh_%7Bt-1%7D%7DL%20%3D%20%26(%5Chat%7By%7D_%7Bt-1%7D%5ET-y_%7Bt-1%7D%5ET)%5Ccdot%20V%20%2B%20(%5Chat%7By%7D_t%5ET%20-%20y_t%5ET)%5Ccdot%20V%5Ccdot%20diag(1-(h_t)%5E2)%5Ccdot%20W%5C%5C%0A%2B%26%20%5Cnabla_%7Bh_%7Bt%2B1%7D%7DL%20%5Ccdot%20diag(1-(h_%7Bt%2B1%7D)%5E2)%20%5Ccdot%20W%20%5Ccdot%20diag(1-(h_t)%5E2)%5Ccdot%20W%0A%0A%5Cend%7Balign%7D

这是两个time step的递推公式,我们可以知道当time step的间隔足够大的时候,多个W矩阵会累乘到一起作为晚期time step梯度的系数。这就导致早期的time step梯度受矩阵W的影响很大。就像是1.01%5E%7B100%7D0.99%5E%7B100%7D差距很大一样,梯度下降的进程会受参数初始化的影响,如果W初始化太大会造成梯度爆炸,太小则会造成梯度消失。


[Quant 1.7] 从数理的角度理解一下RNN (2)的评论 (共 条)

分享到微博请遵守国家法律