使用PyTorch-LSTM进行单变量时间序列预测的示例教程

news2024/9/20 17:52:06

时间序列是指在一段时间内发生的任何可量化的度量或事件。尽管这听起来微不足道,但几乎任何东西都可以被认为是时间序列。一个月里你每小时的平均心率,一年里一只股票的日收盘价,一年里某个城市每周发生的交通事故数。在任何一段时间段内记录这些信息都被认为是一个时间序列。对于这些例子中的每一个,都有事件发生的频率(每天、每周、每小时等)和事件发生的时间长度(一个月、一年、一天等)。

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

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

数据准备

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

这只是一个日期轴上单个数字序列的图。下表显示了这个时间序列的前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进行标准化

 fromsklearn.preprocessingimportStandardScaler
 
 # Fit scalers
 scalers= {}
 forxindf.columns:
   scalers[x] =StandardScaler().fit(df[x].values.reshape(-1, 1))
 
 # Transform data via scalers
 norm_df=df.copy()
 fori, keyinenumerate(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。下面的函数详细说明了这是如何完成的。

 # Defining a function that creates sequences and targets as shown above
 defgenerate_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)
   foriinrange(L-tw):
     # Option to drop target from dataframe
     ifdrop_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}
   returndata

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

 classSequenceDataset(Dataset):
 
   def__init__(self, df):
     self.data=df
 
   def__getitem__(self, idx):
     sample=self.data[idx]
     returntorch.Tensor(sample['sequence']), torch.Tensor(sample['target'])
   
   def__len__(self):
     returnlen(self.data)

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

 # Here we are defining properties for our model
 
 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)
 
 # Split the data according to our split ratio and load each subset into a
 # separate DataLoader object
 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层。该模型将为每个训练输入输出单个值。

 classLSTMForecaster(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= []
     foriinrange(n_deep_layers):
       # Last layer (n_hidden x n_outputs)
       ifi==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))
         ifdropout:
           dnn_layers.append(nn.Dropout(p=dropout))
     # compile DNN layers
     self.dnn=nn.Sequential(*dnn_layers)
 
   defforward(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
     ifself.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
     returnself.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'ifUSE_CUDAelse'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天

总结

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

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

本文的代码在这里:

https://avoid.overfit.cn/post/3c8a4160c79041ed8d89b18738f65058

作者:Zain Baquar

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

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

相关文章

Python控制本地浏览器并获取网页数据

1、前言 在自动化办公中&#xff0c;我们经常需要利用爬虫技能去批量获取网页的数据&#xff0c;但是有时候我们在利用爬虫的时候&#xff0c;会遇到一个问题&#xff0c;就是登录的时候要携带参数&#xff0c;不如账号、密码、其他的加密信息 就好比我现在公司&#xff0c;好…

JSP 质量管理系统myeclipse定制开发sqlserver数据库网页模式java编程jdbc

一、源码特点 JSP 质量管理系统是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开 发&#xff0c;数据库为SQLServer2008&#xff0c…

狂飙Linux平台,软件部署大全

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

图形的面积与周长计算程序-课后程序(JAVA基础案例教程-黑马程序员编著-第四章-课后作业)

【案例4-7】图形的面积与周长计算程序 欢迎点赞关注收藏 【案例介绍】 案例描述 长方形和圆形都属于几何图形&#xff0c;都有周长和面积&#xff0c;并且它们都有自己的周长和面积计算公式。使用抽象类的知识设计一个程序&#xff0c;可以计算不同图形的面积和周长。 运行…

Redis服务器配置

服务器基础配置服务器端设定 设置服务器以守护进程的方式运行daemonize yes|no 绑定主机地址bind 127.0.0.1 设置服务器端口号port 6379 设置数据库数量databases 16日志配置 设置服务器以指定日志记录级别loglevel debug|verbose|notice|warning开发期 debug 线上no…

【蓝桥杯PythonB组备赛】【Acwing周赛】第91场非常详细的过程思路分析理解分享Python解

好难哈哈哈我依旧只做对了第一题&#xff0c;第二题在比赛结束后才做出来…… 不过没关系每天努力一点啦~ 分享一下个人做的解析&#xff0c;供大家参考&#xff0c;一起努力哇&#xff01; 目录 A AcWing 4861. 构造数列 1.题目描述 2.思路分析 3.代码实现 B Ac…

从每刻到金蝶云星空通过接口配置打通数据

对接源平台:每刻刻报销是每刻科技旗下的产品&#xff0c;是国内领先的企业差旅及费用管理云平台&#xff0c;为事前差旅预订&#xff0c;事后报销的全流程费用管控服务。每刻报销融合人工智能云计算、移动互联网大数据等先进技术&#xff0c;融合财务共享和信用管理的理念&…

SPDK应用框架

SPDK应用框架SPDK应用框架1&#xff09;对CPU core和线程的管理2&#xff09;线程间的高效通信3&#xff09;I/O的处理模型及数据路径的无锁化机制SPDK用户态块设备层1.内核通用块层2.SPDK用户态通用块层SPDK架构解析3.通用块层的管理4.逻辑卷1&#xff09;内核LVM2&#xff09…

企业级信息系统开发学习笔记1.2 初探Spring——利用组件注解符精简Spring配置文件

文章目录零、本讲学习目标一、课程引入二、打开项目【SpringDemo2021】三、利用组件注解符精简Spring配置文件1、创建net.hw.spring.lesson02包2、将lesson01子包的四个类拷贝到lesson02子包3、修改杀龙任务类 - SlayDragonQuest4、修改救美任务类 - RescueDamselQuest5、修改勇…

2022爱分析·事务型关系数据库市场厂商评估报告:万里数据库

目录 1. 研究范围定义 2. 事务型关系数据库市场定义 3. 厂商评估&#xff1a;万里数据库 4. 入选证书 1. 研究范围定义 在国内数字化转型以及信创建设持续推进的大背景下&#xff0c;众多厂商入局国内数据库市场&#xff0c;为企业提供了面向多种应用场景的数据库&am…

taobao.trade.memo.update( 修改交易备注 )

&#xffe5;开放平台基础API必须用户授权 需要商家或以上权限才可调用此接口&#xff0c;可重复调用本接口更新交易备注&#xff0c;本接口同时具有添加备注的功能 公共参数 点击获取 请求示例 TaobaoClient client new DefaultTaobaoClient(url, appkey, secret); Trade…

2022年AI顶级论文 —生成模型之年(上)

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 过去十年来&#xff0c;人工智能技术在持续提高和飞速发展&#xff0c;并不断冲击着人类的认知。 2012年&#xff0c;在ImageNet图像识别挑战赛中&#xff0c;一种神经网络模型&#xff08;AlexNet&…

Java程序员进阶宝典,让你学习面试无忧!

心净则明,心诚则灵如果你想要一个月速成程序员&#xff0c;那么这篇文章不适合&#xff0c;如果你仅想要在IT圈“耍酷”&#xff0c;那你也不需要研读&#xff0c;如果你执着询问“退化”成为一名程序猿有啥捷径&#xff0c;那我只能告诉你&#xff0c;此路不通&#xff01;不可…

Flink-处理函数(ProcessFunction、KeyedProcessFunction、ProcessWindowFunctionHe侧输出流)

文章目录处理函数基本处理函数&#xff08;ProcessFunction&#xff09;功能和使用ProcessFunction 解析分类按键分区处理函数&#xff08;KeyedProcessFunction&#xff09;定时器&#xff08;Timer&#xff09;和定时服务&#xff08;TimerService&#xff09;KeyedProcessFu…

Vue的模板语法(双大括号表达式、插值、v-bind 指令、v-on、指令缩写)

模板语法前言知识点1、双大括号表达式2、插值2.1 文本2.2 原始 HTML2.3 特性2.4 javascript 表达式3、指令3.1 参数3.2 动态参数3.3 修饰符4、指令缩写4.1 v-bind4.2 v-on前言 相信模板语法大家多少都有所接触&#xff0c;例如百度模板引擎、ejs 等等。同样 Vue.js 也使用了基于…

基于springboot+vue物流项目

基于springbootvue物流项目 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#x…

内网渗透(四十二)之横向移动篇-WMIC远程执行命令横向移动

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

业内人士告诉你,买流量卡时一定要问的几个问题?

互联网时代&#xff0c;流量当然是至关重要&#xff0c;但是&#xff0c;在网上搜索流量卡时&#xff0c;广告可谓是铺天盖地&#xff0c;五花八门&#xff0c;所以&#xff0c;小编提醒大家&#xff0c;为了选择性价比较高的卡&#xff0c;在购买流量卡时一定要关注几个问题。…

深度学习神经网络基础知识(二)权重衰减、暂退法(Dropout)

专栏&#xff1a;神经网络复现目录 深度学习神经网络基础知识(二) 本文讲述神经网络基础知识&#xff0c;具体细节讲述前向传播&#xff0c;反向传播和计算图&#xff0c;同时讲解神经网络优化方法&#xff1a;权重衰减&#xff0c;Dropout等方法&#xff0c;最后进行Kaggle实…

一次简陋的页面登录练习

看着有点丑&#xff0c;果然我还是不太适合写前端哈<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport&q…