目录
循环神经网络RNN
1 RNN简介
1.1 什么是RNN?
RNN是利用顺序信息,如果想要预测句子中的下一个单词,需要知道它前面有哪些单词,根据前面的词来预测后面的词。
RNN中发生的计算的公式如下:
:是时间t时的输入。
可以是对应于句子的第二个单词的one_hot向量。
:是时间t时的隐藏状态。这是网络的“记忆”。
基于先前的隐藏状态和当前步骤的输入计算:
。该函数f通常是非线性,例如tanh或ReLU。
计算第一个隐藏状态所需的,通常初始化为全零。
:是时间t时的输出。例如,如果我们想要预测句子中的下一个单词,那么它就是我们词汇表中概率的向量。
这里有几点需要注意:
- 您可以将隐藏状态
视为网络的内存。
捕获有关所有先前时间步骤中发生的事件的信息。步骤输出
仅根据时间内存计算
。如上面简要提到的,它在实践中有点复杂,因为
通常无法从太多时间步骤中捕获信息。
- 与在每层使用不同参数的传统深度神经网络不同,RNN
在所有步骤中共享相同的参数(上图)。这反映了我们在每个步骤执行相同任务的事实,只是使用不同的输入。这大大减少了我们需要学习的参数总数。
- 上图在每个时间步都有输出,但根据任务,这可能不是必需的。例如,在预测句子的情绪时,我们可能只关心最终的输出,而不是每个单词之后的情绪。同样,我们可能不需要每个时间步的输入。RNN的主要特征是其隐藏状态,它捕获有关序列的一些信息。
1.2 RNN可以做什么
- 语言建模与生成文本:预测给定前一个单词的每个单词的概率。在语言建模中,我们的输入通常是一系列单词(例如编码为单热矢量),我们的输出是预测单词的序列。在训练我们设置的网络时,
因为我们希望步骤输出
成为实际的下一个词。
- 机器翻译:输入是源语言中的一系列单词(例如德语)。我们希望以目标语言输出一系列单词(例如英语)。一个关键的区别是我们的输出只在我们看到完整的输入之后才开始,因为我们翻译的句子的第一个单词可能需要从完整的输入序列中捕获的信息。
- 语音识别:给定来自声波的声学信号的输入序列,我们可以预测一系列语音片段及其概率。
- 生成图像描述
1.3 RNN扩展
双向RNN基于以下思想:时间上的输出Ť可能不仅取决于序列中的先前元素,还取决于未来元素。例如,要预测序列中缺少的单词,您需要查看左侧和右侧上下文。双向RNN非常简单。它们只是两个堆叠在一起的RNN。然后基于两个RNN的隐藏状态计算输出。
深度(双向)RNN类似于双向RNN,只是我们现在每个时间步长有多个层。在实践中,这为我们提供了更高的学习能力(但我们还需要大量的培训数据)。
LSTM网络
2实现RNN
代码:https://github.com/dennybritz/rnn-tutorial-rnnlm
2.1 语言模型
我们的目标是使用递归神经网络构建语言模型。这就是这意味着什么。假设我们有单词的句子。语言模型允许我们预测观察句子(在给定数据集中)的概率为:
在上面的等式中,每个单词的概率都取决于所有先前的单词。实际上,由于计算或内存限制,许多模型很难表示这种长期依赖性。它们通常仅限于仅查看前几个单词。
2.2 训练数据和预处理
我们不需要任何标签来训练语言模型,只需要原始文本。我从Google的BigQuery上的数据集中下载了15,000条冗长的评论。
2.2.1 标记文本
我们有原始文本,但希望以每个单词为基础进行预测。这意味着必须将评论标记为句子,将句子标记为单词。可以用空格分割每个注释,但这不能正确处理标点符号。句子“He left!”应该是:“He”,“left”,“!”。
2.2.2 删除不常用单词
文本中的大多数单词只会出现一两次。删除这些不经常的单词是个好主意。我们将词汇量限制为vocabulary_size最常见的单词,我们将所有未包含在词汇表中的单词替换为UNKNOWN_TOKEN。
例如,如果我们的词汇表中没有包含“非线性”这个词,那么句子“非线性在神经网络中很重要”变成“UNKNOWN_TOKEN在神经网络中很重要”。这个词UNKNOWN_TOKEN将成为我们词汇的一部分,我们将像任何其他词一样预测它。当我们生成新文本时,我们可以UNKNOWN_TOKEN再次替换,例如通过采用不在我们词汇表中的随机采样单词,或者我们可以生成句子,直到我们得到一个不包含未知标记的单词。
2.2.3 前置特殊的开始和结束令牌
我们还想知道哪些单词倾向于开始和结束一个句子。为此,我们预先添加一个特殊SENTENCE_START标记,并SENTENCE_END为每个句子添加一个特殊标记。这允许我们问:鉴于第一个标记是SENTENCE_START,可能的下一个单词(句子的实际第一个单词)是什么?
2.2.4 构建训练数据矩阵
我们的递归神经网络的输入是向量,而不是字符串。所以我们在单词和索引之间创建一个映射index_to_word,和word_to_index。例如,单词“友好”可以在索引2001处。训练示例X 是[0, 179, 341, 416],其中0对应于 SENTENCE_START。相应的标签ÿ是[179, 341, 416, 1]。请记住,我们的目标是预测下一个单词,因此y只是x向量移动了一个位置,最后一个元素是SENTENCE_END令牌。
2.3 建立RNN
输入将是一系列单词,每个
都是单个单词,我们将每个单词表示为大小为vocabulary_size的ont_hot。例如,具有索引36的单词将是所有0的向量和位置36处的1。每个
将成为向量,并且
将是矩阵,每行表示单词。
RNN的等式:
假设我们选择词汇量大小和隐藏的图层大小
。可以将隐藏的图层大小视为我们网络的“内存”。使其更大允许我们学习更复杂的模式,但也会导致额外的计算。然后我们有:
和
是我们希望从数据中学习到的网络参数。因此,我们需要学习总共
参数。
2.3.1 初始化
事实证明,最好的初始化依赖于激活功能(),一个推荐的做法是从区间
随机初始化的权重,其中
是来自上一层的传入连接数。只要将参数初始化为小的随机值,通常就可以正常工作。
2.3.2 前向传播
2.3.3 损失函数
我们的目标是找到参数、
并最小化我们的训练数据的损失函数。损失函数的常见选择是交叉熵损失。如果我们有训练样例(在我们的文本中的单词)和
类(我们的词汇量的大小),那么关于我们的预测
和真实标签的损失
由下式给出:
2.3.4 使用SGD和随时间反向传播训练RNN(BPTT)
SGD背后的想法非常简单。我们遍历所有训练样例,并在每次迭代过程中将参数调整到减少误差的方向。这些方向由损失的梯度给出:。SGD还需要学习率,它定义了我们想要在每次迭代中做出多大的步骤。已经有很多关于如何使用批处理,并行性和自适应学习率来优化SGD的研究。
2.3.5渐变检查
每当实现反向传播时,最好还要实现渐变检查,这是一种验证实现是否正确的方法。梯度检查背后的想法是参数的导数等于该点的斜率,我们可以通过稍微改变参数然后除以变化来近似:
然后,我们将使用反向传播计算的梯度与我们使用上述方法估算的梯度进行比较。如果没有大的差异,我们就是好的。
3 理解反向传播时间算法和梯度消失问题
3.1 反向传播时间(BPTT)
回顾一下我们RNN的基本方程:
将损失或误差定义为交叉熵损失,由下式给出:
是时间步的正确单词
,
是我们的预测。我们通常将完整序列(句子)视为一个训练示例,因此总误差只是每个时间步(字)的误差之和。
我们的目标是相对于参数,计算关于参数和
的误差梯度,然后用随机梯度下降学习好的参数。
我们还对一个训练例子,在每一个时间步的梯度进行求和(整个序列的总误差):
例如:
,
是两个向量的外积。我试图解决的问题是,
仅取决于当前时间步的值,
。如果你有这些,计算V的梯度是一个简单的矩阵乘法。但是
(和
)的情况是不同的。
需要注意的是取决于
,依赖于
和
,我们不能简单地将
视为一个常数,需要再次运用链式法则:
我们总结了每个时间步长对梯度的贡献。换句话说,因为每一步的输出都会依赖于,我们需要
通过网络反向传播渐变到
:
3.2 梯度消失
RNNs在学习长期依赖关系方面存在困难——单词之间的交互相隔几个步骤。这是有问题的,因为英语句子的意思往往是由不太接近的词决定的:“头戴假发的人进去了”。这句话实际上是关于一个男人进去,而不是关于假发。但普通的RNN不太可能捕捉到这样的信息。为了理解原因,让我们仔细看看上面计算的梯度:
请注意,这本身就是一个链式规则。例如,
。还要注意,因为我们相对于向量采用向量函数的导数,结果是一个矩阵(称为雅可比矩阵),其元素都是逐点导数。我们可以重写上面的渐变:
可以看到tanh和sigmoid函数在两端的导数都是0。它们接近一条直线。当这种情况发生时,我们说相应的神经元是饱和的。他们有一个零梯度和驱动其他梯度在以前的层接近于零。因此,随着矩阵中值的减小和多次矩阵乘法(特别是t-k),梯度值呈指数级快速收缩,最终在经过几个时间步长后完全消失。来自“遥远”步骤的梯度贡献变为零,而这些步骤的状态对正在学习的内容没有贡献:最终的学习没有长期依赖关系。消失梯度并不只存在于RNNs中。它们也发生在深度前馈神经网络中。只是RNNs往往非常深(在我们的例子中和句子长度一样深),这使得这个问题更加常见。
很容易想象,根据我们的激活函数和网络参数,如果雅可比矩阵的值很大,我们可以得到梯度爆炸而不是梯度消失,这就是所谓的梯度爆炸问题。梯度消失比梯度爆炸受到更多关注的原因有两个方面。首先,梯度爆炸是显而易见的。您的梯度将成为NaN(不是一个数字),您的程序将崩溃。其次,将梯度裁剪到一个预定义的阈值(如本文所讨论的)是一个非常简单和有效的解决梯度爆炸解决方案。渐变消失的问题更大,因为它在什么时候发生或者如何处理并不明显。
幸运的是,有几种方法可以解决消失梯度问题。适当初始化W矩阵可以减小梯度消失的影响,正规化也可以。更可取的解决方案是使用ReLU,而不是tanh或sigmoid 激活函数。ReLU导数是一个常数,要么为0,要么为1,所以它不太可能受到渐变消失的影响。更流行的解决方案是使用长短期内存(LSTM)或门控递归单元(GRU)架构。LSTMs最早于1997年提出,现在可能是NLP中使用最广泛的模型。GRUs于2014年首次提出,是LSTMs的简化版本。这两种RNN结构都被显式地设计用来处理消失的梯度和有效地学习长期依赖关系。
4 LSTM(长期短期记忆)网络和GRU(门控循环单位)
4.1 LSTM网络
在3.2中, 研究了梯度消失问题如何阻止标准RNN学习长期依赖性。LSTM旨在通过门控机制来对抗梯度消失。为了理解这意味着什么,让我们看看LSTM如何计算隐藏状态(
用来表示元素乘法):
这些方程看起来很复杂,但实际上并不那么难。首先,请注意LSTM层只是计算隐藏状态的另一种方法。以前,我们将隐藏状态计算为 。此单元
的输入是步骤中的当前输入
,以及
之前的隐藏状态。输出是一个新的隐藏状态
。LSTM单元完全相同,只是以不同的方式! 这是了解大局的关键。您基本上可以将LSTM(和GRU)单元视为黑盒子。给定当前输入和先前隐藏状态,它们以某种方式计算下一个隐藏状态。
可参考:http://colah.github.io/posts/2015-08-Understanding-LSTMs/ (了解LSTM网络)
(1)分别称为输入门、遗忘门和输出门。注意它们有完全相同的方程,只是参数矩阵不同。它们被称为gates是因为sigmoid函数将这些向量的值压扁在0到1之间,通过将它们与另一个向量相乘,你就定义了想要“通过”的另一个向量的数量。输入门定义了当前输入要通过的新计算状态的大小。遗忘门定义了希望通过的先前状态的大小。最后,输出门定义了希望向外部网络公开多少内部状态(更高的层和下一个时间步骤)。所有的门都具有相同的维度
,即隐藏状态的大小。
(2)是一个“候选”隐藏状态,它是根据当前输入和先前的隐藏状态计算出来的。这和RNN中的方程完全一样,只是把参数
和
重命名为
和
。但是,我们不像在RNN中那样将
作为新的隐藏状态,而是使用上面的输入门来选取其中的一些
(3)是单位的内存储器。它是前一个内存
乘以遗忘门和新计算的隐藏状态
乘以输入门的组合。因此,直观地说,它是我们希望如何组合以前的内存和新输入的组合。我们可以选择完全忽略旧内存(忘记门0)或完全忽略新计算的状态(输入门全0),但很可能我们希望在这两个极端之间有一些东西。
(4)给定内存,最后通过将内存与输出门相乘来计算输出隐藏状态
。并非所有的内部内存都与网络中其他单元使用的隐藏状态相关
直观地说,普通的RNNs可以看作是LSTMs的一种特殊情况。如果你把所有的1都固定在输入门上,把所有的0都固定在忘记门上(你总是忘记之前的内存),把所有的1都固定在输出门上(你暴露了整个内存),你几乎就得到了标准的RNN。只有一个额外的tanh可以稍微压缩输出。门控机制允许LSTMs显式地对长期依赖关系建模。通过学习它的门的参数,网络学习它的记忆应该如何表现。
可参考:https://arxiv.org/pdf/1503.04069.pdf (评估不同的LSTM架构)
4.2 GRUs
GRU层背后的想法与LSTM层非常相似,方程式也是如此。
GRU具有两个门,复位门和更新门
。直观地,复位门确定如何将新输入与先前存储器组合,并且更新门定义要保留多少先前存储器。如果我们将复位门设置为全1并将更新门设置为全0,即为普通RNN模型。使用门控机制来学习长期依赖关系的基本思想与LSTM中的相同,但有一些关键的区别:
(1)GRU有两个门,LSTM有三个门。
(2)GRU不具有暴露的隐藏状态不同的内部存储器。它们没有LSTM中存在的输出门。
(3)输入门和遗忘门通过更新门耦合, 并且复位门
直接应用于先前的隐藏状态。因此,LSTM中复位门的责任实际上分为两个
和
。
(4)在计算输出时,我们不应用第二个非线性
4.3 GRU与LSTM
在许多任务中,两种架构都可以产生相当的性能,而调整超大参数(如层大小)可能比选择理想架构更重要。GRU具有较少的参数(U和W较小),因此可以更快地训练或需要更少的数据来概括。另一方面,如果您有足够的数据,LSTM的表现力更强可能会带来更好的结果。
4.4 实现
代码:https://github.com/dennybritz/rnn-tutorial-gru-lstm
------------------------------------------------------------------------------------------------
参考资料:http://www.wildml.com/2015/09/recurrent-neural-networks-tutorial-part-1-introduction-to-rnns/