当前位置:   article > 正文

使用Transfomer对时间序列进行预测(基于PyTorch代码)_transform预测值

transform预测值

代码来源

https://github.com/nok-halfspace/Transformer-Time-Series-Forecasting

文章信息:https://medium.com/mlearning-ai/transformer-implementation-for-time-series-forecasting-a9db2db5c820

数据结构

该项目中的数据结构如下图所示:有不同的sensor_id, 然后这些sensor在不同的时间段有不同的humidity. 

数据导入和初步处理

首先是对数据进行初步处理,以下为DataLoader的代码:

  1. class SensorDataset(Dataset):
  2. """Face Landmarks dataset."""
  3. def __init__(self, csv_name, root_dir, training_length, forecast_window):
  4. """
  5. Args:
  6. csv_file (string): Path to the csv file.
  7. root_dir (string): Directory
  8. """
  9. # load raw data file
  10. csv_file = os.path.join(root_dir, csv_name)
  11. self.df = pd.read_csv(csv_file)
  12. self.root_dir = root_dir
  13. self.transform = MinMaxScaler() #对数据进行归一化处理
  14. self.T = training_length
  15. self.S = forecast_window
  16. def __len__(self):
  17. # return number of sensors
  18. return len(self.df.groupby(by=["reindexed_id"]))
  19. # Will pull an index between 0 and __len__.
  20. def __getitem__(self, idx):
  21. # Sensors are indexed from 1
  22. idx = idx+1
  23. # np.random.seed(0)
  24. start = np.random.randint(0, len(self.df[self.df["reindexed_id"]==idx]) - self.T - self.S)
  25. sensor_number = str(self.df[self.df["reindexed_id"]==idx][["sensor_id"]][start:start+1].values.item())
  26. index_in = torch.tensor([i for i in range(start, start+self.T)])
  27. index_tar = torch.tensor([i for i in range(start + self.T, start + self.T + self.S)])
  28. _input = torch.tensor(self.df[self.df["reindexed_id"]==idx][["humidity", "sin_hour", "cos_hour", "sin_day", "cos_day", "sin_month", "cos_month"]][start : start + self.T].values)
  29. target = torch.tensor(self.df[self.df["reindexed_id"]==idx][["humidity", "sin_hour", "cos_hour", "sin_day", "cos_day", "sin_month", "cos_month"]][start + self.T : start + self.T + self.S].values)
  30. # scalar is fit only to the input, to avoid the scaled values "leaking" information about the target range.
  31. # scalar is fit only for humidity, as the timestamps are already scaled
  32. # scalar input/output of shape: [n_samples, n_features].
  33. scaler = self.transform
  34. scaler.fit(_input[:,0].unsqueeze(-1))
  35. _input[:,0] = torch.tensor(scaler.transform(_input[:,0].unsqueeze(-1)).squeeze(-1))
  36. target[:,0] = torch.tensor(scaler.transform(target[:,0].unsqueeze(-1)).squeeze(-1))
  37. # save the scalar to be used later when inverse translating the data for plotting.
  38. dump(scaler, 'scalar_item.joblib')
  39. return index_in, index_tar, _input, target, sensor_number

其中比较重要的一个点是:在这里对初始的数据进行了MinMaxScaler()的处理,也就是进行数据归一化,这在深度学习中是很常见的一个操作。 

时间信息Embedding

与LSTM模型不同,因为在transfoermer的模型中,所有的信息是一股脑丢进去的,所以是不带有时间的信息的。 所以对于时间序列,需要对时间信息进行额外处理。 在如下的代码中对原本的数据集增加了一些信息, 包括sin_hour, cos_hour, sin_day, cos_day有点类似于positional embedding的机制。 所要表达的信息如下图所示:

  1. import pandas as pd
  2. import time
  3. import numpy as np
  4. import datetime
  5. from icecream import ic
  6. # encoding the timestamp data cyclically. See Medium Article.
  7. def process_data(source):
  8. df = pd.read_csv(source)
  9. timestamps = [ts.split('+')[0] for ts in df['timestamp']]
  10. timestamps_hour = np.array([float(datetime.datetime.strptime(t, '%Y-%m-%d %H:%M:%S').hour) for t in timestamps])
  11. timestamps_day = np.array([float(datetime.datetime.strptime(t, '%Y-%m-%d %H:%M:%S').day) for t in timestamps])
  12. timestamps_month = np.array([float(datetime.datetime.strptime(t, '%Y-%m-%d %H:%M:%S').month) for t in timestamps])
  13. hours_in_day = 24
  14. days_in_month = 30
  15. month_in_year = 12
  16. df['sin_hour'] = np.sin(2*np.pi*timestamps_hour/hours_in_day)
  17. df['cos_hour'] = np.cos(2*np.pi*timestamps_hour/hours_in_day)
  18. df['sin_day'] = np.sin(2*np.pi*timestamps_day/days_in_month)
  19. df['cos_day'] = np.cos(2*np.pi*timestamps_day/days_in_month)
  20. df['sin_month'] = np.sin(2*np.pi*timestamps_month/month_in_year)
  21. df['cos_month'] = np.cos(2*np.pi*timestamps_month/month_in_year)
  22. return df
  23. train_dataset = process_data('Data/train_raw.csv')
  24. test_dataset = process_data('Data/test_raw.csv')
  25. train_dataset.to_csv(r'Data/train_dataset.csv', index=False)
  26. test_dataset.to_csv(r'Data/test_dataset.csv', index=False)

然后通过这个代码段,得到有更多变量的新的数据: 

其中需要注意的是因为是在后续的main.py中对数据进行归一化,所以在这个数据集中看不出原本的humidity数据被归一化的处理过程。

带入到Transfomer模型进行计算

在对数据进行了数据的处理以后,接下来就是将数据带入到模型中进行计算

定义transfomer模型

这是在这个项目中很重要的代码段,所以在这里需要重点分析一下。 在代码的一开始作者就提出这个是基于文章《Attention is all you need》来进行计算的。 

Transfomer是由encoder,decoder还有feed forward组成的。

模型构建:在这个项目中,用所有已知的历史数据对未来一个时期的数据进行预测。假设X1到X5分别是过去第1到第5期的历史数据,预测X2的时候,只使用X1的数据来进行预测;预测X3的时候则使用X1和X2的数据; 预测X4的时候使用X1,X2, X3的数据,以此类推。 

Masked self-attention: 因为transfomer中使用了自注意力机制, 但是在预测的过程中,是不可以看到预测的那个时间段之前的数据的,所以在这里使用了masked的机制,简单来说就是将未来的数据设为注意力为很小的一个数字,这样模型就看不到后面的数值了。 

因为在Pytorch中其实已经有了现成的代码段,所以在这里只需要定义参数就可以使用。 听起来很简单,但是在使用的过程中也常常会遇到很多麻烦。 

  • feature_size:使用的特征个数,在该项目中是指时间的6个特征 ('sin_hour', 'cos_hour', 'sin_day', 'cos_day', 'sin_month', 'cos_month')+1个初始数据
  • num_layers:   encoder的层数,这个可以根据模型来具体调整
  • dropout: 这个可以根据模型来具体调整
  • nhead:多层注意力机制的头数,一定要注意的就是特征数必须能被头数整除,否则模型会报错 (该点很好理解,因为本来这个头数就是相当于分开映射的个数,不能整除就不好分)。
  1. import torch.nn as nn
  2. import torch, math
  3. from icecream import ic
  4. import time
  5. """
  6. The architecture is based on the paper “Attention Is All You Need”.
  7. Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N Gomez, Lukasz Kaiser, and Illia Polosukhin. 2017.
  8. """
  9. class Transformer(nn.Module):
  10. # d_model : number of features
  11. def __init__(self,feature_size=7,num_layers=3,dropout=0):
  12. super(Transformer, self).__init__()
  13. self.encoder_layer = nn.TransformerEncoderLayer(d_model=feature_size, nhead=7, dropout=dropout)
  14. self.transformer_encoder = nn.TransformerEncoder(self.encoder_layer, num_layers=num_layers)
  15. self.decoder = nn.Linear(feature_size,1) #feature_size是input的个数,1为output个数
  16. self.init_weights()
  17. #init_weight主要是用于设置decoder的参数
  18. def init_weights(self):
  19. initrange = 0.1
  20. self.decoder.bias.data.zero_()
  21. self.decoder.weight.data.uniform_(-initrange, initrange)
  22. def _generate_square_subsequent_mask(self, sz):
  23. mask = (torch.triu(torch.ones(sz, sz)) == 1).transpose(0, 1)
  24. mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
  25. return mask
  26. def forward(self, src, device):
  27. mask = self._generate_square_subsequent_mask(len(src)).to(device)
  28. output = self.transformer_encoder(src,mask)
  29. output = self.decoder(output)
  30. return output

执行模型

在该模型中,batch_size设为了1,也就是说在该模型中,每个sensor是相互独立的,在计算的时候是分别使用transfomer模型来进行计算。 并不考虑每个sensor之间的关系。 

  1. import argparse
  2. # from train_teacher_forcing import *
  3. from train_with_sampling import *
  4. from DataLoader import *
  5. from torch.utils.data import DataLoader
  6. import torch.nn as nn
  7. import torch
  8. from helpers import *
  9. from inference import *
  10. def main(
  11. epoch: int = 1000,
  12. k: int = 60,
  13. batch_size: int = 1,
  14. frequency: int = 100,
  15. training_length = 48,
  16. forecast_window = 24,
  17. train_csv = "train_dataset.csv",
  18. test_csv = "test_dataset.csv",
  19. path_to_save_model = "save_model/",
  20. path_to_save_loss = "save_loss/",
  21. path_to_save_predictions = "save_predictions/",
  22. device = "cpu"
  23. ):
  24. clean_directory()
  25. train_dataset = SensorDataset(csv_name = train_csv, root_dir = "Data/", training_length = training_length, forecast_window = forecast_window)
  26. train_dataloader = DataLoader(train_dataset, batch_size=1, shuffle=True)
  27. test_dataset = SensorDataset(csv_name = test_csv, root_dir = "Data/", training_length = training_length, forecast_window = forecast_window)
  28. test_dataloader = DataLoader(test_dataset, batch_size=1, shuffle=True)
  29. best_model = transformer(train_dataloader, epoch, k, frequency, path_to_save_model, path_to_save_loss, path_to_save_predictions, device)
  30. inference(path_to_save_predictions, forecast_window, test_dataloader, device, path_to_save_model, best_model)
  31. if __name__ == "__main__":
  32. parser = argparse.ArgumentParser()
  33. parser.add_argument("--epoch", type=int, default=1000)
  34. parser.add_argument("--k", type=int, default=60)
  35. parser.add_argument("--batch_size", type=int, default=1)
  36. parser.add_argument("--frequency", type=int, default=100)
  37. parser.add_argument("--path_to_save_model",type=str,default="save_model/")
  38. parser.add_argument("--path_to_save_loss",type=str,default="save_loss/")
  39. parser.add_argument("--path_to_save_predictions",type=str,default="save_predictions/")
  40. parser.add_argument("--device", type=str, default="cpu")
  41. args = parser.parse_args()
  42. main(
  43. epoch=args.epoch,
  44. k = args.k,
  45. batch_size=args.batch_size,
  46. frequency=args.frequency,
  47. path_to_save_model=args.path_to_save_model,
  48. path_to_save_loss=args.path_to_save_loss,
  49. path_to_save_predictions=args.path_to_save_predictions,
  50. device=args.device,
  51. )

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/700114
推荐阅读
相关标签
  

闽ICP备14008679号