我刚开始使用Keras中的LSTM进行尝试,我发现学习时间序列行为的可能性非常吸引人。我在网上阅读了几篇教程和文章,大多数文章展示了在预测时间序列方面的令人印象深刻的能力,所以我决定尝试一下。首先我注意到的是,我找到的所有文章总是以一种非常不公平的方式使用验证数据。我对预测时间序列的理解是,使用训练数据构建模型,并使用训练数据的最后N个元素来估计序列的未来行为。为了做到这一点,模型必须使用自己的预测作为输入,以向前推进到未来。
然而,我看到人们所做的是,在未来的任何时间点使用真实值作为输入来估计测试集的准确性。这非常不公平,因为它并不能产生真正的预测!
我尝试在Keras中编写自己的LSTM预测代码(请见下面的代码),我从一个相对简单的案例开始,即抛物线和正弦波的组合。不幸的是,结果相当不令人满意。以下是通过更改网络参数获得的一些示例:
你有什么建议可以获得更好的结果吗?如果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')