使用正弦函数和一些可加性噪声来生成序列数据,时间步为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))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-canXx8M6-1662805657790)(https://yingziimage.oss-cn-beijing.aliyuncs.com/img/202209101810997.svg)]
接下来,我们将这个序列转换为模型的“特征-标签”(feature-label)对。基于嵌入维度τ,我们将数据映射为数据对yt = xt 和xt = [xt−τ , . . . , xt−1]。你可能已经注意到,这⽐我们提供的数据样本少了τ个,因为我们没有⾜够的历史记录来描述前τ个数据样本。⼀个简单的解决办法是:如果拥有⾜够⻓的序列就丢弃这⼏项;另⼀个⽅法是⽤零填充序列。在这⾥,我们仅使⽤前600个“特征-标签”对进⾏训练
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)
在这里,我们使用一个相当简单的架构训练模型:一个拥有两个全连接层的多层感知机,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)
epoch 1,loss: 0.074985
epoch 2,loss: 0.060292
epoch 3,loss: 0.057717
epoch 4,loss: 0.056681
epoch 5,loss: 0.058259
由于训练损失很小,因此我们期望模型能有很好的工作效果,让我们看看这在实践中意味着什么。首先检查模型预测下一个时间步的能力,也就是单步预测(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))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ms3SzC7D-1662805657791)(https://yingziimage.oss-cn-beijing.aliyuncs.com/img/202209101810999.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))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5toJA1m7-1662805657792)(https://yingziimage.oss-cn-beijing.aliyuncs.com/img/202209101810000.svg)]
如上⾯的例⼦所⽰,绿线的预测显然并不理想。经过⼏个预测步骤之后,预测的结果很快就会衰减到⼀个常数。为什么这个算法效果这么差呢?事实是由于错误的累积:假设在步骤1之后,我们积累了⼀些错误ϵ1 = ¯ϵ。于是,步骤2的输⼊被扰动了ϵ1,结果积累的误差是依照次序的ϵ2 = ¯ϵ + cϵ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 + 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))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H4WUpSUg-1662805657792)(https://yingziimage.oss-cn-beijing.aliyuncs.com/img/202209101810001.svg)]
上述例子清楚地说明了当我们试图预测更远的未来时,预测的质量是如何变化的。虽然“4步预测”看起来仍然不错,但超过这个跨度的任何预测几乎都是无用的