六、TensorFlow 和 Keras 中的 RNN
在涉及有序数据序列的问题中,例如时间序列预测和自然语言处理,上下文对于预测输出非常有价值。可以通过摄取整个序列而不仅仅是最后一个数据点来确定这些问题的上下文。因此,先前的输出成为当前输入的一部分,并且当重复时,最后的输出结果是所有先前输入的结果以及最后一个输入。 循环神经网络(RNN)架构是用于处理涉及序列的机器学习问题的解决方案。
循环神经网络(RNN)是一种用于处理顺序数据的专用神经网络架构。顺序数据可以是一段时间内的观察序列,如时间序列数据,或字符序列,单词和句子,如文本数据。
标准神经网络的一个假设是,输入数据的排列方式是一个输入不依赖于另一个输入。然而,对于时间序列数据和文本数据,该假设不成立,因为序列中稍后出现的值通常受到之前出现的值的影响。
为了实现这一目标,RNN 通过以下方式扩展了标准神经网络:
- 通过在计算图中添加循环或循环,RNN 增加了将一个层的输出用作相同或前一层的输入的特性。
- RNN 添加存储器单元以存储可在当前计算中使用的先前输入和输出。
在本章中,我们将介绍以下有关 RNN 的主题:
- 简单的循环神经网络
- RNN 变种
- LSTM
- GRU
- TensorFlow 中的 RNN
- Keras 中的 RNN
- Keras 中用于 MNIST 数据的 RNN
接下来的两章将介绍在 TensorFlow 和 Keras 中为时间序列和文本(NLP)数据构建 RNN 模型的实际示例。
简单循环神经网络
这是一个带循环的简单神经网络:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3qbF9y6w-1681566456865)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/fe89a986-6079-4b70-b184-39f9b4b5e8bb.png)]RNN Network
在该图中,神经网络N
接受输入x[t]
以产生输出y[t]
。由于循环,在下一步t+1
,输入y[t]
和输入x[t+1]
产生输出y[t+1]
。在数学上,我们将其表示为以下等式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OZnLtkeR-1681566456866)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/e6eb9d40-90e7-4850-bc5b-dc5fd44fa73f.png)]
当我们展开循环时,RNN 架构在时间步t1
看起来如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ljxKhhaR-1681566456867)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/af2b19d9-701b-402e-8fbc-383fe1b045ba.png)]
随着时间步长的发展,这个循环在时间步骤 5 展开如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wF7hYIs7-1681566456867)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/3166287c-3e95-4248-9324-c17d7e4054b7.png)]
在每个时间步骤,使用相同的学习函数φ(·)
和相同的参数,w
和b
。
输出y
并不总是在每个时间步产生。相反,在每个时间步产生输出h
,并且对该输出h
应用另一个激活函数以产生输出y
。 RNN 的等式现在看起来像这样:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TFeVS97S-1681566456868)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/1109a666-62de-4eac-928c-0888b0db98fa.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kxc54JhZ-1681566456868)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/eb66e53f-2c5e-4d4d-96b1-0d6dfd768ba6.png)]
其中,
w(hx)
是连接到隐藏层的x
输入的权重向量w(hh)
是来自前一时间步的h
的值的权重向量w(yh)
是连接隐藏层和输出层的层的权重向量- 用于
h[t]
的函数通常是非线性函数,例如 tanh 或 ReLU
在 RNN 中,在每个时间步使用相同的参数w(hx), w(hh), w(yh), b(h), b(y)
。这一事实大大减少了我们需要学习的基于序列模型的参数数量。
由此, RNN 在时间步t5
如下展开,假设输出y
仅在时间步t5
产生:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YjC4Bma8-1681566456868)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/e3ba641c-7ad9-439e-87d6-f465c428d146.png)]
简单的 RNN 由 Elman 在 1990 年引入,因此它也被称为 Elman 网络。然而,简单的 RNN 无法满足我们今天的处理需求,因此我们将在下一节中了解 RNN 的变体。
阅读 Elman 的原始研究论文,了解 RNN 架构的起源:
J. L. Elman, Finding Structure in Time, Cogn. Sci., vol. 14, no. 2, pp. 179–211, 1990.
RNN 变种
RNN 架构已经以多种方式扩展,以适应某些问题中的额外需求,并克服简单 RNN 模型的缺点。我们列出了下面的 RNN 架构的一些主要扩展。
- 双向 RNN(BRNN)用于输出依赖于序列的前一个和后一个元素的情况。 BRNN 通过堆叠两个 RNN(称为前向和后向层)来实现,输出是前向和后向层 RNN 的隐藏状态的结果。在前向层中,存储器状态 h 从时间步长
t
流向时间步长t + 1
,并且在后向层中,存储器状态从时间步长t
流出。到时间步t-1
。两个层在时间步t
时采用相同的输入x[t]
,但它们在时间步t
共同产生输出。 - 深双向 RNN(DBRNN)通过添加多个层进一步扩展 BRNN。 BRNN 在时间维度上隐藏了层或单元。但是,通过堆叠 BRNN,我们可以在 DBRNN 中获得分层表示。其中一个显着差异是,在 BRNN 中,我们对同一层中的每个单元使用相同的参数,但在 DBRNN 中,我们对每个堆叠层使用不同的参数。
- 长短期记忆(LSTM)网络通过使用涉及多个非线性函数而不是一个简单非线性函数的架构来扩展 RNN 隐藏状态。 LSTM 由称为单元的黑盒组成,取三个输入:时间
t-1
的工作记忆(h[t-1]
),当前输入(x[t]
)和时间t-1
的长期记忆(c[t-1]
),并产生两个输出:更新的工作记忆(h[t]
)和长期记忆(c[t]
)。单元使用称为门的函数来决定从记忆中选择性地保存和擦除的内容。我们在下面的部分中详细描述了 LSTM。
阅读以下关于 LSTM 的研究论文,以获得有关 LSTM 起源的更多信息:
S. Hochreiter and J. Schmidhuber, Long Short-Term Memory, Neural Comput., vol. 9, no. 8, pp. 1735–1780, 1997.http://www.bioinf.jku.at/publications/older/2604.pdf
- 门控循环单元(GRU)网络是 LSTM 的简化变体。 结合遗忘和输入的功能,在更简单的更新门中进行门控。它还将隐藏状态和单元状态组合成一个单一状态。因此,与 LSTM 相比,GRU 在计算上更便宜。 我们在下面的部分中详细描述了 GRU。
阅读以下研究论文以探索 GRU 的更多细节:
K. Cho, B. van Merrienboer, C. Gulcehre, D. Bahdanau, F. Bougares, H. Schwenk, and Y. Bengio, Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation, 2014.https://arxiv.org/abs/1406.1078 J. Chung, C. Gulcehre, K. Cho, and Y. Bengio, Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling, pp. 1–9, 2014. https://arxiv.org/abs/1412.3555
- seq2seq 模型将编码器 - 解码器架构与 RNN 架构相结合。在 Seq2Seq 架构中,模型训练数据序列,例如文本数据或时间序列数据,然后该模型用于生成输出序列。例如,在英文文本上训练模型,然后从模型生成西班牙文本。 Seq2Seq 模型由编码器和解码器模型组成,它们都使用 RNN 架构构建。可以堆叠 Seq2Seq 模型以构建分层多层模型。
LSTM 网络
当 RNN 在很长的数据序列上进行训练时,梯度往往变得非常大或非常小,它们会消失到几乎为零。 长短期记忆(LSTM)网络通过添加用于控制对过去信息的访问的门,来解决消失/爆炸梯度问题。 LSTM 概念最初由 Hochreiter 和 Schmidhuber 在 1997 年引入。
阅读以下关于 LSTM 的研究论文,以获得有关 LSTM 起源的更多信息:
S. Hochreiter and J. Schmidhuber, Long Short-Term Memory, Neural Comput., vol. 9, no. 8, pp. 1735–1780, 1997. http://www.bioinf.jku.at/publications/older/2604.pdf
在 RNN 中,使用重复使用的学习函数φ
的单个神经网络层,而在 LSTM 中,使用由四个主要函数组成的重复模块。构建 LSTM 网络的模块称为单元。 LSTM 单元通过选择性地学习或擦除信息,有助于在长序列通过时更有效地训练模型。组成单元的函数也称为门,因为它们充当传入和传出单元的信息的网守。
LSTM 模型有两种记忆:
- 用
h
(隐藏状态)表示的工作记忆 - 用
c
(单元状态)表示的长期记忆。
单元状态或长期记忆仅在两个线性相互作用下从一个单元流向另一个单元。 LSTM 将信息添加到长期记忆中,或通过门从长期记忆中删除信息。
下图描绘了 LSTM 单元:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lpig1Mys-1681566456869)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/5cd7f05b-1514-45d1-948f-b023de11377d.png)]The LSTM Cell
通过 LSTM 单元中的门的内部流动如下:
- 遗忘门(或记忆门)
f()
:h[t-1]
和x[t]
按照以下等式作为输入流向f()
门:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z90pEAxO-1681566456869)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/925e0a35-3fef-4e56-a3b9-852ee9704a88.png)]
遗忘门的功能是决定忘记哪些信息以及要记住哪些信息。这里使用sigmoid
激活函数,因此输出 1 表示信息被转移到单元内的下一步骤,输出 0 表示信息被选择性地丢弃。 - 输入门(或保存门)
i()
:h[t-1]
和x[t]
按照以下等式作为输入流向i()
门:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EiTFBTIr-1681566456869)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/9b60f5ae-c253-4d41-8896-c3a94db45567.png)]
输入门的功能是决定是保存还是丢弃输入。输入功能还允许单元了解要保留或丢弃的候选存储器的哪个部分。 - 候选长期记忆:候选长期记忆由
h[t-1]
和x[t]
使用激活函数计算,主要是tanh
,按照下式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kqlqNwGF-1681566456870)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/338bafaf-9c59-40d7-9aa4-29b96248c40b.png)] - 接下来,组合前面的三个计算以得到更新长期记忆,由
c[t]
表示,如下式所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-64p0sy5j-1681566456870)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/f244e66e-0b70-49e7-b1c3-dade32143d29.png)] - 输出门(或聚焦/关注门)
o()
:h[t-1]
和x[t]
按照以下等式作为输入流向o()
门:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Aq1iycWF-1681566456870)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/b32dd2e9-bafa-41ae-ab11-0dfbd95f3a02.png)]
输出门的功能是决定多少信息可用于更新工作记忆。 - 接下来,工作记忆
h[t]
从长期记忆c[t]
和焦点/注意力向量更新,如下式所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UTI1H7H3-1681566456871)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/9e296cf4-d44b-4d7c-b6a5-aa4f7afce542.png)]
其中φ(·)
是激活函数,通常是tanh
。
GRU 网络
LSTM 网络的计算成本很高,因此,研究人员发现了一种几乎同样有效的 RNN 配置,称为门控循环单元(GRU)架构。
在 GRU 中,不使用工作和长期记忆,只使用一种记忆,用h
(隐藏状态)表示。 GRU 单元通过复位和更新门,将信息添加到此状态存储器,或从该状态存储器中删除信息。
下图描绘了 GRU 单元(说明如下图):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-skqtBX4M-1681566456871)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/cb5734c0-dc19-4d5d-bf98-eb448f083dd2.png)]The GRU Cell
GRU 单元中通过门的内部流量如下:
- 更新门
u()
:输入h[t-1]
和x[t]
按照以下公式流向u()
门:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0OVUBrEQ-1681566456871)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/772bddac-9209-47d8-a1c4-eb66fe59fe1f.png)] - 复位门
r()
:输入h[t-1]
和x[t]
按照以下公式流向r()
门:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1QFTESE7-1681566456872)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/a0f59f6c-94df-4742-bd5f-5ac3d81540cf.png)] - 候选状态记忆:候选长期记忆是根据
r()
门,h[t-1]
和x[t]
的输出计算出来的,按照下列公式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WF2wwZP8-1681566456872)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/7ce40c11-c131-493c-8ccf-7759863540c0.png)] - 接下来,组合前面的三个计算以得到更新的状态存储器,由
h[t]
,表示,如下式所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jjw0XbEF-1681566456872)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/519ccb8b-513c-4b1a-a1d7-9f48d6c2577b.png)]
阅读以下研究论文以探索 GRU 的更多细节:
K. Cho, B. van Merrienboer, C. Gulcehre, D. Bahdanau, F. Bougares, H. Schwenk, and Y. Bengio, Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation, 2014. https://arxiv.org/abs/1406.1078 J. Chung, C. Gulcehre, K. Cho, and Y. Bengio, Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling, pp. 1–9, 2014. https://arxiv.org/abs/1412.3555
TensorFlow RNN
在低级 TensorFlow 库中创建 RNN 模型的基本工作流程与 MLP 几乎相同:
- 首先创建形状的输入和输出占位符
(None, #TimeSteps, #Features)
或(批量大小, #TimeSteps, #Features)
- 从输入占位符中,创建一个长度为
#TimeSteps
的列表,其中包含形状的张量(None, #特征)
或(批量大小, #特征)
- 从
tf.rnn.rnn_cell
模块创建所需 RNN 类型的单元 - 使用先前创建的单元和输入张量列表来创建静态或动态 RNN
- 创建输出权重和偏差变量,并定义损失和优化器函数
- 对于所需的周期数,使用损失和优化器函数训练模型
这个基本工作流程将在下一章的示例代码中演示。让我们看看可用于支持先前工作流程的各种类。
TensorFlow RNN 单元类
tf.nn.rnn_cell
模块包含以下用于在 TensorFlow 中创建不同类型单元的类:
tf.contrib.rnn
模块提供以下额外的类用于在 TensorFlow 中创建不同类型的单元:
TensorFlow RNN 模型构建类
TensorFlow 提供了从 RNN 单元对象创建 RNN 模型的类。静态 RNN 类在编译时为时间步骤添加展开的单元,而动态 RNN 类在运行时添加展开的单元用于时间步长。
tf.nn.static_rnn
tf.nn.static_state_saving_rnn
tf.nn.static_bidirectional_rnn
tf.nn.dynamic_rnn
tf.nn.bidirectional_dynamic_rnn
tf.nn.raw_rnn
tf.contrib.rnn.stack_bidirectional_dynamic_rnn
TensorFlow RNN 单元包装器类
TensorFlow 还提供包装其他单元类的类:
tf.contrib.rnn.LSTMBlockWrapper
tf.contrib.rnn.DropoutWrapper
tf.contrib.rnn.EmbeddingWrapper
tf.contrib.rnn.InputProjectionWrapper
tf.contrib.rnn.OutputProjectionWrapper
tf.contrib.rnn.DeviceWrapper
tf.contrib.rnn.ResidualWrapper
有关 TensorFlow 中 RNN 的最新文档,请访问此链接。
Keras 中的 RNN
与 TensorFlow 相比,在 Keras 中创建 RNN 要容易得多。正如您在第 3 章中学到的,Keras 提供了用于创建循环网络的函数式和顺序 API。要构建 RNN 模型,您必须从kera.layers.recurrent
模块添加层。 Keras 在keras.layers.recurrent
模块中提供以下类型的循环层:
SimpleRNN
- LSTM
- GRU
有状态模型
Keras 循环层还支持 RNN 模型,可在批次之间保存状态。您可以通过将stateful
参数作为True
传递来创建有状态 RNN,LSTM 或 GRU 模型。对于有状态模型,为输入指定的批量大小必须是固定值。在有状态模型中,从训练批次中学到的隐藏状态将重新用于下一批。如果您想在训练期间的某个时刻重置记忆,可以通过调用model.reset_states()
或layer.reset_states()
函数使用额外的代码来完成。
我们将在下一章中看到使用 Keras 构建 RNN 的示例。
RNN 的应用领域
RNN 更频繁使用的一些应用领域如下:
- 自然语言模型:RNN 模型已用于自然语言处理(NLP),用于自然语言理解和自然语言生成任务。在 NLP 中,RNN 模型被给予一系列单词并且它预测另一个单词序列。因此,训练的模型可用于生成单词序列,称为文本生成的字段。例如,生成故事和剧本。 NLP 的另一个领域是语言翻译,其中给定一种语言的一系列单词,该模型预测另一种语言的单词序列。
- 语音和语音识别:RNN 模型非常适用于构建模拟音频数据的模型。在语音识别中,RNN 模型被给予音频数据并且它预测一系列语音片段。它可用于训练模型以识别语音命令,甚至用于与基于语音的聊天机器人的对话。
- 图像/视频描述或字幕生成:RNN 模型可与 CNN 结合使用,以生成图像和视频中找到的元素的描述。这些描述也可用于生成图像和视频的标题。
- 时间序列数据:最重要的是,RNN 对时间序列数据非常有用。大多数传感器和系统生成时间顺序很重要的数据。 RNN 模型非常适合于查找模式和预测此类数据。
通过此链接了解有关 RNN 的更多信息:
http://karpathy.github.io/2015/05/21/rnn-effectiveness/
http://colah.github.io/posts/2015-08-Understanding-LSTMs/
http://www.wildml.com/2015/09/recurrent-neural-networks-tutorial-part-1-introduction-to-rnns/
https://r2rt.com/written-memories-understanding-deriving-and-extending-the-lstm.html
Keras 中的用于 MNIST 数据的 RNN
虽然 RNN 主要用于序列数据,但它也可用于图像数据。我们知道图像具有最小的两个维度 - 高度和宽度。现在将其中一个维度视为时间步长,将其他维度视为特征。对于 MNIST,图像大小为28 x 28
像素,因此我们可以将 MNIST 图像视为具有 28 个时间步长,每个时间步长具有 28 个特征。
我们将在下一章中提供时间序列和文本数据的示例,但让我们为 Keras 中的 MNIST 构建和训练 RNN,以快速浏览构建和训练 RNN 模型的过程。
您可以按照 Jupyter 笔记本中的代码ch-06_RNN_MNIST_Keras
。
导入所需的模块:
import keras from keras.models import Sequential from keras.layers import Dense, Activation from keras.layers.recurrent import SimpleRNN from keras.optimizers import RMSprop from keras.optimizers import SGD
获取 MNIST 数据并将数据从 1D 中的 784 像素转换为 2D 中的28 x 28
像素:
from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets(os.path.join(datasetslib.datasets_root, 'mnist'), one_hot=True) X_train = mnist.train.images X_test = mnist.test.images Y_train = mnist.train.labels Y_test = mnist.test.labels n_classes = 10 n_classes = 10 X_train = X_train.reshape(-1,28,28) X_test = X_test.reshape(-1,28,28)
在 Keras 构建SimpleRNN
模型:
# create and fit the SimpleRNN model model = Sequential() model.add(SimpleRNN(units=16, activation='relu', input_shape=(28,28))) model.add(Dense(n_classes)) model.add(Activation('softmax')) model.compile(loss='categorical_crossentropy', optimizer=RMSprop(lr=0.01), metrics=['accuracy']) model.summary()
该模型如下:
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= simple_rnn_1 (SimpleRNN) (None, 16) 720 _________________________________________________________________ dense_1 (Dense) (None, 10) 170 _________________________________________________________________ activation_1 (Activation) (None, 10) 0 ================================================================= Total params: 890 Trainable params: 890 Non-trainable params: 0 _________________________________________________________________
训练模型并打印测试数据集的准确率:
model.fit(X_train, Y_train, batch_size=100, epochs=20) score = model.evaluate(X_test, Y_test) print('\nTest loss:', score[0]) print('Test accuracy:', score[1])
我们得到以下结果:
Test loss: 0.520945608187 Test accuracy: 0.8379
总结
在本章中,我们了解了循环神经网络(RNN)。我们了解了 RNN 的各种变体,并详细描述了其中的两个:长短期记忆(LSTM)网络和门控循环单元(GRU)网络。我们还描述了可用于在 TensorFlow 和 Keras 中构建 RNN 单元,模型和层的类。我们构建了一个简单的 RNN 网络,用于对 MNIST 数据集的数字进行分类。
在下一章中,我们将学习如何构建和训练时间序列数据的 RNN 模型。
七、TensorFlow 和 Keras 中的用于时间序列数据的 RNN
时间序列数据是一系列值,以不同的时间间隔记录或测量。作为序列,RNN 架构是从这些数据训练模型的最佳方法。在本章中,我们将使用示例时间序列数据集来展示如何使用 TensorFlow 和 Keras 构建 RNN 模型。
我们将在本章中介绍以下主题:
- 航空公司乘客(
airpass
)时间序列数据集:
- 描述和下载数据集
- 可视化数据集
- 在 TensorFlow 中预处理 RNN 的数据集
- TensorFlow 中用于时间序列数据的 RNN:
- TensorFlow 中的
SimpleRNN
- TensorFlow 中的 LSTM
- TensorFlow 中的 GRU
- 在 Keras 中为 RNN 预处理数据集
- Keras 中用于时间序列数据的 RNN:
- Keras 的
SimpleRNN
- Keras 的 LSTM
- Keras 的 GRU
让我们从了解样本数据集开始。
您可以按照 Jupyter 笔记本中的代码ch-07a_RNN_TimeSeries_TensorFlow
。
航空公司乘客数据集
为了简洁起见,我们选择了一个名为国际航空公司乘客(航空通票)的非常小的数据集。该数据包含从 1949 年 1 月到 1960 年 12 月的每月总乘客数量。数据集中的数字是指数千的数量。该数据集最初由 Box 和 Jenkins 在 1976 年的工作中使用。它作为 时间序列数据集库(TSDL)的一部分与 Rob Hyndman 教授的各种其他时间序列数据集一起收集。在澳大利亚莫纳什大学。后来,TSDL 被转移到 DataMarket。
加载 airpass 数据集
我们将数据集保存为数据集根目录(~/datasets
)中ts-data
文件夹中的 CSV 文件,并使用以下命令将数据加载到 pandas 数据框中:
filepath = os.path.join(datasetslib.datasets_root, 'ts-data', 'international-airline-passengers-cleaned.csv' ) dataframe = pd.read_csv(filepath,usecols=[1],header=0) dataset = dataframe.values dataset = dataset.astype(np.float32)
从 NumPy 数组中的数据框中提取值并转换为np.float32
:
dataset = dataframe.values dataset = dataset.astype(np.float32)
可视化 airpass 数据集
让我们看一下数据集的外观:
plt.plot(dataset,label='Original Data') plt.legend() plt.show()
airpass
数据集的图如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xsq0rwa0-1681566456872)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/305f4a94-92a4-4164-9ec2-473cc7225413.png)]Airline Passengers Dataset
用于 TensorFlow RNN 模型的数据集预处理
为了使其为学习模型做好准备,通过应用 MinMax 缩放来正则化数据集,该缩放使数据集值介于 0 和 1 之间。您可以尝试根据数据的性质对数据应用不同的缩放方法。
# normalize the dataset scaler = skpp.MinMaxScaler(feature_range=(0, 1)) normalized_dataset = scaler.fit_transform(dataset)
我们使用自己开发的实用函数将数据集拆分为训练和测试数据集。必须拆分数据而不对数据集进行混洗,因为改组数据集会破坏序列。维护数据序列对于训练时间序列模型非常重要。
train,test=tsu.train_test_split(normalized_dataset,train_size=0.67)
然后我们将训练和测试数据集转换为有监督的机器学习集。让我们试着理解监督学习集的含义。假设我们有一系列数据:1,2,3,4,5
。我们想要了解生成数据集的概率分布。为了做到这一点,我们可以假设时间步长t
的值是从时间步长t-1
到tk
的值的结果,其中k
是窗口大小。为简化起见,假设窗口大小为 1。因此,时间步长t
的值(称为输入特征)是时间步长值t-1
的结果,被称为目标。让我们重复一遍所有时间步骤,我们得到下表:
输入值或特征 | 输出值或目标 |
1 | 2 |
2 | 3 |
3 | 4 |
4 | 5 |
我们展示的示例只有一个变量值,它将转换为特征和目标。当目标值取决于一个变量时,它被称为单变量时间序列。同样的逻辑可以应用于多变量时间序列,其中目标取决于多个变量。我们使用x
来表示输入特征,使用y
来表示输出目标。
考虑到这一背景,为了将airpass
数据转换为监督机器学习数据,我们设置了以下超参数:
- 设置用于学习或预测下一个时间步的过去时间步数:
n_x=1
- 设置学习或预测的未来时间步长的数量:
n_y=1
- 设置用于学习的
x
变量的数量;由于当前示例是单变量的,因此设置为 1:
n_x_vars = 1
- 设置要预测的
y
变量的数量;由于当前示例是单变量的,因此设置为 1:
n_y_vars = 1
- 最后,我们通过应用本节开头所述的逻辑将训练和测试数据集转换为
X
和Y
集:
X_train, Y_train, X_test, Y_test = tsu.mvts_to_xy(train, test,n_x=n_x,n_y=n_y)
现在数据已经过预处理并可以输入到我们的模型中,让我们使用 TensorFlow 准备一个SimpleRNN
模型。
TensorFlow 中的简单 RNN
在 TensorFlow 中定义和训练简单 RNN 的工作流程如下:
- 定义模型的超参数:
state_size = 4 n_epochs = 100 n_timesteps = n_x learning_rate = 0.1
这里新的超参数是state_size
。state_size
表示 RNN 单元的权重向量的数量。
- 为模型定义
X
和Y
参数的占位符。X
占位符的形状为(batch_size, number_of_input_timesteps, number_of_inputs)
,Y
占位符的形状为(batch_size, number_of_output_timesteps, number_of_outputs)
。对于batch_size
,我们使用None
,以便我们以后可以输入任意大小的批次。
X_p = tf.placeholder(tf.float32, [None, n_timesteps, n_x_vars], name='X_p') Y_p = tf.placeholder(tf.float32, [None, n_timesteps, n_y_vars], name='Y_p')
- 将输入占位符
X_p
转换为长度等于时间步数的张量列表,在此示例中为n_x
或 1:
# make a list of tensors of length n_timesteps rnn_inputs = tf.unstack(X_p,axis=1)
- 使用
tf.nn.rnn_cell.BasicRNNCell
创建一个简单的 RNN 单元:
cell = tf.nn.rnn_cell.BasicRNNCell(state_size)
- TensorFlow 提供
static_rnn
和dynamic_rnn
便利方法(以及其他方法)分别创建静态和动态 RNN。创建静态 RNN:
rnn_outputs, final_state = tf.nn.static_rnn(cell, rnn_inputs, dtype=tf.float32 )
静态 RNN 在编译时创建单元,即展开循环。动态 RNN 创建单元,即在运行时展开循环 。在本章中,我们仅展示了static_rnn
的示例,但是一旦获得静态 RNN 的专业知识,就应该探索dynamic_rnn
。
static_rnn
方法采用以下参数:
cell
:我们之前定义的基本 RNN 单元对象。它可能是另一种单元,我们将在本章中进一步看到。rnn_inputs
:形状(batch_size, number_of_inputs)
的张量列表。dtype
:初始状态和预期输出的数据类型。
- 定义预测层的权重和偏差参数:
W = tf.get_variable('W', [state_size, n_y_vars]) b = tf.get_variable('b', [n_y_vars], initializer=tf.constant_initializer(0.0))
- 将预测层定义为密集线性层:
predictions = [tf.matmul(rnn_output, W) + b \ for rnn_output in rnn_outputs]
- 输出 Y 是张量的形状;将其转换为张量列表:
y_as_list = tf.unstack(Y_p, num=n_timesteps, axis=1)
- 将损失函数定义为预测标签和实际标签之间的均方误差:
mse = tf.losses.mean_squared_error losses = [mse(labels=label, predictions=prediction) for prediction, label in zip(predictions, y_as_list) ]
- 将总损失定义为所有预测时间步长的平均损失:
total_loss = tf.reduce_mean(losses)
- 定义优化器以最小化
total_loss
:
optimizer = tf.train.AdagradOptimizer(learning_rate).minimize(total_loss)
- 现在我们已经定义了模型,损耗和优化器函数,让我们训练模型并计算训练损失:
with tf.Session() as tfs: tfs.run(tf.global_variables_initializer()) epoch_loss = 0.0 for epoch in range(n_epochs): feed_dict={X_p: X_train.reshape(-1, n_timesteps, n_x_vars), Y_p: Y_train.reshape(-1, n_timesteps, n_x_vars) } epoch_loss,y_train_pred,_=tfs.run([total_loss,predictions, optimizer], feed_dict=feed_dict) print("train mse = {}".format(epoch_loss))
我们得到以下值:
train mse = 0.0019413739209994674
- 让我们在测试数据上测试模型:
feed_dict={X_p: X_test.reshape(-1, n_timesteps,n_x_vars), Y_p: Y_test.reshape(-1, n_timesteps,n_y_vars) } test_loss, y_test_pred = tfs.run([total_loss,predictions], feed_dict=feed_dict ) print('test mse = {}'.format(test_loss)) print('test rmse = {}'.format(math.sqrt(test_loss)))
我们在测试数据上得到以下 mse 和 rmse(均方根误差):
test mse = 0.008790395222604275 test rmse = 0.09375710758446143
这非常令人印象深刻。
这是一个非常简单的例子,只用一个变量值预测一个时间步。在现实生活中,输出受到多个特征的影响,并且需要预测不止一个时间步。后一类问题被称为多变量多时间步进预测问题。这些问题是使用循环神经网络进行更好预测的积极研究领域。
现在让我们重新调整预测和原始值并绘制原始值(请在笔记本中查找代码)。
我们得到以下绘图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GlhgKgww-1681566456873)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/b83815e7-8ee4-469a-a23a-8a71b9c46b30.png)]
令人印象深刻的是,在我们的简单示例中,预测数据几乎与原始数据相匹配。对这种准确预测的一种可能解释是,单个时间步的预测基于来自最后一个时间步的单个变量的预测,因此它们总是在先前值的附近。
尽管如此,前面示例的目的是展示在 TensorFlow 中创建 RNN 的方法。现在让我们使用 RNN 变体重新创建相同的示例。
TensorFlow 中的 LSTM
由于爆炸和消失梯度的问题,简单的 RNN 架构并不总是有效,因此使用了改进的 RNN 架构,例如 LSTM 网络。 TensorFlow 提供 API 来创建 LSTM RNN 架构。
在上一节中展示的示例中,要将简单 RNN 更改为 LSTM 网络,我们所要做的就是更改单元类型,如下所示:
cell = tf.nn.rnn_cell.LSTMCell(state_size)
其余代码保持不变,因为 TensorFlow 会为您在 LSTM 单元内创建门。
笔记本ch-07a_RNN_TimeSeries_TensorFlow
中提供了 LSTM 模型的完整代码。
然而,对于 LSTM,我们必须运行 600 个周期的代码才能使结果更接近基本 RNN。原因是 LSTM 需要学习更多参数,因此需要更多的训练迭代。对于我们的简单示例,它似乎有点过分,但对于较大的数据集,与简单的 RNN 相比,LSTM 显示出更好的结果。
具有 LSTM 架构的模型的输出如下:
train mse = 0.0020806745160371065 test mse = 0.01499235536903143 test rmse = 0.12244327408653947
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-72endx7a-1681566456878)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/5dd03142-e42f-4e1c-bf60-19e31475b853.png)]
TensorFlow 中的 GRU
要将最后一节中的 LSTM 示例更改为 GRU 网络, 按如下方式更改单元类型,TensorFlow 将为您处理其余部分:
cell = tf.nn.rnn_cell.GRUCell(state_size)
笔记本ch-07a_RNN_TimeSeries_TensorFlow
中提供了 GRU 模型的完整代码。
对于小airpass
数据集,GRU 在相同数量的周期中表现出更好的表现。在实践中,GRU 和 LSTM 表现出相当的表现。就执行速度而言,与 LSTM 相比,GRU 模型训练和预测更快。
精通 TensorFlow 1.x:6~10(2)https://developer.aliyun.com/article/1426820