循环神经网络简介
BP算法,CNN之后,为什么还有RNN?
细想BP算法,CNN(卷积神经网络)我们会发现, 他们的输出都是只考虑前一个输入的影响而不考虑其它时刻输入的影响, 比如简单的猫,狗,手写数字等单个物体的识别具有较好的效果. 但是, 对于一些与时间先后有关的, 比如视频的下一时刻的预测,文档前后文内容的预测等, 这些算法的表现就不尽如人意了.因此, RNN就应运而生了。
什么是 RNN?
循环神经网络(Recurrent Neural Network, RNN)是一类以序列(sequence)数据为输入,在序列的演进方向进行递归(recursion)且所有节点(循环单元)按链式连接的递归神经网络(recursive neural network)。
RNN是一种特殊的神经网络结构, 它是根据人的认知是基于过往的经验和记忆
这一观点提出的. 它与DNN,CNN不同的是: 它不仅考虑前一时刻的输入,而且赋予了网络对前面的内容的一种记忆
功能.
RNN之所以称为循环神经网路,即一个序列当前的输出与前面的输出也有关。具体的表现形式为网络会对前面的信息进行记忆并应用于当前输出的计算中,即隐藏层之间的节点不再无连接而是有连接的,并且隐藏层的输入不仅包括输入层的输出还包括上一时刻隐藏层的输出。
应用领域
RNN的应用领域有很多, 可以说只要考虑时间先后顺序的问题都可以使用RNN来解决.这里主要说一下几个常见的应用领域:
① 自然语言处理(NLP): 主要有视频处理, 文本生成, 语言模型, 图像处理
② 机器翻译, 机器写小说
③ 语音识别
④ 图像描述生成
⑤ 文本相似度计算
⑥ 音乐推荐、网易考拉商品推荐、Youtube视频推荐等新的应用领域.
发展历史
代表性RNN
- 基本RNN:循环网络的基本构成
- LSTM:突破性进展的长短期记忆网络
- GRU:新式的Cell模块单元
- NTM:更大记忆体模型的探索
网络热度
基本RNN
首先看一个简单的循环神经网络如下,它由输入层、一个隐藏层和一个输出层组成:
如果把上面有W的那个带箭头的圈去掉,它就变成了最普通的全连接神经网络。
- x 是一个向量,它表示输入层的值(这里面没有画出来表示神经元节点的圆圈);
- s 是一个向量,它表示隐藏层的输出值(这里隐藏层面画了一个节点,你也可以想象这一层其实是多个节点,节点数与向量s的维度相同);
- U 是输入层到隐藏层的权重矩阵
- o 也是一个向量,它表示输出层的值;
- V 是隐藏层到输出层的权重矩阵。
那么,现在我们来看看W是什么。循环神经网络的隐藏层的值s不仅仅取决于当前这次的输入x,还取决于上一次隐藏层的值s。权重矩阵 W就是隐藏层上一次的值作为这一次的输入的权重。这个就是实现时间记忆功能
的方法。
我们给出这个抽象图对应的具体图:
我们从上图就能够很清楚的看到,上一时刻的隐藏层是如何影响当前时刻的隐藏层的。
如果我们把上面的图展开,循环神经网络也可以画成下面这个样子:
这是一个标准的RNN结构图,图中每个箭头代表做一次变换,也就是说箭头连接带有权值。左侧是折叠起来的样子,右侧是展开的样子,左侧中 旁边的箭头代表此结构中的“循环“体现在隐层。
在展开结构中我们可以观察到,在标准的RNN结构中,隐层的神经元之间也是带有权值的。也就是说,随着序列的不断推进,前面的隐层将会影响后面的隐层。图中 代表输出, 代表样本给出的确定值, 代表损失函数,我们可以看到,“损失“也是随着序列的推荐而不断积累的。
除上述特点之外,标准RNN的还有以下特点:
-
权值共享
,图中的W全是相同的,U和V也一样。 - 每一个输入值都只与它本身的那条路线建立权连接,不会和别的神经元连接。
- 隐藏状态可以理解为: h=f(现有的输入+过去记忆总结)
以上是RNN的标准结构 N VS N
(N个输入对应着N个输出),即输入和输出序列必须要是等长的
由于这个限制的存在,经典RNN的适用范围比较小,但也有一些问题适合用经典的RNN结构建模,如:
- 计算视频中每一帧的分类标签。因为要对每一帧进行计算,因此输入和输出序列等长。
- 输入为字符,输出为下一个字符的概率。这就是著名的Char RNN(详细介绍请参考:The Unreasonable Effectiveness of Recurrent Neural Networks,Char RNN可以用来生成文章,诗歌,甚至是代码,非常有意思)。
RNN变体
N VS 1
RNN的标准结构N VS N
在实际中并不能解决所有问题,有的时候,我们要处理的问题输入是一个序列,输出是一个单独的值而不是序列,例如我们输入为一串文字,输出为分类类别,那么输出就不需要一个序列,只需要单个输出。应该怎样建模呢?实际上,我们只在最后一个 上进行输出变换就可以了。
这种结构通常用来处理序列分类
问题。如输入一段文字判别它所属的类别,输入一个句子判断其情感倾向,输入一段视频并判断它的类别等等。
1 VS N
输入不是序列而输出为序列的情况怎么处理?我们可以只在序列开始进行输入计算:
还有一种结构是把输入信息作为每个阶段的输入(输入虽是序列,但不随着序列变化):
下图省略了一些X的圆圈,是一个等价表示:
这种1 VS N的结构可以处理的问题有:
- 从图像生成文字(image caption),此时输入的就是图像的特征,而输出的序列就是一段句子;
- 从类别生成语音或音乐等。
N VS M
下面我们来介绍RNN最重要的一个变种:N vs M
。这种结构又叫Encoder-Decoder
模型,也可以称之为Seq2Seq
模型。
原始的N vs N RNN
要求序列等长,然而我们遇到的大部分问题序列都是不等长的,如机器翻译中,源语言和目标语言的句子往往并没有相同的长度。
为此,Encoder-Decoder结构先将输入数据编码成一个上下文向量c
:
得到有多种方式,最简单的方法就是把Encoder的最后一个隐状态赋值给,还可以对最后的隐状态做一个变换得到,也可以对所有的隐状态做变换。
得到之后,就用另一个RNN网络对其进行解码,这部分RNN网络被称为Decoder
。具体做法就是将c
当做之前的初始状态输入到Decoder中:
还有一种做法是将当做每一步的输入:
由于这种Encoder-Decoder
结构不限制输入和输出的序列长度,因此应用的范围非常广泛,比如:
- 机器翻译。
Encoder-Decoder
的最经典应用,事实上这一结构就是在机器翻译领域最先提出的; - 文本摘要。输入是一段文本序列,输出是这段文本序列的摘要序列;
- 阅读理解。将输入的文章和问题分别编码,再对其进行解码得到问题的答案;
- 语音识别。输入是语音信号序列,输出是文字序列。
Attention机制
在Encoder-Decoder
结构中,Encoder把所有的输入序列都编码成一个统一的语义特征c再解码,**因此, c中必须包含原始序列中的所有信息,它的长度就成了限制模型性能的瓶颈。**如机器翻译问题,当要翻译的句子较长时,一个c可能存不下那么多信息,就会造成翻译精度的下降。
Attention机制通过在每个时间输入不同的c来解决这个问题,下图是带有Attention机制的Decoder:
每一个 c 会自动去选取与当前所要输出的 最合适的上下文信息。具体来说,我们用 衡量 Encoder 中第 阶段的 和解码时第 阶段的相关性,最终 Decoder 中第 阶段的输入的上下文信息 就来自于所有 对 的加权和。
以机器翻译为例(将中文翻译成英文):
输入的序列是“我爱中国”,因此,Encoder 中的就可以分别看做是“我”、“爱”、“中”、“国”所代表的信息。在翻译成英语时,第一个上下文应该和“我”这个字最相关,因此对应的 就比较大,而相应的 就比较小。 应该和“爱”最相关,因此对应的 就比较大。最后的 和 最相关,因此 的值就比较大。
至此,关于Attention模型,我们就只剩最后一个问题了,那就是: 是怎么来的?
事实上, 同样是从模型中学出的,它实际和 Decoder 的第 阶段的隐状态、Encoder第 个阶段的隐状态有关。
同样还是拿上面的机器翻译举例, 的计算(此时箭头就表示对 h’ 和 同时做变换):
的计算:
的计算:
以上就是带有Attention的Encoder-Decoder模型计算的全过程。
可能发现没有
LSTM
的内容,其实是因为LSTM从外部看和RNN完全一样,因此上面的所有结构对LSTM都是通用的
标准RNN的前向输出流程
上面介绍了RNN有很多变种,但其数学推导过程其实都是大同小异。这里就介绍一下标准结构的RNN的前向传播过程。
再来介绍一下各个符号的含义: 是输入, 是隐层单元, 为输出,为损失函数,为训练集的标签。这些元素右上角带的 代表t时刻的状态,其中需要注意的是,隐藏单元 在 时刻的表现不仅由此刻的输入决定,还受 时刻之前时刻的影响。 是权值,同一类型的权连接权值相同。
有了上面的理解,前向传播算法其实非常简单,对于 时刻:
其中 为**函数,一般来说会选择 函数, 为偏置。
时刻的输出就更为简单:
最终模型的预测输出为:
其中 为**函数,通常 RNN 用于分类,故这里一般用 softmax 函数。
公式(1)是隐藏层的计算公式,它是循环层。公式(2)是输出层的计算公式,输出层是一个全连接层
从上面的公式我们可以看出,循环层和全连接层的区别就是循环层多了一个权重矩阵 。
如果反复把公式(1)带入到公式(2) , 先去掉偏置,我们将得到:
KaTeX parse error: No such environment: align at position 8:
\begin{̲a̲l̲i̲g̲n̲}̲
o^{(t)}&=g(Vh^…
从上面可以看出,循环神经网络的输出值 ,是受前面历次输入值 、、、、…影响的,这就是为什么循环神经网络可以往前看任意多个输入值的原因。
有时候公式(1)会写成如下形式:
这是因为公式(3)中的 等于向量, 例如 向量形状是(10, 1),向量的形状是(10, 10),的形状是(20, 1),则的形状是(10, 20),则最终的形状是(10, 1)
我们在看下的形状是(10, 30),而 的形状是(30,1),则(10,30)*(30, 1)=(10, 1),故公式(1)与公式(3)是等价的
;
贴一张吴恩达的手工推导图:
RNN的训练方法——BPTT
BPTT(back-propagation through time)算法是常用的训练RNN的方法,其实本质还是BP算法,只不过RNN处理时间序列数据,所以要基于时间反向传播,故叫随时间反向传播。BPTT的中心思想和BP算法相同,沿着需要优化的参数的负梯度方向不断寻找更优的点直至收敛。综上所述,BPTT算法本质还是BP算法,BP算法本质还是梯度下降法,那么求各个参数的梯度便成了此算法的核心。
再次拿出这个结构图观察,需要寻优的参数有三个,分别是 。与 BP 算法不同的是,其中 和 两个参数的寻优过程需要追溯之前的历史数据,参数 相对简单只需关注目前,那么我们就来先求解参数 的偏导数。
这个式子看起来简单但是求解起来很容易出错,因为其中嵌套着**函数函数,是复合函数的求道过程。
RNN的损失也是会随着时间累加的,所以不能只求t时刻的偏导。
和 的偏导的求解由于需要涉及到历史数据,其偏导求起来相对复杂,我们先假设只有三个时刻,那么在第三个时刻 对 的偏导数为:
相应的, 在第三个时刻对U的偏导数为:
可以观察到,在某个时刻的对 或是 的偏导数,需要追溯这个时刻之前所有时刻的信息,这还仅仅是一个时刻的偏导数,上面说过损失也是会累加的,那么整个损失函数对 和 的偏导数将会非常繁琐。虽然如此但好在规律还是有迹可循,我们根据上面两个式子可以写出L在 时刻对 和 偏导数的通式:
整体的偏导公式就是将其按时刻再一一加起来。
前面说过**函数是嵌套在里面的,如果我们把**函数放进去,拿出中间累乘的那部分:
我们会发现累乘会导致**函数导数的累乘,进而会导致梯度消失
和梯度爆炸
现象的发生。
梯度消失与梯度爆炸
产生原因
我们先来看看这两个**函数的图像。这是sigmoid函数的函数图和导数图。
这是tanh函数的函数图和导数图。
由上图可知当**函数是tanh函数时,tanh函数的导数最大值为1,又不可能一直都取1这种情况,而且这种情况很少出现,那么也就是说,大部分都是小于1的数在做累乘,若当 t 很大的时候, 中 趋向0,举个例子: 也已经接近0了,这是RNN中梯度消失的原因。
你可能会提出异议,RNN明明与深层神经网络不同,RNN的参数都是共享的,而且某时刻的梯度是此时刻和之前时刻的累加,即使传不到最深处那浅层也是有梯度的。这当然是对的,但如果我们根据有限层的梯度来更新更多层的共享的参数一定会出现问题的,因为将有限的信息来作为寻优根据必定不会找到所有信息的最优解。
梯度消失就意味消失那一层的参数再也不更新,那么那一层隐层就变成了单纯的映射层,毫无意义了,所以在深层神经网络中,有时候多加神经元数量可能会比多家深度好。
我们再看公式
中 还需要网络参数 ,如果参数 中的值太大,随着序列长度同样存在长期依赖的情况,那么产生问题就是梯度爆炸,而不是梯度消失了,在平时运用中,RNN比较深,使得梯度爆炸或者梯度消失问题会比较明显。
之前说过我们多用 函数作为**函数,那 函数的导数最大也才1啊,而且又不可能所有值都取到1,那相当于还是一堆小数在累乘,还是会出现“梯度消失“,那为什么还要用它做**函数呢?原因是 函数相对于 函数来说梯度较大,收敛速度更快且引起梯度消失更慢。
还有一个原因是 函数还有一个缺点,Sigmoid函数输出不是零中心对称。sigmoid的输出均大于0,这就使得输出不是0均值,称为偏移现象
,这将导致后一层的神经元将上一层输出的非0均值的信号作为输入。关于原点对称的输入和中心对称的输出,网络会收敛地更好
。
解决方法
RNN的特点本来就是能“追根溯源“利用历史数据,现在告诉我可利用的历史数据竟然是有限的,这就令人非常难受,解决“梯度消失“是非常必要的。解决“梯度消失“的方法主要有:
1、选取更好的**函数
2、改变传播结构
关于第一点,一般选用ReLU函数作为**函数,ReLU函数的图像为:
ReLU函数在定义域大于0部分的导数恒等于1,这样可以解决梯度消失的问题。
另外计算方便,计算速度快,可以加速网络训练。但是,定义域负数部分恒等于零,这样会造成神经元无法**(可通过合理设置学习率,降低发生的概率)。
ReLU有优点也有缺点,其中的缺点可以通过其他操作取避免或者减低发生的概率,是目前使用最多的**函数。
关于第二点,LSTM
结构可以解决这个问题。
BRNN(双向循环神经网络)
在RNN(Bi-directional Recurrent Neural Network)中只考虑了预测词前面的词,即只考虑了上下文中“上文”,并没有考虑该词后面的内容。这可能会错过了一些重要的信息,使得预测的内容不够准确。如果能像访问过去的上下文信息一样,访问未来的上下文,这样对于许多序列标注任务是非常有益的。
双向RNN,即可以从过去的时间点获取记忆,又可以从未来的时间点获取信息。为什么要获取未来的信息呢?
判断下面句子中Teddy是否是人名,如果只从前面两个词是无法得知Teddy是否是人名,如果能有后面的信息就很好判断了,这就需要用的双向循环神经网络。
双向循环神经网络(BRNN)的基本思想是提出每一个训练序列向前和向后分别是两个循环神经网络(RNN),而且这两个都连接着一个输出层。这个结构提供给输出层输入序列中每一个点的完整的过去和未来的上下文信息。下图展示的是一个沿着时间展开的双向循环神经网络。六个独特的权值在每一个时步被重复的利用,六个权值分别对应:输入到向前和向后隐含层(w1, w3),隐含层到隐含层自己(w2, w5),向前和向后隐含层到输出层(w4, w6)。值得注意的是:向前和向后隐含层之间没有信息流,这保证了展开图是非循环的。
至于网络单元到底是标准的RNN还是GRU或者是LSTM是没有关系的,都可以使用。
对于整个双向循环神经网络(BRNN)的计算过程如下:
向前推算(Forward pass):
对于双向循环神经网络(BRNN)的隐含层,向前推算跟单向的循环神经网络(RNN)一样,除了输入序列对于两个隐含层是相反方向的,输出层直到两个隐含层处理完所有的全部输入序列才更新:
向后推算(Backward pass):
双向循环神经网络(BRNN)的向后推算与标准的循环神经网络(RNN)通过时间反向传播相似,除了所有的输出层δδ项首先被计算,然后返回给两个不同方向的隐含层:
深层RNN(DRNN)
深层RNN网络是在RNN模型多了几个隐藏层,是因为考虑到当信息量太大的时候一次性保存不下所有重要信息,通过多个隐藏层可以保存更多的重要信息,正如我们看电视剧的时候也可能重复看同一集记住更多关键剧情。同样的,我们也可以在双向RNN模型基础上加多几层隐藏层得到深层双向RNN模型。
注:每一层循环体中参数是共享的,但是不同层之间的权重矩阵是不同的。
至于网络单元到底是标准的RNN还是GRU或者是LSTM是没有关系的,都可以使用。
参考链接
- https://zhuanlan.zhihu.com/p/30844905
- https://www.jianshu.com/p/8d1c0f96a25c
- https://zhuanlan.zhihu.com/p/53405950
- https://zybuluo.com/hanbingtao/note/541458
- https://blog.csdn.net/jojozhangju/article/details/51982254