【NLP】第三章:长短期记忆网络LSTM

发布于:2024-11-27 ⋅ 阅读:(115) ⋅ 点赞:(0)

三、长短期记忆网络LSTM

循环神经网络的特点就是"记忆单元",就是考虑历史信息,就是从历史信息中获取辅助当前的决策。
"记忆单元"分:simple rnn(就是前面讲的简单rnn结构)、长短期记忆网络(LSTM)、门控循环单元(GRU)、以及双向RNN(Bi-RNN),这些都是RNN的变体。从RNN->GRU->LSTM,网络的记忆能力更强、计算复杂度更大、实际使用更多。本篇讲LSTM。

(一)LSTM简介
LSTM算法来自1997年Hochreiter & Schmidhuber发表的《Long short-term memory》论文,论文指出,用我们前面学的Simple RNN架构学习较长时间间隔的任务(也就是捕捉长距离关系时)通常需要很长的时间。这是因为误差在反传的过程中容易出现梯度爆炸或者梯度消失,网络训练很久也无法收敛。为了解决这个问题,他们提出了Long Short Term 网络:LSTM。

虽然《Long short-term memory》这篇论文在RNN领域,乃至DP领域都是极为重要的论文之一。但是论文并没有从理论上论述LSTM为什么能够缓解梯度消失问题,当然也不保证LSTM不存在梯度消失问题,甚至连门结构为什么这么设计也没有解释。至今在网上仍然存在各种理解和争论,而其为什么能缓解梯度消失的理论依据仍然是缺失的。

此后LSTM被Alex Graves进行了改良和推广,LSTM通过特殊的单元结构实现了稳定的信息流动,从而能够学习跨越超过1000个时间步长的任务,同时极大的避免了误差反向传播过程中可能出现的梯度消失或爆炸问题,使得RNN真正意义上能够好用。

很多初学者学习LSTM,建议看看ChristopherOlah的博文《理解LSTM网络》 https://colah.github.io/posts/2015-08-Understanding-LSTMs/ ,这篇文章非常经典,网上流传也相当之广,是你较快入门LSTM的博文之一。

(二)LSTM原理及架构
其实如果你是初学者,一上来就学LSTM,可能还真是有一些难度的。所以这里建议你先认认真真看看一看Simple RNN: 【NLP】第二章:循环神经网络RNN、DRNN、 BRNN_rnn循环神经网络csdn-CSDN博客 ,再来看本篇,会好理解一些。
理科真不像文科可以跳着学,理科就得基础一点点打牢,环环相扣的理解下来。如果Simple RNN你都看不懂,那就建议你从机器学习看起:https://blog.csdn.net/friday1203/category_12824289.html ,然后再看深度学习。

1、从整体架构上看
下图上半部分是Simple RNN的架构,下半部分是LSTM的宏观架构。看到架构你心中要明确下面几点:

(1)Simple RNN和LSTM都是具有一种重复神经网络模块的链式的形式
都是一种使用简单的神经网络模块重复而成的链式结构。你可以想象成搭积木,你可以把RNN想象成是sequence_length个b,依次罗列起来的B,LSTM是sequence_length个c依次罗列起来的C。b和c就是不同的神经网络模块,B和C就是重复的链式结构。RNN和LSTM不同之处仅仅就在于重复积木块不一样,但其重复方式都是一样的,都是根据sequence_length长度进行重复的。

(2)Simple RNN和LSTM的链式结构是时间维度上的链条,不是空间维度上的链条。
我们都知道,RNN和LSTM都是依次处理序列数据的,所以B和C都是一个循环链,就是按时间维度串在一起的链条而非像FNN\CNN那种,是空间维度上串在一起的链条。所以泛化到任一时刻,它们的架构就是一个b模块(Simple RNN),一个c模块(LSTM),因为仅仅是在时间维度上每个时间步样本依次传播了一次而已,不是空间维度上的传播。

(3)循环网络的链式结构在时间维度上的链条长度是多少呢?
上图中xt是输入序列在t时刻的输入向量。输入序列中有多少个时间步,就将隐藏层展开多少次,每个步态状态对应于展开出来的一个隐藏层。所以链条长度就是你循环了多少次。也就是你一个sequence有几个时间步,链条就有多长。

(4)从哪里能看到循环网络的"记忆"?
循环网络之所以有别FNN/CNN,就是它有"记忆"样本和样本之间信息的能力,这种能力主要是通过它的信息传递方式来实现的,就是通过时间维度上的循环流。
对于SimpleRNN网络来说,其重复模块就是一个隐藏层,隐藏层是捕捉序列信息的,我们称之为"记忆单元",就是上图的ht。
对于LSTM网络来说,它的重复模块叫细胞,细胞上的Ct,Ct叫细胞状态,细胞状态就是记忆样本与样本之间的信息的。
(5)网络的输入
对于RNN的输入来说,并不是每次都必须等步长输入的,就是可以变步长输入,就是没有要求你所有的输入都是等步长,你有几步就循环几次好了,不是说我必须要求都要是比如10步,必须要循环10次,不是的,你有几步循环几次即可。

(6)网络的输出
同理每一时间步都会有输出,但也并不是每个时间步都必须输出的,这个得看我们的任务。不同类型NLP任务会有不同的输出层结构、也就是会有不同的标签输出方式。例如,在对词语/样本进行预测的任务中(比如词性标注、NER实体识别等任务),RNN会在每个时间步都输出词语对应的相应预测标签。但是在对句子进行预测的任务中(例如生成式任务、seq2seq的任务、对句子进行情感分类任务等),RNN可能就只会在最后一个时间步输出句子相对应的预测标签。

(7)网络的训练方式
由于循环神经网络的输出标签的方式不同,所以反向传播的流程自然会和前面学的FNN\CNN有所区别。循环神经网络的训练和普通FNN、CNN网络的训练都不一样,循环神经网络的反向传播是通过时间的反向传播(Backpropagation Through Time, BPTT) 。至于什么是BPTT我们这里不展开,回头单独拿出来讲,避免失焦。

(8)Simple RNN 在训练中经常遇到梯度消失或者梯度爆炸
反向传播就是反向求导的,而求导的链式法则和非线性函数的使用,使得反向传播中会有梯度消失和爆炸问题。在文章《On the difficulty of training recurrent neural networks》中就有详细的解释和实验,其表明:BPTT无法解决长时依赖问题(即当前的输出与前面很长的一段序列有关,一般超过十步就无能为力了),因为BPTT会带来所谓的梯度消失或梯度爆炸问题(the vanishing/exploding gradient problem)。也就是说随着时间步的增加,Simple RNN会丧失记忆远信息的能力。

(9)Simple RNN梯度消失的原因
非RNN网络的梯度消失和爆炸通常是采用残差链接(Residual Connection)的方式解决,也就是说给梯度flow建立捷径能够直接反向传播到输入层附近,进而调整这些神经元的权值。而RNN梯度消失与其他深度网络梯度消失虽然原因类似,但却无法通过直连来解决。因为 RNN是一个时序意义上的深度网络,虽然从表达式上t时刻的loss与之前所有时刻的隐含态h都有关系,即按照时序展开,你会发现已经有类似Residual Connection直接连接每个输入到当前的loss,但是RNN与ResNet的巨大不同在于,这些连接反复使用的都是同一个W。因为Simple RNN是一个时序深度、而非空间深度的网络,所以在每个时间步,其计算的参数W都一样,也叫RNN的权值共享
RNN在反向传播梯度更新时是矩阵连乘,且激活函数的导数恒小于1,这就造成在时序上距离远的求导flow路径传输回来的信号太弱,接近0,出现梯度消失的情况。而近处的信号虽然强,但由于共享权值,参数W太简单,很容易就收敛在一个只考虑近处信号的简单神经网络的局部最优解,而这个解通常不是整个网络的局部最优。因此,RNN就在这样一个连局部最优解都不是的位置上训练缓慢,无法调整权重了。

换句话说就是,如果一条序列足够长,那RNN将很难将信息从较早的时间步传送到后面的时间步。因为梯度消失了,就意味着网络权值的更新停止了,梯度更新停止就意味着那些较早的层停止学习了,也就是RNN忘记了它在较长序列中以前看到的内容,只有短时记忆了。所以Simple RNN从其诞生开始,就远算不上是一个好用的架构,直到它的变体LSTM的出现,循环网络才荣耀登场。

(10)LSTM用门结构来解决梯度消失问题
正是由于RNN是时序的深度,所以无法使用其他深度网络的residual connection的空间直连方式来缓解梯度消失问题,所以RNN的变体——LSTM采用门结构的设计成为了必然的选择。
因为LSTM加入门控,LSTM中的权值就不共享了,权值参数就在了两个不同的向量中了。这使得LSTM的细胞状态Ct(就是所谓的"记忆")就是由两个矩阵相加的组合了,所以其梯度更新也会来自两个矩阵的梯度之和。只要梯度更新不涉及矩阵连乘,那么梯度消失情况就可缓解。因此LSTM也只是缓解了(并非解决)Ct的梯度消失问题,其门状态仍存在梯度消失的情况。GRU同理,只是ht的梯度消失情况被缓解,并非解决。

(11)LSTM如何解决梯度爆炸问题的?
梯度爆炸仅在一些网络的权重参数和激活值过大时发生,导致连乘后的数值越来越大。所以解决办法也很简单,就是梯度裁剪,进行上限设置即可。

2、LSTM架构
在Simple RNN中,重复模块h叫记忆单元。记忆单元其实就是一个非常简单的结构:一个单层全连接线性层FNN+一个激活层。这个记忆单元的状态叫隐藏状态,hidden state,它记录着历史(记忆)信息。

在LSTM中,重复模块A则叫细胞cell,A也有一个状态叫细胞状态Ct,细胞状态就是承载记忆信息的。但是LSTM细胞不像RNN的记忆单元那么简单,LSTM的细胞比较复杂,包含四个交互的层,三个Sigmoid 和一个tanh层,并以一种非常特殊的方式进行交互:

说明:上面的图都是我从 https://colah.github.io/posts/2015-08-Understanding-LSTMs/ 这篇博文中截取的,如果看不懂的可以自行查阅原文。
(1)上图的A就是一个细胞cell。也就是LSTM的重复模块。

(2)细胞的输入:cell会接受两个输入,一个是上个时间步的细胞的输出ht-1,一个是这个时间步的样本输入xt。

(3)细胞的输出:cell会输出两个输出,一个是本时间步的细胞输出ht,一个是本时间步的细胞状态Ct,Cell State。
细胞状态Ct代表的是长时间记忆,它是贯穿整个时序链的数据流,每层都有一个细胞状态的输入和输出。细胞状态可以看作就是一个记忆链条,这个链条经过每个时序细胞都会有忘有记,或者说该忘该记。那么它是如何做到该忘该记?就是后面要讲的门结构控制的。

上图中的紫色ht输出其实和红色ht输出是一样的,只是紫色的输出并非是必须的,这个的看你的任务是什么,就是看你的需求,你的任务不需要紫色输出就可以不用输出了。但是只要还有下一个时间步的样本要循环,那红色的ht就必须得输出。当然最后一个时间步也是要输出ht的。从符号也可以看出ht就是隐藏层的输出。

(4)σ表示的是Sigmoid激活函数,tanh表示的是tanh激活函数。sigmoid可以把数据压缩到0-1之间,tanh则是把数据压缩到-1-1之间。
所以,σ层是用来决定保留还是不保留信息的。或者说是用来更新或者忘记信息的,因为任何数乘以0都得0,乘以1等于本身。所以0就是忘记,1就是记住。或者说0就是“不许任何量通过”,1是“允许任意量通过”,这也是门结构称谓的由来。
所以LSTM的门结构指的就是A中的三个σ(sigmoid激活层)+一个pointwise乘法操作构成的。
第一个σ代表的门被称为遗忘门/忘记门
第二个σ代表的门被称为输入门
第三个σ代表的门被称为输出门
那为什么还有tanh激活函数?据说是。。。。。。.

3、LSTM的门结构,及细胞状态
那么LSTM是如何通过遗忘门、输入门和输出门,来调节细胞状态的呢?或者说如何通过这些门,做到该忘的忘,该记住的记住呢?

从上图我们可以总结出:
(1)遗忘门、输入门、输出门的输入数据都是[ht-1, xt],即不仅要输入上个时间步的细胞的隐藏输出ht-1,还得输入这个时间步的样本数据xt。

(2)遗忘门,从公式上看不就是一个线性变换嘛,你可以形象的理解为,它其实就是一个FNN线性层,外套一个sigmoid激活函数。这个全连接线性层的作用就是,通过学习当前时间步的输入数据[ht-1, xt],来生成一个遗忘系数矩阵。这个遗忘矩阵是用来乘以前一个时间步输出的细胞状态(Ct-1)的。这样细胞通过这个门就学习了之前的记忆什么该丢掉,什么该记住。

(3)输入门则是两个并联的FNN线性层
左侧线路套的是sigmoid激活函数,因为这个层也是学习输入门系数的,就是本次时间步输入的数据[ht-1, xt],哪些是要学习的,哪些是不用学的,就生成一个输入门系数矩阵。和遗忘门一个道理。
右侧线路则是套一个tanh激活函数,因为右侧全连接层才是真正用来学习输入数据[ht-1, xt]本身的。所以两条并联线路各自学习完毕后,二者的乘积就是本次时间步输入数据的一个学习成果。

(4)所以更新细胞状态时,就是遗忘门+输入门。之前的该忘的忘了,该记的记住了;本次时间步的信息该记的记住,不该记的就毙掉。

(5)输出门就更加同理了,也是先学一个输出门系数矩阵Ot,然后对更新后的细胞状态Ct进行tanh正则化后,乘以Ot,就是本次时间步的细胞的隐藏输出ht。

整体全流程如下图:

至此,这些就是LSTM的原理、架构。下面我们看看pytorch中的lstm层的实现。

(三)在pytorch中实现LSTM
通过上面的各个角度的描述,我们知道其实LSTM和第二章的simple rnn一样,都是一个重复模块而已。所以在pytorch中,LSTM和RNN一样都是可以作为一个单独的层而存在的。本部分是我们从代码角度再次理解LSTM。

1、LSTM类的参数

input_size:输入样本的特征个数。
hidden_size:隐藏层的神经元个数。
num_layer默认是1。这个参数和RNN中的一模一样,默认是1个LSTM细胞。如果你设置=2,就表示在物理空间维度上堆叠两个LSTM细胞。也就是stacked LSTM,第二个LSTM接收第一个LSTM的输出并计算最终结果。所以也叫深度LSTM。
bias:就是偏置嘛。因为都是普通的全连接线性层嘛,一般后面都会跟个偏置的。
batch_first=False,和RNN一样,默认这个参数是False,我们最好输入数据也是(seq, batch, features)这样一个结构,计算效率最高。
dropout:这个层没啥好说的,之前写FNN和CNN都写过,参考【深度视觉】第四章:卷积神经网络架构_第四章卷积网络-CSDN博客 

bidirectional:和RNN一样,这个参数设置为True的话,数据流在时间维度上就是双向的,和BRNN一样一样的,就是在时间维度上将正向和反向细胞状态进行串联。BRNN的数据流我已经详细演示了,可以参考 【NLP】第二章:循环神经网络RNN、DRNN、 BRNN_rnn循环神经网络csdn-CSDN博客 ,这里就不演示了,很麻烦。
后面的参数都是上GPU上的设置了,这里就不说它们了。

2、模型参数

很多人到这里就懵掉了,前面不是说三个门都有4个线性层的嘛,怎么不是4个参数矩阵和4个偏置呢?!是的,到这里很多人都会懵掉,但是我们再仔细看看pytorch中的说明文档,看看pytorch的LSTM的内部处理流程:

是不是破案了。那我们下面手动验证一下,看结果一样不一样:

待续。。。。


网站公告

今日签到

点亮在社区的每一天
去签到