循环神经网络和层

循环神经元和层

到目前为止,我们只关注前馈神经网络,其中激活仅在一个方向上流动,从输入层流向输出层。循环神经网络看起来非常像前馈神经网络,除了它还具有指向反向的连接。最简单的RNN由一个神经元接收输入,产生输出并将该输出返送回自身组成。在每个时间步长\(t\)(也称为帧),该循环神经网络接收输入\(x_{(t)}\)和前一个时间步长\(y_{(t-1)}\)的输出。由于在第一个时间步长没有先前的输出,因此通常将其设置为0。

一层递归神经元。在每个时间步长\(t\),每个神经元接收输入向量\(x_{(t)}\)和前一个时间步长的输出向量\(y_{(t-1)}\)。输入和输出都是向量

每个循环神经元都有两组权重:一组用于输入\(x_{(t)}\),另一组用于前一个时间步长\(y_{(t-1)}\)的输出。称这些权重向量为\(w_x\)和\(w_y\)。如果考虑整个循环曾而不仅仅一个神经元,则可以将所有的权重向量放在两个权重矩阵\(W_x\)和\(W_y\)中。然后可以如预期的那样计算整个循环层的输出向量,如公式所示:\(b\)是偏置向量,\(\phi(\cdot)\)是激活函数(例如ReLU)

单个实例的循环层的输出:

\[y_{(t)}=\phi(W_x^Tx_{(t)}+W_y^Ty_{(t-1)}+b) \]

正如前馈神经网络一样,可以通过将时间步长\(t\)处的所有输入都放在输入矩阵\(X_{(t)}\)中,来一次性地计算整个小批量中的递归层的输出

小批量中的所有实例的循环神经元层的输出:

\[Y_{(t)}=\phi(X_{(t)}W_x+Y_{(t-1)}W_y+b)=\phi([X_{(t)}Y_{(t-1)}]W+b)\quad\mbox其中W=[W_x\,W_y]^T \]

在此等式中:
\(Y_{(t)}\)是一个\(m\times n{neurons}\)矩阵,包括小批量处理中每个实例在时间步长t时该层的输出(\(m\)是小批量处理中的实例数量,\(n_{neurons}\)是神经元数量)。

\(X_{(t)}\)是一个\(m\times n_{inputs}\)矩阵,包括所有实例的输出(\(n_{inputs}\)是输入特征的数量)

\(W_x\)是一个\(n_{inputs}\times n_{neurons}\)矩阵,包含当前时间步长的输入的连接权重

\(W_y\)是一个\(n_{neurons}\times n_{neurons}\)矩阵,包含前一个时间步长的输出的连接权重

\(b\)是大小为\(n_{neurons}\)的向量,包含每个神经元的偏置项

  • 权重矩阵\(W_x\)和\(W_y\)经常垂直合并成形状为\((n_{inputs}+n_{neurons})\times n_{neurons}\)的单个权重矩阵\(W\)
  • 符号\([X_{(t)}\,Y_{(t-1)}]\)表示矩阵\(X_{(t)}\)和\(Y_{(t-1)}\)的水平合并

\(Y_{(t)}\)是\(X_{(t)}\)和\(Y_{(t-1)}\)的函数,\(Y_{(t-1)}\)是\(X_{(t-1)}\)和\(Y_{(t-2)}\)的函数,以此类推。自时间\(t=0\)以来,这使\(Y_{(t)}\)成为所有输入的函数。在第一个时间步长\(t=0\)时,没有先前的输出,因此通常假定它们均为零

记忆单元

由于在时间步长\(t\)时递归神经元的输出是先前时间步长中所有输入的函数,因此可以说它具有记忆的形式。在时间步长上保留某些状态的神经网络的一部分称为记忆单元(或简称为单元)。单个循环神经元或一层循环神经元是一个非常基本的单元,它只能学习短模式(通常约为10个步长,但这取决于任务)。

通常,在时间步长\(t\)的单元状态,表示为\(h_{(t)}\)(“h”代表“隐藏”),是该时间步长的某些输入和其前一个时间步长状态的函数:\(h_{(t)}=f(h_{(t-1)},x_{(t)})\)。它在时间步长\(t\)处的输出表示为\(y_{(t)}\),也是先前状态和当前输入的函数。

输入和输出序列

RNN可以同时接收输入序列并产生输出序列。这种类型的序列到序列的网络可用于预测注入股票价格之类的时间序列:输入过去N天的价格作为输入,它必须输出未来偏移一天的价格(即从前N-1天到明天)。

或者,可以向网络提供一个输入序列,并忽略除了最后一个输出外的所有输入。换句话说,这是一个序列到向量的网络。例如,可以向网络提供与电影评论相对应的单词序列,然后网络将输出一个情感的分(例如从-1[恨]到+1[爱])

相反,可以在每个时间步长中一次又一次地向网络提供相同的输入向量,并让其输出一个序列。这是一个向量到序列的网络。例如,输入可以是图像(或CNN地输出),而输出可以是图像的描述。

最后,可能有一个成为编码器的序列到向量的网络,然后是一个成为解码器地向量到序列的网络。例如,这可以用于将句子从一种语言翻译成另一种语言。向网络提供一种语言的句子,编码器会将其转换为单个向量的表征,然后解码器会将此向量解码成另一种语言的句子。这种称为“编码器-解码器(Encoder-Decoder)”的两步模型比使用单个序列到序列的RNN进行即时翻译要好得多:句子的最后一个单词会影响翻译的第一个词,因此在翻译之前需要等待直到看完整个句子。

训练RNN

要训练RNN,就要将其按时间逐步展开,然后简单的使用常规的反向传播。这种策略称为“时间反向传播”(Back Propagation Through Time,BPTT)。

就像在常规反向传播中一样,这里有一个通过展开网络的第一次前向通路。然后使用成本函数\(C(Y_{(0)},Y_{(1)},\cdots,Y_{(T)}\)(其中\(T\)是最大时间步长)来评估输出序列。此成本函数可能会忽略某些输出,在序列到向量RNN中,除最后一个输出外,所有的输出都将被忽略。然后,该成本函数的梯度通过展开的网络反向传播。最后,使用在BPTT期间计算的梯度来更新模型参数。梯度反向通过成本函数使用的所有输出,而不是最终输出。而且,由于在每个时间步长上使用相同的参数\(W\)和\(b\),所以反向传播会做正确的事情,在所有时间步长上求和

预测时间序列

假设你正在研究网站上每小时的活跃用户数、城市的每日温度或使用多个指标来评估每个季度公司的财务状况。在所有这些情况下,数据是每个时间步长一个或多个值的序列。这称为时间序列。在前两个示例中,每个时间步长只有一个值,因此它们是单变量时间序列,而在财务示例中,每个时间步长有多个值(例如,公司的收入、债务等),因此它是一个多变量时间序列。典型的任务是预测未来值,这称为预测。另一个常见的任务是填补空白:预测过去的缺失值。这称为插补。

使用由generate_time_series()函数生成的时间序列来研究:

import numpy as np
import tensorflow as tf
from tensorflow import keras


def generate_time_series(batch_size, n_steps):
    freq1, freq2, offsets1, offsets2 = np.random.rand(
        4, batch_size, 1)  # 生成4个,(batch_size,1)形状的矩阵
    time = np.linspace(0, 1, n_steps)  # 时间为将0-1划分为n_steps个时间步长
    series = .5*np.sin((time-offsets1)*(freq1*10+10))  # 创建时间序列数值:sin(时间-相位)*频率)
    series += .2*np.sin((time-offsets2)*(freq2*20+20))
    series += .1*(np.random.rand(batch_size, n_steps)-.5) # 添加噪声
    return series[..., np.newaxis].astype(np.float32)
C:\ProgramData\Miniconda3\envs\tf2\lib\site-packages\numpy\_distributor_init.py:30: UserWarning: loaded more than 1 DLL from .libs:
C:\ProgramData\Miniconda3\envs\tf2\lib\site-packages\numpy\.libs\libopenblas.GK7GX5KEQ4F6UYO3P26ULGBQYHGQO7J4.gfortran-win_amd64.dll
C:\ProgramData\Miniconda3\envs\tf2\lib\site-packages\numpy\.libs\libopenblas.WCDJNK7YVMPZQ2ME2ZZHJJRJ3JIKNDB7.gfortran-win_amd64.dll
  warnings.warn("loaded more than 1 DLL from .libs:"

此函数根据请求(通过batch_size)参数创建任意数量的时间序列,每个序列的长度为n_steps,并且每个序列中每个时间步长只有一个值(即所有序列都是单变量的)。该函数返回一个形状为[批处理大小,时间步长,1]的NumPy数组,其中每个序列是两个固定振幅但频率和相位随机的正弦波的总和,再加上一点噪声。

在处理时间序列(以及其中类型的序列,例如句子)时,输入特征通常表示为形状为[批处理大小,时间步长,维度]的3D数组,其中单变量时间序列的维度为1,多变量时间序列的维度更多。

现在使用此函数创建训练集、验证集和测试集:

n_steps = 50
series = generate_time_series(10000, n_steps+1)  # 生成10000个时间步长为51的单变量时间序列
# 取前7000个时间步长为50作为输入,最后一个时间步长作为输出。通过前50个时间步长来预测后一个时间步长
X_train, y_train = series[:7000, :n_steps], series[:7000, -1]
X_valid, y_valid = series[7000:9000, :n_steps], series[7000:9000, -1]
X_test, y_test = series[9000:, :n_steps], series[9000:, -1]

X_train包含7000个时间序列(即形状为[7000, 50, 1]),而X_valid包含2000个(从第7000个时间序列到8999个),X_test包含1000个(从9000个时间序列到9999个)。由于要为每个序列预测一个值,因此目标是列向量(例如,y_train的形状为[7000, 1])。

基准指标

在开始使用RNN之前,通常最好有一些基准指标,否则可能会认为模型工作得很哈,但是实际上它的表现要比基本模型差。例如,最简单的方法是预测每个序列中的最后一个值。这被称为单纯预测,有时会令人惊讶地表现不好。在这种情况下,它们给了约0.020的均方误差:

y_pred = X_valid[:, -1]
np.mean(keras.losses.mean_squared_error(y_valid, y_pred))
0.020261222

另一种简单的方法是使用全连接的网络。由于它希望每个输入都有一个平的特征列表,因此需要添加一个Flatten层。只是使用一个简单的线性回归模型,是每个预测是时间序列中值的线性组合:

model = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[50, 1]),
    keras.layers.Dense(1)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train, epochs=20)
model.evaluate(X_test, y_test)
Epoch 1/20
219/219 [==============================] - 0s 1ms/step - loss: 0.2783
Epoch 2/20
219/219 [==============================] - 0s 977us/step - loss: 0.0724
Epoch 3/20
219/219 [==============================] - 0s 863us/step - loss: 0.0486
Epoch 4/20
219/219 [==============================] - 0s 1ms/step - loss: 0.0354
Epoch 5/20
219/219 [==============================] - 0s 867us/step - loss: 0.0275
Epoch 6/20
219/219 [==============================] - 0s 872us/step - loss: 0.0225
Epoch 7/20
219/219 [==============================] - 0s 849us/step - loss: 0.0192
Epoch 8/20
219/219 [==============================] - 0s 863us/step - loss: 0.0168
Epoch 9/20
219/219 [==============================] - 0s 918us/step - loss: 0.0147
Epoch 10/20
219/219 [==============================] - 0s 881us/step - loss: 0.0130
Epoch 11/20
219/219 [==============================] - 0s 890us/step - loss: 0.0114
Epoch 12/20
219/219 [==============================] - 0s 950us/step - loss: 0.0101
Epoch 13/20
219/219 [==============================] - 0s 872us/step - loss: 0.0090
Epoch 14/20
219/219 [==============================] - 0s 838us/step - loss: 0.0080
Epoch 15/20
219/219 [==============================] - 0s 844us/step - loss: 0.0072
Epoch 16/20
219/219 [==============================] - 0s 991us/step - loss: 0.0066
Epoch 17/20
219/219 [==============================] - 0s 867us/step - loss: 0.0060
Epoch 18/20
219/219 [==============================] - 0s 844us/step - loss: 0.0056
Epoch 19/20
219/219 [==============================] - 0s 876us/step - loss: 0.0053
Epoch 20/20
219/219 [==============================] - 0s 867us/step - loss: 0.0050
32/32 [==============================] - 0s 581us/step - loss: 0.0047





0.00474193412810564

如果使用MSE损失和默认的Adam优化器来编译此模型,然后在训练集上训练20个轮次并在验证集上评估,得出的MSE约为0.005,这比单纯预测的方法好得多

实现一个简单的RNN

model = keras.models.Sequential([
    keras.layers.SimpleRNN(1, input_shape=[None, 1])
])

它仅包含有一个有单个神经元的单层。不需要指定输入序列的长度(与之前的模型不同),因为循环神经网络可以处理任意数量的时间步长(这就是将第一个输入的维度设置为None的原因)。默认情况下,SimpleRNN层使用双曲正切激活函数。它的工作原理:初始状态\(h_{(init)}\)设置为0,并将其与第一个时间步长的值\(x_{(0)}\)一起传递给单个循环神经元。神经元计算这些值的加权总和,并将双曲正切激活函数应用于其结果,这得出了第一个输出\(y_0\)。在简单的RNN中,此输出也是新状态\(h_0\)。这个新的状态与西安一个输入值\(x_{(1)}\)一起传递给相同的循环神经网络,并重复该过程直到最后一个时间步长。然后,该层仅仅输出最后一个值\(y_{49}\)。对于每个时间序列,所有这些都是同时执行的。

默认情况下,Keras中的循环层仅返回最终输出。要使它们每个时间步长返回一个输出,必须设置return_sequences=True。

model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train, epochs=20)
model.evaluate(X_test, y_test)
Epoch 1/20
219/219 [==============================] - 7s 29ms/step - loss: 0.0126
Epoch 2/20
219/219 [==============================] - 6s 30ms/step - loss: 0.0112
Epoch 3/20
219/219 [==============================] - 7s 31ms/step - loss: 0.0111
Epoch 4/20
219/219 [==============================] - 7s 32ms/step - loss: 0.0110
Epoch 5/20
219/219 [==============================] - 7s 31ms/step - loss: 0.0110
Epoch 6/20
219/219 [==============================] - 7s 31ms/step - loss: 0.0110
Epoch 7/20
219/219 [==============================] - 7s 32ms/step - loss: 0.0110
Epoch 8/20
219/219 [==============================] - 7s 30ms/step - loss: 0.0110
Epoch 9/20
219/219 [==============================] - 6s 28ms/step - loss: 0.0110
Epoch 10/20
219/219 [==============================] - 6s 29ms/step - loss: 0.0110
Epoch 11/20
219/219 [==============================] - 7s 30ms/step - loss: 0.0110
Epoch 12/20
219/219 [==============================] - 7s 31ms/step - loss: 0.0109
Epoch 13/20
219/219 [==============================] - 7s 31ms/step - loss: 0.0110
Epoch 14/20
219/219 [==============================] - 7s 32ms/step - loss: 0.0110
Epoch 15/20
219/219 [==============================] - 7s 31ms/step - loss: 0.0110
Epoch 16/20
219/219 [==============================] - 7s 32ms/step - loss: 0.0110
Epoch 17/20
219/219 [==============================] - 7s 32ms/step - loss: 0.0110
Epoch 18/20
219/219 [==============================] - 6s 29ms/step - loss: 0.0110
Epoch 19/20
219/219 [==============================] - 6s 29ms/step - loss: 0.0110
Epoch 20/20
219/219 [==============================] - 7s 31ms/step - loss: 0.0109
32/32 [==============================] - 0s 6ms/step - loss: 0.0109





0.010947210714221

MSE只有0.011,因此它比单纯方法好,但它还不能击败简单的线性模型。对于每个神经元,线性模型的每个输入和每个时间步长都有一个参数,加上一个偏置项。相反,对于简单RNN中的每个循环神经元,每个输入和每个隐藏状态维度只有一个参数(在简单RNN中,这只是该层中循环神经元的数量),加上一个偏置项。在这个简单的RNN中,总共只有三个参数。

趋势和季节性

还有许多其他模型可以预测时间序列,例如加权移动平均模型或自回归集成移动平均(ARIMA)模型。其中有些要求你首先删除趋势和季节性。例如,如果正在研究网站上的活动用户数,并且该数字每月以10%的速度增长,则需要从时间序列中消除这种趋势。一旦训练完模型并开始进行预测,你不得不将趋势添加回去来得到最终的预测。同样,如果试图预测每个月出售的防晒乳液的数量,可能会观察到强烈的季节性:因为它在夏季会销售得非常好,而且每年都会重复类似的模式。你必须从时间序列中删除此季节性因素,例如,通过计算每个时间步长的值与一年前的值的差(这种技术称为差分)。同样,在对模型进行训练并做出预测之后,将不得不重新添加季节性模式来得到最终的预测。

使用RNN时,通常不需要执行所有这些操作,但是在某些情况下可能会提高性能,因为该模型不需要学习趋势或季节性。

深度RNN

把多层单元堆叠起来是非常普遍的。这就可以实现深度RNN。

使用tf.keras实现深度RNN非常简单:只需要堆叠循环层。在此示例中,使用了三个SimpleRNN层(也可以添加任何其他类型的循环层,例如LSTM层或GRU层):

model = keras.models.Sequential([
    keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
    keras.layers.SimpleRNN(20, return_sequences=True),
    keras.layers.SimpleRNN(1)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train, epochs=20)
model.evaluate(X_test, y_test)
Epoch 1/20
219/219 [==============================] - 23s 100ms/step - loss: 0.0234
Epoch 2/20
219/219 [==============================] - 23s 105ms/step - loss: 0.0041
Epoch 3/20
219/219 [==============================] - 24s 108ms/step - loss: 0.0036
Epoch 4/20
219/219 [==============================] - 23s 104ms/step - loss: 0.0034
Epoch 5/20
219/219 [==============================] - 24s 108ms/step - loss: 0.0033
Epoch 6/20
219/219 [==============================] - 24s 110ms/step - loss: 0.0033
Epoch 7/20
219/219 [==============================] - 23s 105ms/step - loss: 0.0032
Epoch 8/20
219/219 [==============================] - 22s 100ms/step - loss: 0.0031
Epoch 9/20
219/219 [==============================] - 24s 112ms/step - loss: 0.0031
Epoch 10/20
219/219 [==============================] - 24s 108ms/step - loss: 0.0030
Epoch 11/20
219/219 [==============================] - 23s 103ms/step - loss: 0.0030
Epoch 12/20
219/219 [==============================] - 24s 107ms/step - loss: 0.0030
Epoch 13/20
219/219 [==============================] - 24s 109ms/step - loss: 0.0030
Epoch 14/20
219/219 [==============================] - 23s 103ms/step - loss: 0.0030
Epoch 15/20
219/219 [==============================] - 23s 103ms/step - loss: 0.0029
Epoch 16/20
219/219 [==============================] - 25s 113ms/step - loss: 0.0029
Epoch 17/20
219/219 [==============================] - 23s 103ms/step - loss: 0.0028
Epoch 18/20
219/219 [==============================] - 23s 105ms/step - loss: 0.0028
Epoch 19/20
219/219 [==============================] - 24s 112ms/step - loss: 0.0029
Epoch 20/20
219/219 [==============================] - 23s 104ms/step - loss: 0.0029
32/32 [==============================] - 1s 20ms/step - loss: 0.0028





0.0028372975066304207

MSE为0.0028,终于设法击败了线性模型

最后一层不是理想的:它必须有一个单元,因此要预测一个单变量时间序列,这意味着每个时间步长必须有一个输出值。但是,只有一个单元意味着隐藏状态只是一个数字。

RNN主要使用其他循环层的隐藏状态来传递其所需的所有信息,而不会使用最终层的隐藏状态。此外,在默认情况下SimpleRNN层使用tanh激活函数,因此预测值必须在-1到1的范围内。但是,如果要使用其他激活函数,最好把输出层替换为Dense层:运行速度稍快,精度大致相同,并且可以选择所需的任何输出激活函数。如果做了更改,在最后一个循环层中删除return_sequences=True:

model = keras.models.Sequential([
    keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
    keras.layers.SimpleRNN(20),
    keras.layers.Dense(1)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train, epochs=20)
model.evaluate(X_test, y_test)
Epoch 1/20
219/219 [==============================] - 14s 63ms/step - loss: 0.0340
Epoch 2/20
219/219 [==============================] - 15s 66ms/step - loss: 0.0041
Epoch 3/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0033
Epoch 4/20
219/219 [==============================] - 15s 70ms/step - loss: 0.0032
Epoch 5/20
219/219 [==============================] - 14s 65ms/step - loss: 0.0031
Epoch 6/20
219/219 [==============================] - 15s 68ms/step - loss: 0.0031
Epoch 7/20
219/219 [==============================] - 15s 70ms/step - loss: 0.0031
Epoch 8/20
219/219 [==============================] - 15s 68ms/step - loss: 0.0031
Epoch 9/20
219/219 [==============================] - 15s 66ms/step - loss: 0.0030
Epoch 10/20
219/219 [==============================] - 15s 68ms/step - loss: 0.0030
Epoch 11/20
219/219 [==============================] - 16s 71ms/step - loss: 0.0030
Epoch 12/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0029
Epoch 13/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0030
Epoch 14/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0030
Epoch 15/20
219/219 [==============================] - 14s 65ms/step - loss: 0.0029
Epoch 16/20
219/219 [==============================] - 14s 66ms/step - loss: 0.0029
Epoch 17/20
219/219 [==============================] - 14s 65ms/step - loss: 0.0028
Epoch 18/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0028
Epoch 19/20
219/219 [==============================] - 15s 70ms/step - loss: 0.0029
Epoch 20/20
219/219 [==============================] - 15s 67ms/step - loss: 0.0028
32/32 [==============================] - 1s 14ms/step - loss: 0.0028





0.002804815536364913

输出层换成Dense后,收敛得更快并且性能也一样好。另外,可以根据需要更改输出激活函数。

预测未来几个时间步长

到目前为止,仅仅预测了下一个时间步长的值,但是可以很容易地通过适当地更改目标来预测未来几步的值(例如,要预测下十步,只需将目标更改为未来10步,而不是未来1步)。但是,如何预测接下来的10个值

第一种选择是使用已经训练好的模型,使其预测下一个值,然后将该值加到输入中(就像这个预测值实际上已经有了),然后再次使用该模型预测后面的值,以此类推,如以下代码所示:

series = generate_time_series(1, n_steps+10)
X_new, Y_new = series[:, :n_steps], series[:, n_steps:]
X = X_new
for step_ahead in range(10):
    y_pred_one = model.predict(X[:, step_ahead:])[:, np.newaxis, :]
    X = np.concatenate([X, y_pred_one], axis=1)
Y_pred = X[:, n_steps:]
np.mean(keras.losses.mean_squared_error(Y_new, Y_pred))
0.055745773

由于误差可能会累积,因此对下一个步长的预测通产会被对未来几个时间步长的预测更为准确。

第二种选择是训练RNN一次预测所有10个值,仍然可以使用一个序列到向量的模型,但是它输出10个值而不是1个值。但是,首先需要将目标更改为包含接下来10个值的向量:

series = generate_time_series(10000, n_steps+10)
X_train, Y_train = series[:7000, :n_steps], series[:7000, -10:,0]
X_valid, Y_valid = series[7000:9000, :n_steps], series[7000:9000, -10:,0]
X_test, Y_test = series[9000:, :n_steps], series[9000:, -10:,0]
model = keras.models.Sequential([
    keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
    keras.layers.SimpleRNN(20),
    keras.layers.Dense(10)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X_train, Y_train, epochs=20, validation_data=(X_valid, Y_valid))
model.evaluate(X_test, Y_test)
Epoch 1/20
219/219 [==============================] - 16s 69ms/step - loss: 0.0700 - val_loss: 0.0371
Epoch 2/20
219/219 [==============================] - 17s 76ms/step - loss: 0.0272 - val_loss: 0.0201
Epoch 3/20
219/219 [==============================] - 15s 70ms/step - loss: 0.0168 - val_loss: 0.0138
Epoch 4/20
219/219 [==============================] - 15s 67ms/step - loss: 0.0133 - val_loss: 0.0114
Epoch 5/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0118 - val_loss: 0.0103
Epoch 6/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0109 - val_loss: 0.0115
Epoch 7/20
219/219 [==============================] - 16s 75ms/step - loss: 0.0105 - val_loss: 0.0110
Epoch 8/20
219/219 [==============================] - 16s 74ms/step - loss: 0.0101 - val_loss: 0.0099
Epoch 9/20
219/219 [==============================] - 14s 65ms/step - loss: 0.0101 - val_loss: 0.0094
Epoch 10/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0096 - val_loss: 0.0092
Epoch 11/20
219/219 [==============================] - 16s 73ms/step - loss: 0.0095 - val_loss: 0.0086
Epoch 12/20
219/219 [==============================] - 17s 76ms/step - loss: 0.0094 - val_loss: 0.0089
Epoch 13/20
219/219 [==============================] - 15s 68ms/step - loss: 0.0095 - val_loss: 0.0091
Epoch 14/20
219/219 [==============================] - 15s 68ms/step - loss: 0.0090 - val_loss: 0.0092
Epoch 15/20
219/219 [==============================] - 16s 73ms/step - loss: 0.0090 - val_loss: 0.0085
Epoch 16/20
219/219 [==============================] - 16s 74ms/step - loss: 0.0089 - val_loss: 0.0097
Epoch 17/20
219/219 [==============================] - 16s 73ms/step - loss: 0.0091 - val_loss: 0.0099
Epoch 18/20
219/219 [==============================] - 15s 68ms/step - loss: 0.0090 - val_loss: 0.0111
Epoch 19/20
219/219 [==============================] - 16s 71ms/step - loss: 0.0088 - val_loss: 0.0089
Epoch 20/20
219/219 [==============================] - 16s 73ms/step - loss: 0.0089 - val_loss: 0.0081
32/32 [==============================] - 0s 15ms/step - loss: 0.0082





0.00820199865847826

该模型运行良好:接下里10个时间步长的MSE约为0.0082。这比线性模型好得多。但是仍然可以做得更好:实际上,与其训练模型在最后一个时间步长预测下一个10个值,不如训练模型在每个时间步长来预测下一个10个值。可以将这个序列到向量的RNN转换为序列到序列的RNN。这种技术的优势在于,损失将包含每个时间步长的RNN输出项,而不仅仅是最后一个时间步长的输出项。这意味着将有更多的误差梯度流过模型,它们不需要随时间而“流淌”。它们从每个时间步长的输出中流出。这会稳定和加速训练。

为了清楚期间,模型会在时间步长0时输出一个向量,其中包含时间步长1到10的预测,然后在时间步长1时模型会预测时间步长2到11,以此类推。因此,每个目标必须是与输入序列长度相同的序列,在每个步长都必须包含10维向量:

Y = np.empty((10000, n_steps, 10))
for step_ahead in range(1, 10+1):
    Y[:, :, step_ahead-1] = series[:, step_ahead:step_ahead +
                                   n_steps, 0]  # 创建时间序列,将预测目标设置为X后n_steps个时间步的数据作为序列中的一个数据,时间步与X相同但是是一个拥有10个数值的序列
Y_train = Y[:7000]
Y_valid = Y[7000:9000]
Y_test = Y[9000:]
print(series.shape)
print(X_train.shape)
print(Y_train.shape)
(10000, 60, 1)
(7000, 50, 1)
(7000, 50, 10)

目标中包含了在输入中出现的值X_train和Y_train之间有很多重叠。那不是作弊吗?幸运的是,一点也不是:在每个时间步长上,该模型仅知道过去的时间步长,因此不能向前看,这是一种因果模型。

要将模型转换为序列到序列的模型,必须在所有循环层(甚至最后一层)中设置return_sequences=True,必须在每个时间步长都应用输出Dense层。Keras为此提供了一个TimeDistributed层:它包装了任何层(例如Dense层),在输入序列的每个时间步长应用这个层。它通过重构输入的形状来有效地做到这一点,以便每个时间步长都被视为一个单独的实例(将输入的形状从[批量大小,时间不长,输入维度]调整为[批量大小X时间步长,输出维度]。在此示例中,输入维度为20,因为前一个SimpleRNN有20个单元),然后运行Dense层,最后将输出重构为序列(将输出从[批量大小X时间步长。输出维度]重构为[批量大小,时间步长,输出维度]。在此示例中,由于Dense层有10个单元,因此输出维度为10)。这是更新的模型:

model=keras.models.Sequential([
    keras.layers.SimpleRNN(20,return_sequences=True,input_shape=[None,1]),
    keras.layers.SimpleRNN(20,return_sequences=True),
    keras.layers.TimeDistributed(keras.layers.Dense(10))
])

Dense层实际上支持序列作为输入(甚至是更高维的输入):它像TimeDistributed(Dense(...))一样处理它们,这意味着它仅仅应用于最后一个输出维度(独立于所有的时间步长)。因此,可以使用Dense(10)代替最后一层。但是为了清楚起见,继续使用TimeDistributed(Dense(10)),因为它清楚地表明在每个时间步长都独立应用了Dense层,并且将模型输出一个序列,而不仅仅是一个向量。

训练期间需要所有的输出,但是只有最后一个时间步长的输出才对预测和评估有用。因此,尽管要依赖所有输出的MSE进行计算,但是需要使用自定义指标进行评估,以便在最后一个时间步长来计算输出的MSE:

def last_time_step_mse(Y_true, Y_pred):
    return keras.metrics.mean_squared_error(Y_true[:, -1], Y_pred[:, -1])


optimizer = keras.optimizers.Adam(lr=.01)
model.compile(loss='mse', optimizer=optimizer, metrics=[last_time_step_mse])
model.fit(X_train, Y_train, epochs=20, validation_data=(X_valid, Y_valid))
model.evaluate(X_test, Y_test)
C:\ProgramData\Miniconda3\envs\tf2\lib\site-packages\keras\optimizer_v2\optimizer_v2.py:355: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
  warnings.warn(


Epoch 1/20
219/219 [==============================] - 17s 75ms/step - loss: 0.0469 - last_time_step_mse: 0.0365 - val_loss: 0.0343 - val_last_time_step_mse: 0.0202
Epoch 2/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0331 - last_time_step_mse: 0.0200 - val_loss: 0.0311 - val_last_time_step_mse: 0.0194
Epoch 3/20
219/219 [==============================] - 15s 70ms/step - loss: 0.0288 - last_time_step_mse: 0.0156 - val_loss: 0.0264 - val_last_time_step_mse: 0.0120
Epoch 4/20
219/219 [==============================] - 16s 74ms/step - loss: 0.0260 - last_time_step_mse: 0.0129 - val_loss: 0.0247 - val_last_time_step_mse: 0.0107
Epoch 5/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0239 - last_time_step_mse: 0.0113 - val_loss: 0.0237 - val_last_time_step_mse: 0.0127
Epoch 6/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0221 - last_time_step_mse: 0.0097 - val_loss: 0.0209 - val_last_time_step_mse: 0.0084
Epoch 7/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0210 - last_time_step_mse: 0.0087 - val_loss: 0.0216 - val_last_time_step_mse: 0.0079
Epoch 8/20
219/219 [==============================] - 16s 71ms/step - loss: 0.0205 - last_time_step_mse: 0.0085 - val_loss: 0.0196 - val_last_time_step_mse: 0.0078
Epoch 9/20
219/219 [==============================] - 16s 74ms/step - loss: 0.0200 - last_time_step_mse: 0.0079 - val_loss: 0.0214 - val_last_time_step_mse: 0.0103
Epoch 10/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0201 - last_time_step_mse: 0.0083 - val_loss: 0.0209 - val_last_time_step_mse: 0.0088
Epoch 11/20
219/219 [==============================] - 15s 68ms/step - loss: 0.0195 - last_time_step_mse: 0.0076 - val_loss: 0.0191 - val_last_time_step_mse: 0.0069
Epoch 12/20
219/219 [==============================] - 16s 74ms/step - loss: 0.0193 - last_time_step_mse: 0.0074 - val_loss: 0.0205 - val_last_time_step_mse: 0.0080
Epoch 13/20
219/219 [==============================] - 15s 69ms/step - loss: 0.0191 - last_time_step_mse: 0.0074 - val_loss: 0.0206 - val_last_time_step_mse: 0.0087
Epoch 14/20
219/219 [==============================] - 16s 71ms/step - loss: 0.0203 - last_time_step_mse: 0.0090 - val_loss: 0.0199 - val_last_time_step_mse: 0.0095
Epoch 15/20
219/219 [==============================] - 16s 74ms/step - loss: 0.0190 - last_time_step_mse: 0.0073 - val_loss: 0.0190 - val_last_time_step_mse: 0.0068
Epoch 16/20
219/219 [==============================] - 15s 70ms/step - loss: 0.0192 - last_time_step_mse: 0.0076 - val_loss: 0.0191 - val_last_time_step_mse: 0.0070
Epoch 17/20
219/219 [==============================] - 16s 71ms/step - loss: 0.0294 - last_time_step_mse: 0.0209 - val_loss: 0.0383 - val_last_time_step_mse: 0.0335
Epoch 18/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0347 - last_time_step_mse: 0.0283 - val_loss: 0.0269 - val_last_time_step_mse: 0.0177
Epoch 19/20
219/219 [==============================] - 16s 72ms/step - loss: 0.0258 - last_time_step_mse: 0.0158 - val_loss: 0.0232 - val_last_time_step_mse: 0.0127
Epoch 20/20
219/219 [==============================] - 16s 75ms/step - loss: 0.0225 - last_time_step_mse: 0.0118 - val_loss: 0.0210 - val_last_time_step_mse: 0.0102
32/32 [==============================] - 0s 15ms/step - loss: 0.0208 - last_time_step_mse: 0.0100





[0.020847154781222343, 0.010038050822913647]

得到的验证MSE约为,可以将此方法与第一个方法结合使用:使用这个RNN来预测接下来的10个值,然后将这些值合并到输入时间序列,然后再次使用该模型预测接下来的10个值。对于长期预测而言,它可能不太准确,但是如果目标是生成原始音乐或文本,则可能会很好。

预测时间序列是,将一些误差与预测一起使用通常很有用。为此,在每个记忆单元内添加一个MC Dropout层,删除部分输入和隐藏状态。训练后,为了预测新的时间序列,多次使用模型,并在每个时间步长计算预测的均值和标准差

简单的RNN可以很好的预测时间序列或处理其他类型的序列,但是在长序列上表现不佳。

上一篇:Pytorch与深度学习自查手册4-训练、可视化、日志输出、保存模型


下一篇:situ课题组工作站神经网络训练速度测评