打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
针对初学者的循环神经网络介绍

  简单介绍什么是RNN,它们如何运行,以及如何用Python从头构建一个RNN。

  专栏Matplotlib视频教程作者:Python部落10币17人已购查看

  循环神经网络(RNN)是一种专门处理序列的神经网络。它们通常用于自然语言处理(NLP)任务,因为它们在处理文本方面非常有效。在本文中,我们将探索什么是RNN,了解它们是如何工作的,并使用Python从头构建一个真正的RNN(仅使用numpy库)。

  这篇文章假设你有神经网络的基本知识。我对神经网络的介绍涵盖了你需要知道的一切,所以我建议你先读一下(

  victorzhou/blog/intro-to-neural-networks/ )。

  我们开始吧!

  标准神经网络(以及CNN)的一个问题是,它们只能处理预先确定的大小: 它们接受固定大小的输入并产生固定大小的输出。RNN是有用的,因为它允许我们使用可变长度的序列作为输入和输出。下面是一些RNN的例子:

  

  输入为红色,RNN本身为绿色,输出为蓝色。来源:Andrej Karpathy

  这种处理序列的能力使RNN非常有用。例如:

  机器翻译(比如谷歌翻译)是通过“多对多”RNN完成的。原始文本序列被输入一个RNN,然后该RNN会生成翻译文本作为输出。情感分析 (例如,这是一个积极的还是消极的评论?)通常是通过“多对一” 的RNN完成的。要分析的文本被输入一个RNN,然后该RNN会生成一个单独的输出分类(例如,这是一个积极的评论)。

  在本文的后面,我们将从头构建一个“多对一”的RNN来执行基本的情感分析。

  我们假设有一个带有输入x0,x1,...xn的“多对多”的RNN,我们希望它产生输出y0,y1,...yn。这些xi 和yi是向量,可以有任意的维数。

  RNN利用以任意给定的步长t反复地更新一个隐藏状态h来运行,h是一个向量,也可以有任意的维数。

  下一个隐藏状态ht是使用前一个隐藏状态ht -1和下一个输入xt进行计算的。下一个输出yt是使用ht进行计算的。

  一个多对多RNN

  这就是使RNN循环的东西: 它对每个步骤使用相同的权重。具体来说,一个典型的标准RNN只使用3组权重来进行计算:

  Wxh, 用于所有的 xt → ht 连接。Whh, 用于所有的 ht-1 → ht 连接。Why, 用于所有的 ht → yt 连接。

  我们也对我们的RNN使用两个偏差:

  bh,计算ht时加上。by, 计算yt时加上。

  我们用矩阵表示权重,用向量表示偏差。这3个权重和2个偏差就构成了整个RNN!

  下面是把所有东西放在一起的方程式:

  

  不要略过这些方程。停下来,盯着这个方程看一分钟。另外,记住权重是矩阵,其它变量是向量。

  所有的权重都使用矩阵乘法进行应用,并将偏差加到结果乘积中。然后,我们使用tanh作为第一个方程的激活函数(其它激活函数像sigmoid也可以使用)。

  不知道什么是激活函数?请认真阅读我之前提到的神经网络介绍。

  我们来动手干吧!我们将从头实现一个RNN来执行一个简单的情感分析任务: 确定给定的文本字符串是积极的还是消极的。

  下面是我为本文收集的小数据集中的一些例子:

  

  由于这是一个分类问题,我们将使用“多对一" RNN。这与我们前面讨论的“多对多”RNN类似,只不过它只使用最终的隐藏状态来产生一个输出y:

  

  一个多对一RNN

  每个xi都是一个向量,表示文本中的一个单词。输出y将是一个包含两个数字的向量,一个表示积极的,另一个表示消极的。我们将使用Softmax将这些值转换为概率,并最终在积极的/消极的之间进行决定。

  我们来开始构建我们的RNN!

  我前面提到的数据集由两个Python字典组成:

  

  True=积极的,False=消极的

  我们必须做一些预处理才能把数据转换成可用的格式。首先,我们将构造一个包含我们数据中所有单词的词汇表:

  

  vocab现在包含了至少在一个训练文本中出现的所有单词的列表。接下来,我们将分配一个整数索引来表示vocab中的每个单词。

  

  我们现在可以用对应的整数索引表示任意给定的单词!这是必要的,因为RNN不能理解单词——我们必须给它们提供数字。

  最后,回想一下RNN的每个输入xi都是一个向量。我们将使用one-hot向量,它除了包含一个1之外,其余值都是0。每个one-hot向量中的“1”将位于这个单词对应的整数索引处。

  由于我们的词汇表中有18个唯一的单词,每个xi将是一个18维的one-hot向量。

  

  稍后,我们将使用createInputs()来创建向量输入,并将其传入我们的RNN。

  是时候开始实现我们的RNN了!我们将从初始化我们的RNN所需要的3个权重和2个偏差开始:

  

  注意:我们除以1000是为了减小权重的初始方差。这并不是初始化权重的最佳方法,但它很简单,适合本文。

  我们使用np.random.randn()从标准正态分布来初始化我们的权重。

  接下来,我们来实现RNN的正向传递。还记得我们之前看到的这两个方程吗?

  

  下面是这些同样的方程被写入代码中的形式:

  

  很简单,对吧?注意,我们在第一步中将h初始化为零向量,因为此时没有可以供我们使用的前一个h。

  让我们来试试:

  

  如果你需要复习一下Softmax,请阅读我对Softmax的简要说明。

  我们的RNN可以运行,但还不是很有用。我们来改变这一点……

  为了训练我们的RNN,我们首先需要一个损失函数。我们将使用交叉熵损失函数,它通常与Softmax配对使用。我们是这样计算它的:

  

  其中pc 是我们的RNN对正确类(积极的或消极的)的预测概率。例如,如果一个积极的文本被我们的RNN预测为90%的积极度,则损失为:

  

  现在我们有了一个损失,我们将使用梯度下降训练我们的RNN来最小化损失。这意味着是时候推导一些梯度了!

  以下部分假设你有多变量微积分的基本知识。如果你愿意,你可以跳过它,但我建议即使你不太明白也要略读一下。我们将在推导结果时逐步编写代码,即使表面的理解也会有所帮助。

  如果你想了解这部分的额外背景知识,我建议你先阅读我的《神经网络介绍》中的《训练神经网络》部分。此外,这篇文章的所有代码都在Github上,所以如果你愿意,你可以follow它。

  准备好了吗?我们开始吧。

  7.1 定义

  首先,我们来看一些定义:

  让 y代表来自我们RNN的原始输出。让p代表最终的概率:p=softmax(y).让c 指代一个特定文本例子的真实标签,也可以说是“正确的”类。 让L代表交叉熵损失:L=-ln(pc)让Wxh 、Whh 和Why代表我们的RNN中的3个权重矩阵。让bh和by 代表我们的RNN中的两个偏差向量。

  7.2设置

  接下来,我们需要编辑正向阶段来缓存一些数据,以便在反向阶段中使用。在此过程中,我们还将为反向阶段设置骨架。它是这样的:

  

  想知道我们为什么要进行缓存吗?请阅读我在我的CNN介绍的训练概述中的说明。我在其中做了同样的事情。

  7.3梯度

  是用到数学的时候了!我们从计算?L/?y开始。我们知道:

  

  我将把使用链式法则推导?L/?y的过程留给你,但是推导出来的结果是漂亮的:

  

  例如,如果我们有p=[0.2, 0.2, 0.6],并且其正确的类是c=0,那么我们就会得到 ?L/?y=[?0.8,0.2,0.6] 。这转换称代码也是很容易的:

  

  漂亮!下一步,我们来为Why和by尝试一下梯度,它们的梯度只用与将最终的隐藏状态转换为RNN的输出。我们有:

  

  式中,hn是最终的隐藏状态,因此,

  

  类似地,

  

  现在,我们可以开始实现 backprop()!

  

  提醒: 我们之前在forward()中创建了self.last_hs。

  最后,我们需要Whh 、Wxh 和bh的权重,它们将在RNN中的每一步使用。我们有:

  

  因为改变Wxh会影响每一个ht ,而每一个ht都会影响y,并最终影响L。为了完全计算Wxh的梯度,我们需要反向传播所有的步长,这也被称为随时间反向传播(BPTT):

  

  随时间进行的反向传播

  Wxh被用于所有的xt —> ht 正向连接,因此我们必须反向传播回这些古玩论坛连接的每一个。

  一旦我们到达了一个给定的步长t,我们需要计算?ht/?Wxh

  

  tanh的推导是众所周知的:

  

  我们和平常一样使用链式法则:

  

  类似地,

  

  我们需要的最后一个东西是?y/?ht,我们可以递归地计算它:

  

  我们将从最后的隐藏状态开始,并逆向运行来实现BPTT,这样当我们想要计算?y/?ht+1的时候我们就已经有了?y/?ht!最后的隐藏状态hn是一个例外:

  

  现在我们有了最终实现BPTT和完成backprop()所需要的所有东西:

  

  

  一些需要注意的东西:

  为了方便起见,我们已经将(?L/?y)*(?y/?h)合并到 ?L/?h中了。我们需要不断地更新一个保存最近的?L/?ht+1值的变量d_h,我们计算?L/?ht需要用到这个值。在完成BPTT之后,我们使用np.clip()截取小于-1或大于1的梯度值。这有助于缓解梯度爆炸问题,这是因为有很多相乘项时,梯度就会变的非常大。对于普通的RNN来说,梯度爆炸或梯度消失是很有问题的——像LSTM这样更复杂的RNN通常能够很好地处理它们。一旦所有的梯度被计算出,我们就使用梯度下降来更新权重和偏差。

  我们做到了!我们的RNN是完整的。

  终于到了我们一直等待的时刻——我们来测试我们的RNN!

  首先,我们将编写一个辅助函数来使用我们的RNN处理数据:

  

  现在,我们可以编写训练循环:

  

  运行main.py应该会输出如下内容:

  

  从结果来看我们自己建立的RNN还不错。

  想自己尝试或修改这段代码吗?请在浏览器中运行这个RNN(repl.it/@

  vzhou842/A-RNN-from-scratch)。你也可以在Github上找到它。(

  github/vzhou842/rnn-from-scratch)

  就是这样!在这篇文章中,我们完成了一个循环神经网络的一个演示,包括它们是什么,它们是如何工作的,它们为什么有用,如何训练它们,以及如何实现一个。虽然如此,你还有很多事情可以做:

  学习长短期记忆网络,一个更强大更流行的RNN架构,或者学习门控循环单元(GRU),一个著名的LSTM变体。使用合适的ML库(比如Tensorflow、 Keras或 PyTorch)对更大/更好的RNN进行实验。阅读关于双向RNN的内容。它会正向和反向处理序列,因此,有更多的信息对于输出层来说是可用的。尝试词嵌入(比如 GloVe 或 Word2Vec),你可以使用它们将单词转换成更有用的向量表示形式。 尝试自然语言工具集(NLTK),一个流行的处理人类语言数据的Python库。

  我写了很多关于机器学习的文章,所以如果你有兴趣从我这里获得前沿的ML内容,请订阅我的时事通讯。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
NLP中的变形金刚
直观比较四种NLP模型 - 神经网络,RNN,CNN,LSTM
深度学习史上最全总结(文末有福利)
用 Transformer 和 RLHF「炼」大模型,危?
word2vec中的数学模型
用数据做酷的事!手把手教你搭建问答系统
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服