Course 5-Recurrent Neural Networks--Week 1
吴恩达老师的RNN课程已经学了一遍了,但总觉得自己学得不够明白,怎么办?再来一遍啊。书读百遍其义自见嘛!
第一周的课程主要介绍序列模型的应用场景;深度学习中的RNN基础结构、GRU、LSTM、BRNN;语言模型和序列采样的知识。
1.1 为什么需要序列模型?
本课程中,我们将学习序列模型,深度学习令人兴奋的领域之一。RNN这类模型已经改变了语音识别、自然语言处理等其他领域。在本门课中,你将学习如何自己构建这些模型。我们首先来看一些序列模型派得上用处的例子。
-
语音识别中,输入是一个裁剪过的语音片段
x ,要求将其映射为文本y ,这里的输入和输出都是序列数据,前者是语音序列、后者是文字序列。因此,像RNN及其变种的序列模型在语音识别领域非常有用。 -
音乐生成是另一个有关序列数据的例子,但在这个例子中,只有输出
y 是序列,输入可以是空集或是一个指定音乐种类的整数或者是音乐的前几个音符。 - 在情感分类中,输入
x 是一个序列,输出是一个评分。 - 序列模型在DNA序列分析中也非常有用,DNA是字母A,C,G,T的排列组合。因此,给定一个DNA序列,你可以标记出该DNA序列的哪部分对应一个蛋白质吗?
- 在机器翻译中,给定一个输入序列,要求输出另外一种语言的翻译。
- 在视频行为识别中,给定一个视频帧序列,要求识别行为。
- 在命名实体识别中,给定一个句子,要求确定句子中的人名。
上述这些问题都可以定位成有监督学习,训练集为(x,y)。但它们又属于不同的序列类型问题。有些序列问题的输入
1.2 符号
上一节中,我们看到了序列模型广泛的应用领域。我们先来定义一些构建序列模型的符号。
假设我们要构建一个序列模型,输入
那么在NLP中我们怎样表示这些单词呢,
还有一个问题是,如果我们遇到一个不在词汇表中的单词该怎么办呢?这时候创造一个单词
1.3 RNN模型
本节讨论如何构建一个模型、构建一个神经网络来学习从x到y的映射。我们可以尝试使用标准神经网络来解决这个问题,比如,将9个one-hot向量输入,经过一些隐藏层,再经过输出层,得到输出,每个输出单元告诉我们每个单词是否是人名的一部分,但是,事实证明,这个方法并不好。如下图所示,主要有两个问题:第一,输入和输出在不同的例子中长度是不同的。即使我们设置句子的最大长度,可以用特定符号进行填充,使句子达到最大的长度,这仍然不是一个好的表达;第二,这个问题就更严重了,这样简单的网络结构并不能共享那些从不同文本位置上学习到的特征。特别的,比如当Harry在位置1时,我们学到了它是人名的一部分,当Harry出现在别的位置的时候,我们希望模型也能判断出它是人名的一部分,有点类似与CNN,希望模型对特征的位置不太敏感。使用更好的表带也会减少模型中参数的数量,之前我们使用得到是one-hot向量,每个向量的长度都是10k,这将是一个非常大的输入层,总的输入大小将是
下面要介绍的RNN就没有上述两个问题。那么,什么是RNN呢?如果我们从左到右阅读句子,第一个单词是
预测
RNN从左往右扫描数据,每一时刻所使用的参数也是共享的,从输入x到隐藏层输出a的参数设为
下面用这张整洁的图来说明RNN中的前向计算。前向计算的关键计算式如图中所示,其中,计算隐藏层输出的**函数通常用tanh,偶尔也用relu,使用tanh后,我们有其他方法来避免梯度消失,避免方法将在本周后面进行讨论。计算输出层输出的**函数通常取决于问题本身,如果是二分类问题就用sigmoid,如果是多分类问题就用softmax。对于NER问题来说,y只可能是0或者1,那么这里的**函数就使用sigmoid。图中还给出了更普遍的前向传播公式。
为了帮助我们构建更复杂的神经网络,我们将上图中的前向传播公式进行简化。如下图所示,将画蓝线的部分进行重写,主要运用分块矩阵的思想。最终可将前向传播公式中参数的脚标简化为只有一个变量。
1.4 BPTT
我们已经学习了RNN的基础结构,本节将学习RNN中的后向传播是怎么进行的。显然,与之前一样,反向传播的计算步骤与前向传播是相反的。
前向传播如图所示,为了计算反向传播,我们还需要一个损失函数。我们先来定义一个元素的损失函数,它对应序列中一个具体的单词,使用交叉熵损失函数,如下图所示。然后,对单个单词的损失函数按照时间求和,就可得到整个序列的损失。反向传播如红线所示,通过对损失函数求导,梯度下降法更新,不断优化参数。
目前为止,我们只学到了一种RNN的结构,即输出和输出等长的RNN。下面我们要学习其他类型的RNNs。
1.5 不同类型的RNN网络
目前为止,我们已经了解了输出序列和输出序列等长时的RNN。但对于其他应用来说,
大家是否还记得下图所示的ppt,这是我们在本周第一课中的ppt。这里面展示了输入和输出的各种情况。比如,音乐生成的例子,输入就是空集或者一个整数;再比如电影情感分类,输出就是一个1-5的整数,而输入确实一个序列;在NER中,输入和输出向等长的;但也有一些应用中,输入和输出是不等长的,比如机器翻译。因此,我们可以通过修改基本的RNN模型来处理这些问题。
如下图所示。
- NER这种应用,我们称之为多对多结构。
- 情感分类应用,我们称之为多对一结构。
- 当然了,还有一对一结构,这种我们并不感兴趣。
- 音乐生成应用,我们称之为一对多结构。
- 机器翻译应用,我们称之为多对多结构,它是一种特殊的多对多,因为输入和输出的长度是不相同的。这种结构由两部分组成,分别是编码器和解码器。
再这周结束的时候,你将会对这些结构组件有一个更好的理解。从技术上来说,还有一种结构,那就是基于注意的结构。
现在来总结一下RNN的结构类型。如下图所示。如果将一对一的
下面将介绍序列生成的相关内容。
1.6 语言模型和序列生成
语言建模是NLP中最基础也是最重要的任务之一。而RNN再这一方面就做得很好。本节我们将学习到如何用RNN来构建一个语言模型。
那么,什么是语言模型呢?假设我们在构建一个语音识别系统,听到了一句”The apple and pear salad was delicious.” 那么,所说的可能是下面两种可能的句子,如图所示。人会认为说的是第二个句子,而这就是一个好的语音识别系统所要做的事情,尽管这两个句子听起来几乎一样。而让语音识别系统选择第二个句子的方法就是使用语言模型。语言模型能输出这两句话出现的概率。比如,某一语言模型计算出两句话的概率分别如下图所示。因此,语言模型所做的工作就是,给定任何一句话,告诉我们这句话出现的概率。这一点是语音识别系统和机器翻译的基础。因此,语言模型的基本工作就是对输入的句子序列
那么,如何建立一个语言模型呢?用RNN建立一个这样的模型,首先需要一个训练集,它包含大量英文文本语料,或者是任何一种想要在其之上建立语言模型的语种文本。单词corpus是NLP中的专有名词,表示大量文本或句子的集合。
假设语料中有一句话”Cats average 15 hours of sleep a day.” 那么,我们首先要做的就是,符号化句子(tokenize this sentence),意思就是利用训练集形成一个词汇表,然后将每个单词映射为词汇表中的索引,再将索引值转化为one-hot向量,记为
完成符号化步骤之后(即,将输入句子映射为在词汇表中的一个个独立的符号)。
接下来,我们构建一个RNN来对这些不同序列的概率进行建模。在这个RNN中,我们设
然后,为了训练模型,我们需要定义代价函数,由于输出层是softmax**函数,我们这里使用softmax的损失函数,那么,整个序列的代价就是把各个时刻的损失都相加起来。
而最终,这个序列出现的概率就是把所有的输出相乘,就得到了这个序列的联合概率密度。
上述就是训练RNN语言模型的基本结构,也许一些细节对你来说还比较抽象,别担心,这周的编程练习会让你更加清楚。
下面,我们将用语言模型进行一个非常有趣的事情,那就是从语言模型中采样一个序列。
1.7 采样新的序列
在你训练完成一个序列模型之后,一种让你能大概了解到模型学了些什么的方法就是进行一次序列采样。我们来看看应该怎么做。
首先要记得,序列模型对任意特定单词序列的概率进行建模,即对序列的联合概率进行建模。而我们要做的就是从这个概率分布中进行采样,进而生成新的单词序列。
模型的训练使用下图中上面所示的结构。而对于采样就稍有不同了,我们要对模型生成的第一个单词进行采样。初始输入仍然是
那么,怎样知道序列结束了呢?如果eos是词汇表中的一部分,那么我们可以不断采样,直到采样到eos;如果eos不在词汇表中,我们可以设定采样的长度,直到序列达到规定长度为止。不过在这一过程中,也有可能产生unk这个符号,如果你想确定算法不会产生这个符号,一种可行的方法就是拒绝任何unk的输出,当输出是unk的时候就继续采样。
上述就是如何从RNN语言模型中生成随机序列的例子。
目前为止,我们构建了一个单词层面的RNN模型,即词汇表中都是一些英语单词。取决于我们的应用场景,我们还可以构建一个字符层面的RNN模型。在这种情况下,词汇表就包含了a-z的26个字母、空格符、标点符号、0-9的10个数字,如果想区分大小写,还可以加上大写的26个字母。我们可以通过观察词汇表出现的字符得到字符层面的词汇表。在字符层面上,
下面是一些有趣的例子,它们都是从语言模型中采样出来的。
以上就是基础的RNN结构,以及如何进行训练和采样。接下来将讨论训练RNN的挑战,以及如何适应这些挑战,特别是梯度消失问题,通过建立更强大的RNN模型来解决。
1.8 RNN的梯度消失问题
我们已经了解了RNN是如何工作的、如何运用RNN解决命名实体识别以及语言建模等问题。但是基础的RNN算法有一个问题,那就是梯度消失问题。我们先来讨论这个问题,然后再说明怎么解决这个问题。
我们看到的RNN都长下图的样子。现在,我们以语言模型为例进行说明。句子如下图蓝色笔迹所示,其中,cat和was对应,cats和were对应。这是一个句子中有长程依赖的例子。但是基础的RNN结构并不善于捕捉这种长程依赖。为什么呢?你可能还记得我们在讨论非常深的神经网络时,说到的梯度消失问题。当网络很深的时候,就很难将输出的梯度反向传播到最初的几层上。RNN也是同样的道理。当序列很长的时候,后面时刻的梯度就很难传播到最初时刻上加以计算。实际上,就是网络只能记住它前面几个时刻的信息,隔得太远就记不住了。除了梯度消失,还有个梯度爆炸的问题。但是梯度爆炸要好发现和解决的多,当你看到计算结果有很多NaN出现,这就说明出现梯度爆炸了,其中一个解决方法就是使用gradient clipping。其思想就是给梯度值设置一个范围,如果在这个范围内就用梯度原来的值;如果小于范围内的最小值,就将梯度设置为范围内的最小值;如果大于范围内的最大值,就将梯度设置为范围内的最大值。
相较而言,梯度消失问题就比较难解决了。下面我们将重点讨论梯度消失的问题及其解决方法。
1.9 GRU
我们已经了解了基本的RNN是怎样工作的。本节我们将学习GRU,它是RNN中隐藏层的一种改进,使得RNN可以更好的捕捉长程依赖,从而有助于梯度消失问题的解决。
下图中**值的计算想必大家都很熟悉了,将这个表达式转化成计算图的形式,就如图中左边部分所示。
在GRU单元中,为了使RNN能具有长程依赖,我们增加一个变量
sigmoid函数的取值基本上不是接近0就是接近1,
*表示对应元素的乘积。需要注意的是,如果
上述一系列式子中,
上述都是简化过的GRU,下面来讨论完整的GRU应该是什么样子。(注意,最后一个式子有误,应该是
1.10 LSTM
另一个比GRU还强大和通用的网络单元就是LSTM。下图给出了它们两个的计算对比。LSTM有3个门函数,分别是更新门、遗忘门和输出门。
将LSTM的计算式转化成计算图如下图右上角所示。而真正的使用了LSTM的RNN结构如下图下面的结构所示。其中红线表示的一条路径就是网络中的记忆信息的传输过程,我们可以通过设置恰当的参数,使得
在此基础上也有些变体的版本,如绿色笔迹所示,在计算
什么时候使用GRU,什么时候使用LSTM并没有一个统一的标准。GRU 更简单,因此更容易创建一个更大的网络,运算也会更快。而LSTM更强大和灵活,因为它有3个门。如果一定要选择一个的话,大多数人还是会选择LSTM
1.11 BiRNN
目前为止,你已经了解到了RNN中大多数重要的组件。但还有两个思想可以帮助你构建更强大的模型。其中一个思想就是双向RNN,它可以帮助我们在某个时刻同时使用之前和之后的信息。另一个就是深度RNN,我们将在下节介绍。
我们先来看看双向RNN。例子仍然是我们之前讨论的那个NER的问题。下图中所示的矩形框既可以表示标准的RNN块,也可以表示GRU或者LSTM。只要这些模块都是前向的。
那么,BRNN又是什么样子的呢?它的隐藏层有一个前向的循环单元,依次计算各个隐藏单元的输出;同时,它还有一个后向的循环单元。前、后向的隐藏单元都计算完成后,就可以计算预测结果了。同样的,下图中的矩形块可以是基础RNN、GRU或LSTM中的一种。事实上,除了实时的语音识别系统用的是更复杂的网络结构外,对于很多NLP问题,有LSTM单元的BRNN是用的很普遍的。因此,当我们在进行NLP问题时,并且句子都是完整的,就可以使用BRNN,而这也成为RNN的缺点。
1.12 深度RNNs
目前我们所看到的不同版本的RNN都能工作的很好。但在学习非常复杂的函数的时候,把多个RNN堆叠起来构建更深的网络会更有用。本小结将学习如何构建更深的RNN。
下图左边画出了一个标准神经网络,深度RNN和这个有点像,如下图所示。并以
有时还会看到的一种网络结构就是下图中所示的这样,在输出层之前再加几层隐藏层,但是隐藏层之间没有水平连接。这种结构我们会见的多一点。同样的,这些矩阵块可以是基础的RNN、GRU或者LSTM,我们也可以把这个网络构建成双向的。