本文将会使用大量的图片和公式推导通俗易懂地讲解RNN,LSTM,Seq2Seq和attention注意力机制(结合colah's blog 和CS583),但愿帮助初学者更好掌握且入门。node
目录
-
RNN -
LSTM -
Seq2Seq -
注意力机制 -
参考
RNN(递归神经网络)
咱们知道人类并非从零开始思考东西,就像你读这篇文章的时候,你对每一个字的理解都是创建在前几个字上面。你读完每一个字后并非直接丢弃而后又从零开始读下一个字,由于你的思想是具备持续性的,不少东西你要经过上下文才能理解。git
然而传统的神经网络并不能作到持续记忆理解这一点,这是传统神经网络的主要缺点。举个例子,你打算使用传统的神经网络去对电影里每一个时间点发生的事情进行分类的时候,传统的神经网络先让不能使用前一个事件去推理下一个事件。
github
RNN(递归神经网络)能够解决这个问题。他们是带有循环的神经网络,容许信息在其中保留。在上图中,A表明神经网络主体,
表示网络的输入,
表示网络的输出。循环结构容许信息从当前输出传递到下一次(下个时间点)的网络输入。web
这些循环让递归神经网络看起来有点神秘,然而若是你再思考一下,RNN其实和传统的神经网络并无太多的不一样。RNN能够看做是一个网络的屡次拷贝,其中每次网络的输出都是下一次的输入。咱们能够思考一下咱们若是展开这个循环结构会是什么样的:这种像是链状的网络结构代表RNN和序列以及列表有着自然的联系,他们是处理这些序列数据的自然的神经网络。并且很明显咱们能够看出,输入输出的序列是具备相同的时间长度的,其中的每个权值都是共享的(不要被链式形状误导,本质上只有一个cell)。数组
在最近的几年,RNN在不少问题上都取得了成功:好比语音识别,语音模型,翻译,图片注释等等,可是RNN存在着梯度消息/爆炸以及对长期信息不敏感的问题,因此LSTM就被提出来了。如今不少问题的成功都必须归功于LSTM,它是递归神经网络的一种,它在许多的任务中表现都比普通的RNN更好,因此接下来咱们来探索一下这个神奇的网络。微信
LSTM
长期依赖问题
人们但愿RNN能够将一些以前的信息链接到当前的任务中来,好比使用以前的视频帧来帮助理解当前帧。若是RNN能够作到将会很是有用。那实际RNN能作到吗?这要视状况而定。网络
有时候,咱们只须要当前的信息来完成当前的任务。举个例子,一个语音模型试图基于以前的单词去预测下一个单词。若是咱们尝试预测“the clouds are in the sky”,咱们不须要太多的上下文信息——很明显最后一个单词会是sky。在像这样不须要太多的相关信息的场合下,RNN能够学习到以前使用的信息。可是咱们要注意,也有不少场景须要使用更多的上下文。当咱们试图去预测“I grew up in France… I speak fluent French”这句话的最后一个单词,最近的信息会代表这应该是一种语言的名字,可是若是咱们须要知道具体是哪种语语言,咱们须要France这个在句子中比较靠前的上下文信息,相关信息和须要预测的点的间隔很大的状况是常常发生的。app
不幸的是,随着间隔变大,RNN变得没法链接到太前的信息。理论上RNN彻底能够处理这种长期依赖(long-term dependencies)的问题。人们能够经过当心地选择参数来解决这个问题。使人悲伤的是,实践代表RNN并不能很好地解决这个问题,Hochreiter (1991) [German] and Bengio, et al. (1994)发现了RNN为何在这些问题上学习很困难的缘由。编辑器
而LSTM则没有这个问题。svg
LSTM网络
长期短时间记忆网络-一般叫作LSTM-是一种特殊结构的RNN,它可以学习长期依赖。它在大量的问题有惊人的效果,如今已经被普遍使用。
LSTM被明确设计来避免长期依赖问题,记住长时间的信息对LSTM来讲只是常规操做,不像RNN那样费力不讨好。
全部的RNN都有不断重复网络自己的链式形式。在标准的RNN中,这个重复复制的模块只有一个很是简单的结果。例如一个tanh层:LSTM也有这样的链式结构,可是这个重复的模块和上面RNN重复的模块结构不一样:LSTM并非只是增长一个简单的神经网络层,而是四个,他们以一种特殊的形式进行交互:
读者不须要担忧看不懂,接下来咱们将会一步步理解这个LSTM图。首先咱们先了解一下图中的符号:
在上图中,每条线表示一个向量,从一个输出节点到其余节点的输入节点。粉红色的圆圈表示逐点式操做,就像向量加法。黄色的盒子是学习好的神经网络层。线条合表明联结,线条分叉则表示内容被复制到不一样的地方。
LSTM背后的核心思想
LSTM的核心之处就是它的cell state(神经元状态),在下图中就是那条贯穿整个结果的水平线。这个cell state就像是一个传送带,他只有很小的线性做用,但却贯穿了整个链式结果。信息很容易就在这个传送带上流动可是状态却不会改变。cell state上的状态至关于长期记忆,而下面的
则表明短时间记忆。LSTM有能力删除或者增长cell state中的信息,这一个机制是由被称为门限的结构精心设计的。
门限是一种让信息选择性经过的方式,它们是由sigmoid神经网络层和逐点相乘器作成的。sigmoid层输出0和1之间的数字来描述一个神经元有多少信息应该被经过。输出0表示这些信息所有不能经过,而输出1则表示让全部信息都经过。
一个LSTM有三个这样的门限,去保护和控制神经元的状态。
一步步推导LSTM
LSTM的第一步就是决定什么信息应该被神经元遗忘。这是一个被称为“遗忘门层”的sigmod层组成。他输入 和 (上一次的输出以及这轮的输入),而后在 的每一个神经元状态输出0和1之间的数字。同理1表示彻底保留这些信息,0表示彻底遗忘这个信息。
让咱们再次回到一开始举的例子:根据以前的词语去预测下一个单词的语言模型。在这个问题中,cell state或许包括当前主语中的性别信息,因此咱们可使用正确的代词。而当咱们看到一个新的主语(输入),咱们会去遗忘以前的性别信息。咱们使用下图中的公式计算咱们的“遗忘系数”
下一步就是决定咱们要在cell state中保留什么信息。这包括两个部分。首先,一个被称为“输入门层”的sigmoid层会决定咱们要更新的数值。而后一个tanh层生成一个新的候选数值
,它会被增长到cell state中。在下一步中,咱们将会组合这两步去生成一个新的更新状态值。
在那个语言模型例子中,咱们想给cell state增长主语的性别,来替换咱们将要遗忘的旧的主语。如今是时候去更新旧的神经元状态
到新的神经元状态
。以前咱们已经决定了要作什么,下一步咱们就去作。
咱们给旧的状态乘一个遗忘系数 ,来遗忘掉咱们以前决定要遗忘的信息,而后咱们增长 。这是新的候选值,由咱们想多大程度更新每一个状态的值决定。
在语言模型中,就像上面描述的,这是咱们实际上要丢弃以前主语的性别信息,增长新的主语的性别信息的地方。最后,咱们须要决定咱们要输出什么。这个输出是创建在咱们的cell state的基础上,可是这里会有一个滤波器。首先,咱们使用sigmoid层决定哪一部分的神经元状态须要被输出;而后咱们让cell state通过tanh(让输出值变成-1到1之间)层而且乘上sigmod门限的输出,这样咱们就只输出咱们想要输出的。
对于那个语言模型的例子,当咱们看到一个新的主语的时候,或许咱们想输出相关动词的信息,由于动词是跟在主语后面的。例如,它或许要输出主语是单数仍是复数的,而后咱们就知道主语后动词的语态了。
LSTM的一些变体
上面讲的都是一些常规的LSTM,但并非全部的LSTM都是上面这种形式。实际上如今不少包含LSTM的论文都有小的差别,可是它值得一提。
Gers & Schmidhuber (2000)引入了一个流行的LSTM变体,它增长了一个窥视孔链接。这意味着咱们让门限层监视cell state的状态。上图中给每个门限都增长了窥视孔,可是有些论文只是给一部分的门限增长窥视孔,并非所有都加上。
另一个变体是使用组合遗忘和输入门,而不是分开决定哪些神经元须要遗忘信息,哪些须要增长新的信息,咱们组合起来决定。咱们只遗忘那些须要被放入新信息的状态,一样咱们旨在旧信息被遗忘以后才输入新的信息。一个更神奇的LSTM变体是门递归单元(也就是你们常说的GRU),它组合遗忘门和输入门为一个更新门,它合并了cell state和隐层状态,而且作了一些其余的改变。最终这个模型比标准的LSTM更简单,而且变得愈来愈流行。
这里只介绍了几个最有名的LSTM的变体,还有更多变体没有介绍,就像Yao, et al.(2015)深度门递归神经网络(Depth Gated RNNs)。这里也有一些处理长期依赖问题问题的彻底不一样的方法,就像Koutnik, et al(2014)提出的时钟机递归神经网络(Clockwork RNNs)。
结论
咱们一开始提到人们使用RNN取得了卓越的成果,但其实本质上都是使用LSTM取得的,他们的确在多数任务上表现得更好。
写下来一系列等式之后,LSTM看起来挺吓人,但在文中一步步解释后它变得能够理解了。咱们不由想问:是否有比LSTM更好的模型?学者一致认为:那就是attention注意力机制。核心观点就是让RNN每一步都监视一个更大的信息集合并从中挑选信息。例如:若是你使用RNN去为一个图像生成注释,它会从图像中挑选一部分去预测输出的单词。接下来在讲解attention以前,咱们会先聊聊Seq2Seq。
Seq2Seq
我将会结合一个机器翻译的例子来给你们形象地介绍Seq2Seq。在这个例子中,咱们试图将英语转换为德语,这里要注意这里是一个多对多的模型,并且输入和输出的长度都不固定。
准备数据
由于只是作一个例子,因此咱们在www.manythings.org/anki/这个网站选一个小规模的数据来训练一个简单的Seq2Seq便可,咱们能够看到左边是英语句子,右边则是翻译的德语句子。
咱们先进行一下预处理,好比把大写字母变成小写,把标点符号去掉等等。
预处理完以后咱们要作tokenization,即把一句话分红不少个单词或者字符,这里要注意作tokenization的时候要用两个tokenization,英语用一个,德语用一个;tokenization以后要创建两个字典,一个英语字典,一个德语字典,后面会解释我为何要这么作。
tokenization能够是char-level,也能够是word-level,顾名思义前者就是会把一句话分为一个个字符,然后者则会把一句话分红一个个单词,为了简单方便,咱们使用char-level来讲明。
通过tokenization以后一句话变成了一个list,每一个元素都是一个字符,但实际中通常都使用word-level,由于他们的数据集足够大,这在以后会解释。
咱们前面说了tokenization要用两个不一样的字典,这是由于不一样的语言它的字母表不一样,没法进行统一的映射,如上图所示。
若是你使用word-level,那就更有必要使用两个不一样的字典,好比不少德语单词在英语字典中是找不到的,并且不一样语言分词方便也是不同的。
左边是英语字典,包括26个字母和一个空格符,德语字典删去了一些不经常使用字母后再加入空格符,另外能够发现德语字典多了一个起始符和一个终止符,这里用什么都行,只要别跟字典字符冲突就能够,后面你们就知道这两个符号的做用。
tokenization结束以后每句话就变成了一个字符字典,而后原字符通过字典映射后就变成了下面这个序列,对于德语也是同样。
接下来咱们还能够把这些数字变成One-hot向量表示,黑色表示1,白色表示0。通过One-hot每一个字符就变成了一个向量,每句话就变成了一个矩阵,这就是咱们的输入,如今数组准备好了,咱们来搭建咱们的Seq2Seq模型。
搭建并训练Seq2Seq模型
Seq2Seq有一个编码器和一个解码器,编码器通常是LSTM或者其余模型用于提取特征,它的最后一个输出就是从这句话得出的最后的特征,而其余的隐层输出都被丢弃。编码器提取特征以后就到了解码器,解码器靠编码器最后输出的特征也就是
来知道这句话是"go away",这里要强调一下Decoder的初始状态就是Encoder的最后一个状态,如今Decoder开始输出德语字母,这里Decoder也是一个LSTM模型,他每次接受一个输入而后输出下一个字母的几率,第一个输入必须是起始符,这就是咱们为何要在德语字典中要加入起始符的缘由。Decoder会输出一个几率分布p向量,起始符后面的第一个字母是m,咱们将m作一个one-hot编码做为y标签,用标签y和预测p作一个CrossEntropy来做为咱们的损失函数优化,梯度从Decoder传回Encoder。
而后输入是两个字符,起始符和m,下一个字母是a,咱们将a作one-hot编码做为y标签,将它与咱们输出的几率分布作一个CrossEntropy来做为损失函数,一直进行这个循环,应该就很好理解了。
最后一轮将整句德语做为输入,将中止符作标签y,再进行CrossEntropy,拿全部的英语和德语来训练咱们的编码器和解码器,这就是咱们的训练过程了。
总结一下,咱们使用英语句子的one-hot矩阵做为encoder的输入,encoder网络由LSTM组成来提取特征,它的输出是最后一个状态
和传送带
,decoder网络的初始状态是
,decoder网络的输入是德语句子,decoder输出当前状态
`,而后全链接层输出下一个字符的预测,这样咱们的训练阶段就结束了。
预测阶段
一样,咱们先把句子输入到咱们的Encoder里面,Encoder会输入最后状态
,做为这句话的特征送给Decoder。
做为Decoder的初始状态,这样解码器就知道这句话是go away,首先把起始符输入,有了新的状态解码器就会把状态更新为
而且预测下一个字符,decoder输出的是每一个字符的几率值,咱们能够根据这个几率值进行预测,好比咱们能够选取几率值最大的字符,也能够对几率进行随机抽样,我可能会获得字符m,因而我把m记录下来。
如今状态是
,把新生成的字符m做为LSTM的输入,接下来再更新状态为
,而且输出一个几率分布,根据几率分布抽样咱们获得字符a,记录下字符a,并一直进行这个循环。
运行14轮了状态是
,再结合上一轮生成的字符e,根据decoder输出的几率分布抽样,咱们抽到了终止符,一旦抽到了终止符,就终止文本生成,并返回记录下的字符串,德语也就被成功翻译了。
总结
Seq2Seq模型有一个encoder网络和一个Decoder网络,在咱们的例子中encoder的输入是英语句子,每输入一个词RNN就会更新状态并记录下来,encoder最后一个状态就是这个句子的特征,并把以前的状态丢弃。把这个状态做为decoder的初始状态,初始化后decoder就知道这个句子了,首先把起始符做为decoder的输入,而后一步步更新,输出状态和几率分布预测下一个字符,再把预测的字符做为下一个输入,重复这个过程,最后直到预测终止符就返回输出的这个序列。
如何提高?
咱们的encoder和decoder都是LSTM,encoder把全部句子的特征压缩到最后一个状态,理想状况下encoder最后一个状态包含完整的信息,假如句子很长,那么句子有些信息就会被遗忘,那么Decoder就没有完整的句子信息,那decoder输出的德语句子就不完整。一种简单方法就是使用双向LSTM,双向LSTM简单来讲就是用两条链,从左到右这条链可能会遗忘最左边的信息,而从右往左的这条链可能会遗忘右边的信息,这样结合起来就不容易遗忘句子信息,这里要注意只是encoder用双向LSTM,decoder是单向LSTM,他要生成正确顺序的序列。
此次咱们用的是char-level比较方便,可是最好仍是使用word-level,由于用单词代替字母,序列就会短大概4.5倍,就不容易遗忘,可是用word-level须要大的数据集,获得的单词大概就是一万,one-hot以后向量的维度也就是一万,太大了,须要embedding进行降维,由于embedding参数不少,因此若是数据集不够很容易过拟合。
另一种方法改进就是multi-Task learning,咱们还能够多加入几个任务,好比让英语句子让他本身翻译成英语句子,这样encoder只有一个可是数据多了一倍,这样encoder就能被训练的更好,固然你还能够添加其余语言的任务,经过借助其余语言更好训练encoder,这样虽然decoder没有变得更好,可是由于encoder提取的更好最后效果也会变好。
固然还有一个方法就是使用注意力机制,这个对机器翻译提升做用很大,咱们接下来就讲解这个注意力机制。
注意力机制
咱们知道Seq2Seq模型有一个缺点就是句子太长的话encoder会遗忘,那么decoder接受到的句子特征也就不彻底,咱们看一下下面这个图,纵轴BLUE是机器翻译的指标,横轴是句子的单词量,咱们能够看出用了attention以后模型的性能大大提高。用了注意力机制,Decoder每次更新状态的时候都会再看一遍encoder全部状态,还会告诉decoder要更关注哪部分,这也是attention名字的由来。可是缺点就是计算量很大。
attention原理
在encoder结束以后,attention和decoder同时工做,回忆一下,decoder的初始状态
是encoder最后一个状态,不一样于常规的Seq2Seq,encoder全部状态都要保留,这里须要计算
与每一个状态的相关性,我使用
这个公式表示计算二者相关性,把结果即为
,记作Weight,encoder有m个状态,因此一共有m个
,这里全部的值都是介于0和1的实数,所有加起来为1。下面看一下怎么计算这个类似性。第一种方法是把
和
作concat获得更高的向量,而后求矩阵W与这个向量的乘积,获得一个向量,而后再将tanh做用于向量每个元素,将他压到-1和1之间,最后计算向量V与刚才计算出来的向量的内积,这里的向量V和矩阵W都是参数,须要从训练数据里学习,算出m个
后,须要对他们作一个softmax变换,把输出结果记作
到
,由于是softmax输出,因此他们都大于0相加为1,这是第一篇attention论文提出计算的方法,日后有不少其余计算的方法,咱们来介绍一种更经常使用的方法。
输入仍是
和
,第一步是分别使用两个参数矩阵
,
作线性变换,获得
和
这两个向量,这两个参数矩阵要从训练数据中学习。第二步是计算
与
的内积,因为有m个K向量,因此获得L个
。第三步就是对这些值作一个softmax变换,
到
,由于是softmax输出,因此他们都大于0相加为1。这种计算方法被Transformer模型采用,Transformer模型是当前不少nlp问题采用的先进模型。
刚才讲了两种方法来计算
和
的相关性,如今咱们获得了m个相关性
,每一个
对应每一个状态
,有了这些权重
咱们能够对m个状态计算加权平均,获得一个Context vector
。每个Context vector都会对应一个decoder状态
接下来咱们来看一下decoder是怎么计算新的状态的。咱们来回顾一下,假如不用attention,咱们是这样更新状态的,新的状态
是旧状态
与新输入
`的函数,看一下下图左边的公式,将二者作concat,而后乘上权重矩阵加上偏置b,最后经过tanh就是咱们的新状态,也就是说状态的更新仅仅是根据上一个状态,并不会看encoder的状态。用attention的话更新状态还要用到咱们计算出的Context vector
,把三个参数一块儿作concat后更新。
回忆一下,
是全部encoder状态
的加权平均,因此
知道输入
到
的完整信息,decoder新的状态
依赖于
,这样RNN遗忘的问题就解决了。下一步则是计算context vector
,跟以前同样,先计算权重
,这里是计算
跟以前encoder全部状态的相关性,获得了m个
,注意一下这里的权重也是要更新的,上一轮算的是跟
的相关性如今算的是跟
的相关性,这样就能够经过加权平均计算出新的
。
Decoder接受新的输入
,仍是用那个公式计算出新状态,而后一直循环下去直到结束。
咱们知道在这个过程当中咱们会计算出不少权重
,咱们思考一下咱们究竟计算了多少个
?想要计算出一个context vector
,咱们要计算出m个类似性权重
,因此每轮更新都须要计算m个权重,假如一共有t个state,那么一共就要计算m×t个权重,也就是encoder和decoder数量的乘积。attention为了避免遗忘,代价就是高数量级的计算。
权重 的实际意义
这张图下面是encoder,上面是decoder,attention会把decoder全部状态与encoder全部状态计算类似性,也就是
.在这张图中每条线就对应一个
,线越粗说明相关性越高。好比下面,法语中的zone就是英语的Area,因此二者的线就很粗。
总结
此次仅仅是从机器翻译的角度介绍了attention的一个应用,attention在业内仍是有不少应用的,好比self-attention,Transformer应用,但愿以此为印子可以打开读者attention的大门,以为有用的读者能够点波关注+右下角的在看。
参考
1.colah's bloghttp://colah.github.io/posts/2015-08-Understanding-LSTMs/
2.CS583 https://github.com/wangshusen/DeepLearning
本文分享自微信公众号 - 计算机视觉漫谈()。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。