关于RNN理论和实践的一些总结


前言

说明:讲解时会对相关文章资料进行思想、结构、优缺点,内容进行提炼和记录,相关引用会标明出处,引用之处如有侵权,烦请告知删除。
转载请注明:DengBoCong

本篇文章主要总结我在学习过程中遇到的RNN、其相关变种,并对相关结构进行说明和结构图展示。内容包括RNN、RecNN、多层、双向、RNNCell等等,同时包括在计算框架(TensorFlow及PyTorch)API层面的一些理解记录。本篇文章不进行深入推导和底层原理介绍,仅做总结记录,感兴趣者可自行根据内容详细查阅资料。

RNN(递归神经网络)包括Recurrent Neural Network和Recursive Neural Network两种,分别为时间递归神经网络和结构递归神经网络。

计算框架版本:

  • TensorFlow2.3
  • PyTorch1.7.0

相关知识

在进行后面内容的陈述之前,先来简单结合计算框架说明一下vanilla RNN、LSTM、GRU之间的区别。虽然将vanilla RNN、LSTM、GRU这个三个分开讲进行对比,但是不要忘记它们都是RNN,所以在宏观角度都是如下结构:
在这里插入图片描述
而它们区别在于中间的那个隐藏状态计算单元,这里贴出它们的计算单元的细节,从左到右分别是vanilla RNN、LSTM、GRU。
在这里插入图片描述
看了隐藏单元之后,你有没有发现LSTM和其他两个的输入多了一个cell state,LSTM的门道就在这,cell state 就是实现LSTM的关键(ps:GRU其实也有分hidden state和cell state,不过在GRU中它们两个是相同的)。细节我不去深究,感兴趣的自行查看论文:

我这里就简单的结合TensorFlowPyTorch说明一下cell state和hidden state,首先看下面两个计算框架的调用(详细参数自行查阅文档,这里只是为了说明state):

# TensorFlow中的LSTM调用
whole_seq_output, final_memory_state, final_carry_state =
			tf.keras.layers.LSTM(4, return_sequences=True, return_state=True)(inputs)
# Pytorch中的LSTM调用
output, (hn, cn) = torch.nn.LSTM(10, 20, 2)(input, (h0, c0))


# TensorFlow中的GRU调用
whole_sequence_output, final_state =
			tf.keras.layers.GRU(4, return_sequences=True, return_state=True)(inputs)
# Pytorch中的GRU调用
output, hn = torch.nn.GRU(10, 20, 2)(input, h0)

以TensorFlow举例(PyTorch默认都返回),当return_state参数设置为True时,将会返回隐藏层状态,即cell_state。在LSTM 的网络结构中,直接根据当前input 数据,得到的输出称为 hidden state,还有一种数据是不仅仅依赖于当前输入数据,而是一种伴随整个网络过程中用来记忆,遗忘,选择并最终影响hidden state结果的东西,称为 cell state。cell state默认是不输出的,它仅对输出 hidden state 产生影响。通常情况,我们不需要访问cell state,但当需要对 cell state 的初始值进行设定时,就需要将其返回。所以在上面的TensorFlow对LSTM的调用中,final_memory_state是最后一个timestep的状态,final_carry_state是最后一个timestep的cell state。既然见到LSTM和GRU,那下面就贴一张它们的状态更新公式图以作记录:
在这里插入图片描述
后面简要阐述的所有RNN及其变种,都是代指vanilla RNN、LSTM、GRU三个,只不过为了方便描述,以RNN作为总称进行说明。

TensorFlow中,RNN类是作为如第一张结构图那些的宏观结构,所以它有一个cell参数,你可以根据实际需要传入SimpleRNNCell、LSTMCell和GRUCell(这三个你就可以理解成上面讲的计算单元),它们三个可以单独使用,在一些地方特别管用。

PyTorch中大致是一样的,不过RNN类则是标准的RNN实现的,而不是像Tensorflow那样的架构,PyTorch同样有RNNCell、LSTMCell和GRUCell

标准RNN

RNN忽略单元细节的具体结构图如下。从图中就能够很清楚的看到,上一时刻的隐藏层是如何影响当前时刻的隐藏层的(注意这里Output的数量画少了,看起来不够形象,应该是 X = [ x 1 , x 2 , . . . , x m ] X=[x_1,x_2,...,x_m] X=[x1,x2,...,xm] O = [ o 1 , o 2 , . . . , o m ] O=[o_1,o_2,...,o_m] O=[o1,o2,...,om])。这里的Output是对应时间步的状态,而 s s s 是隐藏状态,一般在实践中用它来初始化RNN。
在这里插入图片描述
当然,可以换一种方式画结构图,如下图所示,按照RNN时间线展开。注意了,隐藏层 s t s_t st 不仅取决于 x t x_t xt 还取决与 s t − 1 s_{t-1} st1
在这里插入图片描述
从上面总结公式如下:
o t = g ( V s t ) ( 1 ) o_t=g(V_{s_t}) \quad\quad (1) ot=g(Vst)(1) s t = f ( U x t + W s t − 1 ) ( 2 ) s_t=f(U_{x_t}+W_{s_{t-1}}) \quad\quad (2) st=f(Uxt+Wst1)(2)
式(1)是输出层的计算公式,输出层是一个全连接层,也就是它的每个节点都和隐藏层的每个节点相连。 V V V是输出层的权重矩阵, g g g是激活函数。式(2)是隐藏层的计算公式,它是循环层。 U U U 是输入 x x x 的权重矩阵, W W W 是上一次的值作为这一次的输入的权重矩阵, f f f 是激活函数。从宏观意义上来说,循环层和全连接层的区别就是循环层多了一个权重矩阵 W W W。通过循环带入得下式:
o t = V f ( U x t + W f ( U x t − 1 + W f ( U x t − 2 + W f ( U x t − 3 + . . . ) ) ) ) o_t=Vf(U_{x_t}+Wf(U_{x_{t-1}}+Wf(U_{x_{t-2}}+Wf(U_{x_{t-3}}+...)))) ot=Vf(Uxt+Wf(Uxt1+Wf(Uxt2+Wf(Uxt3+...))))
从上面可以看出,循环神经网络的输出值 o t o_t ot,是受前面历次输入值 x t x_t xt x t − 1 x_{t-1} xt1 x t − 2 x_{t-2} xt2 x t − 3 x_{t-3} xt3、…影响的,这就是为什么循环神经网络可以往前看任意多个输入值的原因。

双向RNN

论文:Link
在这里插入图片描述
从上图可以看出,双向RNN的隐藏层要保存两个值,一个 A A A 参与正向计算,另一个值 A ′ A' A 参与反向计算(注意了,正向计算和反向计算不共享权重),最终的输出值取决于 A A A A ′ A' A 的计算方式。其计算方法有很多种,这里结合TensorFlow和PyTorch说明:

# TensorFlow中,需要使用Bidirectional来实现双向RNN,如下所示
# 其中merge_mode就是A和A'两者的计算方式:{'sum', 'mul', 'concat', 'ave', None}
tf.keras.layers.Bidirectional(
    layer, merge_mode='concat', weights=None, backward_layer=None, **kwargs
)

# PyTorch则不同,在各RNN的具体实现中,都有一个bidirectional参
# 数来控制是否是双向的,可自行查看PyTorch的API文档,特别说明的是
# PyTorch没有merge_mode,所以双向RNN直接会返回正向和反向的状态,
# 需要你自行进行合并操作

Multi-layer(stacked) RNN

将多个RNN堆叠成多层RNN,每层RNN的输入为上一层RNN的输出,如下图所示。多层 (Multi-layer) RNN 效果很好,但可能会常用到 skip connections 的方式
在这里插入图片描述

深度循环神经网络

前面我们介绍的循环神经网络只有一个隐藏层,我们当然也可以堆叠两个以上的隐藏层,这样就得到了深度循环神经网络,如下图所示:
在这里插入图片描述
我们把第 i i i 个隐藏层的值表示为 s t ( i ) s_t^{(i)} st(i) s t ′ ( i ) s_t^{'(i)} st(i),则深度循环神经网络的计算方式可以表示为:
o t = g ( V ( i ) s t ( i ) + V ′ ( i ) s t ′ ( i ) ) o_t=g(V^{(i)}s_t^{(i)}+V^{'(i)}s_t^{'(i)}) ot=g(V(i)st(i)+V(i)st(i)) s t ( i ) = f ( U ( i ) s t ( i − 1 ) + W ( i ) s t − 1 ) s_t^{(i)}=f(U^{(i)}s_t^{(i-1)}+W^{(i)}s_{t-1}) st(i)=f(U(i)st(i1)+W(i)st1) s t ′ ( i ) = f ( U ′ ( i ) s t ′ ( i − 1 ) + W ′ ( i ) s t + 1 ′ ) s_t^{'(i)}=f(U^{'(i)}s_t^{'(i-1)}+W^{'(i)}s_{t+1}^{'}) st(i)=f(U(i)st(i1)+W(i)st+1) s t ( 1 ) = f ( U ( 1 ) x t + W ( 1 ) s t − 1 ) s_t^{(1)}=f(U^{(1)}x_t+W^{(1)}s_{t-1}) st(1)=f(U(1)xt+W(1)st1) s t ′ ( 1 ) = f ( U ′ ( 1 ) x t + W ′ ( 1 ) s t + 1 ′ ) s_t^{'(1)}=f(U^{'(1)}x_t+W^{'(1)}s_{t+1}^{'}) st(1)=f(U(1)xt+W(1)st+1)

Recursive Neural Network

RNN适用于序列建模,而许多NLP问题需要处理树状结构,因此提出了RecNN的概念。与RNN将前序句子编码成状态向量类似,RecNN将每个树节点编码成状态向量。RecNN中的每棵子树都由一个向量表示,其值由其子节点的向量表示递归确定。
在这里插入图片描述
RecNN接受的输入为一个有n个单词的句子的语法分析树,每个单词都表示为一个向量,语法分析树表示为一系列的生成式规则。举个例子,The boy saw her duck的分析树如下图:
在这里插入图片描述
对应的生成式规则(无标签+有标签)如下图:
在这里插入图片描述
RecNN的输出为句子的内部状态向量(inside state vectors),每一个状态向量都对应一个树节点。具体RecNN细节自行详细查阅资料。

补充

普遍来看, 神经网络都会有梯度消失和梯度爆炸的问题,其根源在于现在的神经网络在训练的时候,大多都是基于BP算法,这种误差向后传递的方式,即多元函数求偏导中,链式法则会产生 vanishing,而 RNN 产生梯度消失的根源是权值矩阵复用。

循环神经网络的训练算法:BPTT

BPTT算法是针对循环层的训练算法,它的基本原理和BP算法是一样的,也包含同样的三个步骤:

  • 前向计算每个神经元的输出值
  • 反向计算每个神经元的误差项 δ j \delta_j δj 值,它是误差函数 E E E 对神经元 j j j 的加权输入 n e t j net_j netj 的偏导数
  • 计算每个权重的梯度
  • 最后再用随机梯度下降算法更新权重。

RNN的梯度爆炸和消失问题

不幸的是,实践中前面介绍的几种RNNs并不能很好的处理较长的序列。一个主要的原因是,RNN在训练中很容易发生梯度爆炸和梯度消失,这导致训练时梯度不能在较长序列中一直传递下去,从而使RNN无法捕捉到长距离的影响。通常来说,梯度爆炸更容易处理一些。因为梯度爆炸的时候,我们的程序会收到NaN错误。我们也可以设置一个梯度阈值,当梯度超过这个阈值的时候可以直接截取。梯度消失更难检测,而且也更难处理一些。总的来说,我们有三种方法应对梯度消失问题:

  • 合理的初始化权重值。初始化权重,使每个神经元尽可能不要取极大或极小值,以躲开梯度消失的区域。
  • 使用 r e l u relu relu 代替 s i g m o i d sigmoid sigmoid t a n h tanh tanh 作为激活函数。
  • 使用其他结构的RNNs,比如长短时记忆网络(LTSM)和Gated Recurrent Unit(GRU),这是最流行的做法。

参考资料:

已标记关键词 清除标记
课程简介: 历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring Boot、Spring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:
©️2020 CSDN 皮肤主题: 程序猿惹谁了 设计师:白松林 返回首页