循环神经网络1-序列模型:从电影评分到股票预测

139 阅读7分钟

1. 引言

想象一下,你正在看网飞(Netflix)上的电影。作为一个忠实的用户,你可能会对每一部电影都给出评价。然而,随着时间的推移,人们对电影的看法可能会发生变化。例如,奥斯卡颁奖后,某些电影的评分可能会上升,尽管电影本身并没有改变。这种现象被称为锚定效应。此外,人们在看了很多好电影后,可能会对下一部电影的期望值提高,这种现象被称为享乐适应。还有一些电影因为季节性因素(比如在八月看圣诞电影)或导演的不当行为而受到影响。

这些现象表明,电影评分并不是固定不变的,而是随着时间变化的。因此,如果我们能够捕捉到这些时间上的变化,就可以更准确地预测电影评分。这种基于时间序列的预测不仅仅适用于电影评分,还可以应用于股票价格、天气预测、语音识别等领域。

2. 序列模型的基本概念

2.1 什么是序列数据?

序列数据是指按时间顺序排列的数据。例如,股票价格、电影评分、语音信号等都是序列数据。序列数据的特点是,数据的顺序非常重要。如果我们打乱数据的顺序,数据的意义可能会完全改变。例如,文本“狗咬人”和“人咬狗”虽然由相同的字组成,但意义却截然不同。

2.2 序列模型的挑战

序列模型的主要挑战在于如何捕捉数据中的时间依赖性。例如,预测明天的股票价格要比预测昨天的股票价格困难得多。这是因为预测未来的数据(外推法)比预测过去的数据(内插法)更具挑战性。

3. 统计工具

为了处理序列数据,我们需要一些统计工具和深度学习模型。接下来,我们将介绍两种常用的序列模型:自回归模型马尔可夫模型

3.1 自回归模型

自回归模型(Autoregressive Model)是一种简单的序列模型,它假设当前时刻的数据只依赖于过去几个时刻的数据。例如,假设我们想预测今天的股票价格 xtx_t,我们可以使用过去 kk 天的股票价格 xt1,xt2,,xtkx_{t-1}, x_{t-2}, \dots, x_{t-k} 来进行预测。公式如下:

xt=f(xt1,xt2,,xtk)+ϵtx_t = f(x_{t-1}, x_{t-2}, \dots, x_{t-k}) + \epsilon_t

其中,ff 是一个回归函数,ϵt\epsilon_t 是噪声项。

自回归模型的一个优点是,它的参数数量是固定的,因此可以很容易地使用深度学习模型进行训练。

3.2 马尔可夫模型

马尔可夫模型(Markov Model)是一种特殊的自回归模型,它假设当前时刻的数据只依赖于前一个时刻的数据。这种假设被称为马尔可夫条件。公式如下:

P(xtxt1,xt2,,x1)=P(xtxt1)P(x_t | x_{t-1}, x_{t-2}, \dots, x_1) = P(x_t | x_{t-1})

马尔可夫模型的一个优点是,它可以大大简化计算。特别是当数据是离散的时候,我们可以使用动态规划等算法高效地计算概率分布。

4. 训练序列模型

4.1 数据生成

为了训练序列模型,我们首先需要生成一些序列数据。假设我们使用正弦函数和一些噪声来生成数据:

import torch

import d2l

T = 1000  # 总共产生1000个点
time = torch.arange(1, T + 1, dtype=torch.float32)
x = torch.sin(0.01 * time) + torch.normal(0, 0.2, size=(T,))

d2l.plot(time, x, 'time', 'x', xlim=[1, 1000], figsize=(6, 4))

8-1-数据.png

4.2 特征和标签

接下来,我们将序列数据转换为特征和标签对。假设我们使用过去 4 个时间步的数据来预测下一个时间步的数据:

tau = 4
features = torch.zeros((T - tau, tau))
print(features.shape)  # torch.Size([996, 4])

for i in range(tau):
    features[:, i] = x[i:T - tau + i]

labels = x[tau:].reshape((-1, 1))
print(labels.shape)  # torch.Size([996, 1])

4.3 模型训练

我们使用一个简单的多层感知机(MLP)来训练模型:

from torch import nn


def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)


def get_net():
    net = nn.Sequential(
        nn.Linear(4, 18),
        nn.ReLU(),
        nn.Linear(18, 1)
    )
    net.apply(init_weights)
    return net


loss = nn.MSELoss(reduction='none')

训练模型的代码如下:

def train(net, train_iter, loss, epochs, lr):
    trainer = torch.optim.Adam(net.parameters(), lr)
    for epoch in range(epochs):
        for X, y in train_iter:
            trainer.zero_grad()
            l = loss(net(X), y)
            l.sum().backward()
            trainer.step()
        print(f'epoch {epoch + 1}, '
              f'loss: {d2l.evaluate_loss(net, train_iter, loss):f}')


batch_size, train_n = 16, 600
train_iter = d2l.load_array((features[:train_n], labels[:train_n]),
                            batch_size=batch_size, is_train=True)
net = get_net()
train(net, train_iter, loss, 5, 0.01)
epoch 1, loss: 0.060478
epoch 2, loss: 0.056809
epoch 3, loss: 0.053373
epoch 4, loss: 0.051347
epoch 5, loss: 0.050917

5. 预测

5.1 单步预测

单步预测是指使用当前时刻的数据来预测下一个时刻的数据。我们可以使用训练好的模型来进行单步预测:

onestep_preds = net(features)
print(onestep_preds.shape)  # torch.Size([996, 1])

d2l.plot(X=[time, time[tau:]], Y=[x.detach().numpy(), onestep_preds.detach().numpy()],
         xlabel='time', ylabel='x', legend=['data', '1-step preds'],
         xlim=[1, 1000], figsize=(6, 5.5))
  • X=[time, time[tau:]] 的作用是将实际数据的时间点与预测结果的时间点进行对齐,这样在绘制图表时,能够正确地展示实际数据与预测数据在相应时间点的对比。
    • 第一个 time 是原始时间序列,代表实际观测的数据点。
    • 第二个 time[tau:] 是预测结果对应的时间序列。由于 onestep_preds 的大小为 [996, 1],它预测的是从 tau 开始的时间点的数据(即:从 time[tau] 开始的时间序列)。

8-1-onesteppreds.png

5.2 多步预测

多步预测是指使用当前时刻的数据来预测未来多个时刻的数据。由于我们没有未来的真实数据,因此必须使用模型自己的预测来进行多步预测:

multistep_preds = torch.zeros(T)
multistep_preds[:train_n + tau] = x[:train_n + tau]
for i in range(train_n + tau, T):
    multistep_preds[i] = net(multistep_preds[i - tau:i])

d2l.plot(
    [time, time[tau:], time[train_n + tau:]],
    [x.detach().numpy(), onestep_preds.detach().numpy(), multistep_preds[train_n + tau:].detach().numpy()],
    xlabel='time', ylabel='x', legend=['data', '1-step preds', 'multi-step preds'],
    xlim=[1, 1000], figsize=(6, 5.5)
)

8-1-mutisteppreds.png

5.3 多步预测的局限性

虽然多步预测在某些情况下可以工作得不错,但它也有一些明显的局限性:

  1. 误差累积:在多步预测中,每一步的预测都依赖于前一步的预测结果。如果某一步的预测出现了误差,这个误差会传递到下一步,导致误差逐渐累积。最终,预测结果可能会偏离真实值很远。例如,在股票价格预测中,多步预测可能会因为误差累积而变得不准确。
  2. 模型假设的局限性:多步预测依赖于模型对序列数据的假设。如果模型的假设与实际情况不符,预测结果可能会变得不可靠。例如,如果模型假设序列是平稳的(即序列的统计特性不随时间变化),但实际序列是非平稳的,那么多步预测的效果可能会很差。
  3. 长期依赖性问题:某些序列数据具有长期依赖性,即当前时刻的数据可能依赖于很久以前的数据。在这种情况下,简单的自回归模型或马尔可夫模型可能无法捕捉到这种长期依赖性,从而导致多步预测的失败。
  4. 计算复杂度:随着预测步数的增加,多步预测的计算复杂度也会增加。特别是当我们需要预测很远的未来时,计算量可能会变得非常大。

为了克服这些局限性,研究人员提出了许多改进方法,例如使用更复杂的模型(如循环神经网络RNN长短期记忆网络LSTM) 或引入注意力机制来捕捉长期依赖性。这些方法可以在一定程度上缓解多步预测的问题,但仍然需要根据具体问题选择合适的模型和方法。

6. 总结

序列模型是处理时间序列数据的重要工具。通过自回归模型和马尔可夫模型,我们可以捕捉数据中的时间依赖性。然而,随着预测步数的增加,预测误差会迅速累积,导致预测质量下降。因此,在实际应用中,我们需要谨慎选择模型的预测步数,并考虑使用更复杂的模型来改进多步预测的效果。