目录
1 为什么选择序列模型?(Why Sequence Models?)
2 数学符号(Notation)
3 循环神经网络(Recurrent Neural Network Model)
4 语言模型和序列生成
5 采样
6 循环神经网络的梯度消失
7 GRU(门控循环单元)
9 双向循环神经网络(BRNN)
10 深度循环神经网络(DRNN)
序列模型是深度学习中最精彩的部分之一。循环神经网络(Recurrent Neural Network,RNN)之类的模型在语音识别、自然语言处理和其他领域中引起变革。
我们在进行语音识别时,给定了一个输入音频片段 , 并要求输出对应的文字记录 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 是序列。
对于一个序列数据 ,用符号 来表示这个数据中的第 个元素,用 来表示第 个标签,用 和 来表示输入和输出的长度。对于一段音频,元素可能是其中的几帧;对于一句话,元素可能是一到多个单词。
第 个序列数据的第 个元素用符号,第 个标签即为 。对有 和 。
想要表示一个词语,需要先建立一个词汇表(Vocabulary),或者叫字典(Dictionary)。将需要表示的所有词语变为一个列向量,可以根据字母顺序排列,然后根据单词在向量中的位置,用 one-hot 向量(one-hot vector)来表示该单词的标签:将每个单词编码成一个 向量,其中 |V||V|是词汇表中单词的数量。一个单词在词汇表中的索引在该向量对应的元素为 1,其余元素均为 0。
例如,'zebra'排在词汇表的最后一位,因此它的词向量表示为:
补充:one-hot 向量是最简单的词向量。它的缺点是,由于每个单词被表示为完全独立的个体,因此单词间的相似度无法体现。例如单词 hotel 和 motel 意思相近,而与 cat 不相似,但是
现在来学习如何建立一个模型,建立一个神经网络来学习到 的映射。
对于序列数据,使用标准神经网络存在以下问题:
为了解决这些问题,引入循环神经网络(Recurrent Neural Network,RNN)。一种循环神经网络的结构如下图所示:
当元素 输入对应的时间步 ( TIme Step)的隐藏层的同时,该隐藏层也会接受来自上一时间步的隐藏层的激活值 , 其中 一般直接初始化为零向量。一个时间步输出一个对应的预测结果 .
循环神经网络是从左向右扫描数据,同时每个时间步的参数也是共享的,所以下页幻灯片中我们会详细讲述它的一套参数,我们用 来表示管理着从 到隐藏层的连接的一系列参数,每个时间步使用的都是相同的参数 。而激活值也就是水平联系是由参数 决定的,同时每一个时间步都使用相同的参数 ,同样的输出结果由 决定。
循环神经网络从左向右扫描数据,同时每个时间步的参数也是共享的,输入、激活、输出的参数对应为 。
下图是一个 RNN 神经元的结构:
前向传播过程的公式如下:
激活函数 通常选择 tanh,有时也用 ReLU; 可选 sigmoid 或 softmax,取决于需要的输出类型。
为了进一步简化公式以方便运算,可以将 水平并列为一个矩阵,同时 和 堆叠成一个矩阵。则有:
反向传播
在之前我们已经见过对于前向传播(上图蓝色箭头所指方向)怎样在神经网络中从左到右地计算这些激活项,直到输出所有地预测结果。反向传播地计算方向(上图红色箭头所指方向)与前向传播基本上是相反的。
我们来分析一下前向传播的计算,现在你有一个输入序列, 一直到 然后用 还有 计算出时间步1的激活项,再用 和 计算出,然后计算 等等,一直到 。
为了计算反向传播过程,需要先定义一个损失函数。单个位置上(或者说单个时间步上)某个单词的预测值的损失函数残阳交叉熵损失函数,如下所示:将单个位置上的损失函数相加,得到整个序列的成本函数如下:
循环神经网络的反向传播被称为 通过时间反向传播(Backpropagation through time),因为从右向左计算的过程就像是时间倒流。
RNN反向传播示意图,即更详细的计算公式如下:
不同结构;
某些情况下,输入长度和输出长度不一致。根据所需的输入及输出长度,循环神经网络可分为“一对一”、“多对一”、“多对多”等结构:
目前我们看到的模型的问题是,只使用了这个序列中之前的信息来做出预测,即后文没有被使用。可以通过双向循环神经网络(Bidirectional RNN,BRNN)来解决这个问题。
语言模型(Language Model)是根据语言客观事实而进行的语言抽象数学建模,能够估计某个序列中各元素出现的可能性。例如,在一个语音识别系统中,语言模型能够计算两个读音相近的句子为正确结果的概率,以此为依据作出准确判断。
建立语言模型所采用的训练集是一个大型的语料库(Corpus),指数量众多的句子组成的文本。建立过程的第一步是标记化(Tokenize),即建立字典;然后将语料库中的每个词表示为对应的 one-hot 向量。另外,需要增加一个额外的标记 EOS(End of Sentence)来表示一个句子的结尾。标点符号可以忽略,也可以加入字典后用 one-hot 向量表示。
对于语料库中部分特殊的、不包含在字典中的词汇,例如人名、地名,可以不必针对这些具体的词,而是在词典中加入一个 UNK(Unique Token)标记来表示。
将标志化后的训练集用于训练 RNN,过程如下图所示:
在第一个时间步中,输入的 和 都是零向量, 是通过 softmax 预测出的字典中每个词作为第一个词出现的概率;在第二个时间步中,输入的 是训练样本的标签中的第一个单词 (即“cats”)和上一层的激活项,输出的 表示的是通过 softmax 预测出的、单词“cats”后面出现字典中的其他每个词的条件概率。以此类推,最后就可以得到整个句子出现的概率。
定义损失函数为:
则成本函数为:
在训练好一个语言模型后,可以通过采样(Sample)新的序列来了解这个模型中都学习到了一些什么。
在第一个时间步输入 和 为零向量,输出预测出的字典中每个词作为第一个词出现的概率,根据 softmax 的分布进行随机采样(np.random.choice
),将采样得到的作为第二个时间步的输入 。以此类推,直到采样到 EOS,最后模型会自动生成一些句子,从这些句子中可以发现模型通过语料库学习到的知识。
这里建立的是基于词汇构建的语言模型。根据需要也可以构建基于字符的语言模型,其优点是不必担心出现未知标识(UNK),其缺点是得到的序列过多过长,并且训练成本高昂。因此,基于词汇构建的语言模型更为常用。
基本的RNN算法还有一个很大的问题,就是梯度消失的问题。
举个语言模型的例子,假如看到这个句子(上图编号1所示),“The cat, which already ate ……, was full.”,前后应该保持一致,因为cat是单数,所以应该用was。“The cats, which ate ……, were full.”(上图编号2所示),cats是复数,所以用were。这个例子中的句子有长期的依赖,最前面的单词对句子后面的单词有影响。
之前讨论的训练很深的网络,我们学习了梯度消失的问题。比如说一个很深很深的网络(上图编号4所示),100层,甚至更深,对这个网络从左到右做前向传播然后再反向传播。我们知道如果这是个很深的神经网络,从输出y 得到的梯度很难传播回去,很难影响靠前层的权重,很难影响前面层(编号5所示的层)的计算。
对于有同样问题的RNN,首先从左到右前向传播,然后反向传播。但是反向传播会很困难,因为同样的梯度消失的问题,后面层的输出误差很难影响前面层。的计算。这就意味着,实际上很难让一个神经网络能够意识到它要记住看到的是单数名词还是复数名词,然后在序列后面生成依赖单复数形式的was或者were。而且在英语里面,这中间的内容可以任意长。所以你需要长时间记住单词是单数还是复数,这样后面的句子才能用到这些信息。也正是这个原因,所以基本的RNN模型会有很多局部影响,意味着这个输出主要受附近的值。的影响,上图编号11所示的一个数值主要与附近的输入。上图所示的输出,基本上很难受到序列靠前的输入。的影响,这是因为不管输出是什么,不管是对的,还是错的,这个区域都很难反向传播到序列的前面部分,也因此网络很难调整序列前面的计算。这是基本的RNN算法的一个缺点,如果不管的话,RNN会不擅长处理长期依赖的问题。
尽管我们一直在讨论梯度消失问题,但是,你应该记得我们在讲很深的神经网络时,我们也提到了梯度爆炸,我们在反向传播的时候,随着层数的增多,梯度不仅可能指数型的下降,也可能指数型的上升。事实上梯度消失在训练RNN时是首要的问题,尽管梯度爆炸也是会出现,但是梯度爆炸很明显,因为指数级大的梯度会让你的参数变得极其大,以至于你的网络参数崩溃。所以梯度爆炸很容易发现,因为参数会大到崩溃,你会看到很多NaN,或者不是数字的情况,这意味着你的网络计算出现了数值溢出。如果你发现了梯度爆炸的问题,一个解决方法就是用梯度修剪。梯度修剪的意思就是观察你的梯度向量,如果它大于某个阈值,缩放梯度向量,保证它不会太大,这就是通过一些最大值来修剪的方法。所以如果你遇到了梯度爆炸,如果导数值很大,或者出现了NaN,就用梯度修剪,这是相对比较鲁棒的,这是梯度爆炸的解决方法。然而梯度消失更难解决,这也是我们下几节视频的主题。
我们了解了训练很深的神经网络时,随着层数的增加,导数有可能指数型的下降或者指数型的增加,我们可能会遇到梯度消失或者梯度爆炸的问题。加入一个RNN处理1,000个时间序列的数据集或者10,000个时间序列的数据集,这就是一个1,000层或者10,000层的神经网络,这样的网络就会遇到上述类型的问题。梯度爆炸基本上用梯度修剪就可以应对,但梯度消失比较棘手。我们下节会介绍GRU,门控循环单元网络,这个网络可以有效地解决梯度消失的问题,并且能够使你的神经网络捕获更长的长期依赖。
The cat,which already ate a bunch of food, was full.
The cats,which already ate a bunch of food, were full.
对于以上两个句子,后面的动词单复数形式由前面的名词的单复数形式决定。但是基本的 RNN 不擅长捕获这种长期依赖关系。究其原因,由于梯度消失,在反向传播时,后面层的输出误差很难影响到较靠前层的计算,网络很难调整靠前的计算。
在反向传播时,随着层数的增多,梯度不仅可能指数型下降,也有可能指数型上升,即梯度爆炸。不过梯度爆炸比较容易发现,因为参数会急剧膨胀到数值溢出(可能显示为 NaN)。这时可以采用梯度修剪(Gradient Clipping)来解决:观察梯度向量,如果它大于某个阈值,则缩放梯度向量以保证其不会太大。相比之下,梯度消失问题更难解决。GRU 和 LSTM 都可以作为缓解梯度消失问题的方案。
GRU(Gated Recurrent Units, 门控循环单元)是在基本RNN单元的基础上改进,改善了 RNN 的隐藏层,使其可以更好地捕捉深层连接,并改善了梯度消失问题(无法解决),从而更好的捕捉长距离依赖。
The cat,which already ate a bunch of food, was full.
中间是一个很长的从句,结尾处谓语(be)的形式要根据最开始的主语(cat)的单复数决定。这说明语言中经常会有长距离的依赖。但基本的RNN对这种问题的处理效果并不好,它无法捕捉到这种长距离依赖。
首先,我们给出吴恩达老师画出的基本RNN单元(这里的 和 由原先的、和、堆叠而成,方便表述):
当我们从左到右读上面这个句子时,GRU 单元有一个新的变量称为 ,代表记忆细胞(Memory Cell),其作用是提供记忆的能力,记住例如前例主语cat是单数还是复数等信息。在时间 t,记忆细胞的值 等于输出的激活值 ; 代表下一个 的候选值。 代表更新门(Update Gate),用于决定什么时候更新记忆细胞的值。激活函数使用sigmoid函数,从而使更新门的取值缩放到【0,1】,表示他表示是当前时间步更新记忆细胞 的程度(越接近1,表示更新的程度越大)。以上结构的具体公式为:
当使用 sigmoid 作为激活函数 来得到 时, 的值在 0 到 1 的范围内,且大多数时间非常接近于 0 或 1。当 = 1 时, 被更新为 ,否则保持为 。因为 可以很接近 0,因此 几乎就等于 。在经过很长的序列后,c 的值依然被维持,从而实现“记忆”的功能。
上述公式究竟有什么用呢?前面说 只是候选,是因为决定权在于 ,决定了什么时候去更新.对应上面的例子,这个机制可能就是,从读取到cat开始,就一直在 记录者主语是单数,直到遇到谓语 was,就认为没有必要在记录下去了,就开始更新 的值。
更新门的作用
以上实际上是简化过的 GRU 单元,但是蕴涵了 GRU 最重要的思想。完整的 GRU 单元添加了一个新的相关门(Relevance Gate) ,表示 和 的相关性。因此,表达式改为如下所示:
更新门的结构用图表示如下:
可以看到,GRU仅仅使用了一个门就实现了更新记忆细胞和遗忘记忆细胞,也就是让(1−Γu)控制遗忘的程度。而LSTM中使用了独立的遗忘门(forget gate)来控制遗忘,所以参数更多一些,训练起来也更慢。
通过使用更新门,我们可以选择性的更新记忆细胞(在GRU中就是上一个时间步的隐藏层激活值输出,包含了前面所有时间步内的信息)。当更新门 的值总为0时,相当于保持了记忆,在我们的例子中,就是在谓语was的时间步和主语cat的时间步之间开辟了一条直达的远距离路径。 梯度在这条路径上可以无损地传递,从而不会消失。这有些类似于CNN的 ResNet 中的跳跃连接(short cut)。
此外,完整的GRU单元还设置了一个相关门 (Relevance Gate,也叫重置门Reset Gate) ,用于表示 和 的相关性:
完整的GRU内部结构如下图所示:
8 LSTM(长短期记忆)
LSTM(Long Short Term Memory,长短期记忆)网络比 GRU 更加灵活和强大,可以看作是门控循环单元GRU的复杂版.它额外引入了遗忘门(Forget Gate) 和输出门(Output Gate) 。
GRU 和 LSTM 二者的公式对比
可以看出,GRU的主要特点:
- 总是和 相等.
- 使用了两个门,即更新门 和相关门 .
LSTM 相对于 GRU 的变化在于:
- 和 不相等.
- 使用了三个门:去掉了用于计算候选值 的相关门 ;增加了用于控制遗忘的遗忘门(Forget Gate) , 替换了GRU中的 1- ;增加了用于计算 的输出门 .
LSTM结构图和公式如下:
将多个 LSTM 单元按时间次序连接起来,就得到一个 LSTM 网络。
以上是简化版的 LSTM。在更为常用的版本中,几个门值不仅取决于 和 ,有时也可以偷窥上一个记忆细胞输入的值 ,这被称为**窥视孔连接(Peephole Connection)**。这时,和 GRU 不同, 和门值是一对一的。
常被初始化为零向量。
完整的LSTM网络如下图所示:
可以看到,LSTM网络中,梯度的传播相较于基本RNN多了许多路径。其中,有一条贯穿 的”高速公路“。与CNN的 ResNet 中的跳跃连接(short cut)类似,在这条路径上,假设遗忘门保持等于1,那么梯度将无损地在需要进行远距离依赖的时间步间传输。 由于总的远距离梯度 = 各条路径的远距离梯度之和,即便其他远距离路径梯度消失了,只要保证有一条远距离路径梯度不消失,总的远距离梯度就不会消失(正常梯度 + 消失梯度 = 正常梯度)。因此 LSTM 通过改善一条路径上的梯度问题拯救了总体的远距离梯度。
另外需要强调的是,LSTM除了在结构上天然地克服了梯度消失的问题,更重要的是具有更多的参数来控制模型;通过四倍于RNN的参数量,可以更加精细地预测时间序列变量
GRU与LSTM的比较:
GRU的优点是这是个更加简单的模型,所以更容易创建一个更大的网络,而且它只有两个门,在计算性上也运行得更快,然后它可以扩大模型的规模。
但是LSTM更加强大和灵活,因为它有三个门而不是两个。如果你想选一个使用,我认为LSTM在历史进程上是个更优先的选择,所以如果你必须选一个,我感觉今天大部分的人还是会把LSTM作为默认的选择来尝试。虽然我认为最近几年GRU获得了很多支持,而且我感觉越来越多的团队也正在使用GRU,因为它更加简单,而且还效果还不错,它更容易适应规模更加大的问题.
单向的循环神经网络在某一时刻的预测结果只能使用之前输入的序列信息。双向循环神经网络(Bidirectional RNN,BRNN)可以在序列的任意位置使用之前和之后的数据。
为了了解双向RNN的动机,我们先看一下之前在命名实体识别中已经见过多次的神经网络。这个网络有一个问题,在判断第三个词Teddy(上图编号1所示)是不是人名的一部分时,光看句子前面部分是不够的,为了判断 (上图编号2所示)是0还是1,除了前3个单词,你还需要更多的信息,因为根据前3个单词无法判断他们说的是Teddy熊,还是前美国总统Teddy Roosevelt,所以这是一个非双向的或者说只有前向的RNN。我刚才所说的总是成立的,不管这些单元(上图编号3所示)是标准的RNN块,还是GRU单元或者是LSTM单元,只要这些构件都是只有前向的。
那么一个双向的RNN是如何解决这个问题的?下面解释双向RNN的工作原理。为了简单,我们用四个输入或者说一个只有4个单词的句子,这样输入只有4个, 到 。从这里开始的这个网络会有一个前向的循环单元叫做 还有 我在这上面加个向右的箭头来表示前向的循环单元,并且他们这样连接(下图编号1所示)。这四个循环单元都有一个当前输入x 输入进去,得到预测的 和 。其中 的左箭头表示反向连接。
我们把网络这样向上连接,这个a 反向连接就依次反向向前连接(上图编号2所示)。这样,这个网络就构成了一个无环图。给定一个输入序列 到 ,这个序列首先计算前向的 ,然后计算前向的,接着 , 。而反向序列从计算 开始,反向进行,计算反向的 。你计算的是网络激活值,这不是反向而是前向的传播,而图中这个前向传播一部分计算是从左到右,一部分计算是从右到左。计算完了反向的 ,可以用这些激活值计算反向的 ,然后是反向的 ,把所有这些激活值都计算完了就可以计算预测结果了。
这就是双向循环神经网络,并且这些基本单元不仅仅是标准RNN单元,也可以是GRU单元或者LSTM单元。事实上,很多的NLP问题,对于大量有自然语言处理问题的文本,有LSTM单元的双向RNN模型是用的最多的。所以如果有NLP问题,并且文本句子都是完整的,首先需要标定这些句子,一个有LSTM单元的双向RNN模型,有前向和反向过程是一个不错的首选。这就是双向循环神经网络,并且这些基本单元不仅仅是标准RNN单元,也可以是GRU单元或者LSTM单元。事实上,很多的NLP问题,对于大量有自然语言处理问题的文本,有LSTM单元的双向RNN模型是用的最多的。所以如果有NLP问题,并且文本句子都是完整的,首先需要标定这些句子,一个有LSTM单元的双向RNN模型,有前向和反向过程是一个不错的首选。
双向循环神经网络的工作原理是增加一个反向循环层,结构如下图所示:
因此,有
这个改进的方法不仅能用于基本的 RNN,也可以用于 GRU 或 LSTM。缺点是需要完整的序列数据,才能预测任意位置的结果。例如构建语音识别系统,需要等待用户说完并获取整个语音表达,才能处理这段语音并进一步做语音识别。因此,实际应用会有更加复杂的模块。
一个标准的神经网络,首先是输入 ,然后堆叠上隐含层,所以这里应该有激活值,比如说第一层是 ,接着堆叠上下一层,激活值 ,可以再加一层, 然后得到预测值 。
循环神经网络的每个时间步上也可以包含多个隐藏层,形成**深度循环神经网络(Deep RNN)**。结构如下图所示:
以 为例,有 .
对于像左边这样标准的神经网络,你可能见过很深的网络,甚至于100层深,而对于RNN来说,有三层就已经不少了。由于时间的维度,RNN网络会变得相当大,即使只有很少的几层,很少会看到这种网络堆叠到100层。但有一种会容易见到,就是在每一个上面堆叠循环层,把这里的输出去掉,然后换成一些深的层,这些层并不水平连接,只是一个深层的网络,然后用来预测 。同样这里也加上一个深层网络,然后预测 。这种类型的网络结构用的会稍微多一点,这种结构有三个循环单元,在时间上连接,接着一个网络在后面接一个网络,当然 和 也一样,这是一个深层网络,但没有水平方向上的连接,所以这种类型的结构我们会见得多一点。通常这些单元没必要非是标准的RNN,最简单的RNN模型,也可以是GRU单元或者LSTM单元,并且,你也可以构建深层的双向RNN网络。由于深层的RNN训练需要很多计算资源,需要很长的时间,尽管看起来没有多少循环层,这个也就是在时间上连接了三个深层的循环层,你看不到多少深层的循环层,不像卷积神经网络一样有大量的隐含层。