Using AMD GPUs for Enhanced Time Series Forecasting with Transformers — ROCm Blogs
时间序列预测(TSF)是信号处理、数据科学和机器学习(ML)等领域的关键概念。TSF 通过分析系统的过去时间模式来预测其未来行为,利用历史数据预测未来数据点。经典的 TSF 方法依赖于各种统计方法。最近,机器学习技术越来越多地用于 TSF,引发了社区关于这些现代方法是否优于经典统计方法的讨论(参见:Are Transformers Effective for Time Series Forecasting? 和 Yes, Transformers are Effective for Time Series Forecasting (+ Autoformer))。
本文将不讨论传统统计方法或基于机器学习的模型哪个更适合预测。相反,我们将提供一个关于实现其中一种关键机器学习方法—Transformers 的实用指南。在本文中,我们将实现一个完整的基于 Transformers 的 TSF 工作流程,从数据预处理到模型训练和评估。
简介
Transformer 架构在时间序列预测中的应用已成为替代传统统计模型(如自回归积分滑动平均(ARIMA)或指数平滑(ETS))的一个重要选择。Transformers 能够捕捉复杂的时间依赖关系,并且能够处理不同类型的输入数据,如文本、音频、图像和时间序列数据。
什么是基于 Transformer 的模型?
基于 Transformer 的模型是一种神经网络架构,设计用于处理序列数据和捕捉长程依赖性。Transformers 最早在论文Attention is All You Need中被提出。虽然最初是为自然语言处理(NLP)任务处理文本数据而设计,Transformers 已被改编用于图像分类、语音识别和预测等应用。
对于时间序列数据和预测,时间序列 Transformer、Autoformer 和 Informer 是一些使用 Transformer 架构来提高预测任务准确性和效率的模型。
时间序列 Transformer 专为分析时间序列数据设计。它通过结合修改后的机制来更好地捕捉数据中的长程依赖性,从而适应原始的 Transformer 架构。
Autoformer 通过集成分解架构和自动相关机制扩展了原始的 Transformer 架构。分解和自动相关机制有助于更好地捕捉时间和周期模式。
Informer 专为长序列时间序列预测设计,它解决了原始 Transformer 的高计算复杂性和内存使用问题。它通过使用概率稀疏自注意力机制和自注意力蒸馏机制,允许更低的时间复杂度,并通过逐步减少输入序列保留主要特征。
在本文中,我们将探索这三种模型在使用 AMD GPUs 进行时间序列预测方面的能力。我们还将使用 traffic_hourly 数据集提供一个实用应用,数据集是 monash_tsf 时间序列数据存储库的一部分。
你可以在这个GitHub 文件夹中找到与本文相关的文件。
要求
• AMD GPU:请参阅ROCm 文档页面 了解支持的硬件和操作系统。
• ROCm 6.1:请参阅ROCm Linux 安装说明 了解安装说明。
• Docker:请参阅在 Ubuntu 上安装 Docker Engine 了解安装说明。
• PyTorch 2.0.1:使用官方 ROCm Docker 镜像rocm/pytorch:rocm6.0_ubuntu22.04_py3.9_pytorch_2.0.1。
跟随本文
本文使用 GluonTS Python 库进行数据管理和转换。更多信息请参阅 [GluonTS - 使用Python进行概率时间序列建模](GluonTS documentation)。
• 克隆仓库并进入博文目录:
git clone git@github.com:ROCm/rocm-blogs.git
cd rocm-blogs/blogs/artificial-intelligence/timeseries_transformers
• 构建并启动容器。关于构建过程的详细信息,请参阅 timeseries_transformers/docker/Dockerfile
文件。
cd docker
docker compose build
docker compose up
• 在浏览器中打开 [http://localhost:8888/lab/tree/src/time_series_transformers.ipynb](http://localhost:8888/lab/tree/src/time_series_transformers.ipynb)并打开 time_series_transformers.ipynb
笔记本。
您可以使用 time_series_transformers.ipynb
笔记本跟随本文操作。
数据集
Monash 时间序列预测 仓库提供了一个广泛的数据集集合,专门用于开发和评估预测模型。在这些数据集中,traffic_hourly 数据集提供了详细的每小时交通流量数据,捕捉道路和网络上的交通流量。通过分析这个数据集,研究人员和实践者可以设计策略以优化交通控制系统并提升城市交通流动性。
我们将访问Hugging Face上提供的这个数据集版本。数据集由包含训练、测试和验证部分的DatasetDict对象组成。每个部分包含862个每小时采样的独立交通数据时间序列。
让我们来可视化部分时间序列数据:
在示例中,我们注意到验证集包含与训练集相同的数据,但在时间上延伸了更长的预测长度(48个点)。这种配置使我们能够将模型的预测与实际结果进行对比评估。类似地,测试集也覆盖了比验证集更长的48个点的预测长度。
接下来我们使用 traffic_hourly
数据集训练前面提到的各个模型。
时间序列Transformer模型
时间序列Transformer是一种深度学习模型,它利用Transformer架构的自注意机制处理序列数据,用于预测任务。传统模型如ARIMA或LSTM在处理长期依赖关系和并行化方面面临挑战。Transformers更有效地捕捉长期依赖关系,并支持并行处理,使其成为时间序列预测的良好选择。
时间序列Transformer适应标准的transformer架构来处理时序数据。它们进行了一些修改,以适合时间序列数据:
- 上下文窗口和预测窗口:时间序列Transformer使用上下文窗口(历史数据)和预测窗口(未来数据)来管理内存和计算需求,而不是处理整个序列。
- 因果掩码:确保模型仅使用过去的数据点来预测未来值。
- 包括时间特征:添加额外的时间特征,如星期几、月份或时间序列的年龄(从第一个数据点到最后一个数据点的顺序时间戳),帮助模型学习季节性模式。
- 处理缺失值:使用注意力掩码管理缺失值,使模型能够忽略数据间隙而无需补插值。
时间序列Transformer模型适合时间序列数据,因为它使用上下文窗口,因果掩码和其他时间特征,以及处理缺失值的机制。这些特性使其能够有效地捕捉时间依赖关系和模式。相比之下,为自然语言处理任务设计的transformer处理整个序列,主要关注标记之间的关系。有关时间序列Transformer模型的更多信息,请参见 [Hugging Face Transformers 说明文档](https://huggingface.co/docs/transformers/v4.41.3/en/model_doc/time_series_transformer#time-series-transformer)。
让我们使用 hourly_traffic
数据集和 Hugging Face 的 TimeSeriesTransformerConfig
类来设置用于预测下一个48小时值(`prediction_length`)的时间序列Transformer模型的参数。
在下面的代码中,我们将 prediction_length
设置为48。这个值来自训练集和验证集长度之间的差异。就超参数值而言,我们将 context_length
设置为 prediction_length
的5倍,编码器和解码器的层数设置为4,每个transformer层输入和输出的特征向量大小 d_layer
设置为32。
换句话说,我们希望基于过去10天(5倍的 prediction_length
)的每小时交通数据预测未来2天(`prediction_length`)的每小时交通量。
prediction_length = 48
freq = "H" # data is sampled in an hourly frequency
lags_sequence = get_lags_for_frequency(freq_str = freq) # Default lags provided by GluonTS for the given frequency
time_features = time_features_from_frequency_str(freq) # Default additional time features provided by GluonTS
config = TimeSeriesTransformerConfig(
prediction_length = prediction_length,
context_length = prediction_length * 5,
# Lags provided by GluonTS
lags_sequence=lags_sequence,
# Add time features and "series age"
num_time_features=len(time_features) + 1,
# Length of train dataset
cardinality=[len(train_dataset)],
# Learn and embedding of size 5
embedding_dimension=[5],
# Transformer parameters:
encoder_layers=4,
decoder_layers=4,
d_model=32,
)
# Instantiate the model
model = TimeSeriesTransformerForPrediction(config)
接下来,我们使用给定参数值实例化相应的数据加载器:
train_dataloader = create_train_dataloader(
config = config,
freq=freq,
data=train_dataset,
batch_size=256,
num_batches_per_epoch=100,
)
test_dataloader = create_backtest_dataloader(
config=config,
freq=freq,
data=test_dataset,
batch_size=64,
)
train_dataloader
和 test_dataloader
是GluonTS的可迭代对象。迭代器的每个元素都是一个包含以下键的字典:
# Print the keys of an instance of the train_dataloader
example = next(iter(train_dataloader))
example.keys()
dict_keys(['past_time_features', 'past_values', 'past_observed_mask', 'future_time_features', 'future_values', 'future_observed_mask'])
我们将模型训练20个周期,然后测量总训练时间。让我们开始训练过程:
# Train the model
from accelerate import Accelerator
from torch.optim import AdamW
from tqdm import tqdm
accelerator = Accelerator()
device = accelerator.device
model.to(device)
optimizer = AdamW(model.parameters(), lr=1e-4, betas=(0.9, 0.95), weight_decay=1e-1,)
model, optimizer, train_dataloader = accelerator.prepare(
model,
optimizer,
train_dataloader,
)
model.train()
start_time = time.time()
for epoch in (range(20)):
for idx, batch in enumerate(train_dataloader):
optimizer.zero_grad()
outputs = model(
past_time_features=batch["past_time_features"].to(device),
past_values=batch["past_values"].to(device),
future_time_features=batch["future_time_features"].to(device),
future_values=batch["future_values"].to(device),
past_observed_mask=batch["past_observed_mask"].to(device),
future_observed_mask=batch["future_observed_mask"].to(device),
)
loss = outputs.loss
# Backprop
accelerator.backward(loss)
optimizer.step()
if idx % 100 == 0:
print(f'Epoch: {epoch}', f'Loss: {loss.item()}')
print(f'Total training time: {time.time() - start_time}')
训练后你会看到类似于以下的输出:
...
Epoch: 15 Loss: -3.1552791595458984
Epoch: 16 Loss: -3.1698923110961914
Epoch: 17 Loss: -3.1928699016571045
Epoch: 18 Loss: -3.072526216506958
Epoch: 19 Loss: -3.2241008281707764
Total training time: 156.99232363700867
在我们的设置中,总训练时间约为157秒。我们还观察到,虽然训练损失随时间减少,但它也是负的。这种行为是预期的,因为我们使用的是负对数似然损失函数,随着训练的进行,该函数倾向于收敛于负无穷。
最后,让我们进行推理并绘制生成的时间序列,以可视化我们的模型的表现:
model.eval()
forecasts = []
for batch in test_dataloader:
outputs = model.generate(
past_time_features=batch["past_time_features"].to(device),
past_values=batch["past_values"].to(device),
future_time_features=batch["future_time_features"].to(device),
past_observed_mask=batch["past_observed_mask"].to(device),
)
forecasts.append(outputs.sequences.cpu().numpy())
forecasts = np.vstack(forecasts)
plot_ts(test_dataset, forecasts, 'Time Series Transformer')
该模型有效地捕捉了训练数据中的季节性和趋势,并能很好地预测时间序列的下48个时间步。
Autoformer(带有自动相关性的分解变压器用于长期序列预测)
Autoformer 是一种设计用于时间序列预测的模型。它的开发目的是解决原始 Transformer 模型在处理时间数据时的局限性。Autoformer 使用分解模块提取时间序列数据的趋势和季节性成分。Autoformer 引入了分解方法,使其能够更好地捕捉数据中的模式,从而在处理时间序列数据的长期依赖性和季节性方面比 Time Series Transformer 更好。
Autoformer 和用于自然语言处理 (NLP) 任务的标准 Transformer 之间的主要区别包括:
- 分解模块:Autoformer 纳入了分解模块,可以将时间序列数据中的趋势和季节性成分分离出来。
- 自动相关机制:代替标准 Transformer 架构中使用的自注意力机制,Autoformer 使用自动相关机制以更高效地捕捉时间依赖性,从而减少计算开销。
- 降低复杂性:Autoformer 架构经过优化,专门处理时间序列数据的特性。特别是,Autoformer 使用了适合数据周期性的简化位置编码,从而降低了复杂性。
更多关于 Autoformer 模型的信息,请参见:Autoformer: Decomposition Transformers with Auto-Correlation for Long-Term Series Forecasting
与 Time Series Transformer 模型一样,我们使用相同的 prediction_length
(预测长度)、`context_length`(上下文长度)以及其余参数值。我们可以如下实例化 AutoformerConfig
类:
from transformers import AutoformerConfig, AutoformerForPrediction
from gluonts.time_feature import time_features_from_frequency_str
from gluonts.time_feature import get_lags_for_frequency
prediction_length = 48
freq = "H"
lags_sequence = get_lags_for_frequency(freq_str = freq)
time_features = time_features_from_frequency_str(freq)
config = AutoformerConfig(
prediction_length=prediction_length,
context_length=prediction_length * 5,
lags_sequence=lags_sequence,
num_time_features=len(time_features) + 1,
cardinality=[len(train_dataset)],
embedding_dimension=[5],
# transformer params:
encoder_layers=4,
decoder_layers=4,
d_model=32,
)
model = AutoformerForPrediction(config)
在实例化相应的数据加载器后,我们继续训练模型,如下所示:
# Train the model
from accelerate import Accelerator
from torch.optim import AdamW
from tqdm import tqdm
accelerator = Accelerator()
device = accelerator.device
model.to(device)
optimizer = AdamW(model.parameters(), lr=1e-4, betas=(0.9, 0.95), weight_decay=1e-1,)
model, optimizer, train_dataloader = accelerator.prepare(
model,
optimizer,
train_dataloader,
)
model.train()
start_time = time.time()
for epoch in (range(20)):
for idx, batch in enumerate(train_dataloader):
optimizer.zero_grad()
outputs = model(
past_time_features=batch["past_time_features"].to(device),
past_values=batch["past_values"].to(device),
future_time_features=batch["future_time_features"].to(device),
future_values=batch["future_values"].to(device),
past_observed_mask=batch["past_observed_mask"].to(device),
future_observed_mask=batch["future_observed_mask"].to(device),
)
loss = outputs.loss
# Backprop
accelerator.backward(loss)
optimizer.step()
if idx % 100 == 0:
print(f'Epoch: {epoch}', f'Loss: {loss.item()}')
print(f'Total training time: {time.time() - start_time}')
输出将类似于以下的结果:
...
Epoch: 15 Loss: -2.4633355140686035
Epoch: 16 Loss: -2.78301739692688
Epoch: 17 Loss: -2.3952136039733887
Epoch: 18 Loss: -2.4368600845336914
Epoch: 19 Loss: -2.4414479732513428
Total training time: 271.12153911590576
我们注意到,Autoformer 模型的训练时间比 Time Series Transformer 模型更长。由于 Autoformer 使用分解模块来分离趋势和季节性成分,我们预计会有额外的计算开销,目的是捕捉数据中更复杂的模式。我们来直观看一下模型的性能:
plot_ts(test_dataset, forecasts, 'Autoformer Model')
在上述图中,我们可以看到 Autoformer 模型与 Time Series Transformer 模型的表现相似,均能捕捉到数据的季节性和趋势。
Informer:用于长序列时间序列预测的高效Transformer
Informer是一个设计用于时间序列预测的模型,能够处理长期依赖性并减少计算复杂性,因此适用于大规模时间序列数据。Informer使用了ProbSparse自注意力机制,通过选择采样出一小部分最有信息量的查询,减少了标准自注意力机制的时间和空间复杂性。这里的“查询”指的是表示序列中当前点的向量,模型试图从整个序列中找到与其相关的信息。这与Transformer架构中的概念相同,查询、键和值用于计算注意力分数。在Informer模型中,这些查询通过ProbSparse自注意力机制进行优化,以适应长序列时间序列预测。
Informer的关键点包括:
- Informer使用ProbSparse自注意力机制,根据注意力矩阵的稀疏性选择一个子集的查询,从而在与用于NLP任务的标准Transformer相比降低了计算复杂性。
- Informer采用了一种叫做“蒸馏”的过程,通过一系列蒸馏层逐渐减少序列长度。每一层选择最有信息的点,总结信息并使序列更短,这有助于聚焦数据中的重要模式和趋势。这与标准Transformer不同,标准Transformer没有机制来减少序列长度,这对于非常长的序列来说效率较低。
关于Informer模型的更多信息,请参见:[Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting](https://arxiv.org/abs/2012.07436)
我们可以像之前一样设置`InformerConfig`类的配置参数:
from transformers import InformerConfig, InformerForPrediction
prediction_length = 48
freq = "H"
lags_sequence = get_lags_for_frequency(freq_str = freq)
time_features = time_features_from_frequency_str(freq)
config = InformerConfig(
prediction_length=prediction_length,
context_length=prediction_length * 5,
lags_sequence=lags_sequence,
num_time_features=len(time_features) + 1,
cardinality=[len(train_dataset)],
embedding_dimension=[5],
# Transformer params:
encoder_layers=4,
decoder_layers=4,
d_model=32,
)
model = InformerForPrediction(config)
在实例化数据加载器之后,我们可以按以下步骤训练模型:
# Train the model
from accelerate import Accelerator
from torch.optim import AdamW
from tqdm import tqdm
accelerator = Accelerator()
device = accelerator.device
model.to(device)
optimizer = AdamW(model.parameters(), lr=1e-4, betas=(0.9, 0.95), weight_decay=1e-1,)
model, optimizer, train_dataloader = accelerator.prepare(
model,
optimizer,
train_dataloader,
)
model.train()
start_time = time.time()
for epoch in (range(20)):
for idx, batch in enumerate(train_dataloader):
optimizer.zero_grad()
outputs = model(
past_time_features=batch["past_time_features"].to(device),
past_values=batch["past_values"].to(device),
future_time_features=batch["future_time_features"].to(device),
future_values=batch["future_values"].to(device),
past_observed_mask=batch["past_observed_mask"].to(device),
future_observed_mask=batch["future_observed_mask"].to(device),
)
loss = outputs.loss
# Backprop
accelerator.backward(loss)
optimizer.step()
if idx % 100 == 0:
print(f'Epoch: {epoch}', f'Loss: {loss.item()}')
print(f'Total training time: {time.time() - start_time}')
输出结果如下:
...
Epoch: 14 Loss: -3.1271555423736572
Epoch: 15 Loss: -3.138277769088745
Epoch: 16 Loss: -3.078387975692749
Epoch: 17 Loss: -3.09626841545105
Epoch: 18 Loss: -3.276211977005005
Epoch: 19 Loss: -3.2201414108276367
Total training time: 183.03989911079407
我们观察到训练时间比*Autoformer*模型更短,但与*Time Series Transformer*模型相似。
如前所述,我们可以看到Informer模型在捕捉数据的季节性和趋势方面,与Autoformer和Time Series Transformer模型表现相似。
总结
在这篇博客中,我们探索了使用基于Transformer的方法进行时间序列预测的过程,使用了AMD GPU。通过利用AMD硬件的强大功能,我们展示了从数据预处理到模型训练和评估的完整工作流程。