几个重要的 Python 库
首先,让我们介绍一下我们将使用的主要Python库。
1. PyTorch Lightning
PyTorch Lightning 是一个深度学习框架,专门为专业人工智能研究人员和机器学习工程师设计,他们需要最大的灵活性而不影响可扩展性。
它的核心概念是将学术代码(如模型定义、前向/后向传播、优化器、验证等)与工程代码(如循环、保存机制、TensorBoard日志、训练策略等)分离,从而产生更精简、更易于理解的代码。
主要好处包括:
- 高可重用性 - 它的设计使代码能够在各种项目中重用。
- 易于维护 - 由于其结构化设计,维护代码变得更简单。
- 清晰的逻辑 - 通过抽象样板工程代码,机器学习代码变得更容易识别和理解。
总的来说,PyTorch Lightning是一个非常强大的库,它为组织和管理PyTorch代码提供了一种有效的方法。此外,它还提供了一种结构化的方法来处理常见但复杂的任务,如模型训练、验证和测试。
2. PyTorch Forecasting(预测)
这是一个专门为时间序列预测设计的Python库。由于它是基于PyTorch构建的,您可以利用PyTorch强大的自动差异化和优化库,同时还可以受益于PyTorch Forecasting为时间序列预测提供的便利。
在PyTorch Forecasting中,您可以找到各种预测模型的实现,包括但不限于自回归模型(AR、ARIMA)、状态空间模型(SARIMAX)、神经网络(LSTM、GRU)和集成方法(Prophet、N-Beats)。这意味着您可以在同一框架内试验和比较不同的预测方法,而无需为每种方法编写大量的样板代码。
该库还提供了一系列数据预处理工具,可以帮助您处理时间序列中的常见任务。这些工具包括缺失值插补、缩放、特征提取和滚动窗口转换等。这意味着您可以更加专注于模型的设计和优化,而无需花费大量时间进行数据处理。
它还提供了一个用于评估模型性能的统一接口。它实现了QuantileLoss和SMAPE等时间序列的损失函数和验证指标,并支持早期停止和交叉验证等训练方法。这使您能够更方便地跟踪和增强模型的性能。
如果你正在寻找一种方法来提高时间序列预测项目的效率和可维护性,那么PyTorch Forecasting可能是一个很好的选择。它提供了一种有效而灵活的方法来组织和管理PyTorch代码,使您能够专注于最关键的方面——机器学习模型本身。
该库的详细用法可在其官方文件中找到:https://pytorch-forecasting.readthedocs.io/en/stable。
3. 关于N-HiTS模型
N-HiTS模型通过引入创新的分层插值和多速率数据采样技术,解决了长期预测中的预测波动性和计算复杂性问题。这允许N-HiTS模型有效地近似任何长度的预测范围。
此外,在大规模数据集上进行的大量实验表明,与最新的Transformer架构相比,N-HiTS模型平均提高了近20%的精度,同时还将计算时间减少了一个数量级(50倍)。
论文链接:https://doi.org/10.48550/arXiv.2201.12886。
初始化
首先,我们需要导入所需的库。这些库包括MetaTrader5(用于与MT5终端交互)、PyTorch Lightning(用于训练模型)以及其他一些用于数据处理和可视化的库。
import MetaTrader5 as mt5
import lightning.pytorch as pl
from lightning.pytorch.callbacks import EarlyStopping
import matplotlib.pyplot as plt
import pandas as pd
from pytorch_forecasting import Baseline, NHiTS, TimeSeriesDataSet
from pytorch_forecasting.data import NaNLabelEncoder
from pytorch_forecasting.metrics import MAE, SMAPE, MQF2DistributionLoss, QuantileLoss
from lightning.pytorch.tuner import Tuner
接下来,我们需要初始化MetaTrader5。这是通过调用mt.initialize()函数来完成的。如果你不能简单地使用它来初始化它,你需要将MT5终端的路径作为参数传递给这个函数(在示例中“D:\Project\mt\MT5\terminal64.exe”是我的个人路径位置,在实际应用中你需要将它配置到你自己的路径位置)。如果初始化成功,函数将返回True,否则返回False。
if not mt.initialize("D:\\Project\\mt\\MT5\\terminal64.exe"):
print('initialize() failed!')
else:
print(mt.version())
mt.symbols_total()函数用于获取MT5终端中可用的可交易品种的总数。我们可以用它来判断我们是否能够正确地获得数据。如果总数大于0,我们可以使用mt.copy_rates_from_pos()函数来获取指定交易品种的历史数据。在本例中,我们获得了“GOLD_micro”品种的“mt_data_len”M15(15分钟)周期数据的最新长度。
sb=mt.symbols_total()
rts=None
if sb > 0:
rts=mt.copy_rates_from_pos("GOLD_micro",mt.TIMEFRAME_M15,0,mt_data_len)
mt.shutdown()
最后,我们使用mt.shutdown()函数关闭与MT5终端的连接,并将获得的数据转换为Pandas DataFrame格式。
mt.shutdown()
rts_fm=pd.DataFrame(rts)
现在,让我们讨论如何对从MT5终端获得的数据进行预处理。
我们需要先将时间戳转换为日期:
rts_fm['time']=pd.to_datetime(rts_fm['time'], unit='s')
在这里,我们不再描述如何标记数据。你可以在我之前的两篇文章中找到方法(本文简介中有文章链接)。为了简明地演示如何使用预测模型,我们只需将每个“max_encoder_length+2max_prediction_length”数据片段划分为一组。每组有一个从0到“max_encoder_length+2max_prediction_length-1”的序列,并填充它们。通过这种方式,我们将所需的标签添加到原始数据中。首先,我们需要将原始时间索引(即DataFrame的索引)转换为时间索引。计算原始时间索引的余数除以(max_encoder_length+2max_prediction_length),并将结果用作新的时间索引。这将时间索引映射到从0到“max_encoder_length+2*max_prediction_length-1”的范围内:
rts_fm['time_idx']= rts_fm.index%(max_encoder_length+2*max_prediction_length)
我们还需要将原始时间索引转换为一个组。计算原始时间指数除以“max_encoder_length+2*max_prediction_length”,并将结果用作新组:
rts_fm['series']=rts_fm.index//(max_encoder_length+2*max_prediction_length)
我们将数据预处理部分封装到一个函数中。我们只需要将我们需要获得的数据长度传递给它,它就可以完成数据预处理工作:
def get_data(mt_data_len:int):
if not mt.initialize("D:\\Project\\mt\\MT5\\terminal64.exe"):
print('initialize() failed!')
else:
print(mt.version())
sb=mt.symbols_total()
rts=None
if sb > 0:
rts=mt.copy_rates_from_pos("GOLD_micro",mt.TIMEFRAME_M15,0,mt_data_len)
mt.shutdown()
# print(len(rts))
rts_fm=pd.DataFrame(rts)
rts_fm['time']=pd.to_datetime(rts_fm['time'], unit='s')
rts_fm['time_idx']= rts_fm.index%(max_encoder_length+2*max_prediction_length)
rts_fm['series']=rts_fm.index//(max_encoder_length+2*max_prediction_length)
return rts_fm
重写 pytorch_forecasting.TimeSeriesDataSet 类
重写pytorch_foredicting中的to_datalader()函数。这允许您控制是否对数据进行混洗以及是否丢弃最后一组批处理(主要是为了防止最后一组数据长度不足导致的不可预测错误)。以下是您的操作方法:
class New_TmSrDt(TimeSeriesDataSet):
def to_dataloader(self, train: bool = True,
batch_size: int = 64,
batch_sampler: Sampler | str = None,
shuffle:bool=False,
drop_last:bool=False,
**kwargs) -> DataLoader:
default_kwargs = dict(
shuffle=shuffle,
drop_last=drop_last, #modification
collate_fn=self._collate_fn,
batch_size=batch_size,
batch_sampler=batch_sampler,
)
default_kwargs.update(kwargs)
kwargs = default_kwargs
if kwargs["batch_sampler"] is not None:
sampler = kwargs["batch_sampler"]
if isinstance(sampler, str):
if sampler == "synchronized":
kwargs["batch_sampler"] = TimeSynchronizedBatchSampler(
SequentialSampler(self),
batch_size=kwargs["batch_size"],
shuffle=kwargs["shuffle"],
drop_last=kwargs["drop_last"],
)
else:
raise ValueError(f"batch_sampler {sampler} unknown - see docstring for valid batch_sampler")
del kwargs["batch_size"]
del kwargs["shuffle"]
del kwargs["drop_last"]
return DataLoader(self,**kwargs)
此代码创建从TimeSeriesDataSet继承的新类new_TmSrDt。to_datalader()函数随后在这个新类中被重写,以包括shuffle和drop_last参数。这样,您就可以更好地控制数据加载过程。请记住在代码中使用New_TmSrDt替换TimeSeriesDataSet的实例。