I. 前言
前面已经写了很多关于LSTM时间序列预测的文章:
- 深入理解PyTorch中LSTM的输入和输出(从input输入到Linear输出)
- PyTorch搭建LSTM实现时间序列预测(负荷预测)
- PyTorch搭建LSTM实现多变量时间序列预测(负荷预测)
- PyTorch搭建双向LSTM实现时间序列预测(负荷预测)
- PyTorch搭建LSTM实现多变量多步长时间序列预测(一):直接多输出
- PyTorch搭建LSTM实现多变量多步长时间序列预测(二):单步滚动预测
- PyTorch搭建LSTM实现多变量多步长时间序列预测(三):多模型单步预测
- PyTorch搭建LSTM实现多变量多步长时间序列预测(四):多模型滚动预测
- PyTorch搭建LSTM实现多变量多步长时间序列预测(五):seq2seq
- PyTorch中实现LSTM多步长时间序列预测的几种方法总结(负荷预测)
- PyTorch-LSTM时间序列预测中如何预测真正的未来值
- PyTorch搭建LSTM实现多变量输入多变量输出时间序列预测
上面所有文章都是“单变量输出”,虽然某些文章中提到了“多变量”,但这个多变量只是输入多变量,而不是输出多变量。比如我们利用前24个时刻的[负荷、温度、湿度、压强]预测接下来12个时刻的负荷,此时输入为多变量,虽然有多个输出(多步长),但输出的都是同一变量。
那么有没有办法一次性输出多个变量呢?当然是可以的,在前几篇文章的评论中也有人提到了这个问题,当时我给出的回答是:“这样做效果很不好,不建议这么做”。
II. 多变量输入多变量输出
多变量输入自不必说,不了解的可以去看一下前面几篇文章。
多变量输出是指:我们一次性输出多个变量的预测值。比如我们利用前24小时的[负荷、温度、湿度、压强]预测接下来12个时刻的[负荷、温度、湿度、压强]。实际上,我们可以将多个变量的输出分解开来,看成多个任务,也就是多任务学习,其中每一个任务都是前面提到的多变量输入单变量输出。
具体来讲,假设需要预测四个变量,输出在经过LSTM后得到output,我们将output分别通过四个全连接层,就能得到四个输出。得到四个输出后,我们就可以计算出四个损失函数,对这四个损失函数,本文将其简单求平均以得到最终的损失函数。关于如何组合多任务学习中的损失,已经有很多文献探讨过,感兴趣的可以自行了解。
III. 代码实现
3.1 数据处理
本次实验用到了两个数据集:数据集1包含某个地区的负荷、湿度以及能见度三个特征。数据集2中包含三个地区的负荷值。
数据集1:
数据集2:
依旧使用前24个时刻的三个变量预测后12个时刻的三个变量:
defnn_seq(B, pred_step_size):
data = load_data()
train = data[:int(len(data)*0.7)]
test = data[int(len(data)*0.7):len(data)]# 归一化
train.drop([train.columns[0]], axis=1, inplace=True)
test.drop([test.columns[0]], axis=1, inplace=True)
scalar = MinMaxScaler()
train = scalar.fit_transform(train.values)
test = scalar.transform(test.values)defprocess(dataset, batch_size):
dataset = dataset.tolist()
seq =[]for i inrange(0,len(dataset)-24- pred_step_size, pred_step_size):
train_seq =[]for j inrange(i, i +24):
x =[]for c inrange(len(dataset[0])):# 前24个时刻的所有变量
x.append(dataset[j][c])
train_seq.append(x)# 下几个时刻的所有变量
train_labels =[]for j inrange(len(dataset[0])):
train_label =[]for k inrange(i +24, i +24+ pred_step_size):
train_label.append(dataset[k][j])
train_labels.append(train_label)# tensor
train_seq = torch.FloatTensor(train_seq)
train_labels = torch.FloatTensor(train_labels)
seq.append((train_seq, train_labels))
seq = MyDataset(seq)
seq = DataLoader(dataset=seq, batch_size=batch_size, shuffle=False, num_workers=0, drop_last=True)return seq
Dtr = process(train, B)
Dte = process(test, B)return Dtr, Dte, scalar
3.2 模型搭建
多输入多输出LSTM模型搭建如下:
classLSTM(nn.Module):def__init__(self, input_size, hidden_size, num_layers, output_size, batch_size, n_outputs):super().__init__()
self.input_size = input_size
self.hidden_size = hidden_size
self.num_layers = num_layers
self.output_size = output_size
self.num_directions =1
self.n_outputs = n_outputs
self.batch_size = batch_size
self.lstm = nn.LSTM(self.input_size, self.hidden_size, self.num_layers, batch_first=True)# self.fcs = [nn.Linear(self.hidden_size, self.output_size).to(device) for i in range(self.n_outputs)]
self.fc1 = nn.Linear(self.hidden_size, self.output_size)
self.fc2 = nn.Linear(self.hidden_size, self.output_size)
self.fc3 = nn.Linear(self.hidden_size, self.output_size)defforward(self, input_seq):# print(input_seq.shape)
batch_size, seq_len = input_seq.shape[0], input_seq.shape[1]
h_0 = torch.randn(self.num_directions * self.num_layers, batch_size, self.hidden_size).to(device)
c_0 = torch.randn(self.num_directions * self.num_layers, batch_size, self.hidden_size).to(device)# print(input_seq.size())# input(batch_size, seq_len, input_size)# output(batch_size, seq_len, num_directions * hidden_size)
output, _ = self.lstm(input_seq,(h_0, c_0))
preds =[]
pred1, pred2, pred3 = self.fc1(output), self.fc2(output), self.fc3(output)
pred1, pred2, pred3 = pred1[:,-1,:], pred2[:,-1,:], pred3[:,-1,:]# pred = torch.cat([pred1, pred2], dim=0)
pred = torch.stack([pred1, pred2, pred3], dim=0)# print(pred.shape)return pred
可以看到,由于需要预测三个变量,所以我们在模型中定义了三个全连接层。在得到LSTM的输出后,分别利用三个全连接层得到三个变量的输出,再将三个输出进行拼接,得到最后的pred,pred的shape为:
predict(n_outputs, batch_size, output_size)
其中n_outputs=3,表示一次性预测三个变量,output_size=12表示这里采用了PyTorch搭建LSTM实现多变量多步长时间序列预测(一):直接多输出中的策略,一次性输出接下来12个时刻的预测值,因为是直接多输出,所以这里pred_step_size=output_size。
3.3 模型训练/测试
模型训练的代码如下:
deftrain(args, Dtr, path):
input_size, hidden_size, num_layers = args.input_size, args.hidden_size, args.num_layers
output_size = args.output_size
model = LSTM(input_size, hidden_size, num_layers, output_size, batch_size=args.batch_size,
n_outputs=args.n_outputs).to(device)
loss_function = nn.MSELoss().to(device)if args.optimizer =='adam':
optimizer = torch.optim.Adam(model.parameters(), lr=args.lr,
weight_decay=args.weight_decay)else:
optimizer = torch.optim.SGD(model.parameters(), lr=args.lr,
momentum=0.9, weight_decay=args.weight_decay)
scheduler = StepLR(optimizer, step_size=args.step_size, gamma=args.gamma)# trainingfor i in tqdm(range(args.epochs)):
cnt =0for(seq, labels)in Dtr:
cnt +=1
seq = seq.to(device)
labels = labels.to(device)# (batch_size, n_outputs, pred_step_size)
preds = model(seq)# (n_outputs, batch_size, pred_step_size)# print(labels.shape)# print(preds.shape)# 计算多个损失函数
total_loss =0for k inrange(args.n_outputs):
total_loss = total_loss + loss_function(preds[k,:,:], labels[:, k,:])
total_loss /= preds.shape[0]
total_loss.requires_grad_(True)
optimizer.zero_grad()
total_loss.backward()
optimizer.step()print('epoch', i,':', total_loss.item())
scheduler.step()
state ={'models': model.state_dict(),'optimizer': optimizer.state_dict()}
torch.save(state, path)
经过预测后,我们得到的label和pred的shape分别为:
label(batch_size, n_outputs, pred_step_size)
pred((n_outputs, batch_size, pred_step_size))
由于需要对每一个output计算损失然后相加求平均,所以我们的损失函数求解如下:
total_loss =0for k inrange(args.n_outputs):
total_loss = total_loss + loss_function(preds[k,:,:], labels[:, k,:])
total_loss /= preds.shape[0]
即每次都取出一个output进行计算求和再平均。
模型测试的代码如下:
deftest(args, Dte, scalar, path):print('loading models...')
input_size, hidden_size, num_layers = args.input_size, args.hidden_size, args.num_layers
output_size = args.output_size
model = LSTM(input_size, hidden_size, num_layers, output_size, batch_size=args.batch_size,
n_outputs=args.n_outputs).to(device)# models = LSTM(input_size, hidden_size, num_layers, output_size, batch_size=args.batch_size).to(device)
model.load_state_dict(torch.load(path)['models'])
model.eval()print('predicting...')
ys =[[]for i inrange(args.n_outputs)]
preds =[[]for i inrange(args.n_outputs)]for(seq, targets)in tqdm(Dte):
targets = np.array(targets.data.tolist())# (batch_size, n_outputs, pred_step_size)for i inrange(args.n_outputs):
target = targets[:, i,:]
target =list(chain.from_iterable(target))
ys[i].extend(target)
seq = seq.to(device)with torch.no_grad():
_pred = model(seq)for i inrange(_pred.shape[0]):
pred = _pred[i]
pred =list(chain.from_iterable(pred.data.tolist()))
preds[i].extend(pred)# ys, preds = [np.array(y) for y in ys], [np.array(pred) for pred in preds]
ys, preds = np.array(ys).T, np.array(preds).T
ys = scalar.inverse_transform(ys).T
preds = scalar.inverse_transform(preds).T
for ind,(y, pred)inenumerate(zip(ys, preds),0):print(get_mape(y, pred))
plot(y, pred, ind)
plt.show()
3.4 实验结果
数据集1中包含的是某个地区的负荷、湿度以及能见度三个特征,其预测结果如下所示:
变量负荷湿度能见度MAPE12.83%3.88%19.61%
数据集2中包含三个地区的负荷值:
变量负荷1负荷2负荷3MAPE6.01%12.45%7.50%
观察上述结果我们可以发现,数据集2上的预测效果明显更好,这可能是因为三个负荷变量之间相关性较强。
IV. 源码及数据
稍后整理上传至GitHub。
版权归原作者 Cyril_KI 所有, 如有侵权,请联系我们删除。