首页 文章

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

提问于
浏览
4

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

相反,我所看到的人是在未来的任何时候使用基础事实作为估计的输入来估计测试集的准确性 . 这是非常不公平的,因为它不会产生真正的预测!

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

Example 1

Example 2

Example 3

你有任何建议可以获得更好的结果吗?如果LSTM无法预测这种“简单”信号,它如何预测复杂行为?

谢谢,亚历山德罗

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 回答

  • 2

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

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

    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

  • 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个时代的模型,批量大小为5:

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

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

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

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

相关问题