75. 序列模型的代码实现

1. 训练

在了解了上述统计工具后,让我们在实践中尝试一下! 首先,我们生成一些数据:(使用正弦函数和一些可加性噪声来生成序列数据, 时间步为 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)对。 基于嵌入维度 𝜏 ,我们将数据映射为数据对 𝑦𝑡=𝑥𝑡 和 𝐱𝑡=[𝑥𝑡−𝜏,…,𝑥𝑡−1] 。这比我们提供的数据样本少了 𝜏 个, 因为我们没有足够的历史记录来描述前 𝜏 个数据样本。

一个简单的解决办法是:如果拥有足够长的序列就丢弃这几项; 另一个方法是用零填充序列。 在这里,我们仅使用前600个“特征-标签”对进行训练。

tau = 4
# 因为是用过去4个样本来预测未来一个时刻,那么第1-4个时刻的数据是被用来预测第5个时刻
# 因此,只有后面5~T这T-4个数据是由前面4个时刻预测来的。
# 所以样本数为T-tau。而因为每个样本是由前tau个数据预测来的,所以
# 横坐标表示样本,纵坐标表示每个样本对应的前面tau个数据
features = torch.zeros((T - tau, tau))
# 通过for 循环对features矩阵赋值
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')

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

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)

运行结果:

在这里插入图片描述

2. 预测

由于训练损失很小,因此我们期望模型能有很好的工作效果。 让我们看看这在实践中意味着什么。 首先是检查模型预测下一个时间步的能力, 也就是单步预测(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 , 我们需要一步一步地向前迈进:
在这里插入图片描述

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

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

在这里插入图片描述

上图这个预测是多步预测,也是1000-604+1=397步预测,也就是前604步来预测后面的397步。这样相当于误差累计了307次。

如上面的例子所示,绿线的预测显然并不理想。 经过几个预测步骤之后,预测的结果很快就会衰减到一个常数。 为什么这个算法效果这么差呢?事实是由于错误的累积: 假设在步骤 1 之后,我们积累了一些错误 𝜖1=𝜖¯ 。 于是,步骤 2 的输入被扰动了 𝜖1 , 结果积累的误差是依照次序的 𝜖2=𝜖¯+𝑐𝜖1 , 其中 𝑐 为某个常数,后面的预测误差依此类推。 因此误差可能会相当快地偏离真实的观测结果。

每次预测都有一点误差,这个误差进入到下一个数据的预测,误差又会增加,一直迭代下去,累积误差。

例如,未来 24 小时的天气预报往往相当准确, 但超过这一点,精度就会迅速下降。 我们将在本章及后续章节中讨论如何改进这一点。

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

4步预测的意思是,用前面4步0,1,2,3来预测后面4步4,5,6,7,那么4通过0,1,2,3来预测,5通过1,2,3,4来预测,依此类推。那16步预测就是用前面4步来预测后面16步。

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 步预测”看起来仍然不错,但超过这个跨度的任何预测几乎都是无用的。

可以看出,难点在于去预测很远的未来。即使是很简单的正弦函数,对于去预测比较远的未来也是很困难的事情。

3. Q&A

Q1:在常规范围呢tau是不是越大越好,刚才例子tau=5是不是比4好?

A1:以马尔科夫假设,当然是能观察到更长的数据更好,但是如果tau太大的话,训练样本就会很少;并且tau增大的话,计算量会增大,模型需要更复杂去fit,而样本还少,这就更麻烦了。所以tau不能太大也不能太小,是有一个权衡的。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值