动手学深度学习8.1. 序列模型-笔记&练习(PyTorch)

本节课程地址:序列模型_哔哩哔哩_bilibili

本节教材地址:8.1. 序列模型 — 动手学深度学习 2.0.0 documentation (d2l.ai)

本节开源代码:...>d2l-zh>pytorch>chapter_multilayer-perceptrons>sequence.ipynb


序列模型

想象一下有人正在看网飞(Netflix,一个国外的视频网站)上的电影。 一名忠实的用户会对每一部电影都给出评价, 毕竟一部好电影需要更多的支持和认可。 然而事实证明,事情并不那么简单。 随着时间的推移,人们对电影的看法会发生很大的变化。 事实上,心理学家甚至对这些现象起了名字:

  • 锚定(anchoring)效应:基于其他人的意见做出评价。 例如,奥斯卡颁奖后,受到关注的电影的评分会上升,尽管它还是原来那部电影。 这种影响将持续几个月,直到人们忘记了这部电影曾经获得的奖项。 结果表明( href="https://zh.d2l.ai/chapter_references/zreferences.html#id186">Wuet al., 2017),这种效应会使评分提高半个百分点以上。
  • 享乐适应(hedonic adaption):人们迅速接受并且适应一种更好或者更坏的情况 作为新的常态。 例如,在看了很多好电影之后,人们会强烈期望下部电影会更好。 因此,在许多精彩的电影被看过之后,即使是一部普通的也可能被认为是糟糕的。
  • 季节性(seasonality):少有观众喜欢在八月看圣诞老人的电影。
  • 有时,电影会由于导演或演员在制作中的不当行为变得不受欢迎。
  • 有些电影因为其极度糟糕只能成为小众电影。Plan9from Outer SpaceTroll2就因为这个原因而臭名昭著的。

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

  • 在使用程序时,许多用户都有很强的特定习惯。 例如,在学生放学后社交媒体应用更受欢迎。在市场开放时股市交易软件更常用。
  • 预测明天的股价要比过去的股价更困难,尽管两者都只是估计一个数字。 毕竟,先见之明比事后诸葛亮难得多。 在统计学中,前者(对超出已知观测范围进行预测)称为外推法(extrapolation), 而后者(在现有观测值之间进行估计)称为内插法(interpolation)。
  • 在本质上,音乐、语音、文本和视频都是连续的。 如果它们的序列被我们重排,那么就会失去原有的意义。 比如,一个文本标题“狗咬人”远没有“人咬狗”那么令人惊讶,尽管组成两句话的字完全相同。
  • 地震具有很强的相关性,即大地震发生后,很可能会有几次小余震, 这些余震的强度比非大地震后的余震要大得多。 事实上,地震是时空相关的,即余震通常发生在很短的时间跨度和很近的距离内。
  • 人类之间的互动也是连续的,这可以从微博上的争吵和辩论中看出。

统计工具

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

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

x_t \sim P(x_t \mid x_{t-1}, \ldots, x_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}) 更新的模型。 由于 ht 从未被观测到,这类模型也被称为 隐变量自回归模型(latent autoregressive models)。

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

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) 。

马尔可夫模型

回想一下,在自回归模型的近似法中, 我们使用 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) 由下式给出:

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

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

\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}

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


补充:

1、马尔可夫过程

  • 马尔可夫过程(Markov process)是一类随机过程。由俄国数学家A.A.马尔可夫于1907年提出。该过程具有如下特性:在已知目前状态(现在)的条件下,它未来的演变(将来)不依赖于它以往的演变 (过去 )。
  • 一个马尔科夫过程就是指过程中的每个状态的转移只依赖于之前的 n个状态,这个过程被称为 n阶马尔科夫模型,其中 n 是影响转移状态的数目。最简单的马尔科夫过程就是一阶过程,每一个状态的转移只依赖于其之前的那一个状态。对于一阶马尔科夫模型,则有:
    如果第 i 时刻上的取值依赖于且仅依赖于第 i-1 时刻的取值,即 P(x_i \mid x_{i-1}, x_{i-2}, \ldots, x_1) = = P(x_i \mid x_{i-1})

2、马尔可夫性

P(x_{t+1} \mid x_{t}) = P(x_{t+1} \mid x_t, x_{t-1}, \ldots, x_1)

3、 马尔可夫链

  • 一个随机过程,其中未来的状态只依赖于当前的状态,而与过去的状态无关。在这种情况下,我们通常使用P(x_{t+1} \mid x_{t})来表示在时间 t 的状态下,转移到时间 t+1 的状态的概率。

4、马尔可夫模型

  • 马尔可夫模型(Markov Model)是一种统计模型,广泛应用在语音识别,词性自动标注,音字转换,概率文法、序列分类等各个自然语言处理等应用领域。

5、计算P(x_{t+1} \mid x_{t-1}) 的推导过程:

根据马尔可夫链的性质, x_{t-1} 只依赖于 x_t ,则:

P(x_{t+1} \mid x_t, x_{t-1}) = P(x_{t+1} \mid x_t)

  • Step3:代入、简化表达式:

P(x_{t+1} \mid x_{t-1}) = \frac{\sum_{x_t} P(x_{t+1} \mid x_t)P(x_t, x_{t-1})}{P(x_{t-1})}

  • Step4:进一步简化: 展开

P(x_t, x_{t-1}) = P(x_{t} \mid x_{t-1})P(x_{t-1})

则:

P(x_{t+1} \mid x_{t-1}) = \sum_{x_t} P(x_{t+1} \mid x_t)P(x_t, x_{t-1})

1. 潜(隐)变量模型是一种统计模型,它使用一个或多个不直接观察到的(或称为“潜”的)变量来解释可观察变量之间的关系。潜变量通常被认为是潜在的、不可直接测量的属性或因素,它们影响或产生可观察的变量。

2. 一旦引入了潜变量h, h是可以不断更新的,h与前一个时刻的潜变量和前一个时刻的x相关,等价于建立了两个模型:

  • 一个模型是根据前一个时刻的潜变量h和x,计算新的潜变量h' ;
  • 第二个模型是根据新的潜变量h'和前一个时刻的x,计算新的x。

因果关系

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

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, 而反之则不行 ef="https://zh.d2l.ai/chapter_references/zreferences.html#id69">Hoyeret al., 2009。 而这个向前推进的方向恰好也是我们通常感兴趣的方向。 彼得斯等人 f="https://zh.d2l.ai/chapter_references/zreferences.html#id125">Peterset al., 2017 对该主题的更多内容做了详尽的解释,而我们的上述讨论只是其中的冰山一角。

小结

  • 时序模型中,当前数据跟之前观察到的数据相关
  • 自回归模型使用自身过去数据来预测未来
  • 马尔可夫模型假设当前只跟最近少数数据相关,从而简化模型
  • 潜变量模型使用潜变量来概括历史信息

训练

在了解了上述统计工具后,让我们在实践中尝试一下! 首先,我们生成一些数据:(使用正弦函数和一些可加性噪声来生成序列数据, 时间步为 1,2,…,1000 )

%matplotlib inline
import torch
from torch import nn
from d2l import torch as d2l
T = 1000  # 总共产生1000个点
time = torch.arange(1, T + 1, dtype=torch.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))

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

tau = 4
# 特征向量的数量T-tau(每个特征向量需包含时间点的前tau个时间点数据)
# 特征向量长度tau
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)

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

# 初始化网络权重的函数
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

# 平方损失。注意:MSELoss计算平方误差时不带系数1/2
loss = nn.MSELoss(reduction='none')

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

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}')

net = get_net()
train(net, train_iter, loss, 5, 0.01)

输出结果:
epoch 1, loss: 0.075165
epoch 2, loss: 0.062212
epoch 3, loss: 0.058570
epoch 4, loss: 0.056798
epoch 5, loss: 0.056217

预测

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

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))

 正如我们所料,单步预测效果不错。 即使这些预测的时间步超过了600+4(n_train + tau), 其结果看起来仍然是可信的。 然而有一个小问题:如果数据观察序列的时间步只到604, 我们需要一步一步地向前迈进:

\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

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

multistep_preds = torch.zeros(T)
# 时间步600+tau之前的数值与x相同
multistep_preds[: n_train + tau] = x[: n_train + tau]
# 时间步600+tau之后的数值为基于前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))

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

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

max_steps = 64
features = torch.zeros((T - tau - max_steps + 1, tau + max_steps))
# 列i(i<tau)是来自x的观测,其时间步从(i)到(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)到(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))

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

小结

  • 内插法(在现有观测值之间进行估计)和外推法(对超出已知观测范围进行预测)在实践的难度上差别很大。因此,对于所拥有的序列数据,在训练时始终要尊重其时间顺序,即最好不要基于未来的数据进行训练。
  • 序列模型的估计需要专门的统计工具,两种较流行的选择是自回归模型和隐变量自回归模型。
  • 对于时间是向前推进的因果模型,正向估计通常比反向估计更容易。
  • 对于直到时间步 t 的观测序列,其在时间步 t+k 的预测输出是“ k 步预测”。随着我们对预测时间 k 值的增加,会造成误差的快速累积和预测质量的极速下降。

练习

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

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

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

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

4) 改变神经网络架构并评估其性能。

解:
1)本题英文为“Incorporate more than the past four observations? How many do you really need?”,实际用意为探索tau值的合适取值。
当只改变tau值时,当tau=16时,16-steps preds准确性就很高了;tau>16后,16-steps preds准确性进一步提高,噪声也可以基本匹配上,但64-steps preds仍有整体的偏移;所以tau=16的预测准确性就很高了。
不同tau值的测试代码如下

def test_tau(tau):
    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
    train_iter = d2l.load_array((features[:n_train], labels[:n_train]),
                                batch_size, is_train=True)

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

    net = get_net()
    train(net, train_iter, loss, 5, 0.01)

    max_steps = 64

    features = torch.zeros((T - tau - max_steps + 1, tau + max_steps))
    for i in range(tau):
        features[:, i] = x[i: 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))
test_tau(1)

输出结果:
epoch 1, loss: 0.182106
epoch 2, loss: 0.091007
epoch 3, loss: 0.076376
epoch 4, loss: 0.074805
epoch 5, loss: 0.074060

test_tau(4)

输出结果:
epoch 1, loss: 0.055596
epoch 2, loss: 0.050999
epoch 3, loss: 0.051173
epoch 4, loss: 0.050984
epoch 5, loss: 0.053008

test_tau(8)

输出结果:
epoch 1, loss: 0.064005
epoch 2, loss: 0.055770
epoch 3, loss: 0.051163
epoch 4, loss: 0.048366
epoch 5, loss: 0.046772

test_tau(16)

输出结果:
epoch 1, loss: 0.070664
epoch 2, loss: 0.054690
epoch 3, loss: 0.053113
epoch 4, loss: 0.050526
epoch 5, loss: 0.048684

test_tau(32)

输出结果:
epoch 1, loss: 0.100700
epoch 2, loss: 0.077960
epoch 3, loss: 0.064261
epoch 4, loss: 0.059997
epoch 5, loss: 0.053635

test_tau(64)

输出结果:
epoch 1, loss: 0.075431
epoch 2, loss: 0.058761
epoch 3, loss: 0.063190
epoch 4, loss: 0.055784
epoch 5, loss: 0.047975

2)如果没有噪音,则x = torch.sin(0.01 * time),也即完全符合sin函数的变化规律,那么根据泰勒公式: x(t+1) \approx x(t) + x'(t)(t+1-t) = \sin(0.01x) + 0.01 \cos(0.01t) 所以,tau=1即可预测,tau=1时,1-step preds的预测很准确,4-step preds的预测也比较准确。代码如下:

x = torch.sin(0.01 * time)
test_tau(1)

输出结果:
epoch 1, loss: 0.049211
epoch 2, loss: 0.002614
epoch 3, loss: 0.001396
epoch 4, loss: 0.000881
epoch 5, loss: 0.000593

3)可以在保持特征总数不变的情况下合并旧的观察结果。
这题的说法也有些绕,其实是因为如果简单地将过去的每个观察结果都作为独立的特征,随着时间的增长,特征的数量也会增长,这可能导致维度灾难。因此,关键在于如何在不增加特征总数的前提下,有效地合并旧的观察结果。
可以通过取平均值、最大值、最小值或者使用更复杂的统计量(如标准差、偏度、峰度等)合并旧的观察结果。此外,还可以使用主成分分析(PCA)等降维技术来减少特征的维度,同时尽量保留数据的变异性。
这种做法可以提高正确度,原因在于: - 减少噪声:通过合并,可以减少个别时间点的随机噪声对模型的影响。 - 突出趋势:合并可以更好地反映数据的长期趋势或周期性特征。 - 避免过拟合:减少特征的数量可以降低模型过拟合的风险。

但是合并时要注意避免对预测有用的信息丢失。
比如可以采用滑动窗口的方式生成特征矩阵features,取特征平均值合并,代码如下:

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

x = torch.sin(0.01 * time) + torch.normal(0, 0.2, (T,))

# 设置滑动窗口大小
tau = 4

features = torch.zeros(T - tau, 1)
# 以tau为滑动窗口大小,对于T-tau个时间点,将其之前的tau个时间点对应数值取平均
for i in range(T - tau):
    features[i] = x[i: i + tau].mean(dim=0).unsqueeze(-1)

batch_size, n_train = 16, 600
train_iter = d2l.load_array((features[:n_train], labels[:n_train]),
                            batch_size, is_train=True)

net = get_net()
train(net, train_iter, loss, 5, 0.01)
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))

输出结果:
epoch 1, loss: 0.055337
epoch 2, loss: 0.053314
epoch 3, loss: 0.045330
epoch 4, loss: 0.046244
epoch 5, loss: 0.044574

4)尝试更改初始化函数、调整网络结构(增加层数)、调整学习率,train_loss降低一些。
代码如下:

# 调整初始化函数
def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)

# 调整网络结构
def get_net1():
    net = nn.Sequential(nn.Linear(tau, 50),
                        nn.ReLU(),
                        nn.Linear(50, 10),
                        nn.ReLU(),
                        nn.Linear(10, 1))
    net.apply(init_weights)
    return net

# 更改优化器
def train1(net, train_iter, loss, epochs, lr, wd):
    trainer = torch.optim.AdamW(net.parameters(), lr, weight_decay=wd)
    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}')
def test_net(tau):
    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
    train_iter = d2l.load_array((features[:n_train], labels[:n_train]),
                                batch_size, is_train=True)

    net = get_net1()
    train1(net, train_iter, loss, 5, 0.001, 0.01)

    max_steps = 64

    features = torch.zeros((T - tau - max_steps + 1, tau + max_steps))
    for i in range(tau):
        features[:, i] = x[i: 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))
test_net(4)

输出结果:
epoch 1, loss: 0.217546
epoch 2, loss: 0.086708
epoch 3, loss: 0.053500
epoch 4, loss: 0.051094
epoch 5, loss: 0.049594

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

解:
考虑证券的特性,可能会有以下问题:

  • 过去的回报不代表未来的收益;
  • 回报高往往意味着风险也高,需要同时适配风险承受能力;
  • 另外,市场情况、经济形势、通货膨胀、国际关系等其他宏观因素也都可能影响证券的回报,因此,只关注以往回报购买证券是不够的。

3. 时间是向前推进的因果模型在多大程度上适用于文本呢?
解:
时间作为向前推进的因果模型在文本分析中的应用程度取决于文本的性质和分析的目的,比如以下适用场景:

  • 叙事文本:如果故事的发展遵循线性时间线,事件之间存在明确的因果关系,则时间的因果模型通常非常适用。
  • 历史文本:历史记录通常按照时间顺序排列,事件之间存在因果联系。时间的因果模型有助于理解历史事件的发展和影响。
  • 文本生成:在自然语言处理中,时间的因果模型可以用于生成文本,例如,根据给定的起始条件和事件,预测文本的发展方向。

但是也有一些情况下时间的因果模型可能不完全适用,比如非线性文本不遵循传统的线性时间线,例如某些现代文学作品或非线性叙事的电影。

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

解:
隐(潜)变量自回归模型(Hidden Variable Autoregressive Model,HVAR)用于捕捉数据中的时间序列特性,同时考虑到存在未观察到的(或隐藏的)因素对数据产生影响。
比如第2题中的证券购买问题,除了证券历史数据中的时序特性(回报变化),还需考虑许多经济学指标(市场情况、经济形势、通货膨胀、汇率、利率等等)之间的复杂相互作用,这些指标的关系无法直接被观测到,需要隐变量自回归模型识别和建模这些影响数据动态但未被直接观察到的因素,最终捕捉数据的动力学模型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

scdifsn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值