Keras LSTM预测时间序列被压缩和移位

14

我希望在假期期间通过使用Keras获得一些实践经验,所以我想从股票数据的时间序列预测教科书例子入手。因此,我试图根据过去48小时的平均价格变化(与前一时刻相比的百分比)预测未来一小时的平均价格变化。

然而,当与测试集(甚至是训练集)进行验证时,预测序列的振幅差距很大,并且有时会被移动为始终为正或始终为负,即移开了0%变化,我认为这种情况是不正确的。

我提供了以下最小示例来展示这个问题:

df = pandas.DataFrame.from_csv('test-data-01.csv', header=0)
df['pct'] = df.value.pct_change(periods=1)

seq_len=48
vals = df.pct.values[1:] # First pct change is NaN, skip it
sequences = []
for i in range(0, len(vals) - seq_len):
    sx = vals[i:i+seq_len].reshape(seq_len, 1)
    sy = vals[i+seq_len]
    sequences.append((sx, sy))

row = -24
trainSeqs = sequences[:row]
testSeqs = sequences[row:]

trainX = np.array([i[0] for i in trainSeqs])
trainy = np.array([i[1] for i in trainSeqs])

model = Sequential()
model.add(LSTM(25, batch_input_shape=(1, seq_len, 1)))
model.add(Dense(1))
model.compile(loss='mse', optimizer='adam')
model.fit(trainX, trainy, epochs=1, batch_size=1, verbose=1, shuffle=True)

pred = []
for s in trainSeqs:
    pred.append(model.predict(s[0].reshape(1, seq_len, 1)))
pred = np.array(pred).flatten()

plot(pred)
plot([i[1] for i in trainSeqs])
axis([2500, 2550,-0.03, 0.03])

正如你所看到的,我通过选择最后48小时和下一步来创建训练和测试序列,将其组成一个元组,然后每隔1个小时推进一次,并重复该过程。模型是一个非常简单的1个LSTM和1个密集层。

我本来希望预测出的单个点的图与训练序列的图相当吻合(毕竟它们是在同一集合上训练的),并且在测试序列中进行匹配。然而,在训练数据上,我得到了以下结果:

  • 橙色:真实数据
  • 蓝色:预测数据

enter image description here

有什么想法可能会发生什么事情?我误解了什么吗?

更新:为了更好地展示我所说的平移和压缩,我还绘制了预测值的图形,将其向后偏移以匹配实际数据并乘以比例系数。

plot(pred*12-0.03)
plot([i[1] for i in trainSeqs])
axis([2500, 2550,-0.03, 0.03])

正如你所看到的,预测结果很好地适应了真实数据,只是在某种程度上被压缩和偏移了,我不知道为什么。

在这里输入图片描述


它仍然在训练数据上,因此我对“良好拟合”的期望。如果预测是在新数据上执行的,那么它可能会发散,但在训练集上,我希望模型能够返回一个良好的近似值,而无需重新缩放和移位。 - cdecker
请分享test-data-01.csv文件。 - milos.ai
当然:我已经在这里上传了 https://gist.github.com/anonymous/50dc9e36616605bfbf021642b47a4336 - cdecker
@cdecker,你解决了这个问题吗?特别是始终为正或始终为负的部分?因为我目前的lstm也遇到了完全相同的问题。 - jrenk
4个回答

8

我猜想你正在过拟合,因为你的数据维度只有1,而一个带有25个单元的LSTM对于这样低维的数据集来说似乎过于复杂了。以下是我会尝试的一些方法:

  • 减少LSTM的维度。
  • 添加某种形式的正则化以对抗过拟合。例如,dropout可能是一个不错的选择。
  • 训练更多的epochs或改变学习率。模型可能需要更多的epochs或更大的更新才能找到适当的参数。

更新。 让我总结一下我们在评论区讨论的内容。

仅作澄清,第一个图并不显示对验证集的预测序列,而是针对训练集。因此,我的第一个过拟合解释可能是不准确的。我认为一个适当的问题是:从这样一个低维数据集中真的能够预测未来的价格变化吗?机器学习算法并不是神奇的:只有在数据中存在模式时,它们才会找到模式。

如果仅靠过去的价格变化确实不能很好地说明未来的价格变化,则:

  • 你的模型将学习预测价格变化的平均值(可能在0左右),因为在没有信息特征的情况下,这是产生最低损失的值。
  • 预测结果可能会出现轻微的“偏移”,因为时间步t+1的价格变化与时间步t的价格变化略有相关性(但仍然,预测接近于0是最安全的选择)。这确实是我作为一个非专业人士能够观察到的唯一模式(即时间步t+1的值有时类似于时间步t的值)。
如果在时间步骤t和t+1处的值更具一般相关性,那么我认为模型对这种相关性会更加自信,预测的振幅也会更大。

过拟合不会导致在训练数据上的几乎完美匹配吗?实际情况恰恰相反。而且,到目前为止我尝试的所有模型(简单和复杂的)都得到了这个结果。 - cdecker
哦,我误解了你的情节。你是对的,它应该匹配训练数据。我对股票数据不太熟悉,但在这种情况下,你认为从这样一个低维度的数据集中实际上能够预测未来的价格变动吗?换句话说,一个专家能否仅从过去的价格变化中预测未来的价格变化? - rvinas
1
如果答案是否定的,那么你的结果对我来说是完全有道理的。如果数据中没有明显的模式,你的模型将学习预测价格变化的平均值(可能非常接近于0,对吧?),因为在缺乏信息特征的情况下,这个值会产生最低的损失。然后,预测可能会出现轻微的“偏移”,因为时间步t+1的价格变化与时间步t的价格变化略微相关(但仍然,预测接近于0是最安全的选择)。 - rvinas
如果该特征不包含足够的信息,确实无法指向均值是一个“好”的预测。然而,这并不能解释为什么它会偏向一侧或另一侧。有趣的是,如果我将预测序列乘以4或5,并将其移回到真实数据上,它会给我一个非常好的拟合(请参见编辑后的问题)。 - cdecker
我认为预测序列可能被“移位”的原因是目标值与紧挨着它的值略微相关。也就是说,如果时间步 t 上的值是 0.02,那么模型会学习到,在时间步 t+1 上,该值很可能接近于 0.02。然而,这种影响随后会受到我们之前讨论过的因素的衰减。作为一个非专家,我唯一能观察到的模式确实就是这个(即时间步 t+1 上的值有时候与时间步 t 上的值相似)。 - rvinas
如果时间步长t和t+1处的值在一般情况下更相关,那么模型将更加自信地对这种相关性进行预测,并且预测的振幅会更高。 - rvinas

3
  1. 增加epochs的数量。您可以使用EarlyStopping来避免过拟合。
  2. 您的数据进行了怎样的缩放?时间序列对数据中的异常值非常敏感。例如,尝试使用MinMax((0.1, 0.9)),然后RobustScaler也是一个不错的选择。
  3. 我不确定在数据量不够大时是否真的需要LSTM(seq_len)。为什么不尝试更小的维度呢?

尝试所有这些方法,并尝试过拟合(mse应该在“真实数据集”上接近零)。然后应用正则化。

更新

让我解释一下你得到的原因

plot(pred*12-0.03)

让我们把LSTM层看作一个黑匣子并暂时忘掉它,它返回给我们25个值 - 这就是全部。这些值会被传递到Dense层,在那里我们将对这个由25个值构成的向量应用函数:

优秀的匹配。

y = w * x + b

这里的wb是由NN定义的向量,通常在开始时接近于零。 x是LSTM层后的值,y是目标(单个值)。
当您只有1个纪元时:w和b根本没有适应您的数据(实际上它们都接近于零)。但是如果您应用
plot(pred*12-0.03)

给出预测值后,你(某种方式)将其应用于目标变量 w b 。现在, w b 是单个值,而不是向量,并且它们被应用于单个值。但是它们几乎与Dense层执行相同的工作。
因此,增加周期数以获得更好的拟合效果。
更新2 顺便说一下,我看到数据中有一些异常值。您还可以尝试使用MAE作为损失/准确度指标。

我认为这里的问题不是过拟合(我最初也认为图表涉及验证数据集),如果我理解正确,数据已经在合理范围内(-1,1),因为它是价格百分比变化。我认为这个特征可能不够信息丰富,正如我在其中一条评论中所说的那样。 - rvinas
我同意 - 在提供的代码中,由于epoch数量为1,现在不存在过拟合问题。我只是为了TS而提到它。过拟合问题将在接下来的步骤中出现。关于缩放。我再次同意缩放的重要性,但不确定重新缩放是否有帮助。我只是不知道。可能,非常小的输入值可能会有问题。我不知道,并且手头没有适当的数据集来进行检查。也许这是噪声。在这种情况下,预测几乎没有意义。 - avchauzov
感谢指出dense是线性逼近函数。不幸的是,将epochs设置为100并没有真正改变任何东西。 - cdecker
你还有预测值接近于零吗? 你对数据进行了重新缩放吗? - avchauzov

1

嗨Yanick,你能分享一下你的代码吗?我也想试试。我无法完全理解上面链接中的代码。从cdecker的代码中,我不知道如何处理sy = vals[i+seq_len]在上面的链接中。 - Sowmya

0

因为我也遇到了这个问题,增加batch_size可以完全解决SimpleRNN问题,使用relu、elu作为激活函数,需要将学习率从默认值增加,并尝试配置一些SmoteRNN方案来获取更多的过去数据,或者改变行之间的时间间隔以获得更多模型找到模式的数据。目前似乎对我有用,仍在努力提高准确性至80%以上, 准确率=(mean_absolute_error / test.mean())*100


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