8.1. 序列模型
Open the notebook in Colab
Open the notebook in Colab
Open the notebook in Colab

想象一下你正在看网飞(Netflix,一个国外的视频网站)上的电影。作为一个很棒的网飞用户,你决定对每一部电影都给出评价。毕竟,一部好的电影值得好电影这个名声,而且你想看更多的好电影,对吧?事实证明,事情并不那么简单。随着时间的推移,人们对电影的看法会发生很大的变化。事实上,心理学家甚至对某些效应起了名字:

  • 锚定(anchoring),基于其他人的意见。例如,奥斯卡颁奖后,受到关注的电影的评分会上升,尽管它还是原来那部电影。这种影响将持续几个月,直到人们忘记了这部电影曾经获得的奖项。结果表明,这种效应会使评分提高半个百分点以上 [Wu et al., 2017].

  • 享乐适应(hedonic adaption),即人类迅速接受并且适应一种更好或者更坏的情况作为新的常态。例如,在看了很多好电影之后,人们会强烈期望下部电影会一样好或者更好。因此,在许多精彩的电影被看过之后,即使是一部普通的也可能被认为是糟糕的。

  • 季节性(seasonality)。少有观众喜欢在八月看圣诞老人的电影。

  • 有时候,电影会由于导演或演员在制作中的不当行为变得不受欢迎。

  • 有些电影因为其极度糟糕只能成为小众电影。Plan 9 from Outer SpaceTroll 2 就因为这个原因而臭名昭著的。

简而言之,电影评分决不是固定不变的。因此,使用时间动力学可以得到更准确的电影推荐 [Koren, 2009] 。当然,序列数据不仅仅是关于电影评分的。下面给出了更多的场景。

  • 在使用应用程序时,许多用户都有很强的特定习惯。例如,在学生放学后社交媒体应用更受欢迎。在市场开放时股市交易软件更常用。

  • 预测明天的股价要比填补昨天遗失的股价的更困难,尽管两者都只是估计一个数字。毕竟,先见之明比事后诸葛亮难得多。在统计学中,前者(对超出已知观测范围进行预测)称为 外推法(extrapolation),而后者(在现有观测值之间进行估计)称为 内插法(interpolation)。

  • 在本质上,音乐、语音、文本和视频都是连续的。如果它们的序列被我们重排,那么原有的意义就会失去。文本标题 狗咬人 远没有 人咬狗 那么令人惊讶,尽管组成两句话的字完全相同。

  • 地震具有很强的相关性,即大地震发生后,很可能会有几次较小的余震,这些余震的强度比不是大地震的余震要大得多。事实上,地震是时空相关的,即余震通常发生在很短的时间跨度和很近的距离内。

  • 人类之间的互动也是连续的,这可以从推特上的争吵和辩论中看出。

8.1.1. 统计工具

处理序列数据需要统计工具和新的深度神经网络结构。为了简单起见,我们以 图8.1.1 所示的股票价格(富时100指数)为例。

../_images/ftse100.png

图8.1.1 近30年的富时100指数。

其中,用 \(x_t\) 表示价格,即在 时间步(time step)\(t \in \mathbb{Z}^+\)时,观察到的价格 \(x_t\)。请注意,\(t\) 对于本文中的序列通常是离散的,并随整数或其子集而变化。假设一个交易员想在 \(t\) 日的股市中表现良好,于是通过以下途径预测 \(x_t\)

(8.1.1)\[x_t \sim P(x_t \mid x_{t-1}, \ldots, x_1).\]

8.1.1.1. 自回归模型

为了实现这个预测,交易员可以使用回归模型,例如在 3.3节 中训练的模型。仅有一个主要问题:输入数据的数量,输入 \(x_{t-1}, \ldots, x_1\) 本身因 \(t\) 而异。也就是说,输入数据的数量这个数字将会随着我们遇到的数据量的增加而增加,因此需要一个近似方法来使这个计算变得容易处理。本章后面的大部分内容将围绕着如何有效估计 \(P(x_t \mid x_{t-1}, \ldots, x_1)\) 展开。简单地说,它归结为以下两种策略。

第一种策略,假设在现实情况下相当长的序列 \(x_{t-1}, \ldots, x_1\) 可能是不必要的,因此我们只需要满足某个长度为 \(\tau\) 的时间跨度,即使用观测序列 \(x_{t-1}, \ldots, x_{t-\tau}\)。当下获得的最直接的好处就是参数的数量总是不变的,至少在 \(t > \tau\) 时如此,这就使我们能够训练一个上面提及的深度网络。这种模型被称为 自回归模型(autoregressive models),因为它们就是对自己执行回归。

第二种策略,如 图8.1.2 所示,是保留一些对过去观测的总结 \(h_t\),并且同时更新预测 \(\hat{x}_t\) 和总结 \(h_t\)。这就产生了基于 \(\hat{x}_t = P(x_t \mid h_{t})\) 估计 \(x_t\),以及公式 \(h_t = g(h_{t-1}, x_{t-1})\) 更新的模型。由于 \(h_t\) 从未被观测到,这类模型也被称为 隐变量自回归模型(latent autoregressive models)。

../_images/sequence-model.svg

图8.1.2 隐变量自回归模型

这两种情况都有一个显而易见的问题,即如何生成训练数据。一个经典方法是使用到目前为止的历史观测来预测下一个未来观测。显然,我们并不指望时间会停滞不前。然而,一个常见的假设是虽然特定值 \(x_t\) 可能会改变,但是序列本身的动力学不会改变。这样的假设是合理的,因为新的动力学一定受新的数据影响,而我们不可能用目前所掌握的数据来预测新的动力学。统计学家称不变的动力学为 静止的(stationary)。因此,无论我们做什么,整个序列的估计值都将通过以下的方式获得

(8.1.2)\[P(x_1, \ldots, x_T) = \prod_{t=1}^T P(x_t \mid x_{t-1}, \ldots, x_1).\]

注意,如果我们处理的是离散的对象(如单词),而不是连续的数字,则上述的考虑仍然有效。唯一的差别是,对于离散的对象,我们需要使用分类器而不是回归模型来估计 \(P(x_t \mid x_{t-1}, \ldots, x_1)\)

8.1.1.2. 马尔可夫模型

回想一下,在自回归模型的近似法中,我们使用 \(x_{t-1}, \ldots, x_{t-\tau}\) 而不是 \(x_{t-1}, \ldots, x_1\) 来估计 \(x_t\)。只要这种近似是精确的,我们就说序列满足 马尔可夫条件(Markov condition)。特别是,如果 \(\tau = 1\),得到一个 一阶马尔可夫模型(first-order Markov model),\(P(x)\) 由下式给出:

(8.1.3)\[P(x_1, \ldots, x_T) = \prod_{t=1}^T P(x_t \mid x_{t-1}) \text{ where } P(x_1 \mid x_0) = P(x_1).\]

当假设 \(x_t\) 仅是离散值时,这样的模型特别棒,因为在这种情况下,使用动态规划可以沿着马尔可夫链精确地计算结果。例如,我们可以高效地计算\(P(x_{t+1} \mid x_{t-1})\)

(8.1.4)\[\begin{split}\begin{aligned} P(x_{t+1} \mid x_{t-1}) &= \frac{\sum_{x_t} P(x_{t+1}, x_t, x_{t-1})}{P(x_{t-1})}\\ &= \frac{\sum_{x_t} P(x_{t+1} \mid x_t, x_{t-1}) P(x_t, x_{t-1})}{P(x_{t-1})}\\ &= \sum_{x_t} P(x_{t+1} \mid x_t) P(x_t \mid x_{t-1}) \end{aligned}\end{split}\]

利用这一事实,我们只需要考虑过去观察中的一个非常短的历史:\(P(x_{t+1} \mid x_t, x_{t-1}) = P(x_{t+1} \mid x_t)\)。动态规划的详细介绍超出了本节的范围,而动态规划这些计算工具已经在控制算法和强化学习算法广泛使用。

8.1.1.3. 因果关系

原则上,将 \(P(x_1, \ldots, x_T)\) 倒序展开也没啥问题。毕竟,基于条件概率公式,我们总是可以写出:

(8.1.5)\[P(x_1, \ldots, x_T) = \prod_{t=T}^1 P(x_t \mid x_{t+1}, \ldots, x_T).\]

事实上,如果基于一个马尔可夫模型,我们还可以得到一个反向的条件概率分布。然而,在许多情况下,数据存在一个自然的方向,即在时间上是前进的。很明显,未来的事件不能影响过去。因此,如果我们改变 \(x_t\),可能会影响未来发生的事情 \(x_{t+1}\),但不能反过来。也就是说,如果我们改变 \(x_t\),基于过去事件得到的分布不会改变。因此,解释 \(P(x_{t+1} \mid x_t)\) 应该比解释 \(P(x_t \mid x_{t+1})\) 更容易。例如,在某些情况下,对于某些可加性噪声 \(\epsilon\),显然我们可以找到 \(x_{t+1} = f(x_t) + \epsilon\),而反之则不行 [Hoyer et al., 2009] 。这是个好消息,因为这个前进方向通常也是我们感兴趣的方向。彼得斯等人写的这本书 [Peters et al., 2017a] 已经解释了关于这个主题的更多内容 ,而我们仅仅触及了它的皮毛。

8.1.2. 训练

在回顾了这么多统计工具之后,让我们在实践中尝试一下。首先,生成一些数据。简单起见,我们使用正弦函数和一些可加性噪声来生成序列数据,时间步为 \(1, 2, \ldots, 1000\)

%matplotlib inline
from mxnet import autograd, gluon, init, np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l

npx.set_np()


T = 1000  # 总共产生1000个点
time = np.arange(1, T + 1, dtype=d2l.float32)
x = np.sin(0.01 * time) + np.random.normal(0, 0.2, (T,))
d2l.plot(time, [x], 'time', 'x', xlim=[1, 1000], figsize=(6, 3))
../_images/output_sequence_ce248f_3_0.svg
%matplotlib inline
import torch
from torch import nn
from d2l import torch as d2l


T = 1000  # 总共产生1000个点
time = torch.arange(1, T + 1, dtype=d2l.float32)
x = torch.sin(0.01 * time) + torch.normal(0, 0.2, (T,))
d2l.plot(time, [x], 'time', 'x', xlim=[1, 1000], figsize=(6, 3))
../_images/output_sequence_ce248f_6_0.svg
%matplotlib inline
import tensorflow as tf
from d2l import tensorflow as d2l


T = 1000  # 总共产生1000个点
time = tf.range(1, T + 1, dtype=d2l.float32)
x = tf.sin(0.01 * time) + tf.random.normal([T], 0, 0.2)
d2l.plot(time, [x], 'time', 'x', xlim=[1, 1000], figsize=(6, 3))
../_images/output_sequence_ce248f_9_0.svg

接下来,我们需要将这样的序列转换为模型可以训练的特征和标签。基于嵌入维度 \(\tau\),我们将数据映射为数据对 \(y_t = x_t\)\(\mathbf{x}_t = [x_{t-\tau}, \ldots, x_{t-1}]\)。精明的读者可能已经注意到,这比我们提供的数据样本少了 \(\tau\) 个,因为我们没有足够的历史记录来描述前 \(\tau\) 个数据样本。一个简单的解决办法,尤其是如果拥有足够长的序列就丢弃这几项;另一个方法,我们可以用零填充序列。在这里,我们仅使用前600个“特征-标签”(feature-label)对进行训练。

tau = 4
features = np.zeros((T - tau, tau))
for i in range(tau):
    features[:, i] = x[i: T - tau + i]
labels = x[tau:].reshape((-1, 1))

batch_size, n_train = 16, 600
# 只有前`n_train`个样本用于训练
train_iter = d2l.load_array((features[:n_train], labels[:n_train]),
                            batch_size, is_train=True)
tau = 4
features = torch.zeros((T - tau, tau))
for i in range(tau):
    features[:, i] = x[i: T - tau + i]
labels = x[tau:].reshape((-1, 1))

batch_size, n_train = 16, 600
# 只有前`n_train`个样本用于训练
train_iter = d2l.load_array((features[:n_train], labels[:n_train]),
                            batch_size, is_train=True)
tau = 4
features = tf.Variable(tf.zeros((T - tau, tau)))
for i in range(tau):
    features[:, i].assign(x[i: T - tau + i])
labels = tf.reshape(x[tau:], (-1, 1))

batch_size, n_train = 16, 600
# 只有前`n_train`个样本用于训练
train_iter = d2l.load_array((features[:n_train], labels[:n_train]),
                            batch_size, is_train=True)

在这里,训练模型使用一个相当简单的结构:只是一个拥有两个全连接层的多层感知机,ReLU激活函数和平方损失。

# 一个简单的多层感知机
def get_net():
    net = nn.Sequential()
    net.add(nn.Dense(10, activation='relu'),
            nn.Dense(1))
    net.initialize(init.Xavier())
    return net

# 平方损失
loss = gluon.loss.L2Loss()
# 初始化网络权重的函数
def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)

# 一个简单的多层感知机
def get_net():
    net = nn.Sequential(nn.Linear(4, 10),
                        nn.ReLU(),
                        nn.Linear(10, 1))
    net.apply(init_weights)
    return net

# 平方损失
loss = nn.MSELoss()
# 普通的多层感知机模型
def get_net():
    net = tf.keras.Sequential([tf.keras.layers.Dense(10, activation='relu'),
                              tf.keras.layers.Dense(1)])
    return net

# 最小均方损失
# 注:L2损失=1/2*MSE损失。TensorFlow的MSE损失与MXNet的L2Loss相差2倍。
# 因此,我们将损失值减半,得到TF中的L2Loss
loss = tf.keras.losses.MeanSquaredError()

现在,准备训练模型了。实现下面的训练代码的方式与前面几节(如 3.3节 )中的循环训练基本相同。因此,我们不会深入探讨太多细节。

def train(net, train_iter, loss, epochs, lr):
    trainer = gluon.Trainer(net.collect_params(), 'adam',
                            {'learning_rate': lr})
    for epoch in range(epochs):
        for X, y in train_iter:
            with autograd.record():
                l = loss(net(X), y)
            l.backward()
            trainer.step(batch_size)
        print(f'epoch {epoch + 1}, '
              f'loss: {d2l.evaluate_loss(net, train_iter, loss):f}')

net = get_net()
train(net, train_iter, loss, 5, 0.01)
epoch 1, loss: 0.043722
epoch 2, loss: 0.032005
epoch 3, loss: 0.031168
epoch 4, loss: 0.027494
epoch 5, loss: 0.026561
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.backward()
            trainer.step()
        print(f'epoch {epoch + 1}, '
              f'loss: {d2l.evaluate_loss(net, train_iter, loss):f}')

net = get_net()
train(net, train_iter, loss, 5, 0.01)
epoch 1, loss: 0.057320
epoch 2, loss: 0.051288
epoch 3, loss: 0.052164
epoch 4, loss: 0.050088
epoch 5, loss: 0.051791
def train(net, train_iter, loss, epochs, lr):
    trainer = tf.keras.optimizers.Adam()
    for epoch in range(epochs):
        for X, y in train_iter:
            with tf.GradientTape() as g:
                out = net(X)
                l = loss(y, out) / 2
                params = net.trainable_variables
                grads = g.gradient(l, params)
            trainer.apply_gradients(zip(grads, params))
        print(f'epoch {epoch + 1}, '
              f'loss: {d2l.evaluate_loss(net, train_iter, loss):f}')

net = get_net()
train(net, train_iter, loss, 5, 0.01)
epoch 1, loss: 0.559403
epoch 2, loss: 0.333369
epoch 3, loss: 0.215536
epoch 4, loss: 0.142818
epoch 5, loss: 0.100718

8.1.3. 预测

由于训练损失很小,因此我们期望模型能有很好的工作效果。让我们看看这在实践中意味着什么。首先是检查模型预测下一个时间步发生的是什么的能力,也就是 单步预测(one-step-ahead prediction)。

onestep_preds = net(features)
d2l.plot([time, time[tau:]], [x.asnumpy(), onestep_preds.asnumpy()], 'time',
         'x', legend=['data', '1-step preds'], xlim=[1, 1000], figsize=(6, 3))
../_images/output_sequence_ce248f_51_0.svg
onestep_preds = net(features)
d2l.plot([time, time[tau:]], [x.detach().numpy(), onestep_preds.detach().numpy()], 'time',
         'x', legend=['data', '1-step preds'], xlim=[1, 1000], figsize=(6, 3))
../_images/output_sequence_ce248f_54_0.svg
onestep_preds = net(features)
d2l.plot([time, time[tau:]], [x.numpy(), onestep_preds.numpy()], 'time',
         'x', legend=['data', '1-step preds'], xlim=[1, 1000], figsize=(6, 3))
../_images/output_sequence_ce248f_57_0.svg

正如我们所料,单步预测效果不错。即使这些预测的时间步超过了 \(600+4\)n_train + tau),其结果看起来仍然是可信的。然而有一个小问题:如果数据观察序列的时间步只到 \(604\),那么我们就没办法指望能够得到所有未来的单步预测作为输入。相反,我们需要一步一步地向前迈进:

(8.1.6)\[\begin{split}\hat{x}_{605} = f(x_{601}, x_{602}, x_{603}, x_{604}), \\ \hat{x}_{606} = f(x_{602}, x_{603}, x_{604}, \hat{x}_{605}), \\ \hat{x}_{607} = f(x_{603}, x_{604}, \hat{x}_{605}, \hat{x}_{606}),\\ \hat{x}_{608} = f(x_{604}, \hat{x}_{605}, \hat{x}_{606}, \hat{x}_{607}),\\ \hat{x}_{609} = f(\hat{x}_{605}, \hat{x}_{606}, \hat{x}_{607}, \hat{x}_{608}),\\ \ldots\end{split}\]

通常,对于直到 \(x_t\) 的观测序列,其在时间步 \(t+k\) 处的预测输出 \(\hat{x}_{t+k}\) 称为 :math:`k` 步预测\(k\)-step-ahead-prediction)。由于我们的观察已经到了 \(x_{604}\),它的 \(k\) 步预测是 \(\hat{x}_{604+k}\)。换句话说,我们必须使用我们自己的预测(而不是原始数据)来进行多步预测。让我们看看效果如何。

multistep_preds = np.zeros(T)
multistep_preds[: n_train + tau] = x[: n_train + tau]
for i in range(n_train + tau, T):
    multistep_preds[i] = net(
        multistep_preds[i - tau:i].reshape((1, -1)))

d2l.plot([time, time[tau:], time[n_train + tau:]],
         [x.asnumpy(), onestep_preds.asnumpy(),
          multistep_preds[n_train + tau:].asnumpy()], 'time',
         'x', legend=['data', '1-step preds', 'multistep preds'],
         xlim=[1, 1000], figsize=(6, 3))
../_images/output_sequence_ce248f_63_0.svg
multistep_preds = torch.zeros(T)
multistep_preds[: n_train + tau] = x[: n_train + tau]
for i in range(n_train + tau, T):
    multistep_preds[i] = net(
        multistep_preds[i - tau:i].reshape((1, -1)))

d2l.plot([time, time[tau:], time[n_train + tau:]],
         [x.detach().numpy(), onestep_preds.detach().numpy(),
          multistep_preds[n_train + tau:].detach().numpy()], 'time',
         'x', legend=['data', '1-step preds', 'multistep preds'],
         xlim=[1, 1000], figsize=(6, 3))
../_images/output_sequence_ce248f_66_0.svg
multistep_preds = tf.Variable(tf.zeros(T))
multistep_preds[:n_train + tau].assign(x[:n_train + tau])
for i in range(n_train + tau, T):
    multistep_preds[i].assign(tf.reshape(net(
        tf.reshape(multistep_preds[i - tau: i], (1, -1))), ()))

d2l.plot([time, time[tau:], time[n_train + tau:]],
         [x.numpy(), onestep_preds.numpy(),
          multistep_preds[n_train + tau:].numpy()], 'time',
         'x', legend=['data', '1-step preds', 'multistep preds'],
         xlim=[1, 1000], figsize=(6, 3))
../_images/output_sequence_ce248f_69_0.svg

如上面的例子所示,这是一个巨大的失败。经过几个预测步骤之后,预测的结果很快就会衰减到一个常数。为什么这个算法效果这么差呢?最终事实是由于错误的累积。假设在步骤 \(1\) 之后,我们积累了一些错误 \(\epsilon_1 = \bar\epsilon\)。于是,步骤 \(2\)输入(input)被扰动了 \(\epsilon_1\),结果积累的误差是依照次序的 \(\epsilon_2 = \bar\epsilon + c \epsilon_1\),其中 \(c\) 为某个常数,后面的预测误差依此类推。因此误差可能会相当快地偏离真实的观测结果。例如,未来 \(24\) 小时的天气预报往往相当准确,但超过这一点,准确率就会迅速下降。我们将在本章及后续章节中讨论如何改进这一点。

基于 \(k = 1, 4, 16, 64\),通过对整个序列预测的计算,让我们更仔细地看一下\(k\)步预测的困难。

max_steps = 64

features = np.zeros((T - tau - max_steps + 1, tau + max_steps))
# 列 `i` (`i` < `tau`) 是来自 `x` 的观测
# 其时间步从 `i + 1` 到 `i + T - tau - max_steps + 1`
for i in range(tau):
    features[:, i] = x[i: i + T - tau - max_steps + 1]

# 列 `i` (`i` >= `tau`) 是 (`i - tau + 1`)步的预测
# 其时间步从 `i + 1` 到 `i + T - tau - max_steps + 1`
for i in range(tau, tau + max_steps):
    features[:, i] = net(features[:, i - tau:i]).reshape(-1)

steps = (1, 4, 16, 64)
d2l.plot([time[tau + i - 1: T - max_steps + i] for i in steps],
         [features[:, (tau + i - 1)].asnumpy() for i in steps], 'time', 'x',
         legend=[f'{i}-step preds' for i in steps], xlim=[5, 1000],
         figsize=(6, 3))
../_images/output_sequence_ce248f_75_0.svg
max_steps = 64

features = torch.zeros((T - tau - max_steps + 1, tau + max_steps))
# 列 `i` (`i` < `tau`) 是来自 `x` 的观测
# 其时间步从 `i + 1` 到 `i + T - tau - max_steps + 1`
for i in range(tau):
    features[:, i] = x[i: i + T - tau - max_steps + 1]

# 列 `i` (`i` >= `tau`) 是 (`i - tau + 1`)步的预测
# 其时间步从 `i + 1` 到 `i + T - tau - max_steps + 1`
for i in range(tau, tau + max_steps):
    features[:, i] = net(features[:, i - tau:i]).reshape(-1)

steps = (1, 4, 16, 64)
d2l.plot([time[tau + i - 1: T - max_steps + i] for i in steps],
         [features[:, (tau + i - 1)].detach().numpy() for i in steps], 'time', 'x',
         legend=[f'{i}-step preds' for i in steps], xlim=[5, 1000],
         figsize=(6, 3))
../_images/output_sequence_ce248f_78_0.svg
max_steps = 64

features = tf.Variable(tf.zeros((T - tau - max_steps + 1, tau + max_steps)))
# 列 `i` (`i` < `tau`) 是来自 `x` 的观测
# 其时间步从 `i + 1` 到 `i + T - tau - max_steps + 1`
for i in range(tau):
    features[:, i].assign(x[i: i + T - tau - max_steps + 1].numpy())

# 列 `i` (`i` >= `tau`) 是 (`i - tau + 1`)步预测
# 其时间步从 `i + 1` 到 `i + T - tau - max_steps + 1`
for i in range(tau, tau + max_steps):
    features[:, i].assign(tf.reshape(net((features[:, i - tau: i])), -1))

steps = (1, 4, 16, 64)
d2l.plot([time[tau + i - 1: T - max_steps + i] for i in steps],
         [features[:, (tau + i - 1)].numpy() for i in steps], 'time', 'x',
         legend=[f'{i}-step preds' for i in steps], xlim=[5, 1000],
         figsize=(6, 3))
../_images/output_sequence_ce248f_81_0.svg

这清楚地说明了当我们试图预测更远的未来时,预测的质量是如何变化的。虽然“\(4\) 步预测”看起来仍然不错,但超过这个跨度的任何预测几乎都是无用的。

8.1.4. 小结

  • 内插法(在现有观测值之间进行估计)和外推法(对超出已知观测范围进行预测)在实践的难度上差别很大。因此,对于你所拥有的序列数据,在训练时始终要尊重其时间顺序,即永远不要基于未来的数据进行训练。

  • 序列模型的估计需要专门的统计工具,两种较流行的选择是自回归模型和隐变量自回归模型。

  • 对于时间是向前推进的因果模型,正向估计通常比反向估计更容易。

  • 对于直到时间步 \(t\) 的观测序列,其在时间步 \(t+k\) 的预测输出是“\(k\)步预测”。随着我们对预测时间 \(k\) 值的增加,会造成误差的快速累积和预测质量的极速下降。

8.1.5. 练习

  1. 改进本节实验中的模型。

    1. 是否包含了过去 \(4\) 个以上的观测结果?你的真实需要是多少个?

    2. 如果没有噪音,你需要多少个过去的观测结果?提示:你可以把 \(\sin\) 和 \(\cos\) 写成微分方程。

    3. 你能在保持特征总数不变的情况下合并旧的观察结果吗?这能提高正确度吗?为什么?

    4. 改变神经网络结构并评估其性能。

  2. 一位投资者想要找到一种好的证券来购买。他查看过去的回报,以决定哪一种可能是表现良好的。这一策略可能会出什么问题呢?

  3. 时间是向前推进的因果模型在多大程度上适用于文本呢?

  4. 举例说明什么时候可能需要隐变量自回归模型来捕捉数据的动力学模型。