本周讲的是循环序列模型
1、为什么选择序列模型
在本课程中你将学会序列模型,它是深度学习中最令人激动的内容之一。循环神经网络(RNN)之类的模型在语音识别、自然语言处理和其他领域中引起变革。在本节课中,你将学会如何自行创建这些模型。我们先看一些例子,这些例子都有效使用了序列模型。
在进行语音识别时,给定了一个输入音频片段 X ,并要求输出对应的文字记录 Y 。这个例子里输入和输出数据都是序列模型,因为 X 是一个按时播放的音频片段,输出 Y 是一系列单词。所以之后将要学到的一些序列模型,如循环神经网络等等在语音识别方面是非常有用的。
音乐生成问题是使用序列数据的另一个例子,在这个例子中,只有输出数据 Y 是序列,而输入数据可以是空集,也可以是个单一的整数,这个数可能指代你想要生成的音乐风格,也可能是你想要生成的那首曲子的头几个音符。输入的 X 可以是空的,或者就是个数字,然后输出序列 Y 。
在处理情感分类时,输入数据 X 是序列,你会得到类似这样的输入:“There is nothing to like in this movie.”,你认为这句评论对应几星?
系列模型在DNA序列分析中也十分有用,你的DNA可以用A、C、G、T四个字母来表示。所以给定一段DNA序列,你能够标记出哪部分是匹配某种蛋白质的吗?
在机器翻译过程中,你会得到这样的输入句:“Voulez-vou chante avecmoi?”(法语:要和我一起唱么?),然后要求你输出另一种语言的翻译结果。
在进行视频行为识别时,你可能会得到一系列视频帧,然后要求你识别其中的行为。
在进行命名实体识别时,可能会给定一个句子要你识别出句中的人名。
所以这些问题都可以被称作使用标签数据 (X,Y) 作为训练集的监督学习。但从这一系列例子中你可以看出序列问题有很多不同类型。有些问题里,输入数据 X 和输出数据 Y 都是序列,但就算在那种情况下, X 和 Y 有时也会不一样长。或者像上图编号1所示和上图编号2的 X 和 Y 有相同的数据长度。在另一些问题里,只有 X 或者只有 Y 是序列。
所以在本节我们学到适用于不同情况的序列模型。
2、数学符号
本节先从定义符号开始一步步构建序列模型。
比如说你想要建立一个序列模型,它的输入语句是这样的:“Harry Potter and Herminoe Granger invented a new spell.”,(这些人名都是出自于J.K.Rowling笔下的系列小说Harry Potter)。假如你想要建立一个能够自动识别句中人名位置的序列模型,那么这就是一个命名实体识别问题,这常用于搜索引擎,比如说索引过去24小时内所有新闻报道提及的人名,用这种方式就能够恰当地进行索引。命名实体识别系统可以用来查找不同类型的文本中的人名、公司名、时间、地点、国家名和货币名等等。
现在给定这样的输入数据 x ,假如你想要一个序列模型输出 y ,使得输入的每个单词都对应一个输出值,同时这个 y 能够表明输入的单词是否是人名的一部分。技术上来说这也许不是最好的输出形式,还有更加复杂的输出形式,它不仅能够表明输入词是否是人名的一部分,它还能够告诉你这个人名在这个句子里从哪里开始到哪里结束。比如Harry Potter(下图编号1所示)、Hermione Granger(下图标号2所示)。
输出9个数字,按索引的位置,用X^<1> X^<2> X^<3>到X^<9> 来索引不同的位置,我将用X^<t>来索引,这个序列的中间位置,t意味着它们是时序序列,但不论是否是时序序列,我都将用t来索引序列中的位置,输出数据也一样,我们还是用y^<1> y^<2> y^<3>到y^<9>来表示输出数据。同时我们用T_x,来表示输入序列的长度,这个例子中输入是9个单词,所以T_x就等于9,我们用T_y来表示输出序列的长度,在这个例子里T_x就等于T_y(T_x和T_y可以有不同的值)。
记得之前我们用的X^(i)来表示第i个训练样本,所以为了指代第t个元素,或者说训练样本 i 的序列中第 t 个元素,
用表示,就代表第i个训练样本的输入序列长度。
我们这个例子是NLP,也就是自然语言处理,这是我们初次涉足自然语言处理,一件我们需要事先决定的事是怎样表示一个序列里单独的单词。
接下来我们讨论一下怎样表示一个句子里单个的词。想要表示一个句子里的单词,第一件事是做一张词表,有时也称为词典,意思是列一列你的表示方法中用到的单词。这个词表(下图所示)中的第一个词是a,也就是说词典中的第一个单词是a,第二个单词是Aaron,然后更下面一些是单词and,再后面你会找到Harry,然后找到Potter,这样一直到最后,词典里最后一个单词可能是Zulu。
因此a是第一个单词,Aaron是第二个单词,在这个词典里,and出现在367这个位置上,Harry是在4075这个位置,Potter在6830,词典里的最后一个单词Zulu可能是第10,000个单词。所以在这个例子中我用了10,000个单词大小的词典,这对现代自然语言处理应用来说太小了。对于商业应用来说,或者对于一般规模的商业应用来说30,000到50,000词大小的词典比较常见,但是100,000词的也不是没有,而且有些大型互联网公司会用百万词,甚至更大的词典。许多商业应用用的词典可能是30,000词,也可能是50,000词。不过我将用10,000词大小的词典做说明,因为这是一个很好用的整数。
如果你选定了10,000词的词典,构建这个词典的一个方法是遍历你的训练集,并且找到前10,000个常用词,你也可以去浏览一些网络词典,它能告诉你英语里最常用的10,000个单词,接下来你可以用one-hot表示法来表示词典里的每个单词。
举个例子,在这里x^<1>表示Harry这个单词,它就是一个第4075行是1,其余值都是0的向量(上图编号1所示),因为那是Harry在这个词典里的位置。
同样 x^<2> 是个第6830行是1,其余位置都是0的向量(上图编号2所示)。
and在词典里排第367,所以 x^<3> 就是第367行是1,其余值都是0的向量(上图编号3所示)。如果你的词典大小是10,000的话,那么这里的每个向量都是10,000维的。
因为a是字典第一个单词, x^<7> 对应a,那么这个向量的第一个位置为1,其余位置都是0的向量(上图编号4所示)。
所以这种表示方法中,x^指代句子里的任意词,它就是个one-hot向量,因为它只有一个值是1,其余值都是0,所以你会有9个one-hot向量来表示这个句中的9个单词,目的是用这样的表示方式表示 X ,用序列模型在 X 和目标输出 Y 之间学习建立一个映射。我会把它当作监督学习的问题,我确信会给定带有 (x,y) 标签的数据。
如果你遇到了一个不在你词表中的单词,答案就是创建一个新的标记,也就是一个叫做Unknow Word的伪造单词,用<UNK>作为标记,来表示不在词表中的单词,我们之后会讨论更多有关这个的内容。
3、循环神经网络模型
现在我们讨论一下怎样才能建立一个模型,建立一个神经网络来学习 X 到 Y 的映射。
可以尝试的方法之一是使用标准神经网络,在我们之前的例子中,我们有9个输入单词。想象一下,把这9个输入单词,可能是9个one-hot向量,然后将它们输入到一个标准神经网络中,经过一些隐藏层,最终会输出9个值为0或1的项,它表明每个输入单词是否是人名的一部分。
但结果表明这个方法并不好,主要有两个问题,
一、是输入和输出数据在不同例子中可以有不同的长度,不是所有的例子都有着同样输入长度 T_x或是同样输出长度的T_y。即使每个句子都有最大长度,也许你能够填充(pad)或零填充(zero pad)使每个输入语句都达到最大长度,但仍然看起来不是一个好的表达方式。
二、一个像这样单纯的神经网络结构,它并不共享从文本的不同位置上学到的特征。具体来说,如果神经网络已经学习到了在位置1出现的Harry可能是人名的一部分,那么如果Harry出现在其他位置,比如$x^{}$时,它也能够自动识别其为人名的一部分的话,这就很棒了。这可能类似于你在卷积神经网络中看到的,你希望将部分图片里学到的内容快速推广到图片的其他部分,而我们希望对序列数据也有相似的效果。和你在卷积网络中学到的类似,用一个更好的表达方式也能够让你减少模型中参数的数量。
之前我们提到过这些(上图编号1所示的x^{<1>}………x^{< T_x>})都是10,000维的one-hot向量,因此这会是十分庞大的输入层。如果总的输入大小是最大单词数乘以10,000,那么第一层的权重矩阵就会有着巨量的参数。但循环神经网络就没有上述的两个问题。
什么是循环神经网络?
在每一个时间步中,循环神经网络传递一个激活值到下一个时间步中用于计算。要开始整个流程,在零时刻需要构造一个激活值a^<0>,这通常是零向量。有些研究人员会随机用其他方法初始化a^<0>,不过使用零向量作为零时刻的伪激活值是最常见的选择,因此我们把它输入神经网络。
循环神经网络是从左向右扫描数据,同时每个时间步的参数也是共享的。我们用W_ax来表示管理着从x^<1>到隐藏层的连接的一系列参数。每个时间步使用的都是相同的参数W_ax。而激活值也就是水平联系是由参数W_aa决定的,同时每一个时间步,都使用相同的参数W_aa,输出结果由W_ya决定。
这个循环神经网络的一个缺点就是它只使用了这个序列中之前的信息来做出预测,不能用到之后的序列信息。
所以这样特定的神经网络结构的一个限制是它在某一时刻的预测仅使用了从序列之前的输入信息并没有使用序列中后部分的信息,我们会在之后的双向循环神经网络(BRNN)的视频中处理这个问题。但对于现在,这个更简单的单向神经网络结构就够我们来解释关键概念了,之后只要在此基础上作出修改就能同时使用序列中前面和后面的信息来预测 y^3,不过我们会在之后的视频讲述这些内容,接下来我们具体地写出这个神经网络计算了些什么。
前向传播过程如下
循环神经网络用的激活函数经常是tanh,不过有时候也会用ReLU,但是tanh是更通常的选择,我们有其他方法来避免梯度消失问题,我们将在之后进行讲述。选用哪个激活函数是取决于你的输出 y ,如果它是一个二分问题,那么我猜你会用sigmoid函数作为激活函数,如果是 k 类别分类问题的话,那么可以选用softmax作为激活函数。不过这里激活函数的类型取决于你有什么样类型的输出 y ,对于命名实体识别来说 y 只可能是0或者1,那我猜这里第二个激活函数 g 可以是sigmoid激活函数。
现在为了帮我们建立更复杂的神经网络,我实际要将这个符号简化一下。
4、通过时间的反向传播
之前我们已经学过了循环神经网络的基础结构,在本节课中我们将来了解反向传播是怎样在循环神经网络中运行的。和之前一样,当你在编程框架中实现循环神经网络时,编程框架通常会自动处理反向传播。但我认为,在循环神经网络中,对反向传播的运行有一个粗略的认识还是非常有用的,让我们来一探究竟。
在之前你已经见过对于前向传播(下图蓝色箭头所指方向)怎样在神经网络中从左到右地计算这些激活项,直到输出所有地预测结果。而对于反向传播,我想你已经猜到了,反向传播地计算方向(下图红色箭头所指方向)与前向传播基本上是相反的。
我们来分析一下前向传播的计算。
然后为了计算反向传播,你还需要一个损失函数。我们先定义一个元素损失函数(上图编号1所示),我将它定义为标准逻辑回归损失函数,也叫交叉熵损失函数。
定义整个序列的损失函数,将 L 定义为(上图编号2所示)
在这个计算图中,通 y^<1>可以计算对应的损失函数,于是计算出第一个时间步的损失函数(上图编号3所示),然后计算出第二个时间步的损失函数,然后是第三个时间步,一直到最后一个时间步,最后为了计算出总体损失函数,我们要把它们都加起来,通过下面的等式(上图编号2所示的等式)计算出最后的 L (上图编号4所示),也就是把每个单独时间步的损失函数都加起来。
这就是完整的计算图,在之前的例子中,你已经见过反向传播,所以你应该能够想得到反向传播算法需要在相反的方向上进行计算和传递信息,最终你做的就是把前向传播的箭头都反过来,在这之后你就可以计算出所有合适的量,然后你就可以通过导数相关的参数,用梯度下降法来更新参数。
在这个反向传播的过程中,最重要的信息传递或者说最重要的递归运算就是这个从右到左的运算,这也就是为什么这个算法有一个很别致的名字,叫做“通过(穿越)时间反向传播(backpropagation through time)”。取这个名字的原因是对于前向传播,你需要从左到右进行计算,在这个过程中,时刻 t 不断增加。而对于反向传播,你需要从右到左进行计算,就像时间倒流。“通过时间反向传播”,就像穿越时光,这种说法听起来就像是你需要一台时光机来实现这个算法一样。
博主附:
RNN前向传播示意图:
RNN反向传播示意图:
5、不同类型的循环神经网络
来看看更多的RNN的结构。
我们应该修改基本的RNN结构来处理这些问题,本节课的内容参考了Andrej Karpathy的博客,一篇叫做《循环神经网络的非理性效果》(“The Unreasonable Effectiveness of Recurrent Neural Networks”)的文章,我们看一些例子。
之前的例子就是“多对多”(many-to-many)的结构,因为输入序列有很多的输入,而输出序列也有很多输出。
处理情感问题是“多对一”(many-to-one)结构,因为很多输入,很多的单词,然后输出一个数字。
为了完整性,还要补充一个“一对一”(one-to-one)的结构(上图编号3所示),这个可能没有那么重要,这就是一个小型的标准的神经网络,输入 x 然后得到输出 y ,我们这个系列课程的前两个课程已经讨论过这种类型的神经网络了。
除了“多对一”的结构,也可以有“一对多”(one-to-many)的结构。对于一个“一对多”神经网络结构的例子就是音乐生成(下图编号1所示),例如使用一个神经网络输出一些音符。对应于一段音乐,输入 x 可以是一个整数,表示你想要的音乐类型或者是你想要的音乐的第一个音符,并且如果你什么都不想输入, x 可以是空的输入,可设为0向量。
这样这个神经网络的结构,首先是你的输入 x ,然后得到RNN的输出,第一个值,然后就没有输入了,再得到第二个输出,接着输出第三个值等等,一直到合成这个音乐作品的最后一个音符,这里也可以写上输入 a^<0>(上图编号3所示)。有一个后面才会讲到的技术细节,当你生成序列时通常会把第一个合成的输出也喂给下一层(上图编号4所示),所以实际的网络结构最终就像这个样子。
我们已经讨论了“多对多”、“多对一”、“一对一”和“一对多”的结构,对于“多对多”的结构还有一个有趣的例子值得详细说一下,就是输入和输出长度不同的情况。你刚才看过的多对多的例子,它的输入长度和输出长度是完全一样的。而对于像机器翻译这样的应用,输入句子的单词的数量,比如说一个法语的句子,和输出句子的单词数量,比如翻译成英语,这两个句子的长度可能不同,所以还需要一个新的网络结构,一个不同的神经网络(上图编号2所示)。首先读入这个句子,读入这个输入,比如你要将法语翻译成英语,读完之后,这个网络就会输出翻译结果。有了这种结构 T_x和 T_y 就可以是不同的长度了。同样,你也可以画上这个 a^<0>。这个网络的结构有两个不同的部分,这(上图编号5所示)是一个编码器,获取输入,比如法语句子,这(上图编号6所示)是解码器,它会读取整个句子,然后输出翻译成其他语言的结果。
这就是一个“多对多”结构的例子,到这周结束的时候,你就能对这些各种各样结构的基本构件有一个很好的理解。严格来说,还有一种结构,我们会在第四周涉及到,就是“注意力”(attention based)结构,但是根据我们现在画的这些图不好理解这个模型。
总结一下这些各种各样的RNN结构,这(上图编号1所示)是“一对一”的结构,当去掉$a^{<0>}$时它就是一种标准类型的神经网络。还有一种“一对多”的结构(上图编号2所示),比如音乐生成或者序列生成。还有“多对一”,这(上图编号3所示)是情感分类的例子,首先读取输入,一个电影评论的文本,然后判断他们是否喜欢电影还是不喜欢。还有“多对多”的结构(上图编号4所示),命名实体识别就是“多对多”的例子,其中T_x = T_y。最后还有一种“多对多”结构的其他版本(上图编号5所示),对于像机器翻译这样的应用,T_x 和 T_y就可以不同了。
现在,你已经了解了大部分基本的模块,这些就是差不多所有的神经网络了,除了序列生成,有些细节的问题我们会在下节课讲解。
6、语言模型和序列生成
在自然语言处理中,构建语言模型是最基础的也是最重要的工作之一,并且能用RNN很好地实现。在本视频中,你将学习用RNN构建一个语言模型,在本周结束的时候,还会有一个很有趣的编程练习,你能在练习中构建一个语言模型,并用它来生成莎士比亚文风的文本或其他类型文本。
所以什么是语言模型呢?比如你在做一个语音识别系统,你听到一个句子,“the apple and pear(pair) salad was delicious.”,所以我究竟说了什么?我说的是 “the apple and pair salad”,还是“the apple and pear salad”?(pear和pair是近音词)。你可能觉得我说的应该更像第二种,事实上,这就是一个好的语音识别系统要帮助输出的东西,即使这两句话听起来是如此相似。而让语音识别系统去选择第二个句子的方法就是使用一个语言模型,他能计算出这两句话各自的可能性。
举个例子,一个语音识别模型可能算出第一句话的概率和第二句话的概率,比较这两个概率值,显然我说的话更像是第二种,因为第二句话的概率比第一句高出1000倍以上,这就是为什么语音识别系统能够在这两句话中作出选择。
所以语言模型所做的就是,它会告诉你某个特定的句子它出现的概率是多少,根据我所说的这个概率,假设你随机拿起一张报纸,打开任意邮件,或者任意网页或者听某人说下一句话,并且这个人是你的朋友,这个你即将从世界上的某个地方得到的句子会是某个特定句子的概率是多少,例如“the apple and pear salad”。它是两种系统的基本组成部分,一个刚才所说的语音识别系统,还有机器翻译系统,它要能正确输出最接近的句子。而语言模型做的最基本工作就是输入一个句子,准确地说是一个文本序列,用 y 来表示这些序列比用 x 来表示要更好,然后语言模型会估计某个句子序列中各个单词出现的可能性。
那么如何建立一个语言模型呢?为了使用RNN建立出这样的模型,你首先需要一个训练集,包含一个很大的英文文本语料库(corpus)或者其它的语言,你想用于构建模型的语言的语料库。语料库是自然语言处理的一个专有名词,意思就是很长的或者说数量众多的英文句子组成的文本。
假如说,你在训练集中得到这么一句话,“Cats average 15 hours of sleep a day.”(猫一天睡15小时),你要做的第一件事就是将这个句子标记化,意思就是像之前视频中一样,建立一个字典,然后将每个单词都转换成对应的one-hot向量,也就是字典中的索引。可能还有一件事就是你要定义句子的结尾,一般的做法就是增加一个额外的标记,叫做EOS(上图编号1所示),它表示句子的结尾,这样能够帮助你搞清楚一个句子什么时候结束,我们之后会详细讨论这个。EOS标记可以被附加到训练集中每一个句子的结尾,如果你想要你的模型能够准确识别句子结尾的话。在本周的练习中我们不需要使用这个EOS标记,不过在某些应用中你可能会用到它,不过稍后就能见到它的用处。于是在本例中我们,如果你加了EOS标记,这句话就会有9个输入,在标记化的过程中,你可以自行决定要不要把标点符号看成标记,在本例中,我们忽略了标点符号,所以我们只把day看成标志,不包括后面的句号,如果你想把句号或者其他符号也当作标志,那么你可以将句号也加入你的字典中。
完成标识化的过程后,这意味着输入的句子都映射到了各个标志上,或者说字典中的各个词上。下一步我们要构建一个RNN来构建这些序列的概率模型。
现在我们来建立RNN模型,我们继续使用“Cats average 15 hours of sleep a day.”这个句子来作为我们的运行样例,我将会画出一个RNN结构。
RNN中的每一步都会考虑前面得到的单词,比如给它前3个单词(上图编号7所示),让它给出下个词的分布,这就是RNN如何学习从左往右地每次预测一个词。
接下来为了训练这个网络,我们要定义代价函数。
如果你用很大的训练集来训练这个RNN,你就可以通过开头一系列单词像是Cars average 15或者Cars average 15 hours of来预测之后单词的概率。现在有一个新句子,为了简单起见,它只包含3个词(如下图所示),现在要计算出整个句子中各个单词的概率,方法就是第一个softmax层会告诉你 y^<1> 的概率(下图编号1所示),这也是第一个输出,然后第二个softmax层会告诉你在考虑 y^<1> 的情况下 y^<2> 的概率(下图编号2所示),然后第三个softmax层告诉你在考虑 y^<1> 和 y^<2> 的情况下 y^<3> 的概率(下图编号3所示),把这三个概率相乘,最后得到这个含3个词的整个句子的概率。
7、对新序列采样
在你训练一个序列模型之后,要想了解到这个模型学到了什么,一种非正式的方法就是进行一次新序列采样,来看看到底应该怎么做。
记住一个序列模型模拟了任意特定单词序列的概率,我们要做的就是对这些概率分布进行采样来生成一个新的单词序列。下图编号1所示的网络已经被上方所展示的结构训练训练过了,而为了进行采样(下图编号2所示的网络),你要做一些截然不同的事情。
第一步要做的就是对你想要模型生成的第一个词进行采样,现在你的第一个时间步得到的是所有可能的输出是经过softmax层后得到的概率,然后根据这个softmax的分布进行随机采样。Softmax分布给你的信息就是第一个词a的概率是多少,第一个词是aaron的概率是多少,第一个词是zulu的概率是多少,还有第一个词是UNK(未知标识)的概率是多少,这个标识可能代表句子的结尾,然后对这个向量使用例如numpy命令,np.random.choice
(上图编号3所示),来根据向量中这些概率的分布进行采样,这样就能对第一个词进行采样了。
然后再到下一个时间步,无论你得到什么样的用one-hot码表示的选择结果,都把它传递到下一个时间步,然后对第三个词进行采样。不管得到什么都把它传递下去,一直这样直到最后一个时间步。
那么你要怎样知道一个句子结束了呢?方法之一就是,如果代表句子结尾的标识在你的字典中,你可以一直进行采样直到得到EOS标识(上图编号6所示),这代表着已经抵达结尾,可以停止采样了。另一种情况是,如果你的字典中没有这个词,你可以决定从20个或100个或其他个单词进行采样,然后一直将采样进行下去直到达到所设定的时间步。不过这种过程有时候会产生一些未知标识(上图编号7所示),如果你要确保你的算法不会输出这种标识,你能做的一件事就是拒绝采样过程中产生任何未知的标识,一旦出现就继续在剩下的词中进行重采样,直到得到一个不是未知标识的词。如果你不介意有未知标识产生的话,你也可以完全不管它们。
这就是你如何从你的RNN语言模型中生成一个随机选择的句子。直到现在我们所建立的是基于词汇的RNN模型,意思就是字典中的词都是英语单词(下图编号1所示)。
根据你实际的应用,你还可以构建一个基于字符的RNN结构,在这种情况下,你的字典仅包含从a到z的字母,可能还会有空格符,如果你需要的话,还可以有数字0到9,如果你想区分字母大小写,你可以再加上大写的字母,你还可以实际地看一看训练集中可能会出现的字符,然后用这些字符组成你的字典(上图编号2所示)。
使用基于字符的语言模型有优点也有缺点,优点就是你不必担心会出现未知的标识,例如基于字符的语言模型会将Mau这样的序列也视为可能性非零的序列。而对于基于词汇的语言模型,如果Mau不在字典中,你只能把它当作未知标识UNK。不过基于字符的语言模型一个主要缺点就是你最后会得到太多太长的序列,大多数英语句子只有10到20个的单词,但却可能包含很多很多字符。所以基于字符的语言模型在捕捉句子中的依赖关系也就是句子较前部分如何影响较后部分不如基于词汇的语言模型那样可以捕捉长范围的关系,并且基于字符的语言模型训练起来计算成本比较高昂。所以我见到的自然语言处理的趋势就是,绝大多数都是使用基于词汇的语言模型,但随着计算机性能越来越高,会有更多的应用。在一些特殊情况下,会开始使用基于字符的模型。但是这确实需要更昂贵的计算力来训练,所以现在并没有得到广泛地使用,除了一些比较专门需要处理大量未知的文本或者未知词汇的应用,还有一些要面对很多专有词汇的应用。
在现有的方法下,现在你可以构建一个RNN结构,看一看英文文本的语料库,然后建立一个基于词汇的或者基于字符的语言模型,然后从训练的语言模型中进行采样。
这里有一些样本,它们是从一个语言模型中采样得到的,准确来说是基于字符的语言模型,你可以在编程练习中自己实现这样的模型。如果模型是用新闻文章训练的,它就会生成左边这样的文本,这有点像一篇不太合乎语法的新闻文本,不过听起来,这句“Concussion epidemic”,to be examined,确实有点像新闻报道。用莎士比亚的文章训练后生成了右边这篇东西,听起来很像是莎士比亚写的东西
8、带有神经网络的梯度消失
你已经了解了RNN时如何工作的了,并且知道如何应用到具体问题上,比如命名实体识别,比如语言模型,你也看到了怎么把反向传播用于RNN。其实,基本的RNN算法还有一个很大的问题,就是梯度消失的问题。这节课我们会讨论,在下几节课我们会讨论一些方法用来解决这个问题。
你已经知道了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 y$得到的梯度很难传播回去,很难影响靠前层的权重,很难影响前面层(编号5所示的层)的计算。
对于有同样问题的RNN,首先从左到右前向传播,然后反向传播。但是反向传播会很困难,因为同样的梯度消失的问题,后面层的输出误差(上图编号6所示)很难影响前面层(上图编号7所示的层)的计算。这就意味着,实际上很难让一个神经网络能够意识到它要记住看到的是单数名词还是复数名词,然后在序列后面生成依赖单复数形式的was或者were。而且在英语里面,这中间的内容(上图编号8所示)可以任意长,所以你需要长时间记住单词是单数还是复数,这样后面的句子才能用到这些信息。也正是这个原因,所以基本的RNN模型会有很多局部影响,上图编号6所示的输出,基本上很难受到序列靠前的输入(上图编号10所示)的影响,这是因为不管输出是什么,不管是对的,还是错的,这个区域都很难反向传播到序列的前面部分,也因此网络很难调整序列前面的计算。这是基本的RNN算法的一个缺点,RNN会不擅长处理长期依赖的问题。
尽管我们一直在讨论梯度消失问题,但是,你应该记得我们在讲很深的神经网络时,我们也提到了梯度爆炸,我们在反向传播的时候,随着层数的增多,梯度不仅可能指数型的下降,也可能指数型的上升。事实上梯度消失在训练RNN时是首要的问题,尽管梯度爆炸也是会出现,但是梯度爆炸很明显,因为指数级大的梯度会让你的参数变得极其大,以至于你的网络参数崩溃。所以梯度爆炸很容易发现,因为参数会大到崩溃,你会看到很多NaN,或者不是数字的情况,这意味着你的网络计算出现了数值溢出。如果你发现了梯度爆炸的问题,一个解决方法就是用梯度修剪。梯度修剪的意思就是观察你的梯度向量,如果它大于某个阈值,缩放梯度向量,保证它不会太大,这就是通过一些最大值来修剪的方法。所以如果你遇到了梯度爆炸,如果导数值很大,或者出现了NaN,就用梯度修剪,这是相对比较鲁棒的,这是梯度爆炸的解决方法。然而梯度消失更难解决。
总结一下,在前面的课程,我们了解了训练很深的神经网络时,随着层数的增加,导数有可能指数型的下降或者指数型的增加,我们可能会遇到梯度消失或者梯度爆炸的问题。加入一个RNN处理1,000个时间序列的数据集或者10,000个时间序列的数据集,这就是一个1,000层或者10,000层的神经网络,这样的网络就会遇到上述类型的问题。梯度爆炸基本上用梯度修剪就可以应对,但梯度消失比较棘手。我们下节会介绍GRU,门控循环单元网络,这个网络可以有效地解决梯度消失的问题,并且能够使你的神经网络捕获更长的长期依赖。
9、GRU 单元
你已经了解了基础的RNN模型的运行机制,在本节课你将会学习门控循环单元,它改变了RNN的隐藏层,使其可以更好地捕捉深层连接,并改善了梯度消失问题,让我们看一看。
这就是RNN隐藏层的单元的可视化呈现。我向展示这张图,因为我们将使用相似的图来讲解门控循环单元。
许多GRU的想法都来分别自于Yu Young Chang, Kagawa,Gaza Hera, Chang Hung Chu和 Jose Banjo的两篇论文。我再引用上个视频中你已经见过的这个句子,“The cat, which already ate……, was full.”,你需要记得猫是单数的,为了确保你已经理解了为什么这里是was而不是were,“The cat was full.”或者是“The cats were full”。当我们从左到右读这个句子,GRU单元将会有个新的变量称为c ,代表细胞,即记忆细胞,记忆细胞的作用是提供了记忆的能力,比如说一只猫是单数还是复数,所以当它看到之后的句子的时候,它仍能够判断句子的主语是单数还是复数。于是在时间 t 处,有记忆细胞有关于t的值c。GRU实际上输出了a^的激活值,于是我们想要用不同的符号c和a来表示记忆细胞的值和输出的激活值,即使它们是一样的。我现在使用这个标记是因为当我们等会说到LSTMs的时候,这两个会是不同的值,但是现在对于GRU,c^的值等于a^的激活值。
所以这些等式表示了GRU单元的计算,在每个时间步,我们将用一个候选值重写记忆细胞,即c~^的值,所以它就是个候选值,替代了c^的值,然后用tanh激活函数来计算参数W_c,我们再传递参数矩阵上一个记忆细胞的值,即目前的输入x^的值,后加上偏差项。所以c~^的值就是个替代值,代替表示c^的值
重点来了,在GRU中真正重要的思想是我们有一个门,我先把这个门叫做Γ_u,这是个下标为u的大写希腊字母Γ,u代表更新门,这是一个0到1之间的值,为了让你直观思考GRU的工作机制,先思考Γ_u,这个一直在0到1之间的门值,实际上这个值是把这个式子带入sigmoid函数得到的,我们还记得sigmoid函数是上图编号5所示这样的,它的输出值总是在0到1之间,对于大多数可能的输入,sigmoid函数的输出总是非常接近0或者非常接近1。在这样的直觉下,可以想到Γ_u在大多数的情况下非常接近0或1。然后这个字母u表示“update”,我选了字母Γ_u是因为它看起来像门。还有希腊字母G,G是门的首字母,所以G表示门。
然后GRU的关键部分就是上图编号3所示的等式,我们刚才写出来的用c~更新c的等式。然后门决定是否要真的更新它。于是我们这么看待它,记忆细胞 c 将被设定为0或者1,这取决于你考虑的单词在句子中是单数还是复数,因为这里是单数情况,所以我们先假定它被设为了1,或者如果是复数的情况我们就把它设为0。然后GRU单元将会一直记住c 的值,直到上图编号7所示的位置,c 的值还是1,这就告诉它,噢,这是单数,所以我们用was。于是门,即Γ_u的作用就是决定什么时候你会更新这个值,特别是当你看到词组the cat,即句子的主语猫,这就是一个好时机去更新这个值。然后当你使用完它的时候,“The cat, which already ate……, was full.”,然后你就知道,我不需要记住它了,我可以忘记它了
让我再画个图来(下图所示)解释一下GRU单元,顺便说一下,当你在看网络上的博客或者教科书或者教程之类的,这些图对于解释GRU和我们稍后会讲的LSTM是相当流行的,我个人感觉式子在图片中比较容易理解。
你现在已经理解GRU最重要的思想了,这实际上只是简化过的GRU单元,现在来描述一下完整的GRU单元。
对于完整的GRU单元我要做的一个改变就是在我们计算的第一个式子中给记忆细胞的新候选值加上一个新的项,我要添加一个门Γ_r(下图编号1所示),你可以认为 r 代表相关性(relevance)。这个Γ_u门告诉你计算出的下一个c 的候选值跟c 有多大的相关性。
所以这就是GRU,即门控循环单元,这是RNN的其中之一。这个结构可以更好捕捉非常长范围的依赖,让RNN更加有效。然后我简单提一下其他常用的神经网络,比较经典的是这个叫做LSTM,即长短时记忆网络,我们在下节课将。
10、长短期记忆(LSTM)
LSTM即长短时记忆网络比GRU更加有效,让我们看看。
还记得GRU的公式:更新门和相关门。
LSTM是一个比GRU更加强大和通用的版本,这多亏了 Sepp Hochreiter和 Jurgen Schmidhuber,感谢那篇开创性的论文,它在序列模型上有着巨大影响。我感觉这篇论文是挺难读懂的,虽然我认为这篇论文在深度学习社群有着重大的影响,它深入讨论了梯度消失的理论,我感觉大部分的人学到LSTM的细节是在其他的地方,而不是这篇论文。
下图是LSTM的式子,注意新增了遗忘门和输出门,
图片解释一下
你可能会想到这里和一般使用的版本会有些不同,最常用的版本可能是门值不仅取决于a^ x^,有时候也可以偷窥一下c^<t-1>的值,这叫做窥视孔连接,在于一个技术上的细节,比如这有一个100维的向量,你有一个100维的隐藏的记忆细胞单元,然后比如第50个c^的元素只会影响第50个元素对应的那个门,所以关系是一对一的,于是并不是任意这100维的c^可以影响所有的门元素。相反的,第一个c^的元素只能影响门的第一个元素,第二个元素影响对应的第二个元素,如此类推。但如果你读过论文,见人讨论“偷窥孔连接”,那就是在说c^也能影响门值。
11、双向神经网络
现在,你已经了解了大部分RNN模型的关键的构件,还有两个方法可以让你构建更好的模型,其中之一就是双向RNN模型,这个模型可以让你在序列的某点处不仅可以获取之前的信息,还可以获取未来的信息,第二个就是深层的RNN。
为了了解双向RNN的动机,我们先看一下之前在命名实体识别中已经见过多次的神经网络。这个网络有一个问题,在判断第三个词Teddy是不是人名的一部分时,光看句子前面部分是不够的,为了判断y^ 是0还是1,除了前3个单词,你还需要更多的信息,因为根据前3个单词无法判断他们说的是Teddy熊,还是前美国总统Teddy Roosevelt,所以这是一个非双向的或者说只有前向的RNN。我刚才所说的总是成立的,不管这些单元是标准的RNN块,还是GRU单元或者是LSTM单元,只要这些构件都是只有前向的。
那么一个双向的RNN(BRNN)是如何解决这个问题的?下面解释双向RNN的工作原理。为了简单,我们用四个输入或者说一个只有4个单词的句子。有前向和反向两个部分,先前向再反向回来。
这就是双向循环神经网络,并且这些基本单元不仅仅是标准RNN单元,也可以是GRU单元或者LSTM单元。事实上,很多的NLP问题,对于大量有自然语言处理问题的文本,有LSTM单元的双向RNN模型是用的最多的。所以如果有NLP问题,并且文本句子都是完整的,首先需要标定这些句子,一个有LSTM单元的双向RNN模型,有前向和反向过程是一个不错的首选。
以上就是双向RNN的内容,这个改进的方法不仅能用于基本的RNN结构,也能用于GRU和LSTM。通过这些改变,你就可以用一个用RNN或GRU或LSTM构建的模型,并且能够预测任意位置,即使在句子的中间,因为模型能够考虑整个句子的信息。这个双向RNN网络模型的缺点就是你需要完整的数据的序列,你才能预测任意位置。比如说你要构建一个语音识别系统,那么双向RNN模型需要你考虑整个语音表达,但是如果直接用这个去实现的话,你需要等待这个人说完,然后获取整个语音表达才能处理这段语音,并进一步做语音识别。对于实际的语音识别的应用通常会有更加复杂的模块,而不是仅仅用我们见过的标准的双向RNN模型。但是对于很多自然语言处理的应用,如果你总是可以获取整个句子,这个标准的双向RNN算法实际上很高效。
12、深层循环神经网络
目前你学到的不同RNN的版本,每一个都可以独当一面。但是要学习非常复杂的函数,通常我们会把RNN的多个层堆叠在一起构建更深的模型。本节课我们会学到如何构建这些更深的RNN。
这是我们一直见到的标准的RNN。只是把符号改一下,不再用原来的a^<0>表示0时刻的激活值了,而是加上一个方括号[1],来表示这是第一层,所以我们现在用a^[l]来表示第l层的激活值,这个表示第t个时间点。这样就可以表示第一层第一个时间点的激活值a^[1]<1>。
然后我们把这些堆叠在上面,这就是一个有三个隐层的新的网络。
具体例子看看怎么算的,比如
另外
对于像左边这样标准的神经网络,你可能见过很深的网络,甚至于100层深,而对于RNN来说,有三层就已经不少了。由于时间的维度,RNN网络会变得相当大,即使只有很少的几层,很少会看到这种网络堆叠到100层。但有一种会容易见到,就是在每一个上面堆叠循环层,把这里的输出去掉(上图编号1所示),然后换成一些深的层,这些层并不水平连接,只是一个深层的网络,然后用来预测 y^<1>。同样这里(上图编号2所示)也加上一个深层网络,然后预测 y^<1>。这种类型的网络结构用的会稍微多一点,这种结构有三个循环单元,在时间上连接,接着一个网络在后面接一个网络,当然 y^<3>和 y^<4>也一样,这是一个深层网络,但没有水平方向上的连接,所以这种类型的结构我们会见得多一点。通常这些单元(上图编号3所示)没必要非是标准的RNN,最简单的RNN模型,也可以是GRU单元或者LSTM单元,并且,你也可以构建深层的双向RNN网络。由于深层的RNN训练需要很多计算资源,需要很长的时间,尽管看起来没有多少循环层,这个也就是在时间上连接了三个深层的循环层,你看不到多少深层的循环层,不像卷积神经网络一样有大量的隐含层。
这就是深层RNN的内容,从基本的RNN网络,基本的循环单元到GRU,LSTM,再到双向RNN,还有深层版的模型。这节课后,你已经可以构建很不错的学习序列的模型了。
部分笔记内容参考自这里 。