使用LSTM预测一个简单的合成时间序列。为什么效果这么差?

我刚开始使用Keras中的LSTM进行尝试,我发现学习时间序列行为的可能性非常吸引人。我在网上阅读了几篇教程和文章,大多数文章展示了在预测时间序列方面的令人印象深刻的能力,所以我决定尝试一下。首先我注意到的是,我找到的所有文章总是以一种非常不公平的方式使用验证数据。我对预测时间序列的理解是,使用训练数据构建模型,并使用训练数据的最后N个元素来估计序列的未来行为。为了做到这一点,模型必须使用自己的预测作为输入,以向前推进到未来。

然而,我看到人们所做的是,在未来的任何时间点使用真实值作为输入来估计测试集的准确性。这非常不公平,因为它并不能产生真正的预测!

我尝试在Keras中编写自己的LSTM预测代码(请见下面的代码),我从一个相对简单的案例开始,即抛物线和正弦波的组合。不幸的是,结果相当不令人满意。以下是通过更改网络参数获得的一些示例:

示例1

示例2

示例3

你有什么建议可以获得更好的结果吗?如果LSTM不能预测这样一个“简单”的信号,它们如何能预测复杂的行为?

谢谢,Alessandro

import osimport numpy as npfrom matplotlib import pyplot as pltimport keras# Number of vectors to consider in the time windowlook_back = 50N_datapoints = 2000train_split = 0.8# Generate a time signal composed of a linear function and a sinusoidt = np.linspace(0, 200, N_datapoints)y = t**2 + np.sin(t*2)*1000y -= y.mean()y /= y.std()plt.plot(y)# Reshape the signal into fixed windows for trainingdef create_blocks(y, look_back=1):    x_data, y_data = [], []    for i in range(0, len(y)-look_back-1):        x_data.append(y[i:i+look_back])        y_data.append(y[i+look_back])    return np.array(x_data), np.array(y_data)x_data, y_data = create_blocks(y, look_back)# Split data in training and testingN_train = int(x_data.shape[0]*train_split)x_train = x_data[:N_train, :, None]y_train = y_data[:N_train, ]x_test = x_data[N_train:-1, :, None]y_test = y_data[N_train:-1:, ]# Get the time vector for train and test (just to plot)t_train = t[0:N_train-1, None]t_test = t[N_train:-1, None]# Networkfrom keras import Model, Inputfrom keras.layers import LSTM, Dense, Activation, BatchNormalization, Dropoutinputs = Input(shape=(look_back, 1))net = LSTM(32, return_sequences=False)(inputs)net = Dense(32)(net)net = Dropout(0.25)(net)outputs = Dense(1)(net)model = Model(inputs=inputs, outputs=outputs)model.compile(optimizer=keras.optimizers.rmsprop(), loss='mean_squared_error')model.summary()# Callbackfrom keras.callbacks import Callbackclass PlotResuls(Callback):    def on_train_begin(self, logs=None):        self.fig = plt.figure()    def save_data(self, x_test, y, look_back, t_test):        self.x_test = x_test        self.y = y        self.t_test = t_test        self.look_back = look_back    def on_epoch_end(self, epoch, logs=None):        if epoch % 20 == 0:            plt.clf()            y_pred = self.x_test[0, ...]            for i in range(len(x_test)+1):                new_prediction = model.predict(y_pred[None, -self.look_back:, ])                y_pred = np.concatenate((y_pred, new_prediction), axis=0)            plt.plot(t, y, label='GT')            plt.plot(self.t_test, y_pred, '.-', label='Predicted')            plt.legend()            plt.pause(0.01)            plt.savefig('lstm_%d.png' % epoch)plot_results = PlotResuls()plot_results.save_data(x_test, y, look_back, t_test)model.fit(x_train, y_train, validation_data=(x_test, y_test), epochs=100000, batch_size=32, callbacks=[plot_results])

回答:

正如Primusa的回答中所展示的,允许循环层输出其隐藏状态,使用return_sequences=True,结合Bidirectional层,有助于更好地捕捉时间模式。此外,我认为你需要对你试图近似的函数类型有一定的直觉——尝试将其分解成多个函数,并为每个函数构建一个子网络,通常会加速学习过程,尤其是在使用适当的激活组合时。应用权重正则化也很重要,因为它可以阻止由于误差累积导致的极端发散。还要注意,除非你使用stateful=True,否则你需要为网络提供足够长的时间框架来检查长程模式(即,如果时间窗口较小,抛物线很容易近似为一条直线)。

具体来说,下面的修改在20个epoch后达到了(仍在迅速下降的)均方误差(1.0223e-04/0.0015),在100个epoch后达到了(2.8111e-05/3.0393e-04),回溯期为100(请注意,我还将你的优化器改成了Adam,我个人更喜欢这个):

from keras import Model, Inputfrom keras.layers import (LSTM, Dense, Activation, BatchNormalization,                       Dropout, Bidirectional, Add)inputs = Input(shape=(look_back, 1))bd_seq = Bidirectional(LSTM(128, return_sequences=True,                            kernel_regularizer='l2'),                        merge_mode='sum')(inputs)bd_sin = Bidirectional(LSTM(32, return_sequences=True,                             kernel_regularizer='l2'),                        merge_mode='sum') (bd_seq)bd_1 = Bidirectional(LSTM(1, activation='linear'),                      merge_mode='sum')(bd_seq)bd_2 = Bidirectional(LSTM(1, activation='tanh'),                      merge_mode='sum')(bd_sin)output = Add()([bd_1, bd_2])model = Model(inputs=inputs, outputs=output)model.compile(optimizer='adam', loss='mean_squared_error')

20个epoch

100个epoch

Related Posts

L1-L2正则化的不同系数

我想对网络的权重同时应用L1和L2正则化。然而,我找不…

使用scikit-learn的无监督方法将列表分类成不同组别,有没有办法?

我有一系列实例,每个实例都有一份列表,代表它所遵循的不…

f1_score metric in lightgbm

我想使用自定义指标f1_score来训练一个lgb模型…

通过相关系数矩阵进行特征选择

我在测试不同的算法时,如逻辑回归、高斯朴素贝叶斯、随机…

可以将机器学习库用于流式输入和输出吗?

已关闭。此问题需要更加聚焦。目前不接受回答。 想要改进…

在TensorFlow中,queue.dequeue_up_to()方法的用途是什么?

我对这个方法感到非常困惑,特别是当我发现这个令人费解的…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注