📜  CNTK-递归神经网络

📅  最后修改于: 2020-12-10 05:11:45             🧑  作者: Mango


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

介绍

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

什么是递归神经网络?

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

  • 众所周知,在常规神经网络中,我们只能提供一个输入。这将其限制为仅导致一个预测。举个例子,我们可以使用常规的神经网络来翻译文本。

  • 另一方面,在递归神经网络中,我们可以提供导致单个预测的一系列样本。换句话说,使用RNN,我们可以基于输入序列来预测输出序列。例如,在翻译任务中已经有许多成功的RNN实验。

递归神经网络的用途

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

预测单个输出

在深入研究步骤之前,RNN如何基于序列预测单个输出,让我们看一下基本RNN的样子-

单输出

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

此外,由于具有环回连接,RNN可以将生成的输出与序列中下一个元素的输入进行组合。这样,RNN将在整个序列上建立一个可用于进行预测的内存。

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

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

  • 之后,要生成更新的隐藏状态,我们需要采用初始隐藏状态并将其与输入序列中的第二个元素组合。

  • 最后,要生成最终的隐藏状态并预测RNN的输出,我们需要在输入序列中使用final元素。

这样,借助此环回连接,我们可以教导RNN识别随时间发生的模式。

预测序列

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

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

  • 之后,要生成更新的隐藏状态和输出序列中的第二个元素,我们需要将初始隐藏状态与相同的样本进行组合。

  • 最后,要再更新一次隐藏状态并预测输出序列中的最后一个元素,我们需要再一次提供样本。

预测序列

如我们所见,如何基于序列预测单个值以及如何基于单个值预测序列。现在让我们看看如何预测序列的序列。在这种情况下,为了使用RNN进行预测,我们可以执行以下步骤-

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

  • 之后,要更新隐藏状态并预测输出序列中的第二个元素,我们需要采用初始隐藏状态。

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

RNN的工作

为了了解递归神经网络(RNN)的工作,我们需要首先了解网络中递归层的工作方式。因此,首先让我们讨论e如何通过标准循环层来预测输出。

使用标准RNN层预测输出

如前所述,RNN中的基本层与神经网络中的常规层完全不同。在上一节中,我们还在图中演示了RNN的基本体系结构。为了更新首次进入序列的隐藏状态,我们可以使用以下公式-

Rnn层

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

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

第一步

接下来,我们可以按照以下顺序重复更新第三步和最后一步的隐藏状态的过程:

最后一步

当我们按顺序处理了所有上述步骤后,我们可以计算出如下输出:

计算输出

对于上面的公式,我们使用了第三组权重和最后时间步骤中的隐藏状态。

高级循环单元

基本循环层的主要问题是消失的梯度问题,因此,它不是很擅长学习长期相关性。用简单的话来说,基本的循环层不能很好地处理长序列。这就是为什么其他一些更适合于较长序列的循环图层类型的原因如下-

长期记忆(LSTM)

长期记忆(LSTM)

Hochreiter&Schmidhuber引入了长期短期记忆(LSTM)网络。它解决了使基本的循环层能够长时间记住事物的问题。 LSTM的体系结构如上图所示。如我们所见,它具有输入神经元,记忆细胞和输出神经元。为了解决梯度消失的问题,长期短期存储网络使用显式存储单元(存储先前的值)和随后的门-

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

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

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

门控循环单元(GRU)

门控循环单元(GRU)

梯度递归单位(GRU)是LSTM网络的细微变化。它的门少了一个,并且接线方式与LSTM略有不同。上图显示了它的体系结构。它具有输入神经元,门控存储单元和输出神经元。门控循环单元网络具有以下两个门-

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

    • 上次状态应保留多少信息?

    • 上一层应提供多少信息?

  • 重置门重置门的功能与LSTMs网络的忘记门非常相似。唯一的区别是它的位置略有不同。

与长期短期存储网络相比,门控循环单元网络稍快且易于运行。

创建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进行建模-

  • 首先,如果只需要循环层的最终输出,则可以将折叠层与循环层结合使用,例如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)