使用 PyTorch+LSTM 进行单变量时间序列预测(附完整源码)
时间序列是指在一段时间内发生的任何可量化的度量或事件。尽管这听起来微不足道,但几乎任何东西都可以被认为是时间序列。一个月里你每小时的平均心率,一年里一只股票的日收盘价,一年里某个城市每周发生的交通事故数。
在任何一段时间段内记录这些信息都被认为是一个时间序列。对于这些例子中的每一个,都有事件发生的频率(每天、每周、每小时等)和事件发生的时间长度(一个月、一年、一天等)。
在本教程中,我们将使用 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。
这里需要注意的是,根据训练模型时选择的参数,你预测的越长(远),模型就越容易表现出它自己的偏差,开始预测平均值。因此,如果没有必要,我们不希望总是预测得太超前,因为这会影响预测的准确性。
这在下面的函数中实现:
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天
总结
我们这个模型表现的还算一般!但是我们通过这个示例完整的介绍了时间序列预测的全部过程,我们可以通过尝试架构和参数的调整使模型变得得更好,预测得更准确。
本文只处理单变量时间序列,其中只有一个值序列。还有一些方法可以使用多个系列来进行预测。这被称为多元时间序列预测,我将在以后的文章中介绍。
相关文章:

使用 PyTorch+LSTM 进行单变量时间序列预测(附完整源码)
时间序列是指在一段时间内发生的任何可量化的度量或事件。尽管这听起来微不足道,但几乎任何东西都可以被认为是时间序列。一个月里你每小时的平均心率,一年里一只股票的日收盘价,一年里某个城市每周发生的交通事故数。 在任何一段时间段内记…...

操作系统(day12)-- 虚拟内存;页面分配策略
虚拟内存管理 虚拟内存的基本概念 传统存储管理方式的特征、缺点 一次性: 作业必须一次性全部装入内存后才能开始运行。驻留性:作业一旦被装入内存,就会一直驻留在内存中,直至作业运行结束。事实上,在一个时间段内&…...
Git commit 提交没有被远端分支合并,撤销本次commit
问题:今天修改代码,误把项目配置文件修改为本地数据库连接,需要撤销本次commit 记录。解决办法:第一步:使用git log 查看所有commit 记录。第二步:使用git show commitID 查看指定commit 文件修改记录。第三…...

Netty核心原理(线程模型、核心API)与入门案例详解
Netty核心原理(线程模型、核心API)与入门案例详解 文章目录Netty核心原理(线程模型、核心API)与入门案例详解Netty 介绍原生 NIO 存在的问题概述线程模型线程模型基本介绍传统阻塞 I/O 服务模型Reactor 模型单 Reactor 单线程Nett…...

【 java 8】Lambda 表达式
📋 个人简介 💖 作者简介:大家好,我是阿牛,全栈领域优质创作者。😜📝 个人主页:馆主阿牛🔥🎉 支持我:点赞👍收藏⭐️留言Ὅ…...

改进YOLO系列 | 谷歌团队 | CondConv:用于高效推理的条件参数化卷积
CondConv:用于高效推理的条件参数化卷积 论文地址:https://arxiv.org/pdf/1904.04971.pdf 代码地址:https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet/condconv 卷积层是现代深度神经网络的基本构建模块之一。其中一个基本假设是,卷积核应该对数…...

SQL高级 --优化
一、SQL查询的解析 关联查询过多索引失效(单值、符合) 二、mysql explain使用简介 1、关于id的说明: 2 、select_type 常见和常用的值有如下几种: 分别用来表示查询的类型,主要是用于区别普通查询、联合查询、子…...

【C++】空间配置器
空间配置器,听起来高大上,那它到底是什么东西呢? 1.什么是空间配置器? 空间配置器是STL源码中实现的一个小灶,用来应对STL容器频繁申请小块内存空间的问题。他算是一个小型的内存池,以提升STL容器在空间申…...

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

通过openssl生成pfx证书
通过centos7上自带的openssl工具来生成。首先创建一个pfxcert目录。然后进入此目录。 1.生成.key文件(内含被加密后的私钥),要求输入一个自定义的密码 [rootlocalhost cert]# openssl genrsa -des3 -out server.key 2048 Generating RSA priv…...
华为OD机试真题Python实现【敏感字段加密】真题+解题思路+代码(20222023)
敏感字段加密 题目 给定一个由多个命令字组成的命令字符串; 字符串长度小于等于127字节,只包含大小写字母,数字,下划线和偶数个双引号命令字之间以一个或多个下划线_进行分割可以通过两个双引号""来标识包含下划线_的命令字或空命令字(仅包含两个双引号的命令字…...

我的 System Verilog 学习记录(1)
引言 技多不压身,准备开始学一些 System Verilog 的东西,充实一下自己,这个专栏的博客就记录学习、找资源的一个过程,希望可以给后来者一些借鉴吧,IC找工作的都加把油! 本文是准备先简单介绍一下环境搭建…...

金三银四,我不允许你们不知道这些软件测试面试题
01、您所熟悉的测试用例设计方法都有哪些?请分别以具体的例子来说明这些方法在测试用例设计工作中的应用。 答:有黑盒和白盒两种测试种类,黑盒有等价类划分法,边界分析法,因果图法和错误猜测法。白盒有逻辑覆盖法&…...
【UnityEditor】Unity将Multiple Sprite分割成多张png小图
如题,代码如下 using UnityEngine; using UnityEditor; using System.IO;public class SplitTexture {[MenuItem("ExtraTools/SplitTexture")]static void DoSplitTexture(){// 获取所选图片Texture2D selectedImg Selection.activeObject as Texture2D…...

独立搭建 handle server
本节主要介绍,如何搭建一个与 GHR隔离的 handle sever,不与外界有任何连通。 下载文件 访问地址下载最新版:http://www.handle.net/download_hnr.html 这里以 9.3.0 版本作为讲解 解压服务端,解压客户端 # 解压 tar -xzvf handle-9.3.0-distribution.tar.gz# 到目录下 …...
记一次KindEditor表格修改无效问题
项目说明 项目是由UmiJS创建的(ReactAnt Design4.2),项目需求是富文本编辑器录入多样内容,可供查看。 通过各方探索以及客户的沟通,选定了KindEditor编辑器,通过iframe嵌入。但仍有很多不符合要求的地方,所以要进行很…...

一种图片展示的完美方案,图片展示,object-fill
通常一般的处理 <style>.img-container {width: 300px;height: 200px;background: #f60;}img {width: 100%;height: 100%;}</style> </head> <body><div class"img-container"><img src"./行道树.png" alt""&g…...

社科院杜兰金融管理硕士——考研初试成绩已出,关于分数“6线”你有了解吗
多地公布了2023考研初试成绩查询时间,部分省份今日就能查询到考研初试成绩,考研学子们此刻的心情应该是很忐忑吧,关于分数的“6线”你都知道有哪些吗?我们跟随社科院杜兰金融管理硕士项目一起去了解一下。1.国家线教育部依据硕士生…...

Talk | 清华大学交叉信息研究院助理教授杜韬:利用计算方法探究流固耦合
本期为TechBeat人工智能社区第474期线上Talk! 北京时间2月15日(周三)20:00,清华大学交叉信息研究院助理教授——杜韬的Talk将准时在TechBeat人工智能社区开播! 他与大家分享的主题是: “利用计算方法探究流固耦合”,届时将介绍流固…...

2023年,智能家居实体门店如何选品?
作者 | 启明 编辑 | 小沐 出品 | 智哪儿 zhinaer.cn2023年,是智能家居实体门店的机会与破局之年,作为智能家居实体门店老板,我们应该具备什么样的增长思维呢?上篇文章智哪儿谈了智能家居增长思维之流量思维 ,这篇文章我…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...

多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...