极差的预测能力:LSTM时间序列

7

我尝试使用LSTM模型进行时间序列预测。以下是我的试验代码。这段代码可以正常运行,你也可以在没有依赖的情况下尝试它。

import numpy as np, pandas as pd, matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense, TimeDistributed, Bidirectional
from sklearn.metrics import mean_squared_error, accuracy_score
from scipy.stats import linregress
from sklearn.utils import shuffle

fi = 'pollution.csv'
raw = pd.read_csv(fi, delimiter=',')
raw = raw.drop('Dates', axis=1)
print (raw.shape)

scaler = MinMaxScaler(feature_range=(-1, 1))
raw = scaler.fit_transform(raw)

time_steps = 7
def create_ds(data, t_steps):
    data = pd.DataFrame(data)
    data_s = data.copy()
    for i in range(time_steps):
        data = pd.concat([data, data_s.shift(-(i+1))], axis = 1)   
    data.dropna(axis=0, inplace=True)
    return data.values

ds = create_ds(raw, time_steps)
print (ds.shape)
n_feats = raw.shape[1]
n_obs = time_steps * n_feats

n_rows = ds.shape[0]
train_size = int(n_rows * 0.8)

train_data = ds[:train_size, :]
train_data = shuffle(train_data)

test_data = ds[train_size:, :]

x_train = train_data[:, :n_obs]
y_train = train_data[:, n_obs:]
x_test = test_data[:, :n_obs]
y_test = test_data[:, n_obs:]

x_train = x_train.reshape(1, x_train.shape[0], x_train.shape[1])
y_train = y_train.reshape(1, y_train.shape[0], y_train.shape[1])
x_test = x_test.reshape(1, x_test.shape[0], x_test.shape[1])

print (x_train.shape)
print (y_train.shape)
print (x_test.shape)
print (y_test.shape)

model = Sequential()
model.add(LSTM(64, return_sequences=True, input_shape=(None, x_train.shape[2]), stateful=True, batch_size=1))
model.add(LSTM(32, return_sequences=True, stateful=True))
model.add(LSTM(n_feats, return_sequences=True, stateful=True)) 

model.compile(loss='mse', optimizer='rmsprop')
model.fit(x_train, y_train, epochs=10, batch_size=1, verbose=2)  
y_predict = model.predict(x_test)
y_predict = y_predict.reshape(y_predict.shape[1], y_predict.shape[2])

y_predict = scaler.inverse_transform(y_predict)

y_test = scaler.inverse_transform(y_test)
y_test = y_test[:,0]
y_predict = y_predict[:,0]

print (y_test.shape)
print (y_predict.shape)

plt.plot(y_test, label='True')
plt.plot(y_predict,  label='Predict')
plt.legend()
plt.show()

enter image description here

但预测非常差。如何改进预测?您有任何改进的想法吗?

通过重新设计架构和/或层,有没有改进预测的想法?


2
数据看起来相当随机。也许这是LSTM在不过度拟合的情况下能做到的最好水平。一个好的经验法则是,如果你自己无法预测数据,就不应该期望神经网络能够做到。 - Primusa
预测看起来相当不错,除非有一些关于振荡周期的规则,那么您可以使用更强大的模型捕获该周期。但是如果这个周期没有遵循任何模式,那么这是一个好的预测。 - Daniel Möller
5个回答

13

如果您想使用我代码中的模型(您提供的链接),则需要正确整理数据:(1个序列,总时间步数,5个特征)

重要提示:我不确定这是否是完成此任务的最佳方式或最佳模型,但是该模型将预测输入之后的7个时间步长 (time_shift=7)。

数据和初始变量

    fi = 'pollution.csv'
raw = pd.read_csv(fi, delimiter=',')
raw = raw.drop('Dates', axis=1)
print("raw shape:")
print (raw.shape)
#(1789,5) - 1789 time steps / 5 features

scaler = MinMaxScaler(feature_range=(-1, 1))
raw = scaler.fit_transform(raw)

time_shift = 7 #shift is the number of steps we are predicting ahead
n_rows = raw.shape[0] #n_rows is the number of time steps of our sequence
n_feats = raw.shape[1]
train_size = int(n_rows * 0.8)


#I couldn't understand how "ds" worked, so I simply removed it because in the code below it's not necessary

#getting the train part of the sequence
train_data = raw[:train_size, :] #first train_size steps, all 5 features
test_data = raw[train_size:, :] #I'll use the beginning of the data as state adjuster


#train_data = shuffle(train_data) !!!!!! we cannot shuffle time steps!!! we lose the sequence doing this

x_train = train_data[:-time_shift, :] #the entire train data, except the last shift steps 
x_test = test_data[:-time_shift,:] #the entire test data, except the last shift steps
x_predict = raw[:-time_shift,:] #the entire raw data, except the last shift steps

y_train = train_data[time_shift:, :] 
y_test = test_data[time_shift:,:]
y_predict_true = raw[time_shift:,:]

x_train = x_train.reshape(1, x_train.shape[0], x_train.shape[1]) #ok shape (1,steps,5) - 1 sequence, many steps, 5 features
y_train = y_train.reshape(1, y_train.shape[0], y_train.shape[1])
x_test = x_test.reshape(1, x_test.shape[0], x_test.shape[1])
y_test = y_test.reshape(1, y_test.shape[0], y_test.shape[1])
x_predict = x_predict.reshape(1, x_predict.shape[0], x_predict.shape[1])
y_predict_true = y_predict_true.reshape(1, y_predict_true.shape[0], y_predict_true.shape[1])

print("\nx_train:")
print (x_train.shape)
print("y_train")
print (y_train.shape)
print("x_test")
print (x_test.shape)
print("y_test")
print (y_test.shape)

模型

您的模型对于这个任务来说不够强大,因此我尝试了一个更大的模型(但另一方面,它太过强大)。

model = Sequential()
model.add(LSTM(64, return_sequences=True, input_shape=(None, x_train.shape[2])))
model.add(LSTM(128, return_sequences=True))
model.add(LSTM(256, return_sequences=True))
model.add(LSTM(128, return_sequences=True))
model.add(LSTM(64, return_sequences=True))
model.add(LSTM(n_feats, return_sequences=True)) 

model.compile(loss='mse', optimizer='adam')

拟合

需要注意的是,我必须训练超过2000个epoch,才能使模型有良好的结果。
我添加了验证数据,以便我们可以比较训练和测试的损失。

#notice that I'm predicting from the ENTIRE sequence, including x_train      
#is important for the model to adjust its states before predicting the end
model.fit(x_train, y_train, epochs=1000, batch_size=1, verbose=2, validation_data=(x_test,y_test))  

预测

重要提示:在基于序列开头预测序列结尾时,模型需要看到开头以调整内部状态,因此我将预测整个数据集(x_predict),而不仅仅是测试数据。

y_predict_model = model.predict(x_predict)

print("\ny_predict_true:")
print (y_predict_true.shape)
print("y_predict_model: ")
print (y_predict_model.shape)


def plot(true, predicted, divider):

    predict_plot = scaler.inverse_transform(predicted[0])
    true_plot = scaler.inverse_transform(true[0])

    predict_plot = predict_plot[:,0]
    true_plot = true_plot[:,0]

    plt.figure(figsize=(16,6))
    plt.plot(true_plot, label='True',linewidth=5)
    plt.plot(predict_plot,  label='Predict',color='y')

    if divider > 0:
        maxVal = max(true_plot.max(),predict_plot.max())
        minVal = min(true_plot.min(),predict_plot.min())

        plt.plot([divider,divider],[minVal,maxVal],label='train/test limit',color='k')

    plt.legend()
    plt.show()

test_size = n_rows - train_size
print("test length: " + str(test_size))

plot(y_predict_true,y_predict_model,train_size)
plot(y_predict_true[:,-2*test_size:],y_predict_model[:,-2*test_size:],test_size)

显示全部数据

enter image description here

显示末尾部分以获取更多详细信息

请注意,这个模型过度拟合,这意味着它可以学习训练数据,但在测试数据中表现不佳。

为了解决这个问题,您必须尝试实验性地使用较小的模型,使用dropout层和其他技术来防止过度拟合。

还要注意,这些数据很可能包含大量随机因素,这意味着模型将无法从中学习任何有用的东西。当你制作较小的模型以避免过度拟合时,你可能会发现模型对于训练数据的预测结果也越来越糟糕。

enter image description here

找到完美的模型并不是一件容易的事情,这是一个开放性问题,你必须进行实验。也许LSTM模型并不是解决方案。也许你的数据根本不可预测等等。这并没有一个确定的答案。

如何知道模型是否好

通过训练中的验证数据,您可以比较训练和测试数据的损失。

Train on 1 samples, validate on 1 samples
Epoch 1/1000
9s - loss: 0.4040 - val_loss: 0.3348
Epoch 2/1000
4s - loss: 0.3332 - val_loss: 0.2651
Epoch 3/1000
4s - loss: 0.2656 - val_loss: 0.2035
Epoch 4/1000
4s - loss: 0.2061 - val_loss: 0.1696
Epoch 5/1000
4s - loss: 0.1761 - val_loss: 0.1601
Epoch 6/1000
4s - loss: 0.1697 - val_loss: 0.1476
Epoch 7/1000
4s - loss: 0.1536 - val_loss: 0.1287
Epoch 8/1000
.....

两者应该一起下降。当测试数据不再下降,但训练数据仍在改善时,您的模型开始过拟合。





尝试另一个模型

我能做到的最好的(但我没有真正尝试过)是使用这个模型:

model = Sequential()
model.add(LSTM(64, return_sequences=True, input_shape=(None, x_train.shape[2])))
model.add(LSTM(128, return_sequences=True))
model.add(LSTM(128, return_sequences=True))
model.add(LSTM(64, return_sequences=True))
model.add(LSTM(n_feats, return_sequences=True)) 

model.compile(loss='mse', optimizer='adam')

当损失大约为:
loss: 0.0389 - val_loss: 0.0437

在这一点之后,验证损失开始上升(所以在这一点之后的训练完全没有用)。
结果: enter image description here 这表明该模型只能学习非常整体的行为,例如具有较高值的区域。但高频率要么过于随机,要么该模型不够好...

create_ds 的作用是使用 7 个变量(t-7t-6t-5t-4t-3t-2t-1)来生成 5 个特征。因此,总共有 7*5=35 个特征被输入到 X(train_x 或 test_x)中,而 5 个特征被输入到 Y(train_y 或 test_y)。在你的回答中,你只使用了 t-7 变量作为 X。你是否能够调整你的回答来涵盖这 35 个特征? - user9703439
@hiker,我恐怕不行。你知道,我并不是一个机器学习专家......我只是“熟练使用keras”而已。 - Daniel Möller
@DanielMöller 我认为,这个程序中最大的偏差源是 raw = scaler.fit_transform(raw)。它将训练和测试数据一起缩放,从而导致预测结果出现偏差。你怎么看? - Roman
我认为这些数据相当随机,没有太多可以做的。 - Daniel Möller

5

您可以考虑更改您的模型:

import numpy as np, pandas as pd, matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense, TimeDistributed, Bidirectional
from sklearn.metrics import mean_squared_error, accuracy_score
from scipy.stats import linregress
from sklearn.utils import shuffle

fi = 'pollution.csv'
raw = pd.read_csv(fi, delimiter=',')
raw = raw.drop('Dates', axis=1)
print (raw.shape)

scaler = MinMaxScaler(feature_range=(-1, 1))
raw = scaler.fit_transform(raw)

time_steps = 7
def create_ds(data, t_steps):
    data = pd.DataFrame(data)
    data_s = data.copy()
    for i in range(time_steps):
        data = pd.concat([data, data_s.shift(-(i+1))], axis = 1)   
    data.dropna(axis=0, inplace=True)
    return data.values

ds = create_ds(raw, time_steps)
print (ds.shape)
n_feats = raw.shape[1]
n_obs = time_steps * n_feats

n_rows = ds.shape[0]
train_size = int(n_rows * 0.8)

train_data = ds[:train_size, :]
train_data = shuffle(train_data)

test_data = ds[train_size:, :]

x_train = train_data[:, :n_obs]
y_train = train_data[:, n_obs:]
x_test = test_data[:, :n_obs]
y_test = test_data[:, n_obs:]

print (x_train.shape)
print (x_test.shape)
print (y_train.shape)
print (y_test.shape)

x_train = x_train.reshape(x_train.shape[0], time_steps, n_feats)
x_test = x_test.reshape(x_test.shape[0], time_steps, n_feats)

print (x_train.shape)
print (x_test.shape)
print (y_train.shape)
print (y_test.shape)

model = Sequential()
model.add(LSTM(64, input_shape=(time_steps, n_feats), return_sequences=True))
model.add(LSTM(32, return_sequences=False))
model.add(Dense(n_feats))

model.compile(loss='mse', optimizer='rmsprop')
model.fit(x_train, y_train, epochs=10, batch_size=1, verbose=1, shuffle=False)

y_predict = model.predict(x_test)
print (y_predict.shape)
y_predict = scaler.inverse_transform(y_predict)

y_test = scaler.inverse_transform(y_test)
y_test = y_test[:,0]
y_predict = y_predict[:,0]

print (y_test.shape)
print (y_predict.shape)

plt.plot(y_test, label='True')
plt.plot(y_predict,  label='Predict')
plt.legend()
plt.show()

enter image description here

但我真的不知道你的实现方法有什么优点:

* both x and y are 3d (1,steps,features) rather than x in 3d (samples, time-steps, features) and y in 2d (samples, features)
* input_shape=(None, x_train.shape[2])
* last layer - model.add(LSTM(n_feats, return_sequences=True, stateful=True)) 

可能有人能提供更好的答案。


我按照@Daniel Möller的代码思路进行了跟随,认为它具有一定的价值。https://github.com/danmoller/TestRepo/blob/master/TestBookLSTM.ipynb - Roman
@hiker,我正在查看你的代码,发现有非常重要的差异,导致它不能像我的代码那样运行。1- x_train包含35个特征(应该只包含5个),2- 似乎你在洗牌数据,因此失去了步骤的顺序,3- 你正在训练一个stateful=True模型而没有重置状态(请注意,在我的代码中,第一个模型不是stateful的,只有第二个模型是 - 第二个模型的目的是无限输出1步并将此步作为输入,并且我没有训练第二个模型)- 这些差异肯定会使一切都不同。 - Daniel Möller
显然,没有像“你的数据应该像我的”这样的规则,但是你的模型肯定必须根据你的数据进行调整。我的模型没有这样做。 - Daniel Möller
关于3D中的x和y。这个答案也是3D的(这是keras的规则,使用非3D数据训练LSTMs是不可能的)。-- input_shape =(None,features)意味着您可以输入任何时间步长的长度。(您不需要恰好7个)--另一个区别:长度为7意味着您正在训练小的时间窗口,而我的模型适用于一次训练整个序列。---最后,关于LSTM而不是Dense,这是模型设计中的一种可能性(可以有任何您想要的层),哪个更好,我不知道,测试可以回答。 - Daniel Möller

4

阅读原始代码,作者首先对数据集进行缩放,然后将其分成训练和测试子集。这意味着关于测试子集(例如波动性等)的信息已经“泄漏”到了训练子集中。

推荐的方法是首先进行训练/测试分割,仅使用训练子集计算缩放参数,并使用这些参数分别对训练和测试子集进行缩放。


2

我不确定你能做什么,因为这些数据似乎没有明显的模式。如果我看不出来,我怀疑LSTM也看不出来。不过你的预测看起来像一条很好的回归线。


2
虽然这是一个很好的一般想法,而且你很可能是正确的,但使用神经网络的一个伟大特点就是能够发现也许我们的大脑无法察觉的模式。但这并不意味着每个数据都有这样的模式。 - Daniel Möller

1

我自己正在创建一个预测类似数据的模型,我创建了一个SMOTErnn解决方案来添加过去的数据,我发现使用batch_size更高且步幅更大的TimeSeriesGenrator可以获得更好的性能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接