RNN(循环神经网络)教程:TensorFlow 示例

为什么我们需要循环神经网络(RNN)?

循环神经网络 (RNN) 允许您对记忆单元进行建模以保存数据并建模短期依赖关系。它还用于时间序列预测,以识别数据相关性和模式。它还通过提供与人脑类似的行为来帮助产生序列数据的预测结果。

人工神经网络的结构相对简单,主要涉及矩阵乘法。在第一步中,输入乘以初始随机权重和偏差,用激活函数进行变换,然后使用输出值进行预测。此步骤给出了网络与现实的距离的概念。

所应用的度量是损失。损失函数越高,模型越愚蠢。为了提高网络的知识,需要通过调整网络的权重进行一些优化。随机梯度下降是用于在正确方向上改变权重值的方法。一旦进行调整,网络就可以使用另一批数据来测试其新知识。

幸运的是,误差比以前低了,但还不够小。优化步骤迭代进行,直到误差最小化,即无法提取更多信息。

这种模型的问题在于,它没有任何记忆。这意味着输入和输出是独立的。换句话说,模型并不关心之前发生了什么。当你需要预测时间序列或句子时,它会产生一些问题,因为网络需要有关历史数据或过去单词的信息。

为了解决这个问题,人们开发了一种新型架构:循环神经网络(以下简称 RNN)

什么是循环神经网络 (RNN)?

A 递归神经网络(RNN) 是一类 人工神经网络 其中不同节点之间的连接形成有向图,以提供时间动态行为。它有助于对来自前馈网络的顺序数据进行建模。它的工作原理与人类大脑类似,可以提供预测结果。

循环神经网络与传统神经网络非常相似,只是在神经元中添加了记忆状态。添加记忆的计算很简单。

想象一个简单的模型,只有一个神经元接受一批数据。在传统的神经网络中,模型通过将输入与权重和激活函数相乘来产生输出。使用 RNN,这个输出会被多次发送回自身。我们称之为 时间步长 输出成为下一个矩阵乘法的输入的时间量。

例如,在下图中,你可以看到网络由一个神经元组成。网络计算输入和权重之间的矩阵乘法,并使用激活函数添加非线性。它成为 t-1 时的输出。此输出是第二个矩阵乘法的输入。

递归神经网络(RNN)
递归神经网络(RNN)

下面,我们在 TensorFlow 中编写一个简单的 RNN 来了解步骤和输出的形状。

该网络由以下部分组成:

  • 四个输入
  • 六个神经元
  • 2 时间步

网络将按下图所示进行。

递归神经网络(RNN)

该网络之所以被称为“循环”网络,是因为它在每个激活方块中执行相同的操作。在使用激活函数之前,网络会计算输入和先前输出的权重。

import numpy as np
import tensorflow as tf
n_inputs = 4
n_neurons = 6
n_timesteps = 2
The data is a sequence of a number from 0 to 9 and divided into three batches of data.
## Data 
X_batch = np.array([
        [[0, 1, 2, 5], [9, 8, 7, 4]], # Batch 1
        [[3, 4, 5, 2], [0, 0, 0, 0]], # Batch 2
        [[6, 7, 8, 5], [6, 5, 4, 2]], # Batch 3
    ])

我们可以用数据占位符、循环阶段和输出来构建网络。

  1. 定义数据的占位符
X = tf.placeholder(tf.float32, [None, n_timesteps, n_inputs])

这里:

  • 无:未知,将占用批次的大小
  • n_timesteps:网络将输出发送回神经元的次数
  • n_inputs:每批输入的数量
  1. 定义循环网络

如上图所示,该网络由 6 个神经元组成。网络将计算两个点积:

  • 具有第一组权重的输入数据(即6:等于神经元的数量)
  • 先前的输出带有第二组权重(即6:对应输出的数量)

请注意,在第一次前馈期间,先前输出的值等于零,因为我们没有任何可用的值。

构建 RNN 的对象是 tf.contrib.rnn.BasicRNNCell,其参数为 num_units,用于定义输入的数量

basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons)

现在网络已经定义好了,你可以计算输出和状态

outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)

该对象使用内部循环将矩阵乘以适当的次数。

请注意,循环神经元是前一个时间步骤的所有输入的函数。这就是网络构建自身记忆的方式。前一个时间的信息可以在未来传播。这就是循环神经网络的魔力

## Define the shape of the tensor
X = tf.placeholder(tf.float32, [None, n_timesteps, n_inputs])
## Define the network
basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons)
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
init = tf.global_variables_initializer()
init = tf.global_variables_initializer()
with tf.Session() as sess:
    init.run()
    outputs_val = outputs.eval(feed_dict={X: X_batch})
print(states.eval(feed_dict={X: X_batch}))
[[ 0.38941205 -0.9980438   0.99750966  0.7892596   0.9978241   0.9999997 ]
 [ 0.61096436  0.7255889   0.82977575 -0.88226104  0.29261455 -0.15597084]
 [ 0.62091285 -0.87023467  0.99729395 -0.58261937  0.9811445   0.99969864]]

为了便于说明,您可以打印前一个状态的值。上面打印的输出显示了最后一个状态的输出。现在打印所有输出,您可以注意到状态是每个批次的前一个输出。也就是说,前一个输出包含有关整个序列的信息。

print(outputs_val)    
print(outputs_val.shape)    
[[[-0.75934666 -0.99537754  0.9735819  -0.9722234  -0.14234993
   -0.9984044 ]
  [ 0.99975264 -0.9983206   0.9999993  -1.         -0.9997506
   -1.        ]]

 [[ 0.97486496 -0.98773265  0.9969686  -0.99950117 -0.7092863
   -0.99998885]
  [ 0.9326837   0.2673438   0.2808514  -0.7535883  -0.43337247
    0.5700631 ]]

 [[ 0.99628735 -0.9998728   0.99999213 -0.99999976 -0.9884324
   -1.        ]
  [ 0.99962527 -0.9467421   0.9997403  -0.99999714 -0.99929446
   -0.9999795 ]]]
(3, 2, 6)

递归神经网络(RNN)

输出的形状为(3,2,6):

  • 3:批次数量
  • 2:时间步数
  • 6:神经元数量

循环神经网络的优化与传统神经网络的优化相同。您将在本循环神经网络教程的下一部分中更详细地了解如何编写优化代码。

RNN 的应用

RNN 有多种用途,尤其是在预测未来方面。在金融行业中,RNN 有助于预测股票价格或股市走向(即正向或负向)。

RNN 对于自动驾驶汽车很有用,因为它可以通过预测车辆的轨迹来避免车祸。

RNN 广泛应用于文本分析、图像字幕、情感分析和机器翻译。例如,人们可以利用电影评论来了解观众看完电影后的感受。当电影公司没有足够的时间审查、标记、整合和分析评论时,自动化这项任务非常有用。机器可以以更高的准确度完成这项工作。

RNN 的局限性

理论上,RNN 应该将信息传递到 10 ... 消失梯度问题。如果你还记得的话,神经网络使用梯度下降算法来更新权重。当网络向下层时,梯度会变小。

总之,梯度保持不变意味着没有改进的空间。模型从梯度的变化中学习;这种变化会影响网络的输出。但是,如果梯度的差异太小(即权重变化很小),网络就无法学习任何东西,输出也是如此。因此,面临梯度消失问题的网络无法收敛到一个好的解决方案。

改进LSTM

为了克服 RNN 面临的梯度消失问题,三位研究人员 Hochreiter、Schmidhuber 和 Bengio 使用一种称为长短期记忆 (LSTM) 的架构改进了 RNN。简而言之,LSMT 为网络提供了与最近时间相关的过去信息。机器使用更好的架构来选择信息并将其带回以后的时间。

LSTM 架构在 TensorFlow 中可用,tf.contrib.rnn.LSTMCell。LSTM 不在本教程的讨论范围内。您可以参考官方 文件 了解更多信息

时间序列中的 RNN

在本 TensorFlow RNN 教程中,您将使用带有时间序列数据的 RNN。时间序列依赖于之前的时间,这意味着过去的值包含网络可以学习的相关信息。时间序列预测背后的想法是估计一系列的未来值,比如股票价格、温度、GDP 等。

Keras RNN 和时间序列的数据准备可能有点棘手。首先,目标是预测序列的下一个值,这意味着您将使用过去的信息来估计 t + 1 时的值。标签等于输入序列并向前移动一个周期。其次,输入的数量设置为 1,即每次观察一次。最后,时间步长等于数值序列。例如,如果将时间步长设置为 10,则输入序列将连续返回十次。

看下面的图表,我们在左边表示时间序列数据,在右边表示虚构的输入序列。你创建一个函数来返回从 2001 年 2016 月到 XNUMX 年 XNUMX 月的每一天的随机值的数据集

# To plot pretty figures
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
def create_ts(start = '2001', n = 201, freq = 'M'):
    rng = pd.date_range(start=start, periods=n, freq=freq)
    ts = pd.Series(np.random.uniform(-18, 18, size=len(rng)), rng).cumsum()
    return ts
ts= create_ts(start = '2001', n = 192, freq = 'M')
ts.tail(5)

输出

2016-08-31    -93.459631
2016-09-30    -95.264791
2016-10-31    -95.551935
2016-11-30   -105.879611
2016-12-31   -123.729319
Freq: M, dtype: float64
ts = create_ts(start = '2001', n = 222)

# Left
plt.figure(figsize=(11,4))
plt.subplot(121)
plt.plot(ts.index, ts)
plt.plot(ts.index[90:100], ts[90:100], "b-", linewidth=3, label="A training instance")
plt.title("A time series (generated)", fontsize=14)

# Right
plt.subplot(122)
plt.title("A training instance", fontsize=14)
plt.plot(ts.index[90:100], ts[90:100], "b-", markersize=8, label="instance")
plt.plot(ts.index[91:101], ts[91:101], "bo", markersize=10, label="target", markerfacecolor='red')
plt.legend(loc="upper left")
plt.xlabel("Time")

plt.show()

时间序列中的 RNN

图表右侧显示所有系列。它从 2001 年开始,到 2019 年结束。将所有数据都输入网络是没有意义的,相反,您需要创建一批长度等于时间步长的数据。这批数据将是 X 变量。Y 变量与 X 相同,但偏移了一个周期(即,您想要预测 t+1)。

两个向量的长度相同。您可以在上图的右侧看到这一点。线表示 X 输入的十个值,而红点表示标签 Y 的十个值。请注意,标签比 X 早一个周期开始,比 X 晚一个周期结束。

在 TensorFlow 中构建 RNN 来预测时间序列

现在在这个 RNN 训练中,是时候构建你的第一个 RNN 来预测上面的序列了。你需要为模型指定一些超参数(模型的参数,即神经元的数量等):

  • 输入数量:1
  • 时间步长(时间序列中的窗口):10
  • 神经元数量:120
  • 输出数量:1

您的网络将从 10 天的序列中学习,并包含 120 个循环神经元。您向模型输入一个输入,即一天。您可以随意更改值以查看模型是否有所改进。

在构建模型之前,您需要将数据集拆分为训练集和测试集。整个数据集有 222 个数据点;您将使用前 201 个点来训练模型,使用后 21 个点来测试模型。

定义训练和测试集后,您需要创建一个包含批次的对象。在此批次中,您有 X 值和 Y 值。请记住,X 值滞后一个周期。因此,您使用前 200 个观测值,时间步长等于 10。X_batches 对象应包含 20 个大小为 10*1 的批次。y_batches 具有与 X_batches 对象相同的形状,但提前了一个周期。

步骤1) 创建训练并测试

首先,将系列转换成 麻木 数组;然后定义窗口(即网络将学习的时间数)、输入、输出的数量以及训练集的大小,如下面的 TensorFlow RNN 示例所示。

series = np.array(ts)
n_windows = 20   
n_input =  1
n_output = 1
size_train = 201

之后,只需将数组分成两个数据集。

## Split data
train = series[:size_train]
test = series[size_train:]
print(train.shape, test.shape)
(201,) (21,)

步骤2) 创建函数以返回 X_batches 和 y_batches

为了使其更简单,您可以创建一个返回两个不同数组的函数,一个用于 X_batches,一个用于 y_batches。

让我们编写一个 RNN TensorFlow 函数来构建批次。

请注意,X 个批次滞后一个周期(我们取值为 t-1)。函数的输出应该有三个维度。第一个维度等于批次数量,第二个维度等于窗口大小,最后一个维度等于输入数量。

棘手的部分是正确选择数据点。对于 X 数据点,您选择从 t = 1 到 t =200 的观测值,而对于 Y 数据点,您返回从 t​​ = 2 到 201 的观测值。一旦您有了正确的数据点,就可以轻松重塑序列。

要使用批次构建对象,您需要将数据集拆分为十个长度相等的批次(即 20)。您可以使用 reshape 方法并传递 -1,以便系列与批次大小相似。值 20 是每批次的观察值数量,1 是输入数量。

您需要对标签执行相同的步骤。

请注意,您需要将数据移位到您想要预测的次数。例如,如果您想要预测一天,那么您可以将序列移位 1。如果您想要预测两天,那么将数据移位 2。

x_data = train[:size_train-1]: Select all the training instance minus one day
X_batches = x_data.reshape(-1, windows, input): create the right shape for the batch e.g (10, 20, 1)
def create_batches(df, windows, input, output):
    ## Create X         
        x_data = train[:size_train-1] # Select the data
        X_batches = x_data.reshape(-1, windows, input)  # Reshape the data 
    ## Create y
        y_data = train[n_output:size_train]
        y_batches = y_data.reshape(-1, windows, output)
        return X_batches, y_batches

现在函数已经定义,您可以调用它来创建批次,如下面的 RNN 示例所示。

X_batches, y_batches = create_batches(df = train,
                                      windows = n_windows,
                                      input = n_input,
                                      output = n_output)

您可以打印该形状以确保尺寸正确。

print(X_batches.shape, y_batches.shape)
(10, 20, 1) (10, 20, 1)

您需要创建仅包含一批数据和 20 个观测值的测试集。

请注意,您连续几天进行预测,这意味着第二个预测值将基于测试数据集第一天(t+1)的真实值。事实上,真实值是已知的。

如果要预测 t+2(即提前两天),则需要使用 t+1 的预测值;如果要预测 t+3(即提前三天),则需要使用 t+1 和 t+2 的预测值。这很正常,因为 t+n 天的预测很难准确。

X_test, y_test = create_batches(df = test, windows = 20,input = 1, output = 1)
print(X_test.shape, y_test.shape)
(10, 20, 1) (10, 20, 1)

好了,您的批处理大小已准备好,您可以构建 RNN 架构。请记住,您有 120 个循环神经元。

步骤3) 建立模型

要创建模型,您需要定义三个部分:

  1. 具有张量的变量
  2. RNN
  3. 损失与优化

步骤3.1) 变量

您需要指定具有适当形状的 X 和 y 变量。此步骤很简单。张量具有与对象 X_batches 和 y_batches 相同的维度。

例如,张量 X 是一个占位符(查看教程 Tensorflow (刷新你对变量声明的认识)有三个维度:

  • 注意:批次大小
  • n_windows:窗口的长度。即模型向后看的次数
  • n_input:输入的数量

结果是:

tf.placeholder(tf.float32, [None, n_windows, n_input])
## 1. Construct the tensors
X = tf.placeholder(tf.float32, [None, n_windows, n_input])   
y = tf.placeholder(tf.float32, [None, n_windows, n_output])

步骤3.2) 创建 RNN

在这个 RNN TensorFlow 示例的第二部分中,您需要定义网络的架构。与之前一样,您使用来自 TensorFlow 估算器的对象 BasicRNNCell 和 dynamic_rnn。

## 2. create the model
basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu)   
rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)   

下一部分有点棘手,但可以加快计算速度。您需要将运行输出转换为密集层,然后再次将其转换为与输入相同的维度。

stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron])          
stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output)       
outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output])  

步骤3.3) 创建损失和优化

模型优化取决于你正在执行的任务。在上一篇教程中 CNN,你的目标是对图像进行分类,在这个 RNN 教程中,目标略有不同。你被要求对连续变量与某个类进行比较进行预测。

这种差异很重要,因为它会改变优化问题。连续变量的优化问题是最小化均方误差。要在 TF 中构建这些指标,您可以使用:

  • tf.reduce_sum(tf.square(输出 - y))

RNN 代码的其余部分与之前相同;使用 Adam 优化器来减少损失(即 MSE):

  • tf.train.AdamOptimizer(学习率=学习率)
  • 优化器.最小化(损失)

就这样,你可以把所有东西打包在一起,然后你的模型就可以进行训练了。

tf.reset_default_graph()
r_neuron = 120    

## 1. Construct the tensors
X = tf.placeholder(tf.float32, [None, n_windows, n_input])   
y = tf.placeholder(tf.float32, [None, n_windows, n_output])

## 2. create the model
basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu)   
rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)              

stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron])          
stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output)       
outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output])   

## 3. Loss + optimization
learning_rate = 0.001  
 
loss = tf.reduce_sum(tf.square(outputs - y))    
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)         
training_op = optimizer.minimize(loss)                                          

init = tf.global_variables_initializer() 

您将使用 1500 个时期训练模型,并每 150 次迭代打印一次损失。模型训练完成后,您将在测试集上评估模型并创建一个包含预测的对象,如下面的循环神经网络示例所示。

iteration = 1500 

with tf.Session() as sess:
    init.run()
    for iters in range(iteration):
        sess.run(training_op, feed_dict={X: X_batches, y: y_batches})
        if iters % 150 == 0:
            mse = loss.eval(feed_dict={X: X_batches, y: y_batches})
            print(iters, "\tMSE:", mse)
    
    y_pred = sess.run(outputs, feed_dict={X: X_test})
0 	MSE: 502893.34
150 	MSE: 13839.129
300 	MSE: 3964.835
450 	MSE: 2619.885
600 	MSE: 2418.772
750 	MSE: 2110.5923
900 	MSE: 1887.9644
1050 	MSE: 1747.1377
1200 	MSE: 1556.3398
1350 	MSE: 1384.6113

最后,在这个 RNN 深度学习教程中,你可以绘制序列的实际值和预测值。如果你的模型被修正了,预测值应该放在实际值的上面。

如你所见,该模型还有改进的空间。你可以自行更改超参数,例如窗口、循环神经元数量的批大小。

plt.title("Forecast vs Actual", fontsize=14)
plt.plot(pd.Series(np.ravel(y_test)), "bo", markersize=8, label="Actual", color='green')
plt.plot(pd.Series(np.ravel(y_pred)), "r.", markersize=8, label="Forecast", color='red')
plt.legend(loc="lower left")
plt.xlabel("Time")

plt.show()
预测与实际

预测与实际

总结

循环神经网络是一种强大的架构,可用于处理时间序列或文本分析。前一状态的输出是反馈,以保留网络随时间或单词序列变化的记忆。

在 TensorFlow 中,您可以使用以下代码来训练时间序列的 TensorFlow 循环神经网络:

模型参数

n_windows = 20   
n_input =  1
n_output = 1
size_train = 201

定义模型

X = tf.placeholder(tf.float32, [None, n_windows, n_input])   
y = tf.placeholder(tf.float32, [None, n_windows, n_output])

basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu)   
rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)              

stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron])          
stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output)       
outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output])

构建优化

learning_rate = 0.001  
 
loss = tf.reduce_sum(tf.square(outputs - y))    
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)         
training_op = optimizer.minimize(loss)                                          

训练模型

init = tf.global_variables_initializer() 
iteration = 1500 

with tf.Session() as sess:
    init.run()
    for iters in range(iteration):
        sess.run(training_op, feed_dict={X: X_batches, y: y_batches})
        if iters % 150 == 0:
            mse = loss.eval(feed_dict={X: X_batches, y: y_batches})
            print(iters, "\tMSE:", mse)
    
    y_pred = sess.run(outputs, feed_dict={X: X_test})