CNTK - 循环神经网络



现在,让我们了解如何在 CNTK 中构建循环神经网络 (RNN)。

简介

我们学习了如何使用神经网络对图像进行分类,这是深度学习中一项标志性的工作。但是,神经网络擅长的另一个领域以及正在进行的大量研究是循环神经网络 (RNN)。在这里,我们将了解什么是 RNN 以及如何在需要处理时间序列数据的情况下使用它。

什么是循环神经网络?

循环神经网络 (RNN) 可以定义为能够进行时间推理的特殊神经网络。RNN 主要用于需要处理随时间变化的值(即时间序列数据)的场景。为了更好地理解它,让我们比较一下常规神经网络和循环神经网络:

  • 众所周知,在常规神经网络中,我们只能提供一个输入。这限制了它只能产生一个预测结果。举个例子,我们可以使用常规神经网络来完成文本翻译工作。

  • 另一方面,在循环神经网络中,我们可以提供一系列样本,从而产生单个预测。换句话说,使用 RNN,我们可以根据输入序列预测输出序列。例如,在翻译任务中,RNN 进行了一些相当成功的实验。

循环神经网络的用途

RNN 可以以多种方式使用。其中一些如下所示:

预测单个输出

在深入探讨 RNN 如何根据序列预测单个输出的步骤之前,让我们看看基本的 RNN 是什么样子:

Single Output

如上图所示,RNN 包含一个回环连接到输入,并且每当我们馈送一系列值时,它都会将序列中的每个元素处理为时间步。

此外,由于回环连接,RNN 可以将生成的输出与序列中下一个元素的输入结合起来。通过这种方式,RNN 将在整个序列上构建一个记忆,该记忆可用于进行预测。

为了使用 RNN 进行预测,我们可以执行以下步骤:

  • 首先,为了创建初始隐藏状态,我们需要馈送输入序列的第一个元素。

  • 然后,为了生成更新的隐藏状态,我们需要获取初始隐藏状态并将其与输入序列中的第二个元素结合起来。

  • 最后,为了生成最终隐藏状态并预测 RNN 的输出,我们需要获取输入序列中的最后一个元素。

通过这种方式,借助此回环连接,我们可以训练 RNN 识别随时间发生的模式。

预测序列

上面讨论的基本 RNN 模型也可以扩展到其他用例。例如,我们可以使用它根据单个输入预测一系列值。在这种情况下,为了使用 RNN 进行预测,我们可以执行以下步骤:

  • 首先,为了创建初始隐藏状态并预测输出序列中的第一个元素,我们需要将输入样本馈送到神经网络。

  • 然后,为了生成更新的隐藏状态和输出序列中的第二个元素,我们需要将初始隐藏状态与相同的样本结合起来。

  • 最后,为了再次更新隐藏状态并预测输出序列中的最后一个元素,我们将样本再次馈送。

预测序列

我们已经了解了如何根据序列预测单个值以及如何根据单个值预测序列。现在让我们看看如何为序列预测序列。在这种情况下,为了使用 RNN 进行预测,我们可以执行以下步骤:

  • 首先,为了创建初始隐藏状态并预测输出序列中的第一个元素,我们需要获取输入序列中的第一个元素。

  • 然后,为了更新隐藏状态并预测输出序列中的第二个元素,我们需要获取初始隐藏状态。

  • 最后,为了预测输出序列中的最后一个元素,我们需要获取更新的隐藏状态和输入序列中的最后一个元素。

RNN 的工作原理

要了解循环神经网络 (RNN) 的工作原理,我们需要首先了解网络中的循环层如何工作。因此,首先让我们讨论如何使用标准循环层预测输出。

使用标准 RNN 层预测输出

正如我们之前所讨论的那样,RNN 中的基本层与神经网络中的常规层有很大不同。在上一节中,我们还在图中演示了 RNN 的基本架构。为了更新序列中第一个时间步的隐藏状态,我们可以使用以下公式:

Rnn Layer

在上述等式中,我们通过计算初始隐藏状态与一组权重的点积来计算新的隐藏状态。

现在对于下一步,当前时间步的隐藏状态用作序列中下一个时间步的初始隐藏状态。因此,为了更新第二个时间步的隐藏状态,我们可以重复在第一个时间步中执行的计算,如下所示:

First Step

接下来,我们可以重复更新序列中第三个和最后一个时间步的隐藏状态的过程,如下所示:

Last Step

当我们处理完序列中的所有上述步骤后,我们可以计算输出,如下所示:

Calculate Output

对于上述公式,我们使用了第三组权重和来自最终时间步的隐藏状态。

高级循环单元

基本循环层的主要问题是梯度消失问题,并且由于此问题,它在学习长期相关性方面表现不佳。简单来说,基本循环层不能很好地处理长序列。这就是为什么其他一些更适合处理更长序列的循环层类型如下所示:

长短期记忆 (LSTM)

Long-Short Term Memory (LSTM)

长短期记忆 (LSTM) 网络由 Hochreiter & Schmidhuber 引入。它解决了使基本循环层能够长时间记住事物的问题。LSTM 的架构如上图所示。我们可以看到它具有输入神经元、记忆单元和输出神经元。为了克服梯度消失问题,长短期记忆网络使用显式记忆单元(存储先前值)和以下门:

  • 遗忘门 - 顾名思义,它告诉记忆单元忘记先前值。记忆单元存储值,直到门(即“遗忘门”)告诉它忘记它们。

  • 输入门 - 顾名思义,它向单元格添加新内容。

  • 输出门 - 顾名思义,输出门决定何时将来自单元格的向量传递到下一个隐藏状态。

门控循环单元 (GRU)

Gated Recurrent Units (GRUs)

门控循环单元 (GRU) 是 LSTM 网络的一个轻微变体。它少了一个门,并且其连接方式与 LSTM 略有不同。其架构如上图所示。它具有输入神经元、门控记忆单元和输出神经元。门控循环单元网络具有以下两个门:

  • 更新门 - 它确定以下两件事:

    • 应从上一个状态保留多少信息?

    • 应从上一层传入多少信息?

  • 重置门 - 重置门的功能与 LSTM 网络的遗忘门非常相似。唯一的区别是它位于略微不同的位置。

与长短期记忆网络相比,门控循环单元网络速度稍快且更容易运行。

创建 RNN 结构

在我们开始对任何数据源的输出进行预测之前,我们需要首先构建 RNN,构建 RNN 与我们在上一节中构建常规神经网络的方式非常相似。以下是构建一个的代码:

from cntk.losses import squared_error
from cntk.io import CTFDeserializer, MinibatchSource, INFINITELY_REPEAT, StreamDefs, StreamDef
from cntk.learners import adam
from cntk.logging import ProgressPrinter
from cntk.train import TestConfig
BATCH_SIZE = 14 * 10
EPOCH_SIZE = 12434
EPOCHS = 10

堆叠多层

我们还可以堆叠 CNTK 中的多个循环层。例如,我们可以使用以下层的组合:

from cntk import sequence, default_options, input_variable
from cntk.layers import Recurrence, LSTM, Dropout, Dense, Sequential, Fold
features = sequence.input_variable(1)
with default_options(initial_state = 0.1):
   model = Sequential([
      Fold(LSTM(15)),
      Dense(1)
   ])(features)
target = input_variable(1, dynamic_axes=model.dynamic_axes)

如上代码所示,我们在 CNTK 中对 RNN 建模有以下两种方式:

  • 首先,如果我们只需要循环层的最终输出,我们可以将Fold层与循环层(如 GRU、LSTM 或甚至 RNNStep)结合使用。

  • 其次,作为替代方法,我们还可以使用Recurrence块。

使用时间序列数据训练 RNN

构建模型后,让我们看看如何在 CNTK 中训练 RNN:

from cntk import Function
@Function
def criterion_factory(z, t):
   loss = squared_error(z, t)
   metric = squared_error(z, t)
   return loss, metric
loss = criterion_factory(model, target)
learner = adam(model.parameters, lr=0.005, momentum=0.9)

现在,要将数据加载到训练过程中,我们必须从一组 CTF 文件中反序列化序列。以下代码包含create_datasource函数,这是一个用于创建训练和测试数据源的有用实用函数。

target_stream = StreamDef(field='target', shape=1, is_sparse=False)
features_stream = StreamDef(field='features', shape=1, is_sparse=False)
deserializer = CTFDeserializer(filename, StreamDefs(features=features_stream, target=target_stream))
   datasource = MinibatchSource(deserializer, randomize=True, max_sweeps=sweeps)
return datasource
train_datasource = create_datasource('Training data filename.ctf')#we need to provide the location of training file we created from our dataset.
test_datasource = create_datasource('Test filename.ctf', sweeps=1) #we need to provide the location of testing file we created from our dataset.

现在,由于我们已经设置了数据源、模型和损失函数,我们可以开始训练过程。这与我们在前面使用基本神经网络的章节中所做的方法非常相似。

progress_writer = ProgressPrinter(0)
test_config = TestConfig(test_datasource)
input_map = {
   features: train_datasource.streams.features,
   target: train_datasource.streams.target
}
history = loss.train(
   train_datasource,
   epoch_size=EPOCH_SIZE,
   parameter_learners=[learner],
   model_inputs_to_streams=input_map,
   callbacks=[progress_writer, test_config],
   minibatch_size=BATCH_SIZE,
   max_epochs=EPOCHS
)

我们将获得类似以下的输出:

输出:

average  since  average  since  examples
loss      last  metric  last
------------------------------------------------------
Learning rate per minibatch: 0.005
0.4      0.4    0.4      0.4      19
0.4      0.4    0.4      0.4      59
0.452    0.495  0.452    0.495   129
[…]

验证模型

实际上,使用 RNN 进行预测与使用任何其他 CNK 模型进行预测非常相似。唯一的区别是我们需要提供序列而不是单个样本。

现在,由于我们的 RNN 最终完成了训练,我们可以通过使用一些样本序列对其进行测试来验证模型,如下所示:

import pickle
with open('test_samples.pkl', 'rb') as test_file:
test_samples = pickle.load(test_file)
model(test_samples) * NORMALIZE

输出:

array([[ 8081.7905],
[16597.693 ],
[13335.17 ],
...,
[11275.804 ],
[15621.697 ],
[16875.555 ]], dtype=float32)
广告