使用LSTM预测简单的合成时间序列。为什么这么糟糕?

时间:2018-04-20 23:22:10

标签: python tensorflow machine-learning keras lstm

我刚刚开始在keras玩LSTM,我发现学习时间序列行为的可能性非常令人着迷。 我已经在线阅读了几篇教程和文章,其中大多数都在预测时间序列方面显示了令人印象深刻的功能,所以我试了一下。我注意到的第一件事是我发现的所有文章总是以非常不公平的方式使用验证数据。我对预测时间序列的想法是我使用训练数据来构建模型并使用训练数据的最后 N 元素来估计系列的未来行为。要做到这一点,模型必须使用自己的预测作为输入,以便在未来向前迈进。

我认为人们所做的是使用基础事实作为估算的输入来估计未来任何时间测试集的准确性。这是非常不公平的,因为它不会产生真正的预测!

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

Example 1

Example 2

Example 3

您有什么建议可以获得更好的结果吗? LSTM如何预测复杂的行为,如果他们无法预测这样一个简单的"信号φ

谢谢你, 的Alessandro

import os
import numpy as np
from matplotlib import pyplot as plt
import keras

# Number of vectors to consider in the time window
look_back = 50
N_datapoints = 2000
train_split = 0.8

# Generate a time signal composed of a linear function and a sinusoid
t = np.linspace(0, 200, N_datapoints)
y = t**2 + np.sin(t*2)*1000
y -= y.mean()
y /= y.std()

plt.plot(y)

# Reshape the signal into fixed windows for training
def 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 testing
N_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]

# Network
from keras import Model, Input
from keras.layers import LSTM, Dense, Activation, BatchNormalization, Dropout

inputs = 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()

# Callback
from keras.callbacks import Callback
class 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])

2 个答案:

答案 0 :(得分:4)

正如Primusa的答案中所示,允许复发层使用return_sequences=True输出其隐藏状态以及Bidirectional层已被证明可以更好地捕获时间模式。另外,我认为你需要对你试图近似的函数类型有一种直觉 - 试图将它分解为多个函数并为每个函数构建一个子网络通常会加速学习过程,特别是在使用适当的激活组合时。应用权重正则化也是相关的,因为它可以由于误差累积而停止极端分歧。另请注意,除非您使用stateful=True,否则您需要为网络提供足够长的时间范围来检查远程模式(例如,如果时间窗很小,抛物线很容易近似为一条线)

具体而言,以下变更达到(仍然迅速下降)的MSE(1.0223e-04 / 0.0015) )在20个时期之后和(2.8111e-05 / 3.0393e-04)之后的100个时期,回顾率为100(请注意,我已将您的优化器更改为 Adam ,我只是更喜欢):< / p>

from keras import Model, Input
from 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 epochs

100 epochs

答案 1 :(得分:2)

虽然神经网络非常复杂和强大,但它们并不是一个神奇的盒子。很多时候,您需要对网络进行微调以获得更好的结果。

我调整了你的模型,我得到了这些结果:

enter image description here

虽然它们并非极其准确,但我会说它们比您在问题中发布的结果要好得多。这种神经网络对波的频率有清晰的认识,但需要更多的工作来确定线的总体趋势。您可以看到它的预测能力在接近曲线的最大值时会变差。

我使用的模型是:

model = Sequential()
model.add(Bidirectional(LSTM(8, return_sequences=True),input_shape=(50, 1),))
model.add(LSTM(8, return_sequences=True))
model.add(LSTM(4, return_sequences=False))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')

我将你的look_back时间从100缩短到50,以缩短训练时间。我以50的批量训练模型50个时代:

c.fit(epochs=50, batch_size=5)

我的笔记本电脑上花了大约15分钟(在CPU上训练而不是GPU)。

我用来提高其准确性的主要技巧是双向LSTM,它涉及两个LSTMS,一个序列向前馈送,另一个序列向后馈送。这样做的想法是使用未来的数据来理解曲线的上下文。

请注意,未来数据的使用仅限于培训期间。在实际预测期间,仅使用先前的数据来预测下一个点,并且我也使用了您的“滚动预测”概念,其中预测稍后用作输入。