使用 RNN 进行非线性多元时间序列响应预测

Non-linear multivariate time-series response prediction using RNN

我正在尝试根据内部和外部气候预测墙壁的湿热响应。根据文献研究,我相信 RNN 应该可以做到这一点,但我无法获得很好的准确性。

数据集有 12 个输入特征(外部和内部气候数据的时间序列)和 10 个输出特征(湿热响应时间序列),均包含 10 年的每小时值。此数据是用湿热模拟软件创建的,没有遗漏数据。

数据集特征:

数据集目标:

与大多数时间序列预测问题不同,我想预测每个时间步的输入特征时间序列的全长响应,而不是时间序列的后续值(例如金融时间-系列预测)。我没能找到类似的预测问题(在类似或其他领域),所以如果你知道一个,非常欢迎参考。


我认为这应该可以通过 RNN 实现,所以我目前使用的是 Keras 的 LSTM。在训练之前,我按以下方式预处理我的数据:

  1. 舍弃第一年的数据,因为墙的湿热响应的第一个时间步长受初始温度和相对湿度的影响。
  2. 分成训练和测试集。训练集包含前8年的数据,测试集包含剩余2年的数据。
  3. 使用来自 Sklearn 的 StandardScaler 规范化训练集(零均值,单位方差)。类似地使用来自训练集的均值方差对测试集进行归一化。

结果为:X_train.shape = (1, 61320, 12)y_train.shape = (1, 61320, 10)X_test.shape = (1, 17520, 12)y_test.shape = (1, 17520, 10)

由于这些是长时间序列,我使用有状态 LSTM 并使用 stateful_cut() 函数按照 here 的说明切割时间序列。我只有 1 个样本,所以 batch_size 是 1。对于 T_after_cut 我试过 24 和 120 (24*5); 24 似乎给出了更好的结果。这导致 X_train.shape = (2555, 24, 12)y_train.shape = (2555, 24, 10)X_test.shape = (730, 24, 12)y_test.shape = (730, 24, 10)

接下来,我按如下方式构建和训练 LSTM 模型:

model = Sequential()
model.add(LSTM(128, 
               batch_input_shape=(batch_size,T_after_cut,features), 
               return_sequences=True,
               stateful=True,
               ))
model.addTimeDistributed(Dense(targets)))
model.compile(loss='mean_squared_error', optimizer=Adam())

model.fit(X_train, y_train, epochs=100, batch_size=batch=batch_size, verbose=2, shuffle=False)

很遗憾,我没有得到准确的预测结果;甚至对于训练集也没有,因此该模型具有很高的偏差。

The prediction results of the LSTM model for all targets


如何改进我的模型?我已经尝试过以下方法:

  1. 不丢弃数据集的第一年 -> 无显着差异
  2. 区分输入特征时间序列(从当前值中减去先前值)-> 稍差的结果
  3. 最多四个堆叠的 LSTM 层,都具有相同的超参数 -> 结果没有显着差异但训练时间更长
  4. LSTM 层之后的 Dropout 层(虽然这通常用于减少方差并且我的模型具有高偏差)-> 稍微好一点的结果,但差异可能不具有统计显着性

我是不是对有状态的 LSTM 做错了什么?我需要尝试不同的 RNN 模型吗?我应该以不同的方式预处理数据吗?

此外,训练非常缓慢:上面的模型大约需要 4 个小时。因此我不愿意做一个广泛的超参数网格搜索...

最后,我设法通过以下方式解决了这个问题:

  • 使用更多样本进行训练而不是仅使用 1 个(我使用 18 个样本进行训练,6 个样本进行测试)
  • 保留第一年的数据,因为所有样本的输出 time-series 具有相同的 'starting point' 并且模型需要此信息来学习
  • 标准化输入和输出特征(零均值,单位方差)。我发现这提高了预测准确性和训练速度
  • 按照 here 所述使用有状态 LSTM,但在纪元之后添加重置状态(代码见下文)。我使用了 batch_size = 6T_after_cut = 1460T_after_cut越长,训练越慢;如果 T_after_cut 较短,准确度会略有下降。如果有更多样本可用,我认为使用更大的 batch_size 会更快。
  • 使用 CuDNNLSTM 代替 LSTM,这加快了训练时间 x4!
  • 我发现更多的单位导致更高的准确性和更快的收敛(更短的训练时间)。我还发现 GRU 与 LSTM 一样准确,对于相同数量的单元收敛得更快。
  • 在训练期间监控验证损失并使用提前停止

LSTM 模型构建和训练如下:

def define_reset_states_batch(nb_cuts):
  class ResetStatesCallback(Callback):
    def __init__(self):
      self.counter = 0

    def on_batch_begin(self, batch, logs={}):
    # reset states when nb_cuts batches are completed
      if self.counter % nb_cuts == 0:
        self.model.reset_states()
      self.counter += 1

    def on_epoch_end(self, epoch, logs={}):
    # reset states after each epoch
      self.model.reset_states()
      return(ResetStatesCallback)    

model = Sequential()
model.add(layers.CuDNNLSTM(256, batch_input_shape=(batch_size,T_after_cut ,features),
  return_sequences=True,
  stateful=True))
model.add(layers.TimeDistributed(layers.Dense(targets, activation='linear')))

optimizer = RMSprop(lr=0.002)
model.compile(loss='mean_squared_error', optimizer=optimizer)

earlyStopping = EarlyStopping(monitor='val_loss', min_delta=0.005, patience=15, verbose=1, mode='auto')
ResetStatesCallback = define_reset_states_batch(nb_cuts)
model.fit(X_dev, y_dev, epochs=n_epochs, batch_size=n_batch, verbose=1, shuffle=False, validation_data=(X_eval,y_eval), callbacks=[ResetStatesCallback(), earlyStopping])

这给了我非常令人满意的准确性(R2 超过 0.98): 该图显示了 2 年内墙壁的温度(左)和相对湿度(右)(训练中未使用的数据),红色为预测值,黑色为真实输出。残差表明误差非常小,并且 LSTM 学会了捕捉 long-term 依赖关系来预测相对湿度。