我试图根据室内和室外气候预测墙体的湿热响应。基于文献研究,我认为这应该是可以用RNN实现的,但我一直无法获得良好的准确性。
数据集有12个输入特征(室外和室内气候数据的时间序列)和10个输出特征(湿热响应的时间序列),两者都包含10年的每小时值。这些数据是通过湿热模拟软件创建的,没有缺失数据。
与大多数时间序列预测问题不同,我希望在每个时间步预测输入特征时间序列的整个长度的响应,而不是时间序列的后续值(例如金融时间序列预测)。我没有找到类似的预测问题(在类似或其他领域),所以如果你知道这方面的例子,非常欢迎提供参考文献。
我认为这应该是可以用RNN实现的,所以我目前正在使用Keras中的LSTM。在训练之前,我以以下方式预处理数据:
- 丢弃第一年的数据,因为墙体湿热响应的初始时间步骤受初始温度和相对湿度的影响。
- 将数据分为训练集和测试集。训练集包含前8年的数据,测试集包含剩余的2年数据。
- 使用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()
函数。我只有一个样本,所以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.add(TimeDistributed(Dense(targets)))
model.compile(loss='mean_squared_error', optimizer=Adam())
model.fit(X_train, y_train, epochs=100, batch_size=batch_size, verbose=2, shuffle=False)
不幸的是,我没有得到准确的预测结果;即使是对于训练集也是如此,因此模型存在高偏差。
我如何改进我的模型?我已经尝试了以下方法:
- 不丢弃数据集的第一年 -> 没有显著差异
- 对输入特征的时间序列进行差分处理(从当前值中减去前一个值)-> 结果略差
- 最多四个堆叠的LSTM层,所有层使用相同的超参数 -> 结果没有显著差异,但训练时间更长
- 在LSTM层后添加Dropout层(尽管这通常用于减少方差,而我的模型具有高偏差)-> 结果略有改善,但差异可能没有统计学意义
我在使用有状态的LSTM时是否做错了什么?我是否需要尝试不同的RNN模型?我应该以不同的方式预处理数据吗?
此外,训练速度非常慢:上述模型大约需要4小时。因此,我不太愿意进行广泛的超参数网格搜索…
回答:
最终,我以以下方式解决了这个问题:
- 使用更多样本进行训练,而不是仅使用1个样本(我使用了18个样本进行训练,6个样本进行测试)
- 保留第一年的数据,因为所有样本的输出时间序列具有相同的“起点”,模型需要这些信息来学习
- 标准化输入和输出特征(零均值,单位方差)。我发现这提高了预测准确性和训练速度
- 按照这里描述的使用有状态的LSTM,但在每个epoch后添加重置状态(见下方代码)。我使用了
batch_size = 6
和T_after_cut = 1460
。如果T_after_cut
更长,训练速度会更慢;如果T_after_cut
更短,准确性会略有下降。如果有更多样本可用,我认为使用更大的batch_size
会更快。 - 使用CuDNNLSTM代替LSTM,这将训练时间加速了4倍!
- 我发现更多的单位会导致更高的准确性和更快的收敛(更短的训练时间)。此外,我发现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={}):
# 在完成nb_cuts批次后重置状态
if self.counter % nb_cuts == 0:
self.model.reset_states()
self.counter += 1
def on_epoch_end(self, epoch, logs={}):
# 在每个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学会了捕捉长期依赖性以预测相对湿度。