【问题标题】:如何用 Keras 解释 RNN 的输出?
【发布时间】:2021-12-30 03:51:38
【问题描述】:

我想使用 RNN 进行时间序列预测,以使用 96 个反向步骤来预测未来的 96 个步骤。为此,我有以下代码:

#Import modules
import pandas as pd
import numpy as np
import tensorflow as tf
from sklearn.preprocessing import StandardScaler
from tensorflow import keras

# Define the parameters of the RNN and the training
epochs = 1
batch_size = 50
steps_backwards = 96
steps_forward = 96
split_fraction_trainingData = 0.70
split_fraction_validatinData = 0.90
randomSeedNumber = 50
helpValueStrides =  int(steps_backwards /steps_forward)

#Read dataset
df = pd.read_csv('C:/Users1/Desktop/TestValues.csv', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0]}, index_col=['datetime'])

# standardize data

data = df.values
indexWithYLabelsInData = 0
data_X = data[:, 0:3]
data_Y = data[:, indexWithYLabelsInData].reshape(-1, 1)


scaler_standardized_X = StandardScaler()
data_X = scaler_standardized_X.fit_transform(data_X)
data_X = pd.DataFrame(data_X)
scaler_standardized_Y = StandardScaler()
data_Y = scaler_standardized_Y.fit_transform(data_Y)
data_Y = pd.DataFrame(data_Y)


# Prepare the input data for the RNN

series_reshaped_X =  np.array([data_X[i:i + (steps_backwards+steps_forward)].copy() for i in range(len(data) - (steps_backwards+steps_forward))])
series_reshaped_Y =  np.array([data_Y[i:i + (steps_backwards+steps_forward)].copy() for i in range(len(data) - (steps_backwards+steps_forward))])


timeslot_x_train_end = int(len(series_reshaped_X)* split_fraction_trainingData)
timeslot_x_valid_end = int(len(series_reshaped_X)* split_fraction_validatinData)

X_train = series_reshaped_X[:timeslot_x_train_end, :steps_backwards] 
X_valid = series_reshaped_X[timeslot_x_train_end:timeslot_x_valid_end, :steps_backwards] 
X_test = series_reshaped_X[timeslot_x_valid_end:, :steps_backwards] 

   
Y_train = series_reshaped_Y[:timeslot_x_train_end, steps_backwards:] 
Y_valid = series_reshaped_Y[timeslot_x_train_end:timeslot_x_valid_end, steps_backwards:] 
Y_test = series_reshaped_Y[timeslot_x_valid_end:, steps_backwards:]                                
   
   
# Build the model and train it

np.random.seed(randomSeedNumber)
tf.random.set_seed(randomSeedNumber)

model = keras.models.Sequential([
keras.layers.SimpleRNN(10, return_sequences=True, input_shape=[None, 3]),
keras.layers.SimpleRNN(10, return_sequences=True),
keras.layers.Conv1D(16, helpValueStrides, strides=helpValueStrides), 
keras.layers.TimeDistributed(keras.layers.Dense(1))
])

model.compile(loss="mean_squared_error", optimizer="adam", metrics=['mean_absolute_percentage_error'])
history = model.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, validation_data=(X_valid, Y_valid))

#Predict the test data
Y_pred = model.predict(X_test)

prediction_lastValues_list=[]

for i in range (0, len(Y_pred)):
  prediction_lastValues_list.append((Y_pred[i][0][1 - 1]))

# Create thw dataframe for the whole data
wholeDataFrameWithPrediciton = pd.DataFrame((X_test[:,1]))
wholeDataFrameWithPrediciton.rename(columns = {indexWithYLabelsInData:'actual'}, inplace = True)
wholeDataFrameWithPrediciton.rename(columns = {1:'Feature 1'}, inplace = True)
wholeDataFrameWithPrediciton.rename(columns = {2:'Feature 2'}, inplace = True)
wholeDataFrameWithPrediciton['predictions'] = prediction_lastValues_list
wholeDataFrameWithPrediciton['difference'] = (wholeDataFrameWithPrediciton['predictions'] - wholeDataFrameWithPrediciton['actual']).abs()
wholeDataFrameWithPrediciton['difference_percentage'] = ((wholeDataFrameWithPrediciton['difference'])/(wholeDataFrameWithPrediciton['actual']))*100


# Inverse the scaling (traInv: transformation inversed)

data_X_traInv = scaler_standardized_X.inverse_transform(data_X)
data_Y_traInv = scaler_standardized_Y.inverse_transform(data_Y)
series_reshaped_X_notTransformed =  np.array([data_X_traInv[i:i + (steps_backwards+steps_forward)].copy() for i in range(len(data) - (steps_backwards+steps_forward))])
X_test_notTranformed = series_reshaped_X_notTransformed[timeslot_x_valid_end:, :steps_backwards] 
predictions_traInv = scaler_standardized_Y.inverse_transform(wholeDataFrameWithPrediciton['predictions'].values.reshape(-1, 1))

edictions_traInv = wholeDataFrameWithPrediciton['predictions'].values.reshape(-1, 1)

# Create thw dataframe for the inversed transformed data
wholeDataFrameWithPrediciton_traInv = pd.DataFrame((X_test_notTranformed[:,0]))
wholeDataFrameWithPrediciton_traInv.rename(columns = {indexWithYLabelsInData:'actual'}, inplace = True)
wholeDataFrameWithPrediciton_traInv.rename(columns = {1:'Feature 1'}, inplace = True)
wholeDataFrameWithPrediciton_traInv['predictions'] = predictions_traInv
wholeDataFrameWithPrediciton_traInv['difference_absolute'] = (wholeDataFrameWithPrediciton_traInv['predictions'] - wholeDataFrameWithPrediciton_traInv['actual']).abs()
wholeDataFrameWithPrediciton_traInv['difference_percentage'] = ((wholeDataFrameWithPrediciton_traInv['difference_absolute'])/(wholeDataFrameWithPrediciton_traInv['actual']))*100
wholeDataFrameWithPrediciton_traInv['difference'] = (wholeDataFrameWithPrediciton_traInv['predictions'] - wholeDataFrameWithPrediciton_traInv['actual'])

这里可以有一些测试数据(不用关心我自己编的实际值,形状很重要)Download test data

如何解释Y_pred 数据的输出?这些值中的哪一个为我提供了未来 96 步的预测值?我附上了“Y_pred”数据的截图。一次在最后一层有 5 个输出神经元,一次只有 1 个。谁能告诉我,如何解释“Y_pred”数据,这意味着 RNN 的预测究竟是什么?我可以在 RNN 模型的输出(最后一层)中使用任何值。 “Y_pred”数据始终具有形状(X_test 的批量大小、时间序列、输出神经元的数量)。我的问题是针对最后一个维度。我认为这些可能是特征,但在我的情况下并非如此,因为我只有 1 个输出特征(您可以看到 Y_trainY_testY_valid 数据的形状)。

**提醒**:赏金即将到期,不幸的是我仍然没有收到任何答复。所以我想提醒你这个问题和赏金。我会非常感谢每一条评论。

【问题讨论】:

  • 你能展示你的模型以及你是如何训练它的吗?
  • @TheGuywithTheHat:感谢戴帽子的家伙的评论。 “模型”和“你如何训练它”到底是什么意思?我已经在上面发布了我的代码。这是整个代码和(几乎)最小的可重现示例。我还包括了测试数据。据我所知,模型是在发布的代码中定义的,从model = keras.models.Sequential([... 行开始,训练命令也在model.compile(loss="mean_squared_error", optimizer="adam", metrics=['mean_absolute_percentage_error'])history = model.fit(X_train, Y_train, ... 行中定义
  • 我的错,我不知何故错过了那个代码块上的滚动条。
  • @TheGuywithTheHat:没问题。你能看看我的代码,也许能告诉我一些关于我的问题的事情吗?我会非常感谢每一条评论。
  • 我稍后可能会进行更彻底的查看,但是在制作 X_trainX_validX_test 时,您是否应该使用 :-steps_backwards 而不是 :steps_backwards ?

标签: python tensorflow keras time-series recurrent-neural-network


【解决方案1】:

详细了解模型输入/输出可能很有用。

当使用keras.layers.SimpleRNN 层和return_sequences=True 时,输出将返回一个 3-D 张量,其中第 0 轴是批量大小,第 1 轴是时间步长,第 2 轴是隐藏单元的数量(对于模型中的两个 SimpleRNN 层来说,都是 10)。

Conv1D 层将生成一个输出张量,其中最后一个维度成为隐藏单元的数量(在您的模型中为 16),因为它只是与输入进行卷积。

keras.layers.TimeDistributed,提供的层(在提供的示例中,Dense(1))将独立应用于批处理中的每个时间步。因此,在 96 个时间步长下,批处理中的每条记录都有 96 个输出。

因此逐步浏览您的模型:

model = keras.models.Sequential([
    keras.layers.SimpleRNN(10, return_sequences=True, input_shape=[None, 3]), # output size is (BATCH_SIZE, NUMBER_OF_TIMESTEPS, 10)
    keras.layers.SimpleRNN(10, return_sequences=True), # output size is (BATCH_SIZE, NUMBER_OF_TIMESTEPS, 10)
    keras.layers.Conv1D(16, helpValueStrides, strides=helpValueStrides) # output size is (BATCH_SIZE, NUMBER_OF_TIMESTEPS, 16),
    keras.layers.TimeDistributed(keras.layers.Dense(1)) # output size is (BATCH_SIZE, NUMBER_OF_TIMESTEPS, 1)
])

为了回答您的问题,您模型的输出张量包含每个样本未来 96 步的预测值。如果更容易概念化,对于 1 个输出的情况,您可以将np.squeeze 应用于model.predict 的结果,这将使输出成为二维:

Y_pred = model.predict(X_test) # output size is (BATCH_SIZE, NUMBER_OF_TIMESTEPS, 1)
Y_pred_squeezed = np.squeeze(Y_pred) # output size is (BATCH_SIZE, NUMBER_OF_TIMESTEPS)

这样,您就有了一个矩形矩阵,其中每一行对应于批次中的一个样本,每一列 i 对应于时间步长 i 的预测。

在预测步骤之后的循环中,所有时间步预测都被丢弃,除了第一个:

for i in range(0, len(Y_pred)):
    prediction_lastValues_list.append((Y_pred[i][0][1 - 1]))

这意味着最终结果只是批次中每个样本的第一个时间步长的预测列表。如果你想要第 96 个时间步的预测,你可以这样做:

for i in range(0, len(Y_pred)):
    prediction_lastValues_list.append((Y_pred[i][-1][1 - 1]))

注意第二个括号的 -1 而不是 0,以确保我们抓住最后一个预测的时间步而不是第一个。

附带说明,要复制结果,我必须对您的代码进行一次更改,特别是在创建 series_reshaped_Xseries_reshaped_Y 时。我在使用np.array 从列表中创建数组时遇到了一个异常:ValueError: cannot copy sequence with size 192 to array axis with dimension 3 ,但是看看你在做什么(沿新轴连接张量),我将其更改为np.stack,这将完成相同的操作目标(https://numpy.org/doc/stable/reference/generated/numpy.stack.html):

series_reshaped_X = np.stack([data_X[i:i + (steps_backwards + steps_forward)].copy() for i in
                              range(len(data) - (steps_backwards + steps_forward))])
series_reshaped_Y = np.stack([data_Y[i:i + (steps_backwards + steps_forward)].copy() for i in
                              range(len(data) - (steps_backwards + steps_forward))])

更新

“当我只有 1 个目标特征时,这 5 个值代表什么?”

这实际上只是 Tensorflow API 的广播功能(这也是 NumPy 的一个功能)。如果你对两个不同形状的张量进行算术运算,它将尝试使它们兼容。在这种情况下,如果将输出层大小更改为“5”而不是“1”(keras.layers.Dense(5)),则输出大小为(BATCH_SIZE, NUMBER_OF_TIMESTEPS, 5) 而不是(BATCH_SIZE, NUMBER_OF_TIMESTEPS, 1),这意味着卷积层的输出为进入 5 个神经元而不是 1 个。当计算两者之间的损失(均方误差)时,标签张量 ((BATCH_SIZE, NUMBER_OF_TIMESTEPS, 1)) 的大小被广播到预测张量 ((BATCH_SIZE, NUMBER_OF_TIMESTEPS, 5)) 的大小。在这种情况下,广播是通过复制列来完成的。例如,如果Y_train 在第一个时间步的第一行有[-1.69862224],而Y_pred 在第一个时间步的第一行有[-0.6132075 , -0.6621697 , -0.7712653 , -0.60011995, -0.48753992],则要执行减法运算,Y_train 中的条目是转换为[-1.69862224, -1.69862224, -1.69862224, -1.69862224, -1.69862224]

这 5 个值中的哪一个是为提前 96 时间步预测选择的“正确”值?

以这种方式训练时没有真正的“正确”值 - 如上所述,这只是 API 的一个特性。所有输出都应收敛到时间步长的单个目标值,它们都与该值进行比较,因此您可以在技术上以这种方式进行训练,但这只是为模型增加了参数和复杂性(您只需要选择一个成为“真实”的预测)。原始答案中详细说明了获得提前 96 个时间步的预测的正确方法,但重申一下,模型的输出包含批次中每个样本的未来时间步预测。可以迭代输出张量以检索每个时间步长、每个样本的预测。此外,确保最终密集层中的神经元数量与您尝试预测的目标值的数量相匹配,否则您将遇到广播问题(并且“正确”的输出将不清楚)。

为了详尽无遗(我推荐这样做),如果您真的想在输出中合并多个神经元,尽管只有一个目标值,您可以执行类似平均结果的操作:

for i in range(0, len(Y_pred)):
    prediction_lastValues_list.append(np.mean(Y_pred[i][0]))

但是这种方法绝对没有任何好处,所以我建议坚持之前的建议。

更新 2

我的模型是只预测一个时间段,即未来 96 个时间步长,还是同时预测这两者之间的所有时间? 该模型正在预测介于两者之间的一切。因此对于时间步 t 的样本,模型的输出是预测 [t + 1, t + 2, ..., t + NUMBER_OF_TIMESTEPS]。根据我的原始答案,“您模型的输出张量包含每个样本未来 96 步的预测值”。要在评估代码中指定这一点,您可以执行以下操作:

Y_pred = np.squeeze(Y_pred)
predictions_for_all_samples_and_timesteps = Y_pred.tolist()

这会产生一个长度为BATCH_SIZE 的列表,并且列表中的每个元素都是一个长度为NUMBER_OF_TIMESTEPS 的列表(要清楚,predictions_for_all_samples_and_timesteps 是一个列表的列表)。 predictions_for_all_samples_and_timesteps 中索引 i 处的元素包含对 X_test 中第 i^th 个样本(行)从 1 到 96 的每个时间步长的预测。

作为旁注,您可以省略 np.squeeze,但随后您将得到一个列表列表,其中内部列表中的每个元素都是一个项目的列表(而不是 [[1, 2, 3, ...], ],输出将看起来像[[[1], [2], [3], ...], ]

更新 3

Y_testY_pred 都是大小为 (BATCH_SIZE, NUMBER_OF_TIMESTEPS, 1) 的 3-D numpy 数组。要比较它们,您可以取两者之间的绝对(或平方)差:

abs_diff = np.abs(Y_pred - Y_test)

这会产生一个相同维度的数组(BATCH_SIZE, NUMBER_OF_TIMESTEPS)。然后,您可以遍历行并为每行生成时间步误差图。

for diff in abs_diff:
    print(diff.shape)
    plt.plot(list(range(diff)), diff)

对于大批量大小(如您在图像中看到的),它可能会变得有点笨拙,所以也许您绘制行的子集。如果您想绘制它,也可以将绝对差异转换为错误百分比:

percentage_diff = abs_diff / Y_test

这将是与实际值的绝对差异,正如我看到您最初在 Pandas 中所做的那样。这个 numpy 数组将具有相同的维度,因此您可以对其进行迭代并以相同的方式生成绘图。

对于未来的查询,请打开一个新问题并提供链接,而不是发布 cmets - 我很乐意继续提供帮助,但我希望继续从中获得声誉。

【讨论】:

  • 非常感谢 danielcahall。我有 2 个问题。 1)虽然我非常感谢您在 ` prediction_lastValues_list.append((Y_pred[i][-1][1 - 1])), my main question has still not been answered. Maybe you can have a look at the screenshot that I posted. When I use keras.layers.TimeDistributed(keras.layers.Dense (5)` 我得到批次中的每个项目和时间序列中的每个项目 5 个值。当我只有 1 个目标特征时,这 5 个值代表什么?这 5 个值中的哪一个是要选择的“正确”值提前 96 步的预测?
  • 2) 关于你的侧节点,我不太明白问题是什么。对我来说,这个问题还没有发生。那么为什么在你运行它的时候会发生呢?也许我们使用不同版本的 numpy?
  • 感谢您的精彩更新。我真的很感谢你的努力。也许最后一个问题(在我接受你的回答之前)。我的模型是只预测一个时间段,即未来 96 个时间步长,还是它也预测介于两者之间的所有内容?例如,如果预测是在下午 00:00,则意味着模型仅预测下午 00:00。第二天或之间的每个时间段,意思是 {00:15, 00:30, ...,23:45, 00:00(第二天)}。因为这实际上是我想要的。我怎么能在这里指定prediction_lastValues_list.append((Y_pred[i][-1][1 - 1]))
  • 感谢您的出色更新和巨大努力。我赞成并接受了您的回答,并将赏金奖励给了您。但是,我仍然有一些后续问题(但我可以理解,如果您不想再回答这些问题)。 1) 我不明白Y_pred = np.squeeze(Y_pred) 究竟做了什么? Y_pred 现在是否包含每个时间段的所有预测(没有挤压它不包含)?
  • 2) 我想将预测值与实际值进行比较。为此,我在代码中使用了数据框wholeDataFrameWithPrediciton_traInv.rename(columns = {indexWithYLabelsInData:'actual'}, inplace = True)wholeDataFrameWithPrediciton_traInv['predictions'] = predictions_traInv 的两列。然后我计算例如两列之间的 RMSE 误差。这是否还考虑了介于两者之间的所有预测因素,还是始终只考虑距当前时隙 96 步的预测时隙?
【解决方案2】:

我仅在一点上不同意@danielcahall:

模型的输出张量包含每个样本未来 96 步的预测值

输出确实包含 96 个时间步,每个输入时间步一个,您可以将输出表示为任何您想要的意思。但这对于您正在尝试做的事情来说并不是一个好的模型。主要原因是您使用的 RNN 是单向的。

x   x   x   x   x   x    # input
|   |   |   |   |   | 
x-->x-->x-->x-->x-->x    # SimpleRNN
|   |   |   |   |   | 
x-->x-->x-->x-->x-->x    # SimpleRNN
|  /|\ /|\ /|\ /|\  | 
| / | \ | \ | \ | \ |
x   x   x   x   x   x    # Conv
|   |   |   |   |   | 
x   x   x   x   x   x    # Dense -> output

所以输出的第一次索引只能看到前 2 个输入时间(感谢 Conv),它看不到后面的时间。第一个预测仅基于旧数据。只有最后几个输出才能看到所有输入。

使用 96 个后退步骤来预测未来的 96 个步骤

大多数输出​​只是看不到所有数据。

如果您尝试从每个输入时间预测未来 1 步,则此模型将是合适的。

要预测未来 96 步,删除 return_sequences=TrueConv 层会更合理。然后展开 Dense 层进行预测:

model = keras.models.Sequential([
    keras.layers.SimpleRNN(10, return_sequences=True, input_shape=[None, 3]), # output size is (BATCH_SIZE, NUMBER_OF_TIMESTEPS, 10)
    keras.layers.SimpleRNN(10), # output size is (BATCH_SIZE, 10)
    keras.layers.Dense(96) # output size is (BATCH_SIZE, 96)
])

这样所有 96 个预测都会看到所有 96 个输入。

更多详情请见https://www.tensorflow.org/tutorials/structured_data/time_series

SimpleRNN 也很糟糕。永远不要使用它超过几个步骤。

【讨论】:

  • 非常感谢 mdaoust 的回答。我对此有一些问题/评论。 1)您写道“第一个预测仅基于旧数据。只有最后几个输出才能看到所有输入。” --> 实际上所有的预测都是基于旧数据。我想用过去的 96 个时间步来预测未来的 96 个时间步。因此,所有预测都只看到旧数据。 2)为什么我要在第二层去掉 return_sequence=true 而不是在第一层去掉?在机器学习书籍中提到,return_sequence =true 会导致训练中更好的收敛。
  • 对我最后的 cmets 或备注有任何 cmets 吗?
  • 你好,mdaoust。我对您建议的方法有进一步的评论:当我按照您的建议删除卷积层并尝试通过设置 steps_backwards = 192 来更改用于预测的过去数据时,我收到错误消息“ValueError: Dimensions must be equal , 但对于 '{{node mean_squared_error/SquaredDifference}} = SquaredDifference[T=DT_FLOAT](sequential_11/time_distributed_11/Reshape_1, IteratorGetNext:1)' 为 192 和 96,输入形状为:[?,192,1], [?, 96,1]。”由history = model.fit(... 行抛出。所以如果没有 Con 层,我无法改变这一点。
  • 我对您的回答还有第四个问题。 4) 当我按照您的建议在最后一个输出层中使用keras.layers.Dense(96) ) 时,输出是您所说的(BATCH_SIZE, 96),而不是使用keras.layers.TimeDistributed(keras.layers.Dense(1) 时的(BATCH_SIZE, NUMBER_OF_TIMESTEPS, 1)。我不明白的是标​​签向量Y_train仍然有大小(BATCH_SIZE, NUMBER_OF_TIMESTEPS, 1)和特征向量X_train(BATCH_SIZE, NUMBER_OF_TIMESTEPS, NUMBER_OF_FEATURES)
  • 您的代码实际上是如何进行培训的?使用时间分布层时,3 维向量 X_train 将映射到 2 维向量(即代码的输出)而不是 3 维向量。这似乎很奇怪。此外,当使用您的方法在最后一层中使用另一个输出大小时,例如keras.layers.Dense(5) 这变得更加不清楚。你介意再详细说明一下吗?我将非常感谢您的每进一步评论。
猜你喜欢
  • 2021-09-21
  • 2018-11-04
  • 2018-03-30
  • 2020-10-24
  • 1970-01-01
  • 2018-08-08
  • 2019-07-21
  • 2021-12-18
  • 1970-01-01
相关资源
最近更新 更多