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

运行结果:

75. 序列模型的代码实现_第1张图片

接下来,我们将这个序列转换为模型的特征-标签(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)

运行结果:

75. 序列模型的代码实现_第2张图片

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

运行结果:

75. 序列模型的代码实现_第3张图片

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

通常,对于直到 的观测序列,其在时间步 + 处的预测输出 ̂ + 称为 步预测( -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))

75. 序列模型的代码实现_第5张图片

上图这个预测是多步预测,也是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
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))

运行结果:

75. 序列模型的代码实现_第6张图片

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

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

3. Q&A

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

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

你可能感兴趣的:(深度学习,python,深度学习)