Recurrent Neural Network Model

符号表示

L5W1-RNN - 图1:输入序列中各数据的位置索引
L5W1-RNN - 图2:输出序列中各数据的位置索引
L5W1-RNN - 图3%20%3C%20t%3E%7D#card=math&code=y%5E%7B%5Cleft%28%20i%20%5Cright%29%20%3C%20t%3E%7D&id=xKveb):第L5W1-RNN - 图4个训练样本中第L5W1-RNN - 图5个元素L5W1-RNN - 图6:输出序列的长度
L5W1-RNN - 图7%7D#card=math&code=T%7Bx%7D%5E%7B%28i%29%7D&id=m3Mwr):第L5W1-RNN - 图8个训练样本的输入序列长度
![](https://g.yuque.com/gr/latex?T
%7By%7D%5E%7B(i)%7D#card=math&code=T_%7By%7D%5E%7B%28i%29%7D&id=qRbTL):第L5W1-RNN - 图9个训练样本的输出序列长度

怎样表示一个序列里的每一个单词?以名字识别任务为例:
首先建立词典表,按字母顺序放在一个列表里。
接下来可以用one-hot表示法来表示词典里的每个单词。
L5W1-RNN - 图10
如果遇到了一个不在词表中的单词,用<UNK>作为标记,也就是Unknow Word

循环神经网络模型

如果使用标准神经网络,有这样2个问题:

  1. 不是每一个样本都有相同的输入或输出。使用最大长度和pad(填充法)不是一个好的表达方式。
  2. 不共享从文本的不同位置上学到的特征。比如神经网络学到位置1的Harry是人名的一部分,那么如果Harry出现在其他位置,它也应该能识别其为人名。

RNN 前向传播

那么循环神经网络是怎么做的呢?在每一个时间步中,循环神经网络传递激活值到下一个时间步中用于计算。即时间步t的激活值会传递到时间步t+1。同时每个时间步的参数是共享的。

在命名实体识别的任务中,输入一句话,识别哪些词是人名。在 RNN 里,就是依次对每一个词进行判断,是人名则输出1,否则输出0。循环神经网络做的是,当它读到句中的第 L5W1-RNN - 图11 个单词时,假设是L5W1-RNN - 图12,它不是仅用L5W1-RNN - 图13就预测出L5W1-RNN - 图14,他也会输入一些来自时间步 L5W1-RNN - 图15 的信息。具体而言,时间步 L5W1-RNN - 图16 的激活值会传递到时间步L5W1-RNN - 图17

L5W1-RNN - 图18

我们用L5W1-RNN - 图19来表示管理着从L5W1-RNN - 图20到隐藏层的连接的一系列参数,每个时间步使用的都是相同的参数L5W1-RNN - 图21。第二个下标L5W1-RNN - 图22意味着L5W1-RNN - 图23要乘以某个L5W1-RNN - 图24类型的量,然下标L5W1-RNN - 图25表示它是用来计算某个L5W1-RNN - 图26类型的变量。

激活值(也就是水平联系)是由参数L5W1-RNN - 图27决定的,同时每一个时间步都使用相同的参数L5W1-RNN - 图28
输出结果由L5W1-RNN - 图29决定。同样的,可以看出这里的L5W1-RNN - 图30乘上了某个L5W1-RNN - 图31类型的量,用来计算出某个L5W1-RNN - 图32类型的量。

RNN 的缺点在于它只使用了之前的信息,但没有用到后面的信息。比如给定句子“Teddy Roosevelt was a great President.”,为了判断 Teddy 是否是人名的一部分,仅仅知道句中前两个词是完全不够的,还需要知道句中后部分的信息,这也是十分有用的,因为句子也可能是这样的,“Teddy bears are on sale!”。(后面介绍的双向循环神经网络 BRNN 可以解决这个问题。)

RNN 中的计算过程

L5W1-RNN - 图33

要开始整个流程,在零时刻需要构造一个激活值L5W1-RNN - 图34,这通常是零向量。 也可以用其他方法随机初始化L5W1-RNN - 图35。接着就是前向传播过程,先计算激活值L5W1-RNN - 图36,然后再计算L5W1-RNN - 图37

L5W1-RNN - 图38#card=math&code=a%5E%7B%3C1%3E%7D%20%3D%20g%7B1%7D%28W%7B%7Baa%7D%7Da%5E%7B%3C%200%20%3E%7D%20%2B%20W%7B%7Bax%7D%7Dx%5E%7B%3C%201%20%3E%7D%20%2B%20b%7Ba%7D%29&id=hb8dq)
L5W1-RNN - 图39#card=math&code=%5Chat%20y%5E%7B%3C%201%20%3E%7D%20%3D%20g%7B2%7D%28W%7B%7Bya%7D%7Da%5E%7B%3C%201%20%3E%7D%20%2B%20b_%7By%7D%29&id=h5x1C)

循环神经网络用的激活函数经常是tanh,不过有时候也会用ReLU。选用哪个激活函数是取决于你的输出L5W1-RNN - 图40,如果它是一个二分问题,那么我猜你会用sigmoid函数作为激活函数,如果是L5W1-RNN - 图41类别分类问题的话,那么可以选用softmax作为激活函数。

对于命名实体识别来说L5W1-RNN - 图42只可能是0或者1,那这里第二个激活函数 L5W1-RNN - 图43 可以是sigmoid激活函数。

更一般的情况下,在L5W1-RNN - 图44时刻,
L5W1-RNN - 图45#card=math&code=a%5E%7B%3C%20t%20%3E%7D%20%3D%20g%7B1%7D%28W%7Baa%7Da%5E%7B%3C%20t%20-%201%20%3E%7D%20%2B%20W%7Bax%7Dx%5E%7B%3C%20t%20%3E%7D%20%2B%20b%7Ba%7D%29&id=L4EuE)
L5W1-RNN - 图46#card=math&code=%5Chat%20y%5E%7B%3C%20t%20%3E%7D%20%3D%20g%7B2%7D%28W%7B%7Bya%7D%7Da%5E%7B%3C%20t%20%3E%7D%20%2B%20b_%7By%7D%29&id=oGV4h)

为了简化这些符号,用下面这种形式来表示:
L5W1-RNN - 图47%20%3Dg(W%7Ba%7D%5Cleft%5Clbrack%20a%5E%7B%3C%20t-1%20%3E%7D%2Cx%5E%7B%7D%20%5Cright%5Crbrack%20%2Bb%7Ba%7D)#card=math&code=a%5E%7B%3C%20t%20%3E%7D%20%3D%20g%28W%7Baa%7Da%5E%7B%3C%20t%20-%201%20%3E%7D%20%2B%20W%7Bax%7Dx%5E%7B%3C%20t%20%3E%7D%20%2B%20b%7Ba%7D%29%20%3Dg%28W%7Ba%7D%5Cleft%5Clbrack%20a%5E%7B%3C%20t-1%20%3E%7D%2Cx%5E%7B%7D%20%5Cright%5Crbrack%20%2Bb%7Ba%7D%29&id=Lqp38)
![](https://g.yuque.com/gr/latex?%5Chat%20y%5E%7B%3C%20t%20%3E%7D%20%3D%20g(W
%7B%7Bya%7D%7Da%5E%7B%3C%20t%20%3E%7D%20%2B%20b%7By%7D)%3D%20g(W%7By%7Da%5E%7B%3C%20t%20%3E%7D%20%2Bb%7By%7D)#card=math&code=%5Chat%20y%5E%7B%3C%20t%20%3E%7D%20%3D%20g%28W%7B%7Bya%7D%7Da%5E%7B%3C%20t%20%3E%7D%20%2B%20b%7By%7D%29%3D%20g%28W%7By%7Da%5E%7B%3C%20t%20%3E%7D%20%2Bb%7By%7D%29&id=pPyUh)
其中,![](https://g.yuque.com/gr/latex?%5B%20%7B%7BW%7D
%7Baa%7D%7D%5Cvdots%20%7B%7BW%7D%7Bax%7D%7D%5D%3DW%7Ba%7D#card=math&code=%5B%20%7B%7BW%7D%7Baa%7D%7D%5Cvdots%20%7B%7BW%7D%7Bax%7D%7D%5D%3DW%7Ba%7D&id=O6lpa),表示把 ![](https://g.yuque.com/gr/latex?%7B%7BW%7D%7Baa%7D%7D#card=math&code=%7B%7BW%7D%7Baa%7D%7D&id=D8hPQ) 和 ![](https://g.yuque.com/gr/latex?%7B%7BW%7D%7Bax%7D%7D#card=math&code=%7B%7BW%7D%7Bax%7D%7D&id=klgV1) 横向拼接,L5W1-RNN - 图48表示将这两个向量堆在一起(竖向拼接)。这种记法的好处是我们可以不使用两个参数矩阵![](https://g.yuque.com/gr/latex?W%7B%7Baa%7D%7D#card=math&code=W%7B%7Baa%7D%7D&id=rnzEs)和![](https://g.yuque.com/gr/latex?W%7B%7Bax%7D%7D#card=math&code=W%7B%7Bax%7D%7D&id=Xgyhm),而是将其压缩成一个参数矩阵![](https://g.yuque.com/gr/latex?W%7Ba%7D#card=math&code=W_%7Ba%7D&id=TrJVQ)。当我们建立更复杂模型时这就能够简化我们要用到的符号。

RNN 前向传播示意图:

L5W1-RNN - 图49

RNN 反向传播

L5W1-RNN - 图50

为了计算反向传播,你需要先定义一个元素损失函数

L5W1-RNN - 图51%20%3D%20-%20y%5E%7B%3Ct%3E%7D%5Clog%5Chat%20%20y%5E%7B%3Ct%3E%7D-(%201-%20y%5E%7B%3Ct%3E%7D)log(1-%5Chat%20y%5E%7B%3Ct%3E%7D)#card=math&code=L%5E%7B%3Ct%3E%7D%28%20%5Chat%20y%5E%7B%3Ct%3E%7D%2Cy%5E%7B%3Ct%3E%7D%29%20%3D%20-%20y%5E%7B%3Ct%3E%7D%5Clog%5Chat%20%20y%5E%7B%3Ct%3E%7D-%28%201-%20y%5E%7B%3Ct%3E%7D%29log%281-%5Chat%20y%5E%7B%3Ct%3E%7D%29&id=BKbOt)

它对应的是序列中一个具体的词,如果它是某个人的名字,那么L5W1-RNN - 图52的值就是1,然后神经网络将输出这个词是名字的概率值,比如0.1。我将它定义为标准逻辑回归损失函数,也叫交叉熵损失函数(Cross Entropy Loss),它和之前我们在二分类问题中看到的公式很像。

所以这是关于单个位置上或者说某个时间步L5W1-RNN - 图53上某个单词的预测值的损失函数。

现在我们来定义整个序列的损失函数,将L5W1-RNN - 图54定义为
L5W1-RNN - 图55%20%3D%20%5C%20%5Csum%7Bt%20%3D%201%7D%5E%7BT%7Bx%7D%7D%7BL%5E%7B%3C%20t%20%3E%7D(%5Chat%20%20y%5E%7B%3C%20t%20%3E%7D%2Cy%5E%7B%3C%20t%20%3E%7D)%7D#card=math&code=L%28%5Chat%20y%2Cy%29%20%3D%20%5C%20%5Csum%7Bt%20%3D%201%7D%5E%7BT%7Bx%7D%7D%7BL%5E%7B%3C%20t%20%3E%7D%28%5Chat%20%20y%5E%7B%3C%20t%20%3E%7D%2Cy%5E%7B%3C%20t%20%3E%7D%29%7D&id=jE1mv)
计算出每个时间步的损失后求和,即为总体损失函数L5W1-RNN - 图56

反向传播算法需要做的就是把前向传播的箭头都反过来,然后你就可以通过导数相关的参数,用梯度下降法来更新参数。
L5W1-RNN - 图57

在这个反向传播的过程中,最重要的信息传递或者说最重要的递归运算就是这个从右到左的运算,因此这个算法有一个很别致的名字,叫做“穿越时间反向传播(backpropagation through time)”。在前向传播从左到右的计算过程中,时刻L5W1-RNN - 图58不断增加,而对于反向传播从右到左进行计算,就像时间倒流。“通过时间反向传播”,就像穿越时光,这种说法听起来就像是你需要一台时光机来实现这个算法一样。

RNN 反向传播示意图:
L5W1-RNN - 图59

到目前为止,你见到了 RNN 中一个主要的例子,其输入序列的长度和输出序列的长度是一样的。在下节课将展示更多的 RNN 架构。

不同类型的 RNN

在上面L5W1-RNN - 图60的命名实体识别例子中,用了一串圆圈表示神经元,大部分时候为了让符号更加简单,此处就以简单的小圈表示。这个就叫做“多对多”(many-to-many)的结构,因为输入序列有很多的输入,而输出序列也有很多输出。

举例情感分类:这里L5W1-RNN - 图61可能是一段文本,比如一个电影的评论,“These is nothing to like in this movie.”(“这部电影没什么好看的。”),所以L5W1-RNN - 图62就是一个序列,而L5W1-RNN - 图63可能是从1到5的一个数字,或者是0或1,这代表正面评价和负面评价,而数字1到5代表电影是1星,2星,3星,4星还是5星。
我们不再在每个时间上都有输出了,而是让这个 RNN 网络读入整个句子,在最后一个时间上得到输出。所以这个神经网络叫做“多对一”(many-to-one)结构,因为它有很多的单词输入,然后输出一个数字。

为了完整性,还要补充一个“一对一”(one-to-one)的结构,这个可能没有那么重要,它就是一个小型的标准的神经网络,输入L5W1-RNN - 图64然后得到输出L5W1-RNN - 图65

L5W1-RNN - 图66

当然也有“一对多”(one-to-many)的结构。比如音乐生成,目标是输出一些音符。对应于一段音乐,输入L5W1-RNN - 图67可以是一个整数,表示你想要的音乐类型或者是你想要的音乐的第一个音符,并且如果你什么都不想输入,L5W1-RNN - 图68可以是空的输入,可设为0向量。

L5W1-RNN - 图69

对于“多对多”的结构还有输入和输出长度不同的情况,比如机器翻译。

L5W1-RNN - 图70

RNN 结构总结
一对一:当去掉L5W1-RNN - 图71时它就是一种标准类型的神经网络。
一对多:音乐生成或者序列生成。
多对一:情感分类
多对多:L5W1-RNN - 图72 命名实体识别。L5W1-RNN - 图73机器翻译。

L5W1-RNN - 图74

语言模型和序列生成

Language model and sequence generation

语言模型,它会告诉你某个句子它出现的概率是多少,它要能正确输出最接近的句子。语言模型做的最基本工作就是,输入一个句子,准确地说是一个文本序列,L5W1-RNN - 图75L5W1-RNN - 图76一直到L5W1-RNN - 图77,然后估计出句子序列中各个单词出现的可能性。

举个例子,一个语音识别模型,它判断一句话的概率是L5W1-RNN - 图78%20%3D%203.2%20%5Ctimes%2010%5E%7B-13%7D#card=math&code=P%28%20%5Ctext%7BThe%20apple%20%20and%20%20pair%20%20salad%7D%29%20%3D%203.2%20%5Ctimes%2010%5E%7B-13%7D&id=TbUoW),而判断是这句话的概率是L5W1-RNN - 图79%20%3D%205.7%20%5Ctimes%2010%5E%7B-10%7D#card=math&code=P%5Cleft%28%5Ctext%7BThe%20apple%20%20and%20%20pear%20salad%7D%20%5Cright%29%20%3D%205.7%20%5Ctimes%2010%5E%7B-10%7D&id=KTHtV),第二句话的概率比第一句高出1000倍以上,这就是为什么语音识别系统能够在这两句话中作出选择。

那么如何建立一个语言模型呢?

为了使用 RNN 建立出这样的模型,你首先需要一个训练集,包含一个很大的文本语料库(corpus)。语料库是自然语言处理的一个专有名词,意思就是很长的或者说数量众多的句子组成的文本。
对于训练集中的文本,你要做的第一件事就是将这个句子标记化(tokenize),也就是建立一个字典,将每个单词都转换成对应的one-hot向量,也就是字典中的索引。

还要定义句子的结尾,一般的做法就是增加一个额外的标记 < EOS>。如果你想把句号或者其他符号也当作标志,那么你可以将句号也加入你的字典中。

L5W1-RNN - 图80

于是在本例中我们,如果你加了EOS标记,这句话就会有9个输入,有L5W1-RNN - 图81L5W1-RNN - 图82一直到L5W1-RNN - 图83。在本例中,我们忽略了标点符号。如果有一些词并不在你的字典里,你可以把这个词替换成UNK,这是未知词的标志。

完成标识化,这意味着输入的句子都映射到了各个标志上。

下一步我们要构建一个 RNN 来构建这些序列的概率模型。我们继续使用“Cats average 15 hours of sleep a day.”这个句子来作为我们的运行样例。

L5W1-RNN - 图84

在第0个时间步,你要计算激活项L5W1-RNN - 图85,它是以L5W1-RNN - 图86作为输入的函数,而L5W1-RNN - 图87被设为0向量。L5W1-RNN - 图88按照惯例也设为0向量。于是L5W1-RNN - 图89通过softmax层来预测字典中的任意单词会是第一个词的概率,L5W1-RNN - 图90的输出就是softmax的计算结果,它只是预测第一个词的概率,而不去管结果是什么。假设字典中有10,000个词,加上结尾标志和未知标志,共10,002个结果,所以softmax层输出10,002种可能的概率值。

然后 RNN 进入下个时间步,仍然使用激活项L5W1-RNN - 图91。现在我们传给它正确的第一个词Cats,也就是L5W1-RNN - 图92,告诉它第一个词就是Cats,这就是为什么L5W1-RNN - 图93。在第二个时间步中,输出结果同样经过softmax层进行预测, RNN 的职责就是预测这些词的概率,它只会考虑之前得到的词。

再进行 RNN 的下个时间步,现在要计算L5W1-RNN - 图94。为了预测第三个词,我们告诉它Cats average是句子的前两个词,所以这是下一个输入,L5W1-RNN - 图95

这句话一共有8个单词。一直到最后,把L5W1-RNN - 图96也就是L5W1-RNN - 图97传进去,输出L5W1-RNN - 图98。在这一步中,通过前面这些得到的单词,不管它们是什么,我们希望预测出是EOS标志的概率最高。

所以 RNN 中的每一步都会考虑前面得到的单词,这就是 RNN 如何学习从左往右地每次预测一个词。预测出每个位置上词的概率,把这8个概率相乘,最后得到这个含8个词的整个句子的概率。

接下来为了训练这个网络,我们要定义代价函数。于是,在某个时间步L5W1-RNN - 图99,如果真正的词是L5W1-RNN - 图100,而神经网络的softmax层预测结果值是L5W1-RNN - 图101,那么就是softmax损失函数,L5W1-RNN - 图102%20%3D%20-%20%5Csum%7Bi%7D%5E%7B%7D%7By%7Bi%7D%5E%7B%3Ct%3E%7D%5Clog%5Chat%20y%7Bi%7D%5E%7B%3Ct%3E%7D%7D#card=math&code=L%5Cleft%28%20%5Chat%20y%5E%7B%3Ct%3E%7D%2Cy%5E%7B%3Ct%3E%7D%3E%5Cright%29%20%3D%20-%20%5Csum%7Bi%7D%5E%7B%7D%7By%7Bi%7D%5E%7B%3Ct%3E%7D%5Clog%5Chat%20y%7Bi%7D%5E%7B%3Ct%3E%7D%7D&id=mwawJ)。而总体损失函数L5W1-RNN - 图103%7D#card=math&code=L%20%3D%20%5Csum_%7Bt%7D%5E%7B%7D%7BL%5E%7B%3C%20t%20%3E%7D%5Cleft%28%20%5Chat%20y%5E%7B%3Ct%3E%7D%2Cy%5E%7B%3Ct%3E%7D%20%5Cright%29%7D&id=yjZ32),也就是把所有单个预测的损失函数都相加起来。

对新序列采样

Sampling novel sequences

在你训练一个序列模型之后,要想了解到这个模型学到了什么,一种非正式的方法就是进行一次新序列采样,来看看到底应该怎么做。

记住,一个序列模型模拟了任意特定单词序列的概率,我们要做的就是对这些概率分布进行采样,来生成一个新的单词序列。

第一步,对模型生成的第一个词进行采样,于是你输入L5W1-RNN - 图104L5W1-RNN - 图105,现在你的第一个时间步得到的是经过softmax层后的所有单词的概率,然后根据向量中这些概率的分布进行随机采用,例如numpy命令np.random.choice,得到第一个词。
第二个时间步,L5W1-RNN - 图106作为输入,然后softmax层就会预测L5W1-RNN - 图107是什么。无论你得到什么one-hot码表示的选择结果,都把它传递到下一个时间步,一直这样直到结束。

那么你要怎样知道一个句子结束了呢?方法之一就是,一直进行采样直到得到EOS标识。另一种情况是,一直采样到所设定的时间步。
过程有时候会产生一些未知标识,表示如果你的字典中没有这个词,如果你不想得到未知标识,你可以拒绝采样过程中产生任何未知的标识,一旦出现就继续在剩下的词中进行重采样,直到得到一个不是未知标识的词。如果你不介意有未知标识产生的话,你也可以完全不管它们。

这就是你如何从你的 RNN 语言模型中生成一个随机选择的句子。直到现在我们所建立的是基于词汇的 RNN 模型,意思就是字典中的词都是英语单词。根据你实际的应用,你还可以构建一个基于字符的 RNN 结构,在这种情况下,你的字典仅包含从a到z的字母(或许会再加上大写的字母),可能还会有空格符和数字0到9。

如果你建立一个基于字符的语言模型,对于前面的例子来说,“Cats average 15 hours of sleep a day.”,在该例中C就是L5W1-RNN - 图108,a就是L5W1-RNN - 图109t就是L5W1-RNN - 图110,空格符就是L5W1-RNN - 图111等等。

使用基于字符的语言模型,优点是你不必担心会出现未知的标识,但主要缺点就是你最后会得到太多太长的序列,大多数英语句子只有10到20个的单词,但却可能包含很多很多字符。所以基于字符的语言模型在捕捉句子中的依赖关系——也就是句子较前部分如何影响较后部分——不如基于词汇的语言模型那样可以捕捉长范围的关系,并且基于字符的语言模型训练起来计算成本比较高昂。所以基于字符的语言模型没有得到广泛地使用,除了一些比较专门需要处理大量未知的文本或者未知词汇的应用,还有一些要面对很多专有词汇的应用。

L5W1-RNN - 图112

这里有一些样本,它们是从一个基于字符的语言模型中采样得到的。如果模型是用新闻文章训练的,它就会生成左边这样的文本,这有点像一篇不太合乎语法的新闻文本,不过听起来,这句“Concussion epidemic”,to be examined,确实有点像新闻报道。用莎士比亚的文章训练后生成了右边这篇东西,听起来很像是莎士比亚写的东西。

RNN的梯度消失

long-term dependence
“The cat, which already ate ……, was full.”,cat是单数,所以应该用was
“The cats, which ate ……, were full.” , cats是复数,所以用were
这个例子中,最前面的单词对句子后面的单词有影响。但是我们目前学到的基本的 RNN 模型,不擅长捕获这种长期依赖效应(long-term dependence)。

因为当网络很深的时候,梯度很难传播回去、影响靠前层的权重。 RNN 也有同样的梯度消失问题,后面层的输出误差很难影响前面层的计算。所以很难让一个神经网络能够意识到它要记住看到的是单数名词还是复数名词,然后在序列后面生成依赖单复数形式的 was 或者 were。

也正是这个原因,基本的 RNN 模型会有很多局部影响,比如输出L5W1-RNN - 图113主要受L5W1-RNN - 图114附近的值的影响,而很难受到序列靠前的输入的影响。这是基本的 RNN 算法的一个缺点,我们会在下几节处理这个问题。

我们在讲很深的神经网络时,也提到了梯度爆炸这个问题。梯度爆炸很容易发现,因为指数级大的梯度会让你的参数变得极其大(NaN),使网络计算出现了数值溢出,以至于网络参数崩溃。

如果你发现了梯度爆炸的问题,一个解决方法就是用梯度修剪。梯度修剪的意思就是观察你的梯度向量,如果它大于某个阈值就缩放梯度向量,保证它不会太大,这就是通过一些最大值来修剪的方法。

而梯度消失更难解决,我们下节会介绍GRU,门控循环单元网络,这个网络可以有效地解决梯度消失的问题,并且能够使你的神经网络捕获更长的长期依赖。

GRU

GRU单元(Gated Recurrent Unit)

[Cho et al., 2014. On the properties of neural machine translation: Encoder-decoder approaches] [Chung et al. 2014. Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling]

L5W1-RNN - 图115

就这张图而言,这就是RNN隐藏层的单元的可视化呈现。激活值L5W1-RNN - 图116将会传softmax单元(或者其他函数)用于产生输出L5W1-RNN - 图117,以及传递给下一个神经单元。

L5W1-RNN - 图118

我们将使用相似的图来讲解门控循环单元。还是以这2句话为例:

“The cat, which already ate ……, was full.”,cat是单数,所以应该用was
“The cats, which ate ……, were full.” , cats是复数,所以用were

GRU单元将会有个新的变量称为L5W1-RNN - 图119cell),即记忆细胞。对于GRUL5W1-RNN - 图120的值等于激活值L5W1-RNN - 图121。在每个时间步,我们将用一个候选值重写记忆细胞,即L5W1-RNN - 图122,它是个候选值,用tanh激活函数来计算,L5W1-RNN - 图123

L5W1-RNN - 图124

GRU中最重要的思想是门(Gate),我先把这个门叫做L5W1-RNN - 图125(是gamma的大写字母),L5W1-RNN - 图126代表更新门(update)。L5W1-RNN - 图127是一个0到1之间的值,由sigmoid函数计算得到,L5W1-RNN - 图128sigmoid函数的输出总是非常接近0或者非常接近1,直观来看,L5W1-RNN - 图129在大多数的情况下非常接近0或1。

可以这样理解,记忆细胞L5W1-RNN - 图130将被设定为0或者1,我们先假定单数设为1,复数设为0。GRU单元将会一直记住L5W1-RNN - 图131的值,直到 was/were 的位置,L5W1-RNN - 图132的值是1,这就告诉它这是单数,所以用wasL5W1-RNN - 图133的作用就是决定什么时候更新这个值。所以L5W1-RNN - 图134。上图中紫色的方块就是这个运算。L5W1-RNN - 图135代表要更新当前的值,否则不更新,也不要忘记这个值是什么。这样即使你一直处理句子到图中编号4所示,L5W1-RNN - 图136应该会一直等L5W1-RNN - 图137,于是它仍然记得猫是单数的。

L5W1-RNN - 图138

以上是简化过的GRU单元。因为sigmoid函数,门很容易取到0值,即L5W1-RNN - 图139很接近0,可能是0.000001或者更小,这就不会有梯度消失的问题了。且L5W1-RNN - 图140几乎就等于L5W1-RNN - 图141,也就是L5W1-RNN - 图142的值也很好地被维持了,即使经过很多很多的时间步。这就是缓解梯度消失问题的关键,因此允许神经网络运行在非常远的依赖词上。

现在说下实现的细节。L5W1-RNN - 图143可以是一个向量,如果你有100维的隐藏的激活值,那么L5W1-RNN - 图144L5W1-RNN - 图145L5W1-RNN - 图146还有画在框中的其他值都是同样的维度(100维),这样的话“”实际上就是元素对应的乘积(![](https://g.yuque.com/gr/latex?c%5E%7B%3Ct%3E%7D%20%3D%20%5CGamma_%7Bu%7D%7B%5Ctilde%7Bc%7D%7D%5E%7B%3Ct%3E%7D%20%2B%5Cleft(%201-%20%5CGamma%7Bu%7D%20%5Cright)*c%5E%7B%3Ct-1%3E%7D#card=math&code=c%5E%7B%3Ct%3E%7D%20%3D%20%5CGamma%7Bu%7D%2A%7B%5Ctilde%7Bc%7D%7D%5E%7B%3Ct%3E%7D%20%2B%5Cleft%28%201-%20%5CGamma%7Bu%7D%20%5Cright%29%2Ac%5E%7B%3Ct-1%3E%7D&id=FZ19r))。所以这里的![](https://g.yuque.com/gr/latex?%5CGamma%7Bu%7D#card=math&code=%5CGamma_%7Bu%7D&id=dGp5P)也是100维的向量,里面的值几乎都是0或者1,就是说这100维的记忆细胞L5W1-RNN - 图147L5W1-RNN - 图148上图编号1所示)就是你要更新的比特。元素对应的乘积做的就是告诉GRU单元哪个记忆细胞的向量维度在每个时间步要做更新。

你现在已经理解GRU最重要的思想了,现在来描述一下完整的GRU单元。

完整的GRU单元就是计算 L5W1-RNN - 图149 的式子中加上一个新的项L5W1-RNN - 图150,你可以认为L5W1-RNN - 图151代表相关性(relevance),这个L5W1-RNN - 图152门告诉你计算出的下一个L5W1-RNN - 图153的候选值L5W1-RNN - 图154L5W1-RNN - 图155有多大的相关性。计算这个门L5W1-RNN - 图156需要参数,一个新的参数矩阵L5W1-RNN - 图157L5W1-RNN - 图158#card=math&code=%5CGamma%7Br%7D%3D%20%5Csigma%28W%7Br%7D%5Cleft%5Clbrack%20c%5E%7B%3Ct-1%3E%7D%2Cx%5E%7B%3Ct%3E%7D%20%5Cright%5Crbrack%20%2B%20b_%7Br%7D%29&id=b1ft5)。

L5W1-RNN - 图159

为什么需要L5W1-RNN - 图160?这是多年来研究者们试验过很多很多不同可能的方法得出来的结论,这样的GRU被发现在很多不同的问题上也是非常健壮和实用的。

这就是GRU,即门控循环单元,这是RNN的其中之一。这个结构可以更好捕捉非常长范围的依赖,让RNN更加有效。然后我简单提一下其他常用的神经网络,比较经典的是LSTM,即长短时记忆网络,我们在下节中讲解。

LSTM

长短期记忆(LSTM(long short term memory)unit)

[Hochreiter & Schmidhuber 1997. Long short-term memory]

LSTM是一个比GRU更加强大和通用的版本。它与GRU的异同点在于:

  1. 计算记忆细胞 L5W1-RNN - 图161 不用相关门 L5W1-RNN - 图162
  2. 更新门不变,L5W1-RNN - 图163#card=math&code=%5CGamma%7Bu%7D%3D%20%5Csigma%28W%7Bu%7D%5Cleft%5Clbrack%20a%5E%7B%3Ct-1%3E%7D%2Cx%5E%7B%3Ct%3E%7D%20%5Cright%5Crbrack%20%2Bb_%7Bu%7D%29&id=sMLKG);
  3. 在记忆细胞的更新式子中,用遗忘门 L5W1-RNN - 图164 取代 L5W1-RNN - 图165L5W1-RNN - 图166#card=math&code=%5CGamma%7Bf%7D%20%3D%5Csigma%28W%7Bf%7D%5Cleft%5Clbrack%20a%5E%7B%3Ct-1%3E%7D%2Cx%5E%7B%3Ct%3E%7D%20%5Cright%5Crbrack%20%2Bb_%7Bf%7D%29&id=RgMod);
  4. 有一个新的输出门,L5W1-RNN - 图167#card=math&code=%5CGamma%7Bo%7D%20%3D%5Csigma%28W%7Bo%7D%5Cleft%5Clbrack%20a%5E%7B%3Ct-1%3E%7D%2Cx%5E%7B%3Ct%3E%7D%20%5Cright%5Crbrack%20%2B%3Eb_%7Bo%7D%29&id=dXkiE);
  5. 不再是L5W1-RNN - 图168,而是L5W1-RNN - 图169

L5W1-RNN - 图170

用图片来表现这些公式:

L5W1-RNN - 图171

简化上图,你会注意到上面这里有条线(红线),它显示了只要你正确地设置了遗忘门和更新门,LSTM是相当容易把L5W1-RNN - 图172的值一直往下传递到右边,比如L5W1-RNN - 图173。这就是为什么LSTMGRU非常擅长于长时间记忆某个值,即使经过很长很长的时间步。

L5W1-RNN - 图174

这就是LSTM。你可能会想到这里和一般使用的版本会有些不同,最常用的版本可能是门值不仅取决于L5W1-RNN - 图175L5W1-RNN - 图176,有时候也可以偷窥一下L5W1-RNN - 图177的值,这叫做“窥视孔连接”(peephole connection)。虽然不是个好听的名字,其实意思就是门值不仅取决于L5W1-RNN - 图178L5W1-RNN - 图179,也取决于上一个记忆细胞的值(L5W1-RNN - 图180),然后“偷窥孔连接”就可以结合这三个门(L5W1-RNN - 图181L5W1-RNN - 图182L5W1-RNN - 图183)来计算了。

在深度学习的历史上,LSTM是更早出现的,而GRU是最近才发明出来的,它可能源于Pavia在更加复杂的LSTM模型中做出的简化。GRU的优点是这是个更加简单的模型,所以更容易创建一个更大的网络,而且它只有两个门,在计算性上也运行得更快,然后它可以扩大模型的规模。但是LSTM更加强大和灵活,因为它有三个门而不是两个。无论是GRU还是LSTM,你都可以用它们来构建捕获更加深层连接的神经网络。

BRNN

双向循环神经网络(Bidirectional RNN

为了了解双向RNN的动机,我们先看一下之前在命名实体识别中的例子,判断单词是否为人名。

L5W1-RNN - 图184

这个网络有一个问题,在判断第三个词Teddy是不是人名的一部分时,光看句子前面部分是不够的。不管这些神经单元是标准的RNN块,还是GRU单元或者是LSTM单元,这些构件都是只有前向的。

双向RNN可以解决这个问题。为了简单,我们用一个只有4个单词的句子,这样输入只有4个,L5W1-RNN - 图185L5W1-RNN - 图186。从这里开始的这个网络会有一个前向的循环单元叫做L5W1-RNN - 图187L5W1-RNN - 图188L5W1-RNN - 图189还有L5W1-RNN - 图190,他们这样连接(下图紫色所示)。

然后增加一个反向循环层,左箭头代表反向连接,这里有L5W1-RNN - 图191L5W1-RNN - 图192L5W1-RNN - 图193L5W1-RNN - 图194。同样,我们把网络这样向上连接,这个L5W1-RNN - 图195反向连接就依次反向向前连接(图中绿色部分)。

L5W1-RNN - 图196

这样,这个网络就构成了一个无环图。给定一个输入序列L5W1-RNN - 图197L5W1-RNN - 图198,这个序列首先计算前向的而反L5W1-RNN - 图199L5W1-RNN - 图200L5W1-RNN - 图201L5W1-RNN - 图202。然后反向计算L5W1-RNN - 图203L5W1-RNN - 图204L5W1-RNN - 图205L5W1-RNN - 图206 。这些是网络激活值,都属于前向传播。把所有这些激活值都计算完了就可以计算预测结果了。

举个例子,我们现在要预测L5W1-RNN - 图207。从左到右,L5W1-RNN - 图208L5W1-RNN - 图209L5W1-RNN - 图210来的信息都会考虑在内,而从L5W1-RNN - 图211来的信息会流过反向的L5W1-RNN - 图212,到反向的L5W1-RNN - 图213再到L5W1-RNN - 图214(下图黄色部分)。这样使得时间3的预测结果不仅输入了过去的信息,还有未来的信息。

L5W1-RNN - 图215

这就是双向循环神经网络,并且这些基本单元不仅仅是标准RNN单元,也可以是GRU单元或者LSTM单元。事实上,很多的NLP问题,对于大量有自然语言处理问题的文本,有LSTM单元的双向RNN模型是用的最多的。

这个双向RNN网络模型的缺点就是你需要完整的数据的序列,你才能预测任意位置。比如说你要构建一个语音识别系统,那么双向RNN模型需要你考虑整个语音表达,但是如果直接用这个去实现的话,你需要等待这个人说完,然后获取整个语音表达才能处理这段语音,并进一步做语音识别。对于实际的语音识别的应用通常会有更加复杂的模块,而不是仅仅用我们见过的标准的双向RNN模型。但是对于很多自然语言处理的应用,如果你总是可以获取整个句子,这个标准的双向RNN算法实际上很高效。

Deep RNNs

要学习非常复杂的函数,通常我们会把RNN的多个层堆叠在一起构建更深的模型。

对于一个标准的神经网络,首先是输入L5W1-RNN - 图216,然后堆叠上隐含层,深层的RNN网络也一样,把它按时间展开就是了。

L5W1-RNN - 图217

对于像左边这样标准的神经网络,你可能见过很深的网络,甚至于100层深,而对于RNN来说,有三层就已经不少了。由于时间的维度,RNN网络会变得相当大,即使只有很少的几层。

不过还有一种情况会容易见到,就是在每一个上面堆叠循环层,把这里的输出去掉,然后换成一些深的层,这些层并不水平连接,只是一个深层的网络,然后用来预测L5W1-RNN - 图218

L5W1-RNN - 图219

通常这些神经单元没必要非是标准的RNN,也可以是GRU单元或者LSTM单元,并且,你也可以构建深层的双向RNN网络。

由于深层的RNN训练需要很多计算资源,需要很长的时间,尽管看起来没有多少循环层。

这就是深层RNN的内容,从基本的RNN网络,基本的循环单元到GRULSTM,再到双向RNN,还有深层版的模型。这节课后,你已经可以构建很不错的学习序列的模型了。