使用 PyTorch+LSTM 进行单变量时间序列预测(附完整源码)

news2025/2/25 15:29:31

时间序列是指在一段时间内发生的任何可量化的度量或事件。尽管这听起来微不足道,但几乎任何东西都可以被认为是时间序列。一个月里你每小时的平均心率,一年里一只股票的日收盘价,一年里某个城市每周发生的交通事故数。

在任何一段时间段内记录这些信息都被认为是一个时间序列。对于这些例子中的每一个,都有事件发生的频率(每天、每周、每小时等)和事件发生的时间长度(一个月、一年、一天等)。

在本教程中,我们将使用 PyTorch-LSTM 进行深度学习时间序列预测。

我们的目标是接收一个值序列,预测该序列中的下一个值。最简单的方法是使用自回归模型,我们将专注于使用LSTM来解决这个问题。

数据准备

让我们看一个时间序列样本。下图显示了2013年至2018年石油价格的一些数据。

技术提升

技术要学会分享、交流,不建议闭门造车。一个人走的很快、一堆人可以走的更远。

完整代码、数据、技术交流提升, 均可加交流群获取,群友已超过2000人,添加时切记的备注方式为:来源+兴趣方向,方便找到志同道合的朋友。

方式①、添加微信号:pythoner666,备注:来自 CSDN + python
方式②、微信搜索公众号:Python学习与数据挖掘,后台回复:加群

这只是一个日期轴上单个数字序列的图。下表显示了这个时间序列的前10个条目。每天都有价格数据。

 date       dcoilwtico  
 2013-01-01 NaN  
 2013-01-02 93.14  
 2013-01-03 92.97  
 2013-01-04 93.12  
 2013-01-07 93.20  
 2013-01-08 93.21  
 2013-01-09 93.08  
 2013-01-10 93.81  
 2013-01-11 93.60  
 2013-01-14 94.27

许多机器学习模型在标准化数据上的表现要好得多。标准化数据的标准方法是对数据进行转换,使得每一列的均值为0,标准差为1。下面的代码scikit-learn进行标准化

 from sklearn.preprocessing import StandardScaler  
   
 # Fit scalers  
 scalers = {}  
 for x in df.columns:  
   scalers[x] = StandardScaler().fit(df[x].values.reshape(-1, 1))  
   
 # Transform data via scalers  
 norm_df = df.copy()  
 for i, key in enumerate(scalers.keys()):  
   norm = scalers[key].transform(norm_df.iloc[:, i].values.reshape(-1, 1))  
   norm_df.iloc[:, i] = norm

我们还希望数据具有统一的频率——在这个例子中,有这5年里每天的石油价格,如果你的数据情况并非如此,Pandas有几种不同的方法来重新采样数据以适应统一的频率,请参考我们公众号以前的文章

对于训练数据我们需要将完整的时间序列数据截取成固定长度的序列。假设我们有一个序列:[1, 2, 3, 4, 5, 6]。

通过选择长度为 3 的序列,我们可以生成以下序列及其相关目标:

[Sequence] Target

[1, 2, 3] → 4

[2, 3, 4] → 5

[3, 4, 5] → 6

或者说我们定义了为了预测下一个值需要回溯多少步。我们将这个值称为训练窗口,而要预测的值的数量称为预测窗口。在这个例子中,它们分别是3和1。下面的函数详细说明了这是如何完成的。

 # 如上所示,定义一个创建序列和目标的函数  
 def generate_sequences(df: pd.DataFrame, tw: int, pw: int, target_columns, drop_targets=False):  
   '''  
  df: Pandas DataFrame of the univariate time-series  
  tw: Training Window - Integer defining how many steps to look back  
  pw: Prediction Window - Integer defining how many steps forward to predict  
   
  returns: dictionary of sequences and targets for all sequences  
  '''  
   data = dict() # Store results into a dictionary  
   L = len(df)  
   for i in range(L-tw):  
     # Option to drop target from dataframe  
     if drop_targets:  
       df.drop(target_columns, axis=1, inplace=True)  
   
     # Get current sequence    
     sequence = df[i:i+tw].values  
     # Get values right after the current sequence  
     target = df[i+tw:i+tw+pw][target_columns].values  
     data[i] = {'sequence': sequence, 'target': target}  
   return data

这样我们就可以在PyTorch中使用Dataset类自定义数据集

 class SequenceDataset(Dataset):  
   
   def __init__(self, df):  
     self.data = df  
   
   def __getitem__(self, idx):  
     sample = self.data[idx]  
     return torch.Tensor(sample['sequence']), torch.Tensor(sample['target'])  
     
   def __len__(self):  
     return len(self.data)

然后,我们可以使用PyTorch DataLoader来遍历数据。使用DataLoader的好处是它在内部自动进行批处理和数据的打乱,所以我们不必自己实现它,代码如下:

 # 这里我们为我们的模型定义属性  
   
 BATCH_SIZE = 16 # Training batch size  
 split = 0.8 # Train/Test Split ratio  
   
 sequences = generate_sequences(norm_df.dcoilwtico.to_frame(), sequence_len, nout, 'dcoilwtico')  
 dataset = SequenceDataset(sequences)  
   
 # 根据拆分比例拆分数据,并将每个子集加载到单独的DataLoader对象中  
 train_len = int(len(dataset)*split)  
 lens = [train_len, len(dataset)-train_len]  
 train_ds, test_ds = random_split(dataset, lens)  
 trainloader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, drop_last=True)  
 testloader = DataLoader(test_ds, batch_size=BATCH_SIZE, shuffle=True, drop_last=True)

在每次迭代中,DataLoader将产生16个(批量大小)序列及其相关目标,我们将这些目标传递到模型中。

模型架构

我们将使用一个单独的LSTM层,然后是模型的回归部分的一些线性层,当然在它们之间还有dropout层。该模型将为每个训练输入输出单个值。

 class LSTMForecaster(nn.Module):  
  
   def __init__(self, n_features, n_hidden, n_outputs, sequence_len, n_lstm_layers=1, n_deep_layers=10, use_cuda=False, dropout=0.2):  
     '''  
    n_features: number of input features (1 for univariate forecasting)  
    n_hidden: number of neurons in each hidden layer  
    n_outputs: number of outputs to predict for each training example  
    n_deep_layers: number of hidden dense layers after the lstm layer  
    sequence_len: number of steps to look back at for prediction  
    dropout: float (0 < dropout < 1) dropout ratio between dense layers  
    '''  
     super().__init__()  
   
     self.n_lstm_layers = n_lstm_layers  
     self.nhid = n_hidden  
     self.use_cuda = use_cuda # set option for device selection  
   
     # LSTM Layer  
     self.lstm = nn.LSTM(n_features,  
                         n_hidden,  
                         num_layers=n_lstm_layers,  
                         batch_first=True) # As we have transformed our data in this way  
       
     # first dense after lstm  
     self.fc1 = nn.Linear(n_hidden * sequence_len, n_hidden)  
     # Dropout layer  
     self.dropout = nn.Dropout(p=dropout)  
   
     # Create fully connected layers (n_hidden x n_deep_layers)  
     dnn_layers = []  
     for i in range(n_deep_layers):  
       # Last layer (n_hidden x n_outputs)  
       if i == n_deep_layers - 1:  
         dnn_layers.append(nn.ReLU())  
         dnn_layers.append(nn.Linear(nhid, n_outputs))  
       # All other layers (n_hidden x n_hidden) with dropout option  
       else:  
         dnn_layers.append(nn.ReLU())  
         dnn_layers.append(nn.Linear(nhid, nhid))  
         if dropout:  
           dnn_layers.append(nn.Dropout(p=dropout))  
     # compile DNN layers  
     self.dnn = nn.Sequential(*dnn_layers)  
   
   def forward(self, x):  
   
     # Initialize hidden state  
     hidden_state = torch.zeros(self.n_lstm_layers, x.shape[0], self.nhid)  
     cell_state = torch.zeros(self.n_lstm_layers, x.shape[0], self.nhid)  
   
     # move hidden state to device  
     if self.use_cuda:  
       hidden_state = hidden_state.to(device)  
       cell_state = cell_state.to(device)  
           
     self.hidden = (hidden_state, cell_state)  
   
     # Forward Pass  
     x, h = self.lstm(x, self.hidden) # LSTM  
     x = self.dropout(x.contiguous().view(x.shape[0], -1)) # Flatten lstm out  
     x = self.fc1(x) # First Dense  
     return self.dnn(x) # Pass forward through fully connected DNN.

我们设置了2个可以自由地调优的参数n_hidden和n_deep_players。更大的参数意味着模型更复杂和更长的训练时间,所以这里我们可以使用这两个参数灵活调整。

剩下的参数如下:sequence_len指的是训练窗口,nout定义了要预测多少步;将sequence_len设置为180,nout设置为1,意味着模型将查看180天(半年)后的情况,以预测明天将发生什么。

 nhid = 50 # Number of nodes in the hidden layer  
 n_dnn_layers = 5 # Number of hidden fully connected layers  
 nout = 1 # Prediction Window  
 sequence_len = 180 # Training Window  
   
 # Number of features (since this is a univariate timeseries we'll set  
 # this to 1 -- multivariate analysis is coming in the future)  
 ninp = 1  
   
 # Device selection (CPU | GPU)  
 USE_CUDA = torch.cuda.is_available()  
 device = 'cuda' if USE_CUDA else 'cpu'  
   
 # Initialize the model  
 model = LSTMForecaster(ninp, nhid, nout, sequence_len, n_deep_layers=n_dnn_layers, use_cuda=USE_CUDA).to(device)

模型训练

定义好模型后,我们可以选择损失函数和优化器,设置学习率和周期数,并开始我们的训练循环。由于这是一个回归问题(即我们试图预测一个连续值),最简单也是最安全的损失函数是均方误差。这提供了一种稳健的方法来计算实际值和模型预测值之间的误差。

优化器和损失函数如下:

 # Set learning rate and number of epochs to train over  
 lr = 4e-4  
 n_epochs = 20  
   
 # Initialize the loss function and optimizer  
 criterion = nn.MSELoss().to(device)  
 optimizer = torch.optim.AdamW(model.parameters(), lr=lr)

下面就是训练循环的代码:在每次训练迭代中,我们将计算之前创建的训练集和验证集的损失:

# Lists to store training and validation losses  
t_losses, v_losses = [], []  
# Loop over epochs  
for epoch in range(n_epochs):  
  train_loss, valid_loss = 0.0, 0.0  
  
  # train step  
  model.train()  
  # Loop over train dataset  
  for x, y in trainloader:  
    optimizer.zero_grad()  
    # move inputs to device  
    x = x.to(device)  
    y  = y.squeeze().to(device)  
    # Forward Pass  
    preds = model(x).squeeze()  
    loss = criterion(preds, y) # compute batch loss  
    train_loss += loss.item()  
    loss.backward()  
    optimizer.step()  
  epoch_loss = train_loss / len(trainloader)  
  t_losses.append(epoch_loss)  
    
  # validation step  
  model.eval()  
  # Loop over validation dataset  
  for x, y in testloader:  
    with torch.no_grad():  
      x, y = x.to(device), y.squeeze().to(device)  
      preds = model(x).squeeze()  
      error = criterion(preds, y)  
    valid_loss += error.item()  
  valid_loss = valid_loss / len(testloader)  
  v_losses.append(valid_loss)  
        
  print(f'{epoch} - train: {epoch_loss}, valid: {valid_loss}')  
plot_losses(t_losses, v_losses)  

这样模型已经训练好了,可以评估预测了。

推理

我们调用训练过的模型来预测未打乱的数据,并比较预测与真实观察有多大不同。

def make_predictions_from_dataloader(model, unshuffled_dataloader):  
  model.eval()  
  predictions, actuals = [], []  
  for x, y in unshuffled_dataloader:  
    with torch.no_grad():  
      p = model(x)  
      predictions.append(p)  
      actuals.append(y.squeeze())  
  predictions = torch.cat(predictions).numpy()  
  actuals = torch.cat(actuals).numpy()  
  return predictions.squeeze(), actuals  

石油历史上的常态化预测与实际价格

我们的预测看起来还不错!预测的效果还可以,表明我们没有过度拟合模型,让我们看看能否用它来预测未来。

预测

如果我们将历史定义为预测时刻之前的序列,算法很简单:

  1. 从历史(训练窗口长度)中获取最新的有效序列。

  2. 将最新的序列输入模型并预测下一个值。

  3. 将预测值附加到历史记录上。

  4. 迭代重复步骤1。

这里需要注意的是,根据训练模型时选择的参数,你预测的越长(远),模型就越容易表现出它自己的偏差,开始预测平均值。因此,如果没有必要,我们不希望总是预测得太超前,因为这会影响预测的准确性。

这在下面的函数中实现:

def one_step_forecast(model, history):  
      '''  
      model: PyTorch model object  
      history: a sequence of values representing the latest values of the time   
      series, requirement -> len(history.shape) == 2  
      
      outputs a single value which is the prediction of the next value in the  
      sequence.  
      '''  
      model.cpu()  
      model.eval()  
      with torch.no_grad():  
        pre = torch.Tensor(history).unsqueeze(0)  
        pred = self.model(pre)  
      return pred.detach().numpy().reshape(-1)  
  
  def n_step_forecast(data: pd.DataFrame, target: str, tw: int, n: int, forecast_from: int=None, plot=False):  
      '''  
      n: integer defining how many steps to forecast  
      forecast_from: integer defining which index to forecast from. None if  
      you want to forecast from the end.  
      plot: True if you want to output a plot of the forecast, False if not.  
      '''  
      history = data[target].copy().to_frame()  
        
      # Create initial sequence input based on where in the series to forecast   
      # from.  
      if forecast_from:  
        pre = list(history[forecast_from - tw : forecast_from][target].values)  
      else:  
        pre = list(history[self.target])[-tw:]  
  
      # Call one_step_forecast n times and append prediction to history  
      for i, step in enumerate(range(n)):  
        pre_ = np.array(pre[-tw:]).reshape(-1, 1)  
        forecast = self.one_step_forecast(pre_).squeeze()  
        pre.append(forecast)  
        
      # The rest of this is just to add the forecast to the correct time of   
      # the history series  
      res = history.copy()  
      ls = [np.nan for i in range(len(history))]  
  
      # Note: I have not handled the edge case where the start index + n is   
      # before the end of the dataset and crosses past it.  
      if forecast_from:  
        ls[forecast_from : forecast_from + n] = list(np.array(pre[-n:]))  
        res['forecast'] = ls  
        res.columns = ['actual', 'forecast']  
      else:  
        fc = ls + list(np.array(pre[-n:]))  
        ls = ls + [np.nan for i in range(len(pre[-n:]))]  
        ls[:len(history)] = history[self.target].values  
        res = pd.DataFrame([ls, fc], index=['actual', 'forecast']).T  
      return res  

我们来看看实际的效果

我们在这个时间序列的中间从不同的地方进行预测,这样我们就可以将预测与实际发生的情况进行比较。我们的预测程序,可以从任何地方对任何合理数量的步骤进行预测,红线表示预测。(这些图表显示的是y轴上的标准化后的价格)

预测2013年第三季度后200天

预测2014/15 后200天

从2016年第一季度开始预测200天

从数据的最后一天开始预测200天

总结

我们这个模型表现的还算一般!但是我们通过这个示例完整的介绍了时间序列预测的全部过程,我们可以通过尝试架构和参数的调整使模型变得得更好,预测得更准确。

本文只处理单变量时间序列,其中只有一个值序列。还有一些方法可以使用多个系列来进行预测。这被称为多元时间序列预测,我将在以后的文章中介绍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/361797.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录算法训练营第四天 | 链表理论基础、4. 两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 02.07. 链表相交、142.环形链表II

打卡第四天&#xff0c;因为科三前几天没有带电脑&#xff0c;现在重新补一下第四天的卡 今日任务 24. 两两交换链表中的节点19.删除链表的倒数第N个节点面试题 02.07. 链表相交142.环形链表II 24. 两两交换链表中的节点 给你一个链表&#xff0c;两两交换其中相邻的节点&#…

C++类和对象_02----对象模型和this指针

目录C对象模型和this指针1、成员变量和成员函数分开存储1.1、空类大小1.2、非空类大小1.3、结论2、this指针概念2.1、解决名称冲突2.2、在类的非静态成员函数中返回对象本身&#xff0c;可使用return *this2.3、拷贝构造函数返回值为引用的时候&#xff0c;可进行链式编程3、空…

Python加密算法种类以及开发场景中的运用

不用说火爆一时&#xff0c;全网热议的Web3.0区块链技术&#xff0c;也不必说诸如微信支付、支付宝支付等人们几乎每天都要使用的线上支付业务&#xff0c;单是一个简简单单的注册/登录功能&#xff0c;也和加密技术脱不了干系&#xff0c;本次我们耙梳各种经典的加密算法&…

【RecBole-GNN/源码】RecBole-GNN中lightGCN源码解析

如果觉得我的分享有一定帮助&#xff0c;欢迎关注我的微信公众号 “码农的科研笔记”&#xff0c;了解更多我的算法和代码学习总结记录。或者点击链接扫码关注【RecBole-GNN/源码】RecBole-GNN中lightGCN源码解析 【RecBole-GNN/源码】RecBole-GNN中lightGCN源码解析 原文&…

【C++】初识CC++内存管理

前言 我们都知道C&C是非常注重性能的语言&#xff0c;因此对于C&C的内存管理是每一个C/C学习者必须重点掌握的内容&#xff0c;本章我们并不是深入讲解C&C内存管理&#xff0c;而是介绍C&C内存管理的基础知识&#xff0c;为我们以后深入理解C&C内存管理做铺…

基于 U-Net 网络的遥感图像语义分割 完整代码+论文

一、研究目的U-Net 是一种由全卷积神经网络启发的对称结构网络&#xff0c;在医疗影像分割领域取得了很好的效果。 此次研究尝试使用 U-Net 网络在对多光谱遥感影像数据集上进行训练&#xff0c;尝试使用卷积神经网络自动分割出建筑&#xff0c;希望能够得到一种自动分割遥感影…

ElementUI分页的实现

官网地址&#xff1a;Element - The worlds most popular Vue UI framework 第一步&#xff1a;拷贝你喜欢的分页类型放在你的组件页面需要用到的分页位置 <el-paginationsize-change"handleSizeChange"current-change"handleCurrentChange":current-p…

1.JAVA-JDK安装

前言&#xff1a;工具下载地址阿里云盘&#xff1a;Java-Jdk&#xff1a;https://www.aliyundrive.com/s/JpV55xhVq2A提取码: j53y一、jdk下载&#xff1a;前往Oracle官网可免费下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads/ 此处我下载的是jdk8&a…

【nas折腾篇】抉择吧,是入门还是放弃

2018年公司一位女同事问群晖的nas是否值得买。我一脸懵&#xff0c;以前给公司买云服务有采购nas盘&#xff0c;直接mount挂到服务器上当存储&#xff0c;但对于单独的nas服务器没有什么概念。一晃几年过去了&#xff0c;陆续刷到些nas服务的视频&#xff0c;周边朋友用nas的也…

nginx的介绍及源码安装

文章目录前言一、nginx介绍二、nginx应用场合三、nginx的源码安装过程1.下载源码包2.安装依赖性-安装nginx-创建软连接-启动服务-关闭服务3.创建nginx服务启动脚本4.本实验---纯代码过程前言 高可用&#xff1a;高可用(High availability,缩写为 HA),是指系统无中断地执行其功…

OSI七层模型与物理层与设备链路层

目录 协议 举例 OSI七层模型 理解七层模型 以下为OSI七层模型数据逐层封装和数据逐层解封的过程 TCP/IP参考模型 数据包的层层封装与层层拆包 各层的数据以及协议 封装所用的协议的数字表示形式 物理层 模拟信号 模拟信号特点 数字信号 数字信号特点 数据通信模…

【存储】etcd的存储是如何实现的(3)-blotdb

前两篇分别介绍了etcd的存储模块以及mvcc模块。在存储模块中&#xff0c;提到了etcd kv存储backend是基于boltdb实现的&#xff0c;其在boltdb的基础上封装了读写事务&#xff0c;通过内存缓存批量将事务刷盘&#xff0c;提升整体的写入性能。botldb是etcd的真正的底层存储。本…

CSS预处理器sass和less

文章目录CSS预处理器什么是CSS预处理器Sass和LESS背景介绍Sass背景介绍LESS的背景介绍Sass安装Sass下载Ruby安装文件安装Ruby安装Sass编译Sass命令行编译命令行编译配置选项四种编译排版演示nested 编译排版格式expanded 编译排版格式compact 编译排版格式compressed 编译排版格…

Ethernet-APL——过程自动化的新黄金标准

| Ethernet-APL为终客户和设备制造商带来益处 Ethernet-APL&#xff08;Advanced Physical Layer&#xff0c;高级物理层&#xff09;是一种两线制以太网物理层&#xff0c;它使用了由IEEE 802.3cg所定义的10BASE-T1L&#xff0c;并采用了新的工艺制造规定&#xff0c;因此构成…

2.21多线程

一.并发编程java实现并发编程的方式是多线程其他语言,主打的 并发编程并不一样Go 主要通过多协程的方式实现并发erlang 是通过actor模型实现并发JS 通过定时器和事件回调的方式实现并发二.多线程在java标准库,提供了一个Thread类,表示/操作线程Thread类可以视为Java标准库提供的…

CCNP350-401学习笔记(401-450题)

401、What is the function of vBond in a Cisco SDWAN deployment? A. initiating connections with SD-WAN routers automatically B. pushing of configuration toward SD-WAN routersC. onboarding of SDWAN routers into the SD-WAN overlay D. gathering telemetry dat…

易点天下基于 StarRocks 全面构建实时离线一体的湖仓方案

作者&#xff1a;易点天下数据平台团队易点天下是一家技术驱动发展的企业国际化智能营销服务公司&#xff0c;致力于为客户提供全球营销推广服务&#xff0c;通过效果营销、品牌塑造、垂直行业解决方案等一体化服务&#xff0c;帮助企业在全球范围内高效地获取用户、提升品牌知…

yolov5源码解读--训练策略

yolov5源码解读--训练策略超参数解读命令行参数train模型迭代测试超参数解读 hyp.scratch.yaml lr0: 0.0032 初始学习率 lrf: 0.12 使用余弦函数动态降低学习率(lr0*lrf) momentum: 0.843 动量 weight_decay: 0.00036 权重衰减项 warmup_epochs: 2.0 预热&#xf…

详解Unicode字符集以及字符编码实现(一)

在日常生活中&#xff0c;我们经常会碰到打开一个文件&#xff0c;但是文件内容乱码的问题&#xff0c;比如我想看《西游记》这部小说。 下载链接&#xff1a;https://m.ijjjxs.com/txt/dl-35-12585.html 点击TXT电子书下载&#xff0c;很快就会下载完成&#xff0c;但是使用…

【测试面试】自我分析+功能+接口自动化+性能测试面试题(大全),知己知彼百战百胜......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 分析自己和面试企业…