循环神经网络(RNN)是一种神经网络,在一层内包含加权连接(与传统的前馈网络相反,后者的连接仅馈送到后续层)。由于RNN包含循环,因此它们可以在处理新输入的同时存储信息。这种内存使它们非常适合处理必须考虑先前输入的任务(例如时间序列数据)。出于这个原因,当前的深度学习网络都是基于RNN的。本教程探讨了RNN背后的思想,并从头开始实施RNN以对序列数据进行预测。神经网络是一种基于高度连接的处理元件(神经元)网络的计算结构,可将输入映射到输出。要快速了解神经网络,请阅读我的另一篇教程“神经网络剖析”,该教程分析了感知器(神经网络的构建块)和具有反向传播学习的多层感知器。在之前的教程中,我探索了前馈网络拓扑。在这种拓扑结构中(如下图所示),输入向量可以通过隐藏层馈入网络,并在最后获得输出。在此网络中,输入以确定性方式映射到输出(每次应用输入时)但是,假设您正在处理时间序列数据。孤立的单个数据点并不完全有用,因为它缺乏重要的属性(例如,数据系列是否在变化?增长?收缩?)。考虑一个自然语言处理应用程序,其中字母或单词代表网络输入。当您考虑理解单词时,字母在上下文中很重要。这些输入单独使用是没有用的,只有当它们放在之前发生的事件的上下文中时才有用。时间序列数据的应用需要一种新的拓扑结构,可以考虑输入的历史。这时候就可以应用RNN了。RNN能够通过反馈来维持内部记忆,因此它支持时间行为。在下面的示例中,隐藏层输出被应用回隐藏层。网络保持前馈(输入首先应用于隐藏层,然后应用于输出层),但RNN通过上下文节点保持内部状态(影响后续输入的隐藏层)。RNN不是一类网络,而是解决不同问题的拓扑结构的集合。循环网络的一个重要方面是,有足够的层和节点,它们是图灵完备的,这意味着它们可以实现任何可计算的功能。RNN的架构RNN在1980年代被引入,它们保持对过去输入的记忆的能力为神经网络开辟了新的问题领域。让我们看看您可以使用的一些架构。HopfieldHopfield网络是一种联想记忆。给定一个输入模式,它会获取与该输入最相似的模式。这种关联(输入和输出之间的连接)类似于人脑的工作方式。给定一部分记忆,人类可以完全回忆起那段记忆,Hopfield网络的工作原理类似。Hopfield网络本质上是二进制的,单个神经元打开(激活)或关闭(不活动)。每个神经元都通过加权连接与其他所有神经元相连(见下图)。每个神经元都用作输入和输出。在初始化时,部分模式被加载到网络中,每个神经元都会更新,直到网络收敛(它会收敛)。输出在收敛时提供(神经元的状态)。Hopfield网络能够学习(通过赫布学习)多种模式,并在输入中存在噪声的情况下收敛以回忆起最接近的模式。Hopfield网络不适合解决时域问题,但适合解决循环问题。简单循环网络简单循环网络是一类流行的循环网络,它包括将状态引入网络的状态层。.状态层影响下一阶段的输入,因此它可以应用于随时间变化的数据模式。您可以以不同的方式应用状态,两种流行的方式是Elman和Jordan网络(见下图)。在Elman网络中,隐藏层提供上下文节点状态层,该层保留对过去输入的记忆。如下图所示,存在一组上下文节点来保存之前隐藏层结果的记忆。另一种流行的拓扑结构是Jordan网络。Jordan网络不同,因为它们将输出层存储在状态层中,而不是保留隐藏层的历史记录。Elman和Jordan网络可以通过标准的反向传播进行训练,它们在序列识别和自然语言处理方面都有应用。请注意,这里只介绍了一个状态层,但很容易看出您可以添加更多状态层,其中状态层的输出作为后续状态层的输入。本教程在Elman网络部分探讨了这个概念。其他网络对循环网络的研究一直没有停止,今天,循环架构正在为处理时间序列数据设定标准。深度学习中的长短期记忆(LSTM)方法已应用于卷积网络,通过生成的语言来描述图像和视频的内容。LSTM包括一个遗忘门,它允许您“训练”单个神经元了解哪些信息是重要的,以及它保留重要信息的时间。LSTM可以处理重要事件之间间隔较长的数据。另一种复杂的架构称为门控循环单元(GRU)。GRU是LSTM的优化,需要更少的参数和资源。RNN训练算法由于RNN具有合并时间序列或序列中的历史信息的特性,因此具有独特的训练算法。梯度下降算法已成功应用于RNN权重优化(通过缩放与其误差导数成比例的权重来最小化误差)。一种流行的技术是时间反向传播(BPTT),它通过累积序列中每个元素的累积误差的权重更新来应用权重更新,以更新权重。对于大型输入序列,此行为会导致权重消失或爆炸(称为梯度消失或爆炸问题)。为了解决这个问题,通常使用混合方法,将BPTT与实时递归学习等其他算法相结合。其他训练方法也可以成功应用于演化RNN。进化算法,如遗传算法或模拟退火,可用于进化候选RNN的种群,然后根据它们的适应度(即它们解决给定问题的能力)重新组合它们。虽然不能保证收敛到一个解决方案,但收敛可以成功地应用于一系列问题,包括RNN进化。RNN的一个有用应用是预测序列。在下一个示例中,我将构建一个RNN并使用它来预测一个小词汇表中单词的最后一个字母。我将单词输入RNN,一次一个字母,网络的输出将代表预测的下一个字母。遗传算法流程在查看RNN示例之前,让我们先了解一下遗传算法背后的流程。遗传算法是一种受自然选择过程启发的优化技术。如下图所示,该算法创建了一个随机种群的候选解决方案(称为染色体),这些候选解决方案对要找到的解决方案的参数进行编码。一旦它们被创建,人口中的每个成员都会针对相应的问题进行测试并分配一个适应值。然后从种群中识别出亲本染色体(***具有更高适应性的染色体)并为下一代创建子染色体。在这一代子染色体中,应用了遗传算子(例如从每个父染色体中取出元素[称为交叉]并向子染色体引入随机变化[称为变异])。然后用新种群再次开始该过程,直到找到合适的候选解决方案。在染色体集中表示神经网络染色体被定义为群体中的一个成员,其中包含要解决的特定问题的代码。在进化RNN的上下文中,染色体由RNN的权重组成,如下图所示。每个染色体包含每个权重的16位值。通过减去范围的一半并乘以0.001,将此值(介于0–65535之间)转换为权重。这意味着编码可以表示-32.767和32.768之间的值,增量为0.001。对于从种群中获取染色体并生成RNN的过程,只需将其定义为使用从染色体转换而来的权重来初始化网络的权重。在此示例中,这表示233个权重。使用RNN预测字母现在,让我们探索字母在神经网络中的应用。神经网络处理数值,因此需要某种表示形式将字母输入网络。在这个例子中,我使用了one-hot编码。one-hotencoding将一个字母转换为一个向量,向量中只有一个元素集。这种编码创建了一个可以在数学上使用的独特特征——例如,表示的每个字母都在网络中应用了自己的权重。虽然在这个实现中我用单热编码表示字母;自然语言处理应用程序以相同的方式表示单词。下图展示了本例中使用的one-hotvector和用于测试的词汇表。所以,现在我有了一种编码,使我的RNN能够处理字母。现在,让我们看看字母是如何在RNN的上下文中处理的。下图展示了字母预测上下文中的Elman式RNN(提供表示字母b的单热向量)。对于测试单词中的每个字母,我将字母编码为单热代码,然后将其作为输入提供给网络。然后以前馈方式执行网络,并以赢家通吃的方式解析输出以确定定义单热向量的获胜元素(在本例中为字母a)。在这个实现中,只检查单词的最后一个字母,验证时忽略其他字母,并且不对它们执行适应度计算。简单的Elman式RNN实现让我们看一下使用遗传算法训练的Elman式RNN的示例实现。这个实现的Linux源代码可以在GitHub上找到。实现包含3个文件:main.c提供主循环,一个测试函数,一个获取种群适应度的函数ga.c实现遗传算法函数rnn.c实现实际的RNN我会强调两个核心函数:遗传算法过程和RNN评价函数。RNN的核心内容可以在RNN_feed_forward函数中找到,它实现了RNN网络的执行(见下面代码)。该功能分为3个阶段,类似于上图所示的网络。在最后阶段,计算隐藏层的输出,它结合了输入层和上下文层(每个层都有自己的一组权重)。在测试给定单词之前,上下文节点已初始化为0。在第二阶段,我会计算输出层的输出。这一步将每个隐藏层神经元与它们自己独特的权重结合起来。***,在第三阶段,我将第一个上下文层神经元传播到第二个上下文层神经元,并将隐藏层输出传播到第一个上下文节点。这一步在网络中实现了两个记忆层。请注意,在隐藏层中,我使用tan函数作为激活函数,使用sigmoid函数作为输出层中的激活函数。tan函数在隐藏层中很有用,因为它的范围是-1到1(它还允许使用隐藏层的正负输出)。在输出层,我对激活one-hot向量的最大值感兴趣,我使用了sigmoid,因为它的范围是0到1。voidRNN_feed_forward(void){inti,j,k;//Stage1:Calculatehiddenlayeroutputsfor(i=0;i
