文章

RNN+LSTM+GRU

RNN+LSTM+GRU

回顾上节

上节课讲的是CNN架构,我们按照时间的顺序跟随ImageNet分类挑战赛中获胜者的脚步,这该算是一种探究方式吧。我们看到的是2012年的AlexNet架构,这是一个九层的卷积网络,它做的很好,它让我们在计算机视觉领域开始了一场深度学习革命,并把很多模型带入了主流。

Desktop View 简单回顾:AlexNet(来自cs231n)

让我们跳过前几年,来到2014年的ImageNet挑战有两个有趣的模型——VGG and GoogleNet。它们的架构变得更深,VGG拥有16层和19层的模型,GoogleNet是一个22层的模型,有趣的一件事是,在2014ImageNet挑战中使用的模型是在批量归一化之前发明的,在批量归一化之前的那个时候,训练这些约20层的深层模型是非常具有挑战性的。实际上,这两种模型都需要借助一些技巧才能使它们收敛。对于VGG,它们有16层和19层的模型,但实际上它们首先训练了一个11层的模型,因为他们能使其收敛,然后在中间添加一些额外的随机层继续训练,便得到了16层和19层的模型。

Desktop View 简单回顾:VGGNet(来自cs231n)

因此,在2014年批量归一化出现之前控制这个训练过程非常困难,GoogleNet也是如此。我们看到GoogleNet有一些辅助分类器,它们被添加在网络的下层,这些都不是为了让类获得更好的分类性能所需要的,这是一种可以将额外的梯度直接注入到网络下层中的方法,这些都是在批量标准化出现之前,一旦网络有了批量标准化,就不再需要这些笨拙的技巧来让这些深层的模型收敛。

然后,我们看到在2015ImageNet挑战中被称作ResNet的很酷的模型,这些残差网络是通过一些快捷连接,也就是小的残差块,我们将输入通过残差块得到输出,然后再添加输入到残差块,从卷积层得到输出,这是一种有趣的架构。实际上它有两个很好的属性,一个是如果我们把残差块中所有权值设为零,那么所有残差块就是恒等的。所以在某种程度上,这个模型是相对容易去训练的,并不需要添加额外的层。另外在神经网络中,添加L2正则化的原因是,一旦你使用了L2正则化,记住网络中的权值将迫使所有的参数趋近于零。也许你的标准卷积架构正趋近于零,也许这有些说不通,但是在残差网络中如果所有的参数逐渐趋向于0,那就是促使模型不再使用。它不需要的层,因为它只是驱使残差块趋向同一性,也就不需要进行分类。另一个非常有用的属性是,残差网络与反向路径中的梯度流有关,如果你还记得在反向传播中加法门的工作原理,当上游梯度经过一个加法门时,它将沿着两个不同的路径。所以当上游梯度到来时,它将通过这些卷积块,但它也会通过这些残差连接直接连接到梯度,所以你可以看到当这些残差块堆叠在一起时,我们的网络最终会有几百个或上千个层,然后这些残差连接提供给梯度一个超级高速公路,使梯度在整个网络中进行反向传播。这使网络可以更容易且更快地训练,实际上,即使网络有几百层的深度也能很好地收敛。

Desktop View 简单回顾:ResNet(来自cs231n)

在机器学习领域中,管理模型的梯度流非常重要。在递归网络中也是很普遍的。所以今天在后面还会再次讨论梯度流的概念。

然后还会看到一些最近新奇的CNN构架,包括DenseNetFractaNet。一旦使用梯度流来思考这些构架,它们就更有意义了。像DenseNetFractaNet,这些模型中都加入了一些额外的快捷或恒等连接,如果你考虑在反向传播中发生了什么,这些额外的有趣的拓扑结构,可以为梯度从网络末端损失层更容易地流向整个网络中的不同层,提供一个直接的路径。所以我认为,在CNN构架中合理地管理梯度流,是我们在过去的几年里看到的最多的东西。随着越来越多的新奇的构架被发明出来,我们将会看到更多的进步。

Desktop View 简单回顾:DenseNet和FractaNet(来自cs231n)

我们也看到了绘制不同模型中浮点运算量、参数数量和运行时间的比较。可以在这个比较中发现一些有趣的特性。一个特性是像VGGAlexNet这样的大型模型拥有大量的参数,而这些参数大部分来自模型中的全连接层,所以AlexNet大概有6200万个参数,如果你看一下AlexNet中最后面的全连接层,激活尺寸从6*6*256变成4096的全连接向量,所以你可以想象一下这一层的权值矩阵,这个权值矩阵是非常巨大的,这个矩阵的元素个数是6*6*256*4096,如果你把它乘出来,你会发现这个单层就有约3800万个参数,因此在这整个AlexNet模型中,一半以上的参数其实只存在于最后那个全连接层中,如果你将所有参数数量加起来,仅仅计算AlexNet全连接层的参数数目,其他的全连接层也都算上,大约有5900万至6200万个参数。在AlexNet中所有全连接层中,当我们对比其他架构,如GoogleNetResNet,这些神经网络没有使用这些大型的全连接层,而是在神经网络的末端使用全局平均池化,这使得神经网络有更好的架构,可以真正大幅降低参数的数量,以及架构中的参数数量。

Desktop View 简单回顾:不同CNN比较(来自cs231n)

RNN、LSTM、GRU

RNN

下面是第十讲,讨论循环神经网络。

在此前的课程中,我们看到了一种被称为vanilla的前馈网络,所有的网络架构都有这种基础架构,会接收一些输入是固定尺寸的对象,比如一幅图片或一个向量,它在通过一些隐层后给出单一的输出结果,如一个分类或针对某组类别的。

Desktop View vanilla 前馈网络(来自cs231n)

Desktop View RNN架构序列:image caption(来自cs231n)

但是在机器学习中,有时我们希望有更加灵活的机器能处理的数据类型。当我们谈到RNN时,就有很大的发挥空间用RNN来处理各种类型的输入和输出数据。一旦使用RNN,我们可以实现1对多的模型,输入是固定尺寸的对象,如一幅图片,但输出是可变长度的序列,如一段文字描述可能造成单词量的不同,因此输出值的长度需要是一个变量。

我们也有多对1的模型,即输入的尺寸是可变的,可能是一段文字,例如想要得出一段文字的情感属性,究竟是积极情感还是消极情感,或者在计算机视觉领域可以输入一个视频,视频的帧数可以是个变量,我们想要把整个视频当做输入,它有着可变的时间长度,最后做出分类决策,例如判断视频中做了什么运动或活动。

Desktop View RNN架构序列:sentiment classification(来自cs231n)

还有一些情况是,我们希望输入和输出尺寸都是可变的,这可能在机器翻译中碰到。输入可能是英文句子,它的长度可变,输出是法语句子,长度也是可变的。关键是英语句子的长度可能与法语句子的长度不同,因此我们需要一些模型能够同时容纳输入和输出的可变长度序列。

Desktop View RNN架构序列:machine translation(来自cs231n)

最后我们可能遇到的情况是,输入是可变长度,例如一段有序的视频,帧数是一个变量,我们要对该序列中的每个元素做出决策。在输入是视频的情况下,这一过程可能是对每一帧都做分类决策,RNN网络就是用于处理大小可变的有序数据的一类模型,使我们可以比较自然地理解模型中所有这些不同的架构。

Desktop View RNN架构序列:video classification on frame level(来自cs231n)

因此,RNN非常重要,即使对有固定输入大小和固定输出大小的问题,RNN也同样很有用。举个例子,我们想要对输入做序列化处理。这里我们收到了一个固定大小的输入,如一幅图像。我们要做出分类决策,即图像中的数字是多少。我们不是做单一的前向传播,然后马上做出决策,而是看看这一图片,观察图片的各种不同部分,然后在完成一组观察后做出最终决策,判断数字到底是几。例如这是1。在这个例子中输入是图片,输出是分类决策。即使在这样的情况下,利用RNN网络来处理可变序列数据,也会形成一些有意思的模型。

Desktop View 利用RNN网络来处理可变序列数据(来自cs231n)

有一篇很酷的论文运用了这一想法,来生成新的图像。我们要让模型来合成看上去像之前训练的那些图片的全新的图片。

Desktop View 利用RNN网络来处理可变序列数据(来自cs231n)

Desktop View 利用RNN网络来处理可变序列数据(来自cs231n)

我们可以用RNN架构来绘制出这些输出图像,大约一次绘制一幅图像。可以看到,虽然输出是固定尺寸的图像,我们可以让这些模型一直工作,每次计算出一部分输出。我们可以用RNN来完成它。

在看完这些应用以及这些RNN能做的事情之后,你可能会问,RNN做的到底是什么?

总体而言,每个RNN网络都有这样一个小小的循环核心单元。它把x作为输入,将其传入RNNRNN有一个内部隐藏态(internal hidden state),这一隐藏态会在RNN每次读取新的输入时更新,然后当模型下一次读取输入时,这一内部隐藏态会将结果反馈至模型。

Desktop View RNN循环核心单元(来自cs231n)

通常来说,我们想让RNN在每一时步都能给出输出,因此就有了这样的模式,它读取输入,更新隐藏态并生成输出。

然后问题就是,这一个循环过程的计算公式是什么?

在这一小小的绿色RNN模块中,我们对某种循环关系用f函数进行了计算,这一f函数依赖权重W,它接受隐藏态ht-1和当前态xt,然后会输出下一个隐藏态,或者更新后的隐藏态,我们称它为ht。接下来当我们读取下一个输入时,这一新的隐藏态ht将再作为输入传入同一个函数,我们同时读取下一个输入xt+1,如果我们想要在网络的每一步都生成一些输出,那么我们可以增加全连接层,每一步都将ht作为输入,根据每一步的隐藏态来做出决策。

Desktop View RNN循环过程的计算公式(来自cs231n)

要注意的一点是,我们用的是同样的函数fw,同样的权重w,每一时步都是如此。

Desktop View RNN循环过程的计算公式(来自cs231n)

所以这几乎是最简单的函数式了,要多简单有多简单,我们称它为vanilla RNN。这里和前一页的函数表达式相同,即读取前一个隐藏态和当下的输入生成下一个隐藏态。然后和你想象的一样简单,我们再用权重矩阵W*h,将其与输入xt相乘,另一个权重矩阵Whh,与前一个隐藏态ht-1相乘。我们将这两部分分别做乘法再相加,然后用tanh函数将结果缩放至-11之间,这样系统中就引入了一些非线性元素。你可能会疑问,为什么这里用tanh,而不用其他非线性函数?在之前的课中我们都说tanh不适用于这一情况,稍后再说。讨论到更高级的架构,如LSTM时再说。

Desktop View vanilla RNN(来自cs231n)

在此架构中,如果我们想在每一时步都生成yt,可能需要另外一个权重矩阵W来接收这一隐藏态,然后将其转换为某个y,每一步都生成例如分类评分的预测。

当我想到RNN时,我会从两方面来思考它。第一,它有一种隐藏态,可以循环反馈给自我。下面这张图会令人有些疑惑。将这张计算图展开更多的时步会更清晰。这样隐藏态里的数据传输流,以及输入、输出、权重的走向就变得更加清晰了。

Desktop View RNN 计算图(来自cs231n)

在第一时步,我们有初始隐层状态h0,通常情况下h0=0。我们有输入项xt,初始隐层状态h0和现在的输入项xt将会被代入fw函数中,计算得出下一个隐层状态h1。在得到下一个输入项后,我们会重复这个过程,现在我们将h1x2代入之前的方程fw,得到新的输出项h2。这个过程将会不断重复,直到用完输入序列中的输入项xt

现在我们可以让这个过程更为清楚一些,即将权重矩阵写在我们的计算流程图上。你会看到我们在每个计算步长中重复使用着相同的权重矩阵,所以我们看到这个fw块,每次都在接收不同的h和不同的x,但这些块都在使用相同的w权重。如果你还记得我们讲过的反向传播中的梯度流,当你在一张计算图中多次重复使用相同的节点,在回溯过程中不断地计算dloss dw,并最终把所有梯度值加到w矩阵上,所以如果你将反向传播的原理应用到这个模型中,你会得到每一个时步下计算出来的梯度。最终的w梯度是所有时步下独立计算出的梯度之和。

Desktop View RNN 计算图(来自cs231n)

同样,可以直接把yt写在这张计算流程图上,这样每个计算步长下输出的ht重新作为输入给之后的神经网络,输出该时步下的ytyt可以是每个时步的类别得分,或是其他类似的东西。

Desktop View RNN 计算图(来自cs231n)

然后来看损失,在大多数情形下,你可以想象在每一个时步下都有一个与输入序列对应的真实标签。这样就可以计算出在每个时步下与输出相对应的损失值。这里的损失通常是softmax损失之类,计算这样的损失需要序列在每个时步下都有与之对应的真实标签。

Desktop View RNN 计算图(来自cs231n)

最终的损失值是这整个训练中这些单独的损失值的总和。现在我们得到了每个时步的损失值,把它们加起来就得到了画面上方的最终的损失值。所以在RNN中反向传播为了训练这个模型,我们需要计算损失函数在w上的梯度,最终的损失值又会回溯到每一个时步的损失。然后每一个时步又会各自计算出在权重w上的梯度,它们的总和就是权重w的最终梯度。

Desktop View RNN 计算图(来自cs231n)

假设有类似这样多对一的情形,比如做诸如情感分析之类的工作,我们通常会根据网络最终的隐层状态做出决策,因为最终隐层状态整合了序列中包含的所有情况。

Desktop View RNN 计算图(来自cs231n)

若是一对多的情况,则会接受固定长的输入项,然后输出不定长的输出项,之后这个固定长的输入项会被初始化为这个模型的初始隐层状态,接着递归神经网络会对输出的单元逐个进行处理。最终会得到不定长的输出序列。输出的每一个元素都得以展现。

Desktop View RNN 计算图(来自cs231n)

那么当我们说到sequence-to-sequence模型,你可能会用其解决如机器翻译之类的问题,这需要输入一个不定长的序列,之后输出一个不定长的序列,可以把它看做是多对一情形与一对多情形的组合,这样就存在两个过程,分别是编码器与解码器。如果现在是在编码器阶段,我们将会接收到一个不定长度的输入序列,可能是一个英语句子,然后整个句子会被编码器网络最终的隐层状态所编码。这是多对一的情形,我们已经把不定长输入编码成了一个单独的向量。

Desktop View Seq2Seq Encoder 计算图(来自cs231n)

接下来是第二部分的解码器网络,这是一对多的情形。它的输入就是前面编码完成的向量,生成的是一个不定长输出序列,可能是用另一种语言表述的相同意思的句子。对于这个不定长输入,我们会在每一个时步下做出预测,比如预测接下来的用词,想象一下把整个训练过程的计算图展开,然后对输出序列的损失求和,像之前一样应用反向传播来训练这个模型。

Desktop View Seq2Seq Decoder 计算图(来自cs231n)

语言模型

它会经常被我们应用到递归神经网络的领域,这个问题就是语言建模。在语言建模问题中,我们想要读取一些语句,从而让神经网络在一定程度上学会生成自然语言,这在字符水平上是可行的,我们让模型逐个生成字符。同样可以在单词层面上让模型逐个生成单词。

举个简单的例子,想象一个字符级的语言模型,网络会读取一串字符序列,然后它需要去预测,这个文本流的下一个字符是什么。在这个例子里,我们有一个很小的字符表helo,以及一个训练序列样例,即单词hello

Desktop View 语言建模(来自cs231n)

在这个语言模型的训练阶段,我们将这个字符序列作为输入项xt,也就是之前提到的输入实例xt,重新输入到我们的递归神经网络中。考虑到每一个输入实例是一个字母,所以需要想办法在神经网络中表示这些字母,实际上我们要做的是找出在神经网络中整个单词的表示方式。在这个例子中,我们的单词由四个字母组成的,每个字母将会由一个其他位置都为零,只有对应单词中字母所在位置的那个元素是1的单位向量来表示。

在这个例子中,因为单词是由四个字母组成helo,在输入序列中我们用四维向量表示字母h,第一空为1,其他都为0。我们也用同样的方式表示输入序列里的其他字母。

Desktop View 语言建模(来自cs231n)

现在随着网络的前向传播,在第一个时步中神经网络会接收到输入h,该输入项会进入到第一个RNN单元内,之后输出yt,即网络对组成单词的每个字母做出的预测,也就是它认为接下来最可能出现的字母。在这个例子中,因为我们在训练的字母序列是hello,那么下一个正确的字母应该是e。但模型在做的只是预测。我想它实际上认为o最有可能是下一个字母。在这种预测错误的情况下,我们可以使用softmax损失函数来度量我们对这些预测结果的不满意程度。接下来的一个时步,我们将给模型输入在训练序列中的第二个字母,也就是e,然后重复执行这个过程。现在我们将e表达为一个向量,利用这个新的输入向量,以及之前计算出的隐层状态,来生成输出一个新的隐层状态,并利用第二个隐层状态,再一次对组成单词的其他字母做出预测。同样的,因为我们的训练序列是hello,在字母e之后我们希望模型做出的预测将是l,在这种情况下,我们的模型可能会认为字母l不太可能出现在下一个位置,这时候高损失就出现了。你不断重复上述的过程,并且如果你用不同的字母序列去训练这个模型,最终它将会学习如何基于之前出现过的字符来预测接下来应出现的字符。

Desktop View 语言建模(来自cs231n)

现在如果我们考虑一下在我们训练完模型后,模型在测试阶段会发生什么?

我们可能想用该模型测试一下样本,并使用这个训练好的神经网络模型去生成新的文本。那种看起来类似于我们训练所使用的文本,所以我们采用的方法是通过输入一些文本的前缀来测试这个模型。在这个实例中,这些前缀只是一个字母h,现在我们在递归神经网络的第一步输入字母h,它会产生一个基于词库中所有的字母得分的分布。现在在训练阶段,我们会使用这些得分去输出一个结果,所以我们使用一个softmax函数,把这些得分转换成一个概率分布,然后我们会从这些概率分布中得到样本输出,去生成序列中的第二个字母。在这种情况下,即使得分情况非常不好,也许我们足够幸运,可以从这个概率分布图中得到字母e,现在我们把字母e,即从概率分布中得到的e,在下一个时间步重新输入到神经网络中,现在我们把顶端的字母eone-hot编码的向量形式重新输入到网络中,然后重复这个过程去生成并输出第二个字母。

Desktop View 语言建模(来自cs231n)

Desktop View 语言建模(来自cs231n)

然后,我们可以一次又一次地重复上述过程去生成一个新的序列,该模型生成序列的过程是基于上一个时间步内预测得到的概率分布,在下一个时间步内生成一个新的字母。

Desktop View 语言建模(来自cs231n)

Q:为什么我们不是输出一个得分最高的字母?

在这种情况下,因为我们基于的是字母的概率分布图,所以我们不可能得到正确的字母,因此我们通过采样来解决这个问题,这个方法能够行得通。但是在实际中,你有时两者都会看到,所以你会选取概率最大的字母。而这种方法有时会更稳定一些,但是一般来说,softmax方法的一个优势在于,它可以让你的模型输出结果多样化,比如你的模型可能有相同的输入,比如相同的前缀,或者在图像标注时使用相同的图像。但是如果你使用概率分布而不是选择得分最大的,那么你会发现这些训练模型实际上可以产生多组不同类型的合理的输出序列,这取决于他们在第一个时间步中的样本,这实际上是一个好处,因为我们的输出结果更加多样了。

Q:在测试阶段,我们是否可以输入一个softmax向量,而不是一个one-hot向量?

A:这存在两个问题,第一个问题是这与训练阶段所使用的数据不同,一般来说,如果你让训练好后的模型在测试阶段去做一些与训练阶段不同的任务,那么模型会产生一些偏差,它通常会输出一些无用的信息,你也会为此感到沮丧。另一个问题是在实际操作中我们的词库可能非常大,而在我们刚刚讲的简单实例中,词库里只有四个元素(字母),所以这个问题的规模不大,但是如果你想一次输出若干个单词,那么你的词库就是英语中所有的单词,而这可能会有数以万计的元素。所以在实际中处理one-hot向量的首选,通常是用稀疏向量,而不是用密集向量。如果你想加载10000个元素的softmax向量,在计算时间上可能会比较长。这就是为什么我们使用one-hot向量,甚至在测试阶段。

假设我们有一个序列,然后每个时间步产生一个输出结果,最后计算一些损失值,这就是沿时间的反向传播方法。因为在前向传播过程中,你在沿着时间做前向计算,然后在误差反向传播过程中,你会逆着时间反向计算所有的梯度,这个误差反向传播过程实际上有些麻烦,如果你想要训练一个很长的序列,比如我们要训练基于维基百科里所有的文本的一个神经网络语言模型,顺便提一下,这个计算过程非常耗时,而且每次我们计算梯度时都必须做一次前向计算,遍历维基百科的所有文本,然后误差反向传播每次也会遍历维基百科的所有文本,并重新计算一次梯度。这个过程非常缓慢,所以模型很难收敛,并且占用非常大的内存,所以这是很糟糕的。

Desktop View 沿时间的反向传播方法(来自cs231n)

在实际应用中,人们通常采用一种近似方法,我们称之为沿时间的截断反向传播方法。这个方法的思想是,即使我们输入的序列很长很长,甚至趋近于无限,我们采用的方法是,在训练模型时前向计算若干步,比如大概100这样的数目,也就是说我们可能会前向计算100步子序列的损失值,然后沿着这个子序列反向传播误差,并计算梯度更新参数。

Desktop View 沿时间的截断反向传播方法(来自cs231n)

现在当我们重复上述过程,仍然会得到网络中的一些隐藏状态,那是我们从第一批数据中计算得到的。现在当我们计算下一批数据时,我们使用这些隐藏状态,所以前向计算过程是相同的。但是现在当我们基于下一批数据计算梯度时,我们只能根据第二批数据反向传播误差。现在我们基于沿时间的截断反向传播法计算一次梯度,这个过程会持续到我们使用下一批数据的时候。

Desktop View 沿时间的截断反向传播方法,重复上述过程(来自cs231n)

我们会复制这些隐藏层的状态值,但是前向计算和反向传播都只是持续一定数量的时间步,所以可以考虑一下序列的梯度下降。记住,当我们在讨论基于大规模数据集时训练模型,对于这些数据集,使用数据集中所有样本来计算梯度的开销非常大,所以我们可以抽取一小部分样本,然后用小样本集的数据来计算在任何图像识别的实例中的梯度。

Desktop View 沿时间的截断反向传播方法,重复上述过程(来自cs231n)

Q:这种方法是在做Mark Hobb假设么?

A:不是的,因为我们在沿着时间使用这些隐藏层的状态值,这是在做Marcovian假设。从某种意义上来说,这是对隐藏状态的假设,但是这个隐藏状态是我们用来预测序列的未来值的。但是这个假设从一开始是基于递归神经网络的计算公式,沿时间反向传播,并不特殊。沿时间截断反向传播算法只是一种近似估计梯度的方法,这种方法不用反向传播遍历,本来非常长的序列。

这听起来很复杂,但实现起来很简单。Andrea做了一个叫做min-char-rnn的实例来实现上述的方法,仅仅用了112Python代码。

Desktop View min-char-rnn(来自cs231n)

它能处理并构建词汇,它使用基于沿时间的截断反向传播算法来训练网络模型,然后可以从该模型计算样本的输出。这不需要很多的代码,所以尽管这听起来像是一个很复杂的过程,但是实际上没有那么难。

一旦我们理解了如何训练一个递归神经网络语言模型,我们可以用它来做很多有意义的事情。我们可以使用任何我们想要的文本,比如从网络上随意摘取一段文本,基于这段文本来训练我们的递归神经网络语言模型,然后生成新的文本。所以在这个实例中,我们摘取了莎士比亚文集中的文本当做数据集,来训练基于莎士比亚文集的递归神经网络语言模型。

Desktop View 训练基于莎士比亚文集的RNN(来自cs231n)

可以看到,在训练的初始阶段产生的是毫无意义的内容,但是随着整个训练过程的进行,最终产生相对能理解的内容。并且在模型已经被训练得非常好之后,它会生成一些看起来非常有莎翁风格的文章,这些可以读出来,而且具有莎士比亚风格。这个模型如果再继续训练下去,让它进一步收敛,然后对一些长句子采样,你能发现它学到了各种精彩的东西,让它看上去真的像一部莎翁的戏剧,它也许知道小标题是用来表示说这些台词的角色,然后生成这些有着精彩对白的文本,这听起来就是莎翁风格。它知道在不同段落之间加入换行符,这很炫酷,这仅仅是从莎士比亚文本的结构中学到的东西。

Desktop View 训练基于莎士比亚文集的RNN(来自cs231n)

下面是一个拓扑数学方面有趣的例子。

Desktop View 拓扑数学的Case(来自cs231n)

Desktop View 拓扑数学的Case(来自cs231n)

下面来拿Linux内核的源代码来寻找一个模型,我们又训练了一个字母级别的模型。又一次我们采样出了这种看起来非常像C代码的结果。知道在if语句之后缩进,知道写注释等等,但有一个问题,虽然知道声明变量,但它不总是使用刚声明的变量,甚至有时会使用从未声明的变量,所以无法编译。

Desktop View linux c代码的Case(来自cs231n)

并且它还考虑逐字背诵GNU证书,它知道需要复写一遍GNU证书,包括证书之后的头文件引用,然后是其他引用,接下来是源代码,它已经很好的掌握了训练数据的通用格式,在训练过程中,我们要求模型去做的是尝试预测句子中的下一个字符,我们没有告诉它任何关于结构的内容,但是不知为何,通过训练的过程,它学会了许多序列化数据的内在结构。

Desktop View linux c代码的Case(来自cs231n)

查找可解释的语义

几年前,我和Andre的这篇论文是关于我们训练的一些类似的模型,然后我们想一窥模型内部的原理,来弄清楚模型的大脑究竟做了什么,怎么做的。我们看到,在循环神经网络RNN中有隐藏的向量,或许每一步都会更新一些向量,我们想要找到这些向量中的某些元素,从中获取可解释的语义,所以我们做的是训练一个语言模型的神经网络,一种字符层级的模型来自这样的训练数据集,然后我们从隐藏向量中选取一个元素来看这个隐藏向量的值。通过一个序列过程可能会获取到一些语义信息和不同隐层状态的样子。

Desktop View 查找可解释的语义(来自cs231n)

你这样做的时候,看起来就像乱七八槽的胡话,

Desktop View 查找可解释的语义(来自cs231n)

再演示一遍是怎么做的。

我们从向量中选择一个元素,然后让句子继续向前运行,通过训练好的模型接下来的每个字符的颜色对应着每个步长之下隐藏向量的单个标量元素的大小。当它读取句子时,你能看到在隐藏状态下的许多向量不是都可以解释的,看起来他们像在做低等级语言建模的过程来判断接下来应该用哪个字母。

但其中一些结果非常不错,这里我们发现了这个向量在找引号,可以看到,这里的一个隐藏元素,这个向量中的一个元素,那里都是蓝色的,一旦找到一个引号,它会激活并在整个上引号之后保持激活状态,接下来当它找到对应的下引号之后,这个神经元会被关闭。所以不知为何,尽管这个模型是被训练用来预测句子中的下一个字符,也会很神奇的学到一些为达到预测目标的其他实用功能,有可能是用到一些神经元来检测双引号。

Desktop View 查找可解释的语义(来自cs231n)

我们也发现像这样,看起来是在统计每个回车段中字符的数量,所以可以看到在每行起始位置这个元素以0开始,在接下来的过程中它会慢慢变得更红,代表着值在递增,然后接下来到新的一行字符时,他又重新变为零。所以可以想象,这个神经元可能让神经网络持续地跟踪什么时候需要书写来生成这些新的换行符。

Desktop View 查找可解释的语义(来自cs231n)

我们还发现了我们在用Linux内核代码训练时,在if语句的条件下发现了一些例子,所以这个可能是让神经网络来判断句子处于if条件内还是条件外,这帮助它来更好的模拟出句子。

Desktop View 查找可解释的语义(来自cs231n)

我们还发现了一些判断是否是注释的内容,还有一些看起来在统计缩进层级的数字,这些都是非常酷的东西,因为这代表着尽管我们在试着训练预测下一个字符的模型,但它最终会学到很多其他关于输入数据的结构的东西。

我们经常用到的到这里已经不是关于计算机视觉的内容了。

图像标注

我们多次提到图像标注模型,我们想训练一个模型用来输入一个图像,然后输出自然语言的图像语义信息,这个问题好多年前有大量的论文都有着相似的方法,但这里展示的是我们实验室的论文,完全不带个人偏见色彩。但这里输出的语义信息可能是可变长度的字符序列,可能不同的标签有不同的字母数量,所以这天生就适合使用循环神经网络模型。

Desktop View 图像标注(来自cs231n)

因此模型看上去应该有一部分卷积网络,用来处理输入的图像信息。然后我们看到还有很多在这个位置工作的卷积神经网络,它们将产生图像的特征向量,然后输入到接下来的循环神经网络语言模型的第一个时序。在训练完模型之后,它会一次一个地产生标题的单词,模型在测试时结果看起来几乎相同,因为这些是我们之前见过的字符级别的语言模型。

Desktop View 图像标注(来自cs231n)

我们将把输入图像通过卷积神经网络,但我们不是使用从一个图像网络模型中得到的softmax分值,而是使用来自模型末端的4096维向量。我们将会用这个向量来概述整个图像的内容。当我讨论递归神经网络语言模型时,我们需要知道模型的第一个初始化输入,来告诉它开始生成文字。在这个例子中,我们会给它一些特殊的开始记号,也就是说,这就是这个句子的开始,请开始生成一些以这个图像信息为条件的文字。

Desktop View 图像标注(来自cs231n)

在之前的递归神经网络语言模型中,我们已经得到了这些矩阵,即把当前时间步的输入,以及前一个时间步的隐藏状态结合得到下一个时间步的隐藏状态,我们现在仍需要添加图片信息。一种方式是用完全不同的方式来整合这些信息,但一个简单的方式是加入第三个权重矩阵,它在每个时间步中添加图像信息来计算下一个隐藏状态。

现在我们将计算词汇表中所有分数的分布,在这里我们的词汇表是类似所有英语词汇的东西,所以它可能会相当大,我们将从分布中采样,并在下一次时间步时当做输入传回,即在(下一个时间步中)输入传回的词汇。然后得到一个关于所有词汇的分布,然后再取样产生下一个词。

Desktop View 图像标注(来自cs231n)

在所有的事情做完之后,我们可以生成完整的句子了。一旦我们采样到特殊停止标记就停止生成。它对应于句子结束的时刻,一旦网络采取这个结束标记,我们停止生成,并且我们已经得到了这个图像的标题。在训练时我们训练它去生成,就像我们在训练时在每个标题末尾都放上结束的标志一样,这样在训练过程中,结束标记就出现在序列的末尾。在测试的时候,它倾向于在完成生成后对这些结束标记进行采样。所以我们采取完全监督的方式训练这个模型,你可以找到带有自然语言标题的图像的数据集。在这种任务中,微软的COCO可能是最大的,也是应用最广泛的数据集。但是你也可以采用纯粹的监督方式来训练这些模型,然后反向传播,共同训练这个递归神经网络语言模型,并且传递梯度到CNN的最后一层,然后更新CNN的权重以调整模型的所有部分来完成此任务。

Desktop View 图像标注(来自cs231n)

Desktop View 图像标注Case(来自cs231n)

一旦训练了这些模型,它们就可以做一些相当合理的事情。这些是一个模型的真实结果,一个经过训练的模型,它说这个是一只猫坐在一个地板上的手提箱上,这是相当令人钦佩的。它知道猫坐在一个树枝上,这也很酷。它知道两个人带着冲浪板在沙滩上走路,所以这些模型真的非常厉害,可以产生比较复杂的标题来描述图片。

但话虽如此,这些模型却不是完美的,它们不是万能的,就像任何机器学习的模型一样。如果你在数据上运行它们,那会与训练数据存在着相当大的不同,它们不能表现的很好。举个例子,它说一个女人手里拿着一只猫,但显然并不是,实际上她穿了一件皮毛大衣,可能是那件大衣的纹理在模型看来像一只猫。后面的几张图片也都是这样。它不是完美的,当你让它们描述与训练数据相似的图片时,它们表现的很好,但是它们确实很难生成与之相差太大的图片的标题。

Desktop View 图像标注失败Case(来自cs231n)

所以,另一个你有时会看到的事是这个稍微高级些的模型,即注意力模型。当我们生成这个标题的文字时,我们允许模型来引导它们的注意到图像的不同部分。

Desktop View 图像标注,注意力模型(来自cs231n)

但是通常的方式是,相比于产生一个单独的向量来整合整个图像,我们的卷积网络倾向于产生由向量构成的网络,即可能给每个图片中特殊的地方都用一个向量表示。

当我们把这个模型向前运行时,除了在每一步中采样,也会产生一个分布,即在图像中它想要看的位置。图像位置的分布可以看成是一种模型在训练过程中应该关注哪里的张量。第一个隐藏状态计算在图片位置上的分布,它将会回到向量集合,给出一个概要矢量,这可能会把注意力集中在图像的一部分上。

Desktop View 图像标注,注意力模型(来自cs231n)

现在这个概要向量得到了反馈,作为神经网络下一时间步的额外输入,它将会产生两个输出。一个是我们在词汇表上的分布,一个是图像位置的分布。

Desktop View 图像标注,注意力模型(来自cs231n)

Desktop View 图像标注,注意力模型(来自cs231n)

整个过程继续下去,它将在每个时间步都会做这两件不同的事情。

Desktop View 图像标注,注意力模型(来自cs231n)

当你训练完模型之后,你可以看到它会在图像中转移注意力,在生成每个图片标题的单词时,你可以看到它生成了一个标题是一只鸟飞过,我不能看到那么远,但是你可以看到每当生成一个标题中的单词时,它的注意力在图片中不同的地方变换。

Desktop View 图像标注,注意力模型(来自cs231n)

这是硬注意力和软注意力的概念。软注意力采用的是加权组合所有图像位置中的所有特征,而在硬注意力的情况下,我们限制模型在每一步只选择一个位置来观察图片。在硬注意力的情况下选择图像的位置有点复杂,因为这不是一个可微函数,所以你需要使用一些比vanilla反向传播算法高级一点的算法,以便能在那样的情况下训练模型,稍后会在增强学习的课程中讲到这个内容。

现在当观察训练之后的其中一个注意力模型,然后运行它来生成标题,你可以看到它在生成一个标题时倾向于集中注意力在可能是显著的或可能有意义的图像中的部分。你可以看到标题是一个女人在公园里扔飞盘,你可以看到这个当它生成词汇时的注意力遮罩,当模型在生成词语飞盘,同时它将注意力集中在这个图像区域上,这实际上包含了飞盘,这真的很酷,我们没有告诉模型在每一时间步中应该往哪里看,它自己把这些在训练过程中都算出来了。它发现看这个图像区域是正确的事情,对这幅图来说,由于这个模型里的所有东西都是可微的,因此我们可以反向传播,通过所有这些软注意力的东西,通过训练全部都出现了,这真的很酷。

Desktop View 图像标注,注意力模型(来自cs231n)

视觉问答

顺便说一下,这个关于递归神经网络和注意力的想法同样适用于其他不是给图像取标题的任务。最近的一个例子是视觉问答。

在这里,我们的模型准备将两个东西作为输入,即一张图像,并且输入一个用自然语言描述的问题,做出一些针对这个图像的提问。这里我们可以看到左边的这幅图,我们会问这样的问题:卡车上的图案是哪个濒危动物?现在模型需要从四个自然语言描述的答案中选一个,也就是根据图像内容选出这个问题的正确答案。

Desktop View 视觉问答(来自cs231n)

Desktop View 视觉问答(来自cs231n)

你可以认为,这是一个将CNNRNN连接起来得到的模型,现在我们讨论的是多对一的情形,我们的模型需要将这个自然语言序列作为输入,我们可以设想针对输入问题的每个元素建立一个递归神经网络,从而将输入的问题概括为一个向量,然后我们可以用CNN将图像也概括为一个向量。现在把CNN得到的向量和输入问题的向量结合,通过RNN编程来预测答案的概率分布。你有时会想到这个想法,将soft special attention结合到视觉问题的回答中,所以在这里可以看到这个模型在试图确定这些问题的答案时,它在图像上仍具有spatial attention

Q:不同的输入如何组合在一起?即如何将编码的问题向量和编码的图像向量组合起来。

A:最简单的一种做法是将它们连接起来,然后粘贴进全连接层中。这是最常见的方法,也是首先应该尝试的方法。有时会使用一些更高级的方法,即在这两个向量之间做乘法,从而得到更为强大的函数。但一般来说,首先应该尝试的是将两个向量连接起来。

总结

现在已经讨论了RNN针对不同问题的一些使用场景,这非常酷,因为它可以通过结合图像和计算机视觉,以及自然语言处理解决很复杂的问题。可以看到把这些模型组合起来,就像乐高玩具的积木一样来处理现实中复杂的问题,比如图像捕捉或者回答视觉问题,仅仅通过将这些相对简单的神经网络模型连接起来。

多层RNN

下面还想指出的是,目前为止,我们讨论的单层递归神经网络只有一种隐藏状态。另一种你经常会见到的是多层递归神经网络,这是一个三层循环神经网络结构。现在把输入传进来,然后在第一层的递归神经网络中产生一系列的隐藏状态,在我们运行了递归神经网络的一层之后,得到了所有的隐藏状态序列。我们可以将这个隐藏状态序列作为第二层递归神经网络的输入序列,之后可以想象一下,下一个隐藏状态序列将如何从递归神经网络的第二层中产生。可以想象一下将这些序列堆叠在各个网络结构的顶部,因为我们知道在其他网络中,更深的模型表现会更好,对各种问题来说都是如此。这个性质同样适用于RNN,对于许多问题,你可以看到两层或三层的递归神经网络模型使用非常普遍。一般不会使用非常深的RNN模型,一般来说使用二层三层或者四层的RNN就已足够。

Desktop View 多层RNN(来自cs231n)

我认为一个很有趣也很重要的值得思考的问题是,我们已经看到这些RNN模型能被用来解决哪些问题,但之后你需要认真地考虑在我们训练它们时,这些模型会发生什么。这里我画出了之前讨论过的vanilla递归神经网络单元,这里我们将Xt作为当前时间步的输入,并且输入前一个隐藏状态h(t-1),然后将这两个向量堆叠起来,我们可以只把他们堆在一起,之后将它与权重矩阵做矩阵乘法来得到一个输出,再将输出送入tan h激活函数,这就得到下一个隐藏状态。这是vanilla递归神经网络的基本函数形式。

Desktop View vanilla递归神经网络的基本函数形式(来自cs231n)

现在我们需要考虑的是,当我们尝试计算梯度时反向传播在这个结构中如何进行。所以在反向传播过程中,我们会得到ht的导数,以及关于ht的损失函数的导数,在反向传播通过这个单元时,我们需要计算关于h(t-1)的损失函数的导数。当我们计算反向传播时,我们可以看到梯度沿着这条红色的路线反向流动,因此梯度会反向流过tanh门,然后会反向流过矩阵乘法门。在课后作业中我们会看到,在实现这些矩阵乘法的层时,当反向传播流过这个矩阵乘法门时,最后实际是用权重矩阵的转置来做矩阵乘法。这意味着每次反向传播经过其中一个vanilla递归神经网络单元时,实际是和部分权重矩阵相乘。

现在你可以设想,我们把许多递归神经网络单元连接成一个序列,因为这是一个递归神经网络,因为我们想要的是一个模型序列,你可以设想梯度流穿过一系列这样的层时会发生什么。之后会有一些不对劲的事情发生,因为当我们要计算关于h0的损失函数的梯度时,反向传播需要经过递归神经网络中的每一个单元,每次反向传播经过一个单元时,都要使用其中某一个W的转置,这意味着最终的表达式对h0的梯度的表达式将会包含很多很多权重矩阵因子,这将会很糟糕。或许你可以不考虑矩阵权重,但假如我们考虑标量,如果我们有一些标量,我们不断地将同一个数值做乘法,不断相乘,可能对四个时间步没问题,但对于有一百或几百个时间步的情况,这样不断对同一个值做乘法是非常糟糕的。在标量的情形中,它要么在这个值绝对值大于1时发生梯度爆炸,要么当这个值绝对值小于1时梯度逐渐消失,减小到0。唯一能够让这不发生的情况是,当这个值恰好为1时,这在实际中很少见。

Desktop View 把许多递归神经网络单元连接成一个序列(来自cs231n)

这让我们可以同样延伸到矩阵的情况,但现在不再是标量的绝对值,你需要关注权重矩阵的最大的奇异值。如果最大奇异值大于1,那么在反向传播时当我们用权重矩阵一个一个相乘时,h0的梯度将会非常非常大。如果这个矩阵非常大,那就是我们称之为梯度爆炸的问题。随着时间步数目的增加,梯度将会随着反向传播流向的深度增加而产生指数级爆炸。如果最大奇异值小于1,情况则相反。这时梯度会缩小,指数级地不断减小,当我们反向传播,不断乘上越来越多乘上权重矩阵因子,这称为梯度消失问题。

Desktop View 延伸到矩阵的情况(来自cs231n)

有个小技巧是大家经常用来解决梯度爆炸问题的方法,称为梯度截断,也就是一种启发式算法。在我们计算梯度后,如果梯度L2范式大于某个阈值就将它剪断并做除法,把它剪断,这样梯度就有最大阈值,这是比较粗暴的方法。但在实际中使用较多的,在训练循环神经网络时,这是一种相对有用的方法来防止发生梯度爆炸问题。

Desktop View 梯度截断(来自cs231n)

而对梯度消失的问题,常见的做法是换一个更加复杂的RNN结构,这就是使用LSTM的原因。

LSTM

LSTM,即长短期记忆网络,是递归神经网络的一种更高级的递归结构。LSTM被设计用来缓解梯度消失和梯度爆炸问题,我们不是直接在输出上想办法,而是设计一些更好的结构来获取更好的梯度流动,可以类比一下我们之前课程中见到过的那些高级的CNN结构。

Desktop View LSTM(来自cs231n)

另一点是LSTM cell,这个想法起源于1997年,所以LSTM这个想法已经非常久远了。人们差不多是在上世纪九十年代就着手研究如何实现这些非常超前的想法。直到20年后的今天,这些模型才开始变得非常流行。这些LSTM的函数形式很有趣,我们之前讲过vanilla递归神经网络,它具备隐藏状态,在每个时间步中利用递归关系来更新隐藏状态。现在考虑LSTM,我们在每个时间步中都维持两个隐藏状态,一个叫ht,就简单叫做隐藏状态,可以类比vanilla递归神经网络中对应的隐藏状态,但是LSTM还有第二个向量ct,叫做单元状态,这个叫做单元状态的向量相当于保留在LSTM内部的隐藏状态,并且不会完全暴露到外部去。我们可以看到通过这个更新公式,也就是说,首先我们可以使用两个输入来计算四个门,即i/f/o/g。我们使用这些门来更新单元状态ct,然后我们将这些单元状态作为参数,来计算下一个时间步中的隐藏状态。

Desktop View LSTM(来自cs231n)

这个函数形式挺有趣的,下面来讲讲为什么用这个结构,还有为什么会这样设计,特别是涉及到梯度消失和梯度爆炸问题。在LSTM中第一件事要做的就是,给定前一时刻的隐藏状态ht和当前时刻的输入向量xt,就像vanilla神经网络一样,在vanilla神经网络中拿这两个输入向量拼接到一起,然后进行矩阵相乘,由此计算得到了RNN中下一个时刻的隐藏状态。

现在LSTM的做法有点不同,我们仍然拿上一时间步的隐藏状态和当前的输入堆叠在一起,然后乘上一个非常大的权重矩阵w,计算得到四个不同的门向量,每个门向量的大小和隐状态都一样,有时候你可能会见到不同的写法,有时候作者们会为每个门都用一个相应的权重矩阵,有时作者又会把这些矩阵结合成一个大的权重矩阵,其实本质都是一样的,都是通过隐藏状态和当前输入来计算四个门,这四个门通常写作i/f/o/g,简称ifog。要记住这四个名字也很容易,i代表输入门,表示LSTM要接受多少新的输入信息。F是遗忘门,表示要遗忘多少之前的单元记忆,就是上一时间步的记忆信息。O是输出门,表示我们要展现多少信息给外部。G没有一个好名字,讲师叫它门之门,G表示我们有多少信息要写到输入单元中去。可以注意到,这四个门都用了不同的非线性函数,输入门、遗忘门和输出门都用了sigmoid,这意味着输出值都在01之间,但是门之门用了tanh函数,这意味着输出都在-11之间。这其实有点怪怪的,但其实是说得通的。

如果你想象这些都是二元的值,想象取到这两个极端的值是什么情景呢?

如果你看看我们算的这些门,如果你看第二条公式,可以看到上一时间步的单元状态是经过了遗忘门的逐元素乘操作,这个遗忘门可以看做都是01的向量,这些值告诉我们对于单元状态中的每个元素,如果遗忘门中的值是零,说明我们想要忘记这个单元状态中的元素值。如果遗忘门中的值是1,说明我们想要记住单元状态中的值。一旦我们使用遗忘门来断开部分单元状态的值,那么我们就需要输入门,即i和g做逐元素乘法,i是由01构成的向量,因为i是经过了一个sigmoid函数得到的,输入门告诉我们,对于单元状态的每个元素值,如果i的值是1,说明我们想要保留单元状态的那个元素,或者如果i的那个位置是0,说明我们不想保留单元状态对应的那个元素。现在考虑门之门,因为这个是tanh函数处理后的结果,所以值都在-11之间,这些值是当前时间步中我们可能会写入到单元状态中去的候选值。如果你看看单元状态的公式,可以看到每个时间步中单元状态都有这些不同的、独立的标量值,在每个时间步中可以被加一或者减去一。就是说在单元状态的内部,我们可以保留或者遗忘之前的状态,在每个时间步中,我们可以给单元状态的每个元素加上或者减去最多是1的值,所以你可以把单元状态的每个元素看做是小的标量计数器,每个时间步中只能自增或者自减,在计算了单元状态ct之后,我们将通过更新过的单元状态来计算隐状态ht,这个向量是要暴露到外部的,因为前面把单元状态解释成计数器,而且每个时间步都是加一或减一,我们想要把这个计数用tanh压缩到01之间,现在用这个输出门逐元素乘上单元状态,当然因为这个输出门是经过sigmoid函数后的结果,所以它的组成元素大部分是0和1,输出门告诉我们,对于单元状态中的每个元素,我们在每个时刻计算外部的隐状态时,到底想不想暴露那个单元状态的元素。

好像有这样一个惯例,就是人们常常会尝试解释LSTM,每个人都会尝试画出自己看着都很迷糊的LSTM示意图。下面这幅图是讲师画的。从图上可以看到LSTM cell的内部发生了什么,也就是说,对于左边所示的上一时间步中的单元状态和隐藏状态,以及当前时间步中的输入xt,我们要把上一时间步的隐藏状态和当前时间步的输入堆积在一起,然后乘上权重矩阵w来得到四个门,这里省略了非线性函数,因为之前的讲义提到过,遗忘门是和上一时间步的单元状态做逐元素乘法,输入门和门之门也是做逐元素乘法,然后加在一起,就得到了下一时间步的单元状态,下一时间步的单元经过一个tanh函数的压缩,又经过输出门的逐元素乘法,得到了下一时间步的隐藏状态。

Desktop View LSTM(来自cs231n)

这些门是从权重矩阵的不同部分来的,如果xh的维度都是h,那么堆在一起就是尺寸为2h的向量,现在我们的权重矩阵的维度大小就是4h*2h,你可以认为是权重矩阵有四块不同的内容,权重矩阵的每块内容都分别用来计算其中一个门,这样写只是为了更清晰,其实是把四个不同的权重矩阵结合成单个大的矩阵w,只是为了符号上的简便,但是这样都是用权重矩阵的不同部分来计算的,但它们都通过使用将两个事物叠加的函数公式和矩阵乘法被计算出来。

可以看下上面的图片,我们可以思考,在反向传播过程中长短期记忆网络单元发生了什么?我们看到在原始循环神经网络的环境中,一些不好的现象在反向传播过程中出现,在这个过程中,我们不断地乘以权值矩阵w,但是现在这个情况在LSTM中看起来确实有一些不同,如果你回想一下在反向传播路径中计算了单元状态的梯度,我们会得到一个漂亮的结果,我们通过传输进来的单元获得了上游梯度,然后通过加法运算向后进行反向传播,记住这个加法运算仅仅是将上游的梯度复制到这两个分支里,这样上游的梯度直接被复制了,并且通过元素相乘的方式直接贯穿了反向传播过程,然后上游的梯度最终通过遗忘门得到相乘后的元素。当我们通过这个单元状态向后反向传播时,对于上游的单元状态梯度唯一会发生的事情,就是它最终会通过遗忘门得到相乘后的元素,

这确实比原始循环神经网络好很多,原因有两个。一个原因是这里的遗忘门是矩阵元素相乘,而不是矩阵相乘。而矩阵元素相乘会比矩阵相乘好一点;第二个原因是矩阵元素相乘,可能会在不同的时间点乘以一个不同的遗忘门,因此要记住在vanilla循环神经网络中,我们会不断地乘以相同的权重矩阵,一遍又一遍。显而易见,这会导致梯度爆炸或者梯度消失,但是在这个LSTM例子中,遗忘门在每一个时间点会发生变化,因此对于这个模型来说,避免出现梯度爆炸或者梯度消失问题会更容易。

最后因为遗忘门是一个sigmoid函数,所以矩阵元素相乘的结果会保证在01之间,这也会使数值性质更好,如果你们想象一下这些矩阵元素一遍又一遍地相乘就会明白。

另一个要注意的就是,在原始循环神经网络环境中,我们看到在反向传播过程中,在每一个梯度传播的时间步长中都会经过一个tanh激活函数,但是现在在一个LSTM中,我们的输出是使用隐藏状态来计算输出yt,因此每一个隐藏状态从最后的隐藏单元状态反向传播到第一个单元状态,在反向传播的路径上我们只通过一个单一的非线性tanh向后传播,而不是在每一个时间步长中单独设置tanh函数。

Desktop View LSTM(来自cs231n)

当你讲上面讲的所有内容联系在一起,你就会发现,通过单元状态进行反向传播的路径是一种梯度高速公路,它使得梯度相对畅通无阻地从模型最末端的损失函数返回到模型最开始的初始单元状态。

Desktop View LSTM(来自cs231n)

Q:那关于w的梯度呢?

A:梯度w的传播方式是,在每一个时间步长中获得当前的单元状态以及当前的隐藏状态,这会给我们在这个时间点的w的局部梯度,所以由于我们的单元状态,这里仅指在vanilla循环神经网络例子中,我们会将这些第一个时间步长w的梯度相加,从而计算出w的最终梯度。但是现在有一个很长的序列,我们仅仅得到序列末尾的梯度,然后进行反向传播,我们会得到每一个时间步长w的局部梯度,而这个w上的局部梯度将会经过ch的梯度,由于在LSTM的例子中我们很好地保存了c的梯度,所以在每一个时间步长的w的局部梯度也会随着时间的推移更加平稳地向前和向后传播。

Q:由于也是非线性的,这是否也会影响梯度消失问题?

A:确实是这样,事实上,你们可能会想这些遗忘门也许总是小于0,或者总是小于1,当持续地让梯度通过这些遗忘门时也许会出现梯度消失现象。人们在训练中使用的一种方法是,他们有时候会初始化遗忘门的偏置参数使其成为达到某种程度的正数,以便在训练开始时这些遗忘门总是非常接近1,至少在训练开始的时候我们并没有这样,由于梯度都被初始化为接近于1,所以经过这些遗忘门的梯度相对简洁,在整个训练的过程中,这个模型会学习这些偏置参数,还会稍微学习一下在哪个地方它需要忘记,在这里确实仍然存在出现梯度消失的可能性,但是相比于vanilla循环神经网络,这个可能性要少得多,这都是因为函数f在每个时间步长中都会发生变化,并且我们进行的是矩阵元素相乘,而不是矩阵相乘。

Desktop View LSTM(来自cs231n)

因此,你们会发现LSTM实际上跟残差网络非常相似,在残差网络中有着一条反向穿过网络的身份认证链接路径,在残差网络中,它为梯度反向传播提供了一条梯度高速公路,现在在LSTM中情况是有些相同的,单元状态的加法和元素乘法一起作用,可以为梯度提供一条相似的梯度高速公路,使得梯度反向通过LSTM中的单元状态。

GRU

顺便提一下,在一篇好的文章里提到过高速公路网络,这种理念介于LSTM单元和残差网络理念之间,这些高速公路网络实际上在残差网络之前出现,而且它们的理念是在高速公路网络的每一层,我们都要计算一个备用的激活函数和一个门函数,这个门函数告诉我们之前在那一层的输入和备用的激活函数之间的关联,激活函数可以是通过卷积得来也可以不是,所以其实这些网络之间有许多结构上的相似之处,人们从训练深度卷积神经网络和深度循环神经网络中得到了很多灵感,这些灵感相互交叉。

Desktop View GRU(来自cs231n)

在自然环境中,你们会看见许多循环神经网络结构的其他类型的变化,其中可能最普遍的除了LSTM,就是GRU,称之为门控循环单元,在这里你们可以看到这些更新的方程,GRU类似于LSTM,在GRU单元中使用元素乘法门和加法一起相互作用,以避免梯度消失问题。

这里还有一篇很酷的文章,叫做LSTM,一个基于搜索的长途冒险行程,非常有创意的标题。这篇文章中,他们尝试去研究LSTM方程,替换掉一个点的非线性特性,就像我们确实需要一个tanh函数来显示输出门一样,而且他们尝试去回答许多不同的问题,这些问题关于每一个非线性特性,关于LSTM更新方程的每一部分,如果我们改变这个模型,并且略微调整这些LSTM方程将会发生什么?一种结论是它们都起着相同的作用,它们当中的一些单元在一个问题上或者另一个问题上比其它的单元性能更好,但一般来说没有一个单元,即他们尝试过的对LSTM的调整,对于解决所有的问题来说,没有一个是明显优于原来的LSTM单元耳朵,所以这会给你一些信心,LSTM更新方程看起来像是不可思议的,但它们不管怎么说都是有用的,你也许也可以用它们来解决你的问题。

得到的一种结论是,可能在这些公式里使用一个LSTM或者GRU并不是那么不可思议,但是通过加法连接和乘法门来管理梯度流的理念,是非常有用的。

总结

总的来说,循环神经网络超级酷。它们可以帮助你解决大量新的问题,它们有时容易受到梯度消失或者梯度爆炸的影响,但是我们可以通过权重剪裁和精妙的网络结果来解决。在卷积神经网络和循环神经网络之间有很多很酷的重叠部分。

Desktop View 总结(来自cs231n)

本文由作者按照 CC BY 4.0 进行授权

© ManShouyuan. 保留部分权利。

本站总访问量 本站访客数人次

🚩🚩🚩🚩🚩🚩