博客园 首页 私信博主 显示目录 隐藏目录 管理 凤⭐尘

吴恩达《深度学习》-第五门课 序列模型(Sequence Models)-第一周 循环序列模型(Recurrent Neural Networks) -课程笔记

第一周 循环序列模型(Recurrent Neural Networks)

1.1 为什么选择序列模型?(Why Sequence Models?)

1.2 数学符号(Notation)

这个输入数据是 9 个单词组成的序列,所以会有 9 个特征集和来表示这 9 个 单词,并按序列中的位置进行索引,用\(𝑥^{<𝑡>}\)来索引这个序列的中间位置。𝑡意味着它们是时序序列,但不论是否是时序序列, 都将用𝑡来索引序列中的位置。

输出数据也是一样,同时用\(𝑇_𝑥\)来表示输入序列的长度,这个例子中输入是 9 个单词,所以\(𝑇_𝑥\) = 9。用\(𝑇_𝑦\) 来表示输出序列的长度。

之前的符号用\(𝑥^{ (𝑖)}\)来表示第𝑖个训练样本,为了指代第𝑡个 元素,或者说是训练样本𝑖的序列中第𝑡个元素用\(𝑥^{ (𝑖)<𝑡>}\)这个符号来表示。是序列长度, 训练集里不同的训练样本会有不同的长度,所以\(𝑇_𝑥^{ (𝑖)}\)代表第𝑖个训练样本的输入 序列长度。\(𝑦^{ (𝑖)<𝑡>}\)代表第𝑖个训练样本中第𝑡个元素,\(𝑇_𝑦^{(𝑖)}\)就是第𝑖个训练样本的输出序列的长度。

要表示一个句子里的单词,第一 件事是做一张词表,有时也称为词典:

举个例子,在这里\(𝑥^{ <1>}\)表示 Harry 这个单词,它就是一个第 4075 行是 1,其余值都是 0 的向量(上图编号 1 所示),因为那是 Harry 在这个词典里的位置。

这种表示方法中,\(𝑥^{<𝑡>}\)指代句子里的任意词,它就是个 one-hot 向量,因为它只有一个值是 1,其余值都是 0。

如果遇到了一个不在词表中的单词,答案就是创建一个新的标记,也就是一个叫做 Unknow Word 的伪单词,用作为标记,来表示不在词表中的单词。

1.3 循环神经网络模型(Recurrent Neural Network Model)

尝试的方法之一是使用标准神经网络:

结果表明这个方法并不好,主要有两个问题:

一、输入和输出数据在不同例子中可以有不同的长度,不是所有的例子都有着同样输入长度\(𝑇_𝑥\)或是同样输出长度的\(𝑇_𝑦\)。也许能够填充(pad)或 零填充(zero pad)使每个输入语句都达到最大长度,但仍然看起来不是一个好的表达方式。

二、一个像这样单纯的神经网络结构,它并不共享从文本的不同位置上学到的特征。具体来说,如果神经网络已经学习到了在位置 1 出现的 Harry 可能是人名的一部分,那么如果 Harry 出现在其他位置,比如\(𝑥^{ <𝑡>}\)时,它也能够自动识别其为人名的一部分的话,这就很棒了。这可能类似于在卷积神经网络中看到的,希望将部分图片里学到的内容快速推广到图片的其他部分,而我们希望对序列数据也有相似的效果。和在卷积网络中学到的类似, 用一个更好的表达方式也能够减少模型中参数的数量。

之前提到过这些(上图编号 1 所示的\(𝑥^{<1>}……𝑥^{<𝑡>}……𝑥^{<𝑇_𝑥>}\))都是 10,000 维的 onehot 向量,因此这会是十分庞大的输入层。如果总的输入大小是最大单词数乘以 10,000,那 么第一层的权重矩阵就会有着巨量的参数。但循环神经网络就没有上述的问题。

循环神经网络,当读到句中的第二个单词时,假设是 \(𝑥^{ <2>}\),它不是仅用\(𝑥^{ <2>}\)就预测出\(\hat{𝑦}^{ <2>}\),也会输入一些来自时间步 1 的信息。具体而言, 时间步 1 的激活值就会传递到时间步 2。

要开始整个流程,在零时刻需要构造一个激活值\(𝑎^{ <0>}\),这通常是零向量。有些研究人员 会随机用其他方法初始化\(𝑎^{<0>}\),不过使用零向量作为零时刻的伪激活值是最常见的选择。

循环神经网络是从左向右扫描数据,同时每个时间步的参数也是共享的,用\(𝑊_{ax}\)来表示管理着从\(𝑥^{ <1>}\)到隐藏层的连接的一系列参数,每个时间步使用的都是相同的参数\(𝑊_{ax}\)。而激活值也就是水平联系是由参数\(𝑊_{𝑎𝑎}\)决定的,同时每一个时间步都使用相同的参数\(𝑊_{𝑎𝑎}\),同样的输出结果由\(𝑊_{ya}\)决定。

这个循环神经网络的一个缺点就是它只使用了这个序列中之前的信息来做出预测,尤其当预测\(\hat{𝑦}^{<3>}\)时,它没有用到\(,,𝑥^{<4>},𝑥^{<5>},𝑥^{<6>}\)等等的信息。这样特定的神经网络结构的一个限制是它在某一时刻的预测仅使用了从序列之前 的输入信息并没有使用序列中后部分的信息,我们会在之后的双向循环神经网络(BRNN) 的视频中处理这个问题。但对于现在,这个更简单的单向神经网络结构就够我们来解释关键 概念了。

举个例子\(𝑊_{ax}\),第二个下标意味着\(𝑊_{ax}\)要乘 以某个𝑥类型的量,然后第一个下标𝑎表示它是用来计算某个𝑎类型的变量。同样的,可以看出这里的\(𝑊_{ya}\)乘上了某个𝑎类型的量,用来计算出某个\(\hat{𝑦}\)类型的量。

循环神经网络用的激活函数经常是 tanh,不过有时候也会用 ReLU,但是 tanh 是更通常的选择,有其他方法来避免梯度消失问题,将在之后进行讲述。选用哪个激活函数取决于你的输出𝑦,如果它是一个二分问题,那么会用 sigmoid 函数作为激活函数, 如果是𝑘类别分类问题的话,那么可以选用 softmax 作为激活函数。不过这里激活函数的类型取决于有什么样类型的输出𝑦,对于命名实体识别来说𝑦只可能是 0 或者 1,那这里 第二个激活函数𝑔可以是 sigmoid 激活函数。

更一般的情况下,在𝑡时刻:

\[𝑎^{<𝑡>} = 𝑔_1(𝑊_{𝑎𝑎}𝑎^{<𝑡−1>} + 𝑊_{𝑎𝑥}𝑥^{<𝑡>} + 𝑏𝑎)\\ 𝑦^{<𝑡>}= 𝑔_2(𝑊_{𝑦𝑎}𝑎^{<𝑡>} + 𝑏𝑦) \]

为了建立更复杂的神经网络,实际要将这个符号简化一下:

这种记法的好处是可以不使用两个参数矩阵\(𝑊_{𝑎𝑎}\)\(𝑊_{𝑎𝑥}\),而是将其压缩成一个参数矩阵\(𝑊_a\)

RNN 前向传播示意图:

1.4 通过时间的反向传播(Backpropagation through time)

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

\[𝐿^{<𝑡>}(\hat{𝑦}^{<𝑡>}, 𝑦^{<𝑡>}) = −𝑦^{<𝑡>}log \hat{𝑦}^{<𝑡>}− (1 − 𝑦^{<𝑡>})𝑙𝑜𝑔(1-\hat{𝑦}^{<𝑡>}) \]

对应的是序列中一个具体的词,如果它是某个人的名字,那么\(𝑦^{<𝑡>}\)的值就是 1,然后神经网络将输出这个词是名字的概率值,比如 0.1。将它定义为标准逻辑回归损失函数, 也叫交叉熵损失函数(Cross Entropy Loss),它和之前在二分类问题中看到的公式很像。 所以这是关于单个位置上或者说某个时间步𝑡上某个单词的预测值的损失函数。

现在我们来定义整个序列的损失函数:

\[L(\hat{y},y)=\sum_{t=1}^{T_x}L^{<t>}(\hat{y}^{<t>},y^{<t>}) \]

反向传播算法需要在相反的方向上进行计算和传递信息,最终就是把前向传播的箭头都反过来,在这之后就可以计算出所有合适的量,然后就可以通过导数相关的参数,用 梯度下降法来更新参数。

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

RNN 反向传播示意图:

1.5 不同类型的循环神经网络(Different types of RNNs)

假如说,你想处理情感分类问题,这里 𝑥可能是一段文本,比如一个电影的评论,“These is nothing to like in this movie.”(“这部电影 没什么还看的。”),所以𝑥就是一个序列,而𝑦可能是从 1 到 5 的一个数字,或者是 0 或 1, 这代表正面评价和负面评价,而数字 1 到 5 代表电影是 1 星,2 星,3 星,4 星还是 5 星。

我们不再在每个时间上都有输出了,而是让这个 RNN 网络读入整个句子,然后在最后一个 时间上得到输出,这样输入的就是整个句子,所以这个神经网络叫做“多对一”(many-to-one) 结构,因为它有很多输入,很多的单词,然后输出一个数字。

为了完整性,还要补充一个“一对一”(one-to-one)的结构这个 可能没有那么重要,这就是一个小型的标准的神经网络,输入𝑥然后得到输出𝑦,我们这个系 列课程的前两个课程已经讨论过这种类型的神经网络了。

除了“多对一”的结构,也可以有“一对多”(one-to-many)的结构。对于一个“一对多”神 经网络结构的例子就是音乐生成对应于一段音乐,输入𝑥 可以是一个整数,表示你想要的音乐类型或者是你想要的音乐的第一个音符,并且如果你什 么都不想输入,𝑥可以是空的输入,可设为 0 向量。

对于“多对多”的结构 还有一个有趣的例子值得详细说一下,就是输入和输出长度不同的情况。你刚才看过的多对 多的例子,它的输入长度和输出长度是完全一样的。而对于像机器翻译这样的应用,输入句 子的单词的数量,比如说一个法语的句子,和输出句子的单词数量,比如翻译成英语,这两 个句子的长度可能不同,所以还需要一个新的网络结构,一个不同的神经网络。

总结一下这些各种各样的 RNN 结构,这(上图编号 1 所示)是“一对一”的结构,当去 掉\(𝑎^{<0>}\)时它就是一种标准类型的神经网络。还有一种“一对多”的结构(上图编号 2 所示), 比如音乐生成或者序列生成。还有“多对一”,这(上图编号 3 所示)是情感分类的例子,首 先读取输入,一个电影评论的文本,然后判断他们是否喜欢电影还是不喜欢。还有“多对多” 的结构(上图编号 4 所示),命名实体识别就是“多对多”的例子,其中\(𝑇_𝑥 = 𝑇_𝑦\)。最后还有一 种“多对多”结构的其他版本(上图编号 5 所示),对于像机器翻译这样的应用,\(𝑇_𝑥\)\(𝑇_𝑦\)就可 以不同了。

1.6 语言模型和序列生成(Language model and sequence generation)

什么是语言模型呢?比如做一个语音识别系统,听到一个句子,“the apple and pear(pair) salad was delicious.”,所以究竟说了什么?说的是 “the apple and pair salad”,还是“the apple and pear salad”?(pear 和 pair 是近音词)。应该更像第二种,事实上,这就是一个好的语音识别系统要帮助输出的东西,即使这两句话听起 来是如此相似。而让语音识别系统去选择第二个句子的方法就是使用一个语言模型,他能计算出这两句话各自的可能性。

举个例子,一个语音识别模型可能算出第一句话的概率是: 𝑃(The apple and pair salad) = \(3.2 × 10^{−13}\), 而第二句话的概率是𝑃(The apple and pear salad) = \(5.7 × 10^{−10}\)

为了使用 RNN 建立出这样的模型,首先需要一个训练集,包含一个很大的英文文本语料库(corpus)或者其它的语言,语料库是自然语言处理的一个专有名词,意思就是很长的或者说数量众多的英文句子组成的文本。

标识化的过程:

假如说,在训练集中得到这么一句话,“Cats average 15 hours of sleep a day.”(猫一天 睡 15 小时),要做的第一件事就是将这个句子标记化,建立 一个字典,然后将每个单词都转换成对应的 one-hot 向量,也就是字典中的索引。可能还有一件事就是要定义句子的结尾,一般的做法就是增加一个额外的标记,叫做 EOS,它表示句子的结尾,这样能够帮助搞清楚一个句子什么时候结束。如果训练集中有一些词并不在字典里,可 以把 Mau 替换成一个叫做 UNK 的代表未知词的标志。

在第 0 个时间步,计算激活项\(𝑎^{ <1>}\),它是以\(𝑥^{ <1>}\)作为输入的函数,而\(𝑥^{<1>}\)会被设为全为 0 的集合,也就是 0 向量。在之 前的\(𝑎^{<0>}\)按照惯例也设为 0 向量,于是\(𝑎^{<1>}\)要做的就是通过 softmax 进行一些预测来计算出第一个词可能会是什么,其结果就是\(\hat{𝑦}^{<1>}\)(上图编号 1 所示),这一步其实就是通过 一个 softmax 层来预测字典中的任意单词会是第一个词的概率,比如说第一个词是𝑎的概率 有多少,第一个词是 Aaron 的概率有多少,第一个词是 cats 的概率又有多少,就这样一直 到 Zulu 是第一个词的概率是多少,还有第一个词是 UNK(未知词)的概率有多少,还有第 一个词是句子结尾标志的概率有多少,表示不必阅读。所以\(\hat{𝑦}^{<1>}\)的输出是 softmax 的计算结 果,它只是预测第一个词的概率,而不去管结果是什么。在例子中,最终会得到单词 Cats。所以 softmax 层输出 10,000 种结果,因为字典中有 10,000 个词,或者会有 10,002 个结果,因为可能加上了未知词,还有句子结尾这两个额外的标志。

然后 RNN 进入下个时间步,在下一时间步中,仍然使用激活项\(𝑎^{<1>}\),在这步要做的是 计算出第二个词会是什么。

一直到最后,会停在第 9 个时间步,然后把\(𝑥^{<9>}\)也就是\(𝑦^{<8>}\)传给它(上图编号 5 所示),也就是单词 day,这里是\(𝑎^{<9>}\),它会输出\(𝑦^{<9>}\),最后的得到结果会是 EOS 标志,在这一步中,通过前面这些得到的单词,希望能预测出 EOS 句 子结尾标志的概率会很高(上图编号 6 所示)。

所以 RNN 中的每一步都会考虑前面得到的单词,比如给它前 3 个单词(上图编号 7 所 示),让它给出下个词的分布,这就是 RNN 如何学习从左往右地每次预测一个词。

接下来为了训练这个网络,定义代价函数:

\[softmax损失函数:L(\hat{y}^{<t>},y^{<t>})=-\sum\nolimits_iy_i^{<t>}log(\hat{y}_i^{<t>})\\ 总体损失函数:L=\sum\nolimits_tL^{<t>}(\hat{y}^{<t>},y^{<t>}) \]

如果用很大的训练集来训练这个 RNN,就可以通过开头一系列单词像是 Cars average 15 或者 Cars average 15 hours of 来预测之后单词的概率。现在有一个新句子,它是 \(,,𝑦^{<1>},𝑦^{<2>},𝑦^{<3>}\),为了简单起见,它只包含 3 个词(如上图所示),现在要计算出整个句子中各个单词的概率,方法就是第一个 softmax 层会告诉\(y^{<1>}\)的概率(上图编号 1 所 示),这也是第一个输出,然后第二个 softmax 层会告诉在考虑\(𝑦^{<1>}\)的情况下\(𝑦^{<2>}\)的概率 (上图编号 2 所示),然后第三个 softmax 层告诉在考虑\(𝑦^{<1>}\)\(𝑦^{<2>}\)的情况下\(𝑦^{<3>}\)的概 率(上图编号 3 所示),把这三个概率相乘,最后得到这个含 3 个词的整个句子的概率。

1.7 对新序列采样(Sampling novel sequences)

要想了解模型学到了什么,一种非正式的方法就是进行一次新序列采样:

第一步要做的就是对你想要模型生成的第一个词进行采样,于是输入\(,𝑥^{<1>} = 0, 𝑎^{<0>} = 0\),现在第一个时间步得到的所有可能的输出是经过 softmax 层后得到的概率, 然后根据这个 softmax 的分布进行随机采样。Softmax 分布给的信息就是第一个词的概率是多少,第一个词是 aaron 的概率是多少,第一个词是 zulu 的概率是多少,还有第一个词 是 UNK(未知标识)的概率是多少,这个标识可能代表句子的结尾,然后对这个向量使用例如 numpy 命令,np.random.choice(上图编号 3 所示),来根据向量中这些概率的分布进行采样,这样就能对第一个词进行采样了。

然后继续下一个时间步,记住第二个时间步需要\(\hat{𝑦}^{<1>}\)作为输入,而现在要做的是把刚刚采样得到的\(\hat{𝑦}^{<1>}\)放到\(𝑎^{<2>}\)(上图编号 4 所示),作为下一个时间步的输入然后 softmax 层就会预测\(\hat{𝑦}^{<2>}\)是什么。

然后再到下一个时间步,无论你得到什么样的用 one-hot 码表示的选择结果,都把它传 递到下一个时间步,然后对第三个词进行采样。不管得到什么都把它传递下去,一直这样直到最后一个时间步。

那么要怎样知道一个句子结束了呢?方法之一就是,如果代表句子结尾的标识在字典中,可以一直进行采样直到得到 EOS 标识(上图编号 6 所示),这代表着已经抵达结尾,可以停止采样了。另一种情况是,如果字典中没有这个词,可以决定从 20 个或 100 个或其他个单词进行采样,然后一直将采样进行下去直到达到所设定的时间步。不过这种过程有时候会产生一些未知标识(上图编号 7 所示),如果要确保算法不会输出这 种标识,能做的一件事就是拒绝采样过程中产生任何未知的标识,一旦出现就继续在剩下的词中进行重采样,直到得到一个不是未知标识的词。

可以构建一个基于字符的 RNN 结构,在这种情况下,字典仅包含从 a 到 z 的字母,可能还会有空格符,如果需要的话,还可以有数字 0 到 9,如果想区分字母大小写,可以再加上大写的字母,还可以实际地看一看训练集中可能会出现的字符,然后用这些字符组成字典(上图编号 2 所示)。

使用基于字符的语言模型有有点也有缺点,优点就是不必担心会出现未知的标识,基于字符的语言模型一个主要缺点就是最后会得到太多太长的序列,大多数英语句子只有 10 到 20 个的单 词,但却可能包含很多很多字符。

1.8 循环神经网络的梯度消失(Vanishing gradients with RNNs)

基本的 RNN 算法还有一个很大的问题,就是梯度消失的问题:

假如看到这个句子(上图 编号 1 所示),“The cat, which already ate ……, was full.”,前后应该保持一致,因为 cat 是单 数,所以应该用 was。“The cats, which ate ……, were full.”(上图编号 2 所示),cats 是复数, 所以用 were。这个例子中的句子有长期的依赖,最前面的单词对句子后面的单词有影响。 但是目前见到的基本的 RNN 模型(上图编号 3 所示的网络模型),不擅长捕获这种长期依赖效应:

比如说一个很深很深的网络(上图编号 4 所示),100 层,甚至更深,对这个网络从左到右做前向传播然后再反向传播。如果这是个很深的神经网络,从输出\(\hat{𝑦}\)得到的梯度很难传播回去, 很难影响靠前层的权重,很难影响前面层(编号 5 所示的层)的计算。对于有同样问题的 RNN,首先从左到右前向传播,然后反向传播。但是反向传播会很困 难,因为同样的梯度消失的问题,后面层的输出误差(上图编号 6 所示)很难影响前面层(上图编号 7 所示的层)的计算。

在反向传播的时候,随着层数的增多,梯度不仅可能指数型的下降,也可能指数型的上升。事实上梯度消失在训练 RNN 时是首要的问题,尽管梯度爆炸也 是会出现,但是梯度爆炸很明显,因为指数级大的梯度会让参数变得极其大,以至于网络参数崩溃。所以梯度爆炸很容易发现,因为参数会大到崩溃,会看到很多 NaN,或不是数字的情况,这意味着网络计算出现了数值溢出。如果发现了梯度爆炸的问题, 一个解决方法就是用梯度修剪。梯度修剪的意思就是观察梯度向量,如果它大于某个阈 值,缩放梯度向量,保证它不会太大,这就是通过一些最大值来修剪的方法。所以如果遇 到了梯度爆炸,如果导数值很大,或者出现了 NaN,就用梯度修剪,这是相对比较鲁棒的, 这是梯度爆炸的解决方法。

1.9 GRU 单元(Gated Recurrent Unit(GRU))

RNN 隐藏层的单元的可视化呈现:

“The cat, which already ate……, was full.”,需要记得猫是单数的,为了确保已经理解了为什么这里是 was 而不 是 were,“The cat was full.”或者是“The cats were full”。当我们从左到右读这个句子,GRU 单元将会有个新的变量称为𝑐,代表细胞(cell),即记忆细胞(下图编号 1 所示)。

记忆细胞的作用是提供记忆的能力,比如说一只猫是单数还是复数,所以当它看到之后的句子的时 候,它仍能够判断句子的主语是单数还是复数。于是在时间𝑡处,有记忆细胞\(𝑐^{<𝑡>}\),然后,这里GRU 实际上输出了激活值\(,𝑎^{<𝑡>},𝑐^{<𝑡>} = 𝑎^{<𝑡>}\)(下图编号 2 所示)。于是想要使用不同的符号和𝑎来表示记忆细胞的值和输出的激活值,即使它们是一样的。

在每个时间步,用一个候选值重写记细胞,即\(𝑐̃^{<𝑡>}\)的值,所以它就是个候选值,替代了\(𝑐^{<𝑡>}\)的值。然后用 tanh 激活函数来计算,\(𝑐̃^{<𝑡>} = 𝑡𝑎𝑛ℎ(𝑊_𝑐 [𝑐^{<𝑡−1>}, 𝑥^{<𝑡>}] + 𝑏_𝑐)\),所以\(𝑐̃^{<𝑡>}\)的值就是个替代值,代替表示\(𝑐^{<𝑡>}\)的值 。

在 GRU 中真正重要的思想是我们有一个门,先把这个门叫做\(𝛤_u\),𝑢代表更新门,这是一个 0 到 1 之间的值。实际上这个值是把这个式子带入 sigmoid 函数得到的,\(𝛤_𝑢 = 𝜎(𝑊_𝑢 [𝑐^{<𝑡−1>}, 𝑥^{<𝑡>}] + 𝑏_𝑢)\)

GRU 的关键部分就是上图编号2所示的等式,我们刚才写出来的用𝑐̃更新𝑐的等式。记忆细胞\(𝑐^{<𝑡>}\)将被设定为 0 或者 1, 这取决于你考虑的单词在句子中是单数还是复数,因为这里是单数情况,所以我们先假定它被设为了 1,或者如果是复数的情况我们就把它设为 0。

然后 GRU 单元将会一直记住\(𝑐^{<𝑡>}\)的 值,直到上图 ”was“的位置,\(𝑐^{<𝑡>}\)的值还是 1,这就告诉它,噢,这是单数,所以我们用 was。于是门\(𝛤_𝑢\)的作用就是决定什么时候你会更新这个值,特别是当你看到词组 the cat, 即句子的主语猫,这就是一个好时机去更新这个值。然后当你使用完它的时候,“The cat, which already ate……, was full.”,然后你就知道,我不需要记住它了,我可以忘记它了。

接下来要给 GRU 用的式子就是\(𝑐^{<𝑡>} = 𝛤_𝑢 ∗ 𝑐̃^{<𝑡>} + (1 − 𝛤_𝑢 ) ∗ 𝑐^{<𝑡−1>}\)

如果这个更新值\(𝛤_𝑢 = 1\),也就是说把这个新值,即\(𝑐^{<𝑡>}\)设为候选值(\(𝛤_𝑢 = 1\)时简化上式,\(𝑐^{<𝑡>} = \tilde{c}^{<𝑡>}\))。

将门值设为 1,然后往前再更新这个值。对于所有在这中间的值,应该把门的值设为 0,即\(𝛤_𝑢 = 0\),意思就是说不更新它,就用旧的值。因为如果\(𝛤_𝑢 = 0\),则\(,𝑐^{<𝑡>} = 𝑐^{<𝑡−1>},𝑐^{<𝑡>}\)等于旧的值。即使一直处理句子到上图"was" 所示,\(𝑐^{<𝑡>}\)应该会一直等\(c^{<𝑡−1>}\),于是它仍然记得猫是单数的。

它的优点就是通过门决定,当你从左到右扫描一个句子的时候,这个时机是要更新某个记忆细胞,还是不更新,不更新(上图所示,中间\(𝛤_𝑢 = 0\)一直为 0,表示一直不更新)直到你真的需要使用记忆细胞的时候(上图所示),这可能在句子之前就决定了。

因为 sigmoid 的值,现在因为门很容易取到 0 值,只要这个值是一个很大的负数,再由于数值上的四舍五入,上面这些门大体上就是 0,或者说非常非常非常接近 0。所以在这样的情况下,这个更 新式子(上图所示的等式)就会变成\(𝑐^{<𝑡>} = 𝑐^{<𝑡−1>}\),这非常有利于维持细胞的值。 因为\(𝛤_𝑢\)很接近 0,可能是 0.000001 或者更小,这就不会有梯度消失的问题了。因为\(𝛤_𝑢\)很接近 0,这就是说\(𝑐^{<𝑡>}\)几乎就等于\(𝑐^{<𝑡−1>}\),而且\(𝑐^{<𝑡>}\)的值也很好地被维持了,即使经过很多很多的 时间步(上图所示)。这就是缓解梯度消失问题的关键,因此允许神经网络运行在 非常庞大的依赖词上,比如说 cat 和 was 单词即使被中间的很多单词分割开。

一些实现的细节:

在这个写下的式子中\(𝑐^{<𝑡>}\)可以是一个向量(上图编号 1 所示),如果你有 100 维的隐藏的激活值,那么\(𝑐^{<𝑡>}\)也是 100 维的,\(\tilde{c}^{<𝑡>}\)也是相同的维度 (\(),\tilde{c}{<𝑡>} = 𝑡𝑎𝑛ℎ(𝑊_𝑐 [𝑐^{<𝑡−1>}, 𝑥^{<𝑡>}] + 𝑏_𝑐)),𝛤_𝑢\)也是相同的维度(\(𝛤_𝑢 = 𝜎(𝑊_𝑢 [𝑐^{<𝑡−1>}, 𝑥^{<𝑡>}] + 𝑏_𝑢)\)), 还有画在框中的其他值。这样的话“*”实际上就是元素对应的乘积(\(𝑐^{<𝑡>} = 𝛤_𝑢 ∗ \tilde{c}^{<𝑡>} + (1 − 𝛤_𝑢 ) ∗ 𝑐^{<𝑡−1>}\)),所以这里的\(:(𝛤_𝑢:(𝛤_𝑢 = 𝜎(𝑊_𝑢 [𝑐^{<𝑡−1>}, 𝑥^{<𝑡>}] + 𝑏_𝑢)\)),即如果门是一个 100 维 的向量,\(𝛤_𝑢\)也就 100 维的向量,里面的值几乎都是 0 或者 1,就是说这 100 维的记忆细胞 \((𝑐^{<𝑡>}(𝑐^{<𝑡>} = 𝑎^{<𝑡>}\)上图编号 1 所示)就是你要更新的比特。

当然在实际应用中\(𝛤_𝑢\)不会真的等于 0 或者 1,有时候它是 0 到 1 的一个中间值(上图编 号 5 所示),但是这对于直观思考是很方便的,就把它当成确切的 0,完全确切的 0 或者就 是确切的 1。元素对应的乘积做的就是告诉 GRU 单元哪个记忆细胞的向量维度在每个时间 步要做更新,所以你可以选择保存一些比特不变,而去更新其他的比特。

对于完整的 GRU 单元:

要做的一个改变就是在计算的第一个式子中给记忆细胞的新候选值加上一个新的项,我要添加一个门\(𝛤_𝑟\)(下图编号 1 所示),你可以认为𝑟代表相关性(relevance)。这个\(𝛤_𝑟\)门告诉你计算出的下一个\(𝑐^{<𝑡>}\)的候选值\(\tilde{c}^{<𝑡>}\)\(𝑐^{<𝑡−1>}\)有多大的相关 性。计算这个门 \(𝛤_𝑟\) 需要参数,正如你看到的这个,一个新的参数矩阵 \(,𝑊_𝑟 , 𝛤_𝑟 = 𝜎(𝑊_𝑟 [𝑐^{<𝑡−1>}, 𝑥^{<𝑡>}] + 𝑏_𝑟)\)

为什么有\(𝛤_𝑟\)?为什 么不用上一张幻灯片里的简单的版本?这是因为多年来研究者们试验过很多很多不同可能 的方法来设计这些单元,去尝试让神经网络有更深层的连接,去尝试产生更大范围的影响, 还有解决梯度消失的问题,GRU 就是其中一个研究者们最常使用的版本,也被发现在很多不 同的问题上也是非常健壮和实用的。你可以尝试发明新版本的单元,只要你愿意。但是 GRU 是一个标准版本,也就是最常使用的。你可以想象到研究者们也尝试了很多其他版本,类似 这样的但不完全是,比如我这里写的这个。然后另一个常用的版本被称为 LSTM,表示长短 时记忆网络。

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

GRU(门控循环单元)。能够在序列中学习非常深的连接。其他类型的单元也可以做到这个,比如 LSTM 即长短时记忆网络,甚至比 GRU 更加有效:

LSTM 是一个比 GRU 更加强大和通用的版本,这多亏了 Sepp Hochreiter 和 Jurgen Schmidhuber,感谢那篇开创性的论文,它在序列模型上有着巨大影响。感觉这篇论文是挺难读懂的,虽然认为这篇论文在深度学习社群有着重大的影响,它深入讨论了梯度消失的理论,感觉大部分的人学到 LSTM 的细节是在其他的地方,而不是这篇论文。

LSTM 主要的式子:

使用 \(\tilde{c}^{<𝑡>} = 𝑡𝑎𝑛ℎ(𝑊_𝑐 [𝑎^{<𝑡−1>}, 𝑥^{<𝑡>}] + 𝑏_𝑐\)来更新它的候选值\(\tilde{c}^{<𝑡>}\)(上图编号 3 所示)

在 LSTM 中不再有\(𝑎^{<𝑡>} = 𝑐^{<𝑡>}\)的情况,这是现在用的是类似于这个式子(上图编 号 4 所示),但是有一些改变,使用\(𝑎^{<𝑡>}\)或者\(𝑎^{<𝑡−1>}\),而不是用\(𝑐^{<𝑡−1>}\)

不用\(𝛤_𝑟\),即相关门。

像以前那样有一个更新门\(𝛤_𝑢\)和表示更新的参数\(,𝑊_𝑢,𝛤_𝑢 = 𝜎(𝑊_𝑢 [𝑎^{<𝑡−1>}, 𝑥^{<𝑡>}] + 𝑏_𝑢)\) (上图编号 5 所示)。

一个 LSTM 的新特性是不只有一个更新门控制,这里的这两项(上图 编号 6,7 所示),将用不同的项来代替它们,要用别的项来取代\(𝛤_𝑢\)\(1 − 𝛤_𝑢\),这里(上 图编号 6 所示)仍用\(𝛤_𝑢\)

然后这里(上图编号 7 所示)用遗忘门(the forget gate),叫它\(𝛤_𝑓\),所以这个\(𝛤_𝑓 = 𝜎(𝑊_𝑓 [𝑎^{<𝑡−1>}, 𝑥^{<𝑡>}] + 𝑏_𝑓)\)(上图编号 8 所示)。

有一个新的输出门,\(𝛤_𝑜 = 𝜎(𝑊_𝑜 [𝑎^{<𝑡−1>}, 𝑥^{<𝑡>}]+> 𝑏_𝑜)\)

记忆细胞的更新值\(𝑐^{<𝑡>} = 𝛤_𝑢 ∗ \tilde{c}^{<𝑡>} + 𝛤_𝑓 ∗ 𝑐^{<𝑡−1>}\)(上图编号 10 所示)这给了记忆细胞选择权去维持旧的值\(𝑐^{<𝑡−1>}\)或者就加上新的值\(\tilde{c}^{<𝑡>}\),所以这里用了 单独的更新门\(𝛤_𝑢\)和遗忘门\(𝛤_f\)

最后\(𝑎^{<𝑡>} = 𝑐^{<𝑡>}\)的式子会变成\(𝑎^{<𝑡>} = 𝛤_𝑜 ∗ 𝑐^{<𝑡>}\)。这就是 LSTM 主要的式子。

(上图编号 10 所示的线),这条线显示了只要正确地设置了遗忘门和更新门,LSTM 是相当容易把\(𝑐^{<0>}\)的值(上图编号 11 所示)一直往下传递到右边,比如\(𝑐^{<3>} = 𝑐^{<0>}\)(上图编号 12 所 示)。这就是为什么 LSTM 和 GRU 非常擅长于长时间记忆某个值,对于存在记忆细胞中的某个值,即使经过很长很长的时间步。

LSTM最常用的版本可能是门值不仅取决于\(𝑎^{<𝑡−1>}\)\(𝑥^{<𝑡>}\),有时候也可以偷窥一下\(𝑐^{<𝑡−1>}\)的值(上图编号 13 所示), 这叫做“窥视孔连接”(peephole connection)。

“偷窥孔连接”其实意思就是门值不仅取决于\(𝑎^{<𝑡−1>}\)\(𝑥^{<𝑡>}\),也取决于上一个记忆细胞的值(\(𝑐^{<𝑡−1>}\)), 然后“偷窥孔连接”就可以结合这三个门(\(、、𝛤_𝑢、𝛤_𝑓、𝛤_𝑜\))来计算了。

什么时候应该用 GRU?什么时候用 LSTM?这里没有统一的准则。

GRU 的优点是这是个更加简单的模型,所以更容易创建一个更大的网络,而且它只有两 个门,在计算性上也运行得更快,然后它可以扩大模型的规模。

但是 LSTM 更加强大和灵活,因为它有三个门而不是两个。

1.11 双向循环神经网络(Bidirectional RNN)

双向 RNN 模型,在序列的某点处不仅可以获取之前的信息,还可以获取未来的信息:

这个网络有一个问题,在判断第三个词 Teddy(上图编号 1 所示)是不是人名的一部分时,光看句子前面部分是不够的,为了判断\(\hat{𝑦}^{<3>}\)(上图编号 2 所示)是 0 还是 1,除了前 3 个单词,你还需要更多的信息,因为根据前 3 个单词无法判断他们说的是 Teddy 熊,还是 前美国总统 Teddy Roosevelt,所以这是一个非双向的或者说只有前向的 RNN。

双向 RNN 的工作原理:

用四个输入或者说一个只有 4 个单词的句子,这样输入只有 4 个,\(到𝑥^{<1>}到\)\(𝑥^{<4>}\)

这个网络会有一个前向的循环单元叫做\(,,还有\vec{𝑎}^{<1>},\vec{𝑎}^{<1>},\vec{𝑎}^{<1>}还有 \vec{𝑎}^{<1>}\)

左箭头代表反向连接,\(\overleftarrow{a}^{<i>}\) 反向连接,这里的左箭头代表反向连接,这个网络就构成了一个无环图。

给定一个输入序列\(𝑥^{<i>}\),这个序列首先计算前向的\(\overrightarrow{𝑎}^{<1>}\),然后计算前向的\(\overrightarrow{𝑎}^{<2>}\),接着\(\overrightarrow{𝑎}^{<3>}\)\(\overrightarrow{𝑎}^{<4>}\)。而反向序列从计算\(\overleftarrow{𝑎}^{<4>}\)开始, 反向进行,计算反向的\(\overleftarrow{𝑎}^{<3>}\)。你计算的是网络激活值,这不是反向而是前向的传播,而图中这个前向传播一部分计算是从左到右,一部分计算是从右到左。计算完了反向的\(\overleftarrow{𝑎}^{<3>}\),可以用这些激活值计算反向的\(\overleftarrow{𝑎}^{<2>}\),然后是反向的\(\overleftarrow{𝑎}^{<1>}\),把所有这些激活值都计算完了就可以计算预测结果了。

举个例子:

比如你要观察时间 3 这里的预测结果,信息从\(𝑥^{<1>}\)过来,流经前向的\(\overrightarrow{𝑎}^{<1>}\)到前向的\(\overrightarrow{𝑎}^{<2>}\),这些函数里都有表达,到前向的\(\overrightarrow{𝑎}^{<3>}\)再到\(\hat{𝑦}^{<3>}\)(上图编号 2 所示)所以从\(,,𝑥^{<1>},𝑥^{<2>},𝑥^{<3>}\)来的信息都会考虑在内,而从\(𝑥^{<4>}\)来的信息会流过反向的\(\overleftarrow{𝑎}^{<4>}\),到反向的\(\overleftarrow{𝑎}^{<3>}\)再到\(\hat{𝑦}^{<3>}\)(上图编号 3 所示的路径)。这样使得时间 3 的预测结果不 仅输入了过去的信息,还有现在的信息,这一步涉及了前向和反向的传播信息以及未来的信 息。

这就是双向循环神经网络,并且这些基本单元不仅仅是标准 RNN 单元,也可以是 GRU 单元或者 LSTM 单元。

1.12 深层循环神经网络(Deep RNNs)

看看这个值(\(𝑎^{[2]<3>}\),上图编号 5 所示)是怎么算的。激活值\(𝑎^{[2]<3>}\)有两个输入,一个是从下面过来的输入(上图编号 6 所示),还有一个是从左边过来 的输入(上图编号 7 所示),\(𝑎^{[2]<3>} = 𝑔(𝑊_𝑎^{[2]} [𝑎^{[2]<2>}, 𝑎^{[1]<3>}] + 𝑏_𝑎^{[2]} )\),这就是这个激活值 的计算方法。参数\(和𝑊_𝑎^{[2]}和𝑏_𝑎^{ [2]}\)在这一层的计算里都一样。

很少会看到这种网络堆叠到 100 层。但有一种会容易见到,就是在每一个上面堆 叠循环层,把这里的输出去掉(上图编号 1 所示),然后换成一些深的层,这些层并不水平 连接,只是一个深层的网络,然后用来预测\(𝑦^{ <1>}\)。这种类型的网络结构用的会稍微多一点,这种结构有三个循环单 元,在时间上连接,接着一个网络在后面接一个网络,这是一个深 层网络,但没有水平方向上的连接,所以这种类型的结构我们会见得多一点。

posted @ 2020-01-14 11:07  凤☆尘  阅读(737)  评论(0编辑  收藏  举报