Torch 预测模型¶
本文档适用于 darts 版本 0.15.0 及更高版本。
我们假设您已经了解 Darts 中的协变量。如果您是新接触此主题,我们建议您先阅读我们的协变量指南。
本指南内容¶
- 引言部分涵盖了关于 Torch 预测模型 (TFMs) 的最重要内容 
- 输入数据使用部分深入介绍了使用 TFMs 训练和预测时输入数据如何使用 
- 高级功能部分提供了一些 TFMs 高级功能的示例 
- 性能优化部分列出了一些在训练期间加速计算的技巧。 
引言¶
在 Darts 中,广义上讲,Torch 预测模型 (TFMs) 是“基于机器学习”的模型,它们指的是基于 PyTorch 的(深度学习)模型。
TFMs 对输入 target 和 *_covariates 系列(如果支持)的固定长度分块(子样本)进行训练和预测。target 是我们想要预测其未来的序列,*_covariates 是过去和/或未来的协变量。
每个分块包含一个输入分块(代表样本的过去)和一个输出分块(代表样本的未来)。样本的预测点位于输入分块的末尾。这些分块的长度必须在创建模型时通过参数 input_chunk_length 和 output_chunk_length 指定(更多关于分块的内容请参阅下一小节)。
# model that looks 7 time steps back (past) and 1 time step ahead (future)
model = SomeTorchForecastingModel(input_chunk_length=7,
                                  output_chunk_length=1,
                                  **model_kwargs)
所有 TFMs 都可以对单个或多个 target 系列进行训练,并且根据其协变量支持(详见本小节),可以使用 past_covariates 和/或 future_covariates。使用协变量时,必须为每个目标系列提供一个专门的过去和/或未来协变量系列。
此外,您可以在训练期间使用包含专用协变量的验证集。如果协变量具有所需的时间跨度,则训练、验证和预测可以使用相同的协变量。(详见本小节)
# fit the model on a single target series with optional past and / or future covariates
model.fit(target,
          past_covariates=past_covariates,
          future_covariates=future_covariates,
          val_series=target_val,  # optionally, use a validation set
          val_past_covariates=past_covariates_val,
          val_future_covariates=future_covariates_val)
# fit the model on multiple target series
model.fit([target, target2, ...],
          past_covariates=[past_covariates, past_covariates2, ...],
          ...
          )
您可以为任何输入的 target 时间序列或作为时间序列序列给出的多个目标生成预测。这对于训练期间未见的序列也有效,只要每个序列包含至少 input_chunk_length 个时间步长。
# predict the next n=3 time steps for any input series with `series`
prediction = model.predict(n=3,
                           series=target,
                           past_covariates=past_covariates,
                           future_covariates=future_covariates)
如果您想了解更多关于我们的 Torch 预测模型的训练和预测过程以及它们如何与协变量配合使用的信息,请继续阅读。
分块训练和预测概览¶
在图 1 中,您可以看到调用 fit() 或 predict() 时,数据如何分配到每个样本的输入和输出分块。在这个例子中,我们查看日频率数据。输入分块从 target 中提取值,并可选地从属于输入分块时间跨度的 past_covariates 和/或 future_covariates 中提取值。这些 future_covariates 的“过去”值被称为“历史未来协变量”。
输出分块只接收属于输出分块时间跨度的可选 future_covariates 值。我们的 past_covariates 的未来值——“未来过去协变量”——仅用于为即将到来的样本的输入分块提供新数据。
所有这些信息都用于预测“未来目标”——“过去目标”结束后的未来 output_chunk_length 个点。
 
图 1:使用 Torch 预测模型进行分块训练/预测概览
当调用 predict() 时,根据您的预测范围 n,模型可以一次性预测(如果 n <= output_chunk_length),或者通过在未来多个分块上进行自回归预测(如果 n > output_chunk_length)。这就是为什么使用 past_covariates 进行预测时,您可能需要提供额外的“past_covariates 的未来值”。
Torch 预测模型协变量支持¶
在底层,Darts 实现了 5 种 {X}CovariatesModel 类,以涵盖前面提到的不同协变量类型组合
| 类 | 过去协变量 | 未来过去协变量 | 未来协变量 | 历史未来协变量 | 
|---|---|---|---|---|
| 
 | ✅ | ✅ | ||
| 
 | ✅ | |||
| 
 | ✅ | ✅ | ||
| 
 | ✅ | ✅ | ✅ | |
| 
 | ✅ | ✅ | ✅ | ✅ | 
表 1:Darts 的“{X}CovariatesModels”协变量支持
每个 Torch 预测模型都继承自一个 {X}CovariatesModel(协变量类名由 X 部分缩写)
| TFM | 
 | 
 | 
 | 
 | 
 | 
|---|---|---|---|---|---|
| 
 | ✅ | ||||
| 
 | ✅ | ||||
| 
 | ✅ | ||||
| 
 | ✅ | ||||
| 
 | ✅ | ||||
| 
 | ✅ | ||||
| 
 | ✅ | ||||
| 
 | ✅ | ||||
| 
 | ✅ | ||||
| 
 | ✅ | 
表 2:Darts 的 Torch 预测模型协变量支持
训练、验证和预测所需目标时间跨度¶
模型会根据序列的时间轴自动提取相关数据。如果协变量系列满足以下要求,您可以在 fit() 和 predict() 中使用相同的协变量系列。
训练只有当您可以从传递给 fit() 的数据中提取至少一个包含输入和输出分块的样本时才能进行。这适用于训练数据和验证数据。在最低所需时间跨度方面,这意味着
- 最小长度为 - input_chunk_length + output_chunk_length的- target系列
- 来自协变量指南 2.3 节中 - fit()的- *_covariates时间跨度要求。
对于预测,您必须提供您希望预测的 target 系列。对于任何预测范围 n,最低时间跨度要求是
- 最小长度为 - input_chunk_length的- target系列
- 来自协变量指南 2.3 节中 - predict()的- *_covariates时间跨度要求。
旁注:我们的 *RNNModels 在创建模型时接受 training_length 参数,而不是 output_chunk_length。在内部,这些模型的 output_chunk_length 会自动设置为 1。对于训练,过去的 target 必须具有最小长度 training_length + 1,对于预测,长度必须为 input_chunk_length。
深入了解使用 TFMs 训练和预测时输入数据如何使用¶
训练¶
让我们看看模型在底层是如何工作的。
假设我们经营一家冰淇淋店,并且想预测下一天的销售额。我们有一年(365天)的日末冰淇淋销售数据和日平均环境温度测量数据。我们还注意到我们的冰淇淋销售额取决于星期几,所以我们想将此信息包含在模型中。
- 过去目标:实际过去的冰淇淋销售额 - ice_cream_sales
- 未来目标:预测下一天的冰淇淋销售额 
- 过去协变量:过去测得的日平均温度 - temperature
- 未来协变量:过去和未来的星期几 - weekday
查看表 1,适合这类协变量的模型将是 SplitCovariatesModel(如果我们不使用未来协变量的历史值),或者 MixedCovariatesModel(如果我们使用)。我们选择 MixedCovariatesModel - 即 TFTModel。
想象一下,我们在过去的冰淇淋销售额中看到了一个周复一周重复的模式。所以我们将 input_chunk_length = 7 天,让模型回顾过去整整一周。可以将 output_chunk_length 设置为 1 天来预测下一天。
现在我们可以创建一个模型并进行训练!图 2 向您展示了 TFTModel 将如何使用我们的数据。
from darts.models import TFTModel
model = TFTModel(input_chunk_length=7, output_chunk_length=1)
model.fit(series=ice_cream_sales,
          past_covariates=temperature,
          future_covariates=weekday)
 
图 2:冰淇淋销售示例中的单个序列概览;Mon1 - Sun1 代表训练数据集中的前 7 天(一年中的第 1 周)。Mon2 是第 2 周的星期一。
调用 fit() 时,模型将构建一个合适的 darts.utils.data.TrainingDataset,它指定如何对数据进行切片以获得训练样本。如果您想自己控制切片,可以实例化自己的 TrainingDataset 并调用 model.fit_from_dataset(),而不是 fit()。默认情况下,大多数模型(并非全部)将构建顺序数据集,这基本上意味着所提供序列中长度为 input_chunk_length + output_chunk_length 的所有子切片都将用于训练。
因此在训练期间,Torch 模型将按顺序遍历训练数据(参见图 3)。模型利用输入分块和输出分块的信息,预测输出分块上的未来目标。训练损失在预测的未来目标和输出分块上的实际目标值之间进行评估。模型通过最小化所有序列上的损失来训练自身。
 
图 3:单个序列中的预测和损失评估
完成第一个序列的计算后,模型会移至下一个序列并执行相同的训练步骤。每个序列的起始点从顺序数据集中随机选择。图 4 显示了如果纯粹巧合,第二个序列在第一个序列后一个时间步长(天)开始时的情况。
这个序列到序列的过程重复进行,直到覆盖所有 365 天。
旁注:“长”target 系列可能会导致非常大量的训练序列/样本。您可以通过 fit() 参数 max_samples_per_ts 为模型应该训练的每个 target 的序列/样本数量设置上限。这将为每个 target 系列选取最近的序列(最接近 target 结尾的序列)。
# fit only on the 10 "most recent" sequences
model.fit(target, max_samples_per_ts=10)
 
图 4:序列到序列:移至下一序列并重复训练步骤
使用验证数据集进行训练¶
您还可以使用验证数据集来训练您的模型
# create train and validation sets
ice_cream_sales_train, ice_cream_sales_val = ice_cream_sales.split_after(training_cutoff)
# train with validation set
model.fit(series=ice_cream_sales_train,
          past_covariates=temperature,
          future_covariates=weekday,
          val_series=ice_cream_sales_val,
          val_past_covariates=temperature,
          val_future_covariates=weekday)
如果您拆分数据,则必须定义一个 training_cutoff(用于拆分数据集的日期或比例),以便训练集和验证集都满足本小节中的最小长度要求
除了按时间拆分外,您还可以使用时间序列的另一个子集作为验证集。
模型的训练方式与之前相同,但额外会在验证数据集上评估损失。如果您想跟踪在验证集上表现最佳的模型,则必须启用检查点保存,如下所示。
预测¶
训练好模型后,我们想预测在我们 365 天训练数据之后的任意天数的未来冰淇淋销售额。
实际的预测与我们按序列训练数据的方式非常相似。根据我们想要预测的天数——预测范围 n——我们区分两种情况
- 如果 - n <= output_chunk_length:我们可以一次性预测- n(使用一次“内部模型调用”)- 在我们的例子中:预测下一天的冰淇淋销售额 ( - n = 1)
 
- 如果 - n > output_chunk_length:我们必须通过多次调用内部模型来预测- n。每次调用输出- output_chunk_length个预测点。我们进行所需次数的调用,直到以自回归方式获得最终的- n个预测点。- 在我们的例子中:一次性预测未来 3 天的冰淇淋销售额 ( - n = 3)
 - 为此,我们需要为 365 天训练数据结束后的未来 - n - output_chunk_length = 2个时间步长(天)提供额外的- past_covariates。不幸的是,我们没有未来- temperature的测量值。但假设我们可以获取未来 2 天的温度预报。我们可以将它们附加到- temperature中,预测就可以工作了!- temperature = temperature.concatenate(temperature_forecast, axis=0) 
prediction = model.predict(n=n,
                           series=ice_cream_sales_train,
                           past_covariates=temperature,
                           future_covariates=weekday)
 
**图 5:使用单个序列进行预测,当 n <= output_chunk_length 时**
 
**图 6:自回归预测,当 n > output_chunk_length 时**
高级功能¶
保存和加载模型状态¶
❗ 警告 ❗ 在 Darts 开发的当前阶段,我们尚未(还未)保证向后兼容性,因此可能并非总是能够加载由旧版本库保存的模型。
对于在 Darts 版本 <= 0.22.0 的 GPU 上训练,并且需要在 Darts 版本 >= 0.23.0 的 CPU 上加载的模型,请参考此问题中提供的代码片段。
自动检查点¶
训练期间的自动检查点功能允许您
- 跟踪最近 5 个周期的模型状态,以及基于验证集损失表现最佳的周期 
- 从检查点加载模型以在训练中断的情况下恢复训练 
- 从检查点加载模型用于推理/预测 
您可以在创建模型时激活检查点功能
model = SomeTorchForecastingModel(..., model_name='my_model', save_checkpoints=True)
# checkpoints are saved automatically
model.fit(...)
# load the model state that performed best on validation set
best_model = model.load_from_checkpoint(model_name='my_model', best=True)
手动保存 / 加载¶
您还可以手动保存模型当前状态并加载它
model.save("/your/path/to/save/model.pt")
loaded_model = model.load("/your/path/to/save/model.pt")
在 GPU 上训练/保存并在 CPU 上加载¶
您可以将一个在 GPU 上训练和保存的模型加载到 CPU(参见详细文档)
# define a model using gpu as accelerator
model = SomeTorchForecastingModel(...,
                                  model_name='my_model',
                                  save_checkpoints=True,
                                  pl_trainer_kwargs={
                                                     "accelerator":"gpu",
                                                     "devices": -1,
                                                     })
# train the model, automatic checkpoints will be created
model.fit(...)
# specify the device to which the model should be loaded
loaded_model = SomeTorchForecastingModel.load_from_checkpoint(model_name='my_model',
                                                              best=True,
                                                              map_location="cpu")
loaded_model.to_cpu()
# run inference
loaded_model.predict(...)
手动保存也可以加载到 CPU
model.save("/your/path/to/save/model.pt")
loaded_model = model.load("/your/path/to/save/model.pt", map_location="cpu")
loaded_model.to_cpu()
重新训练或微调预训练模型¶
要使用不同的优化器和/或学习率调度器重新训练或微调模型,可以将自动检查点中的权重加载到新模型中
# model with identical architecture but different optimizer (default: torch.optim.Adam)
model_finetune = SomeTorchForecastingModel(...,  # use identical parameters & values as in original model
                                           optimizer_cls=torch.optim.SGD,
                                           optimizer_kwargs={"lr": 0.001})
# load the weights from a checkpoint
model_finetune.load_weights_from_checkpoint(model_name='my_model', best=True)
model_finetune.fit(...)
手动保存和学习率调度器也类似
# model with identical architecture but different lr scheduler (default: None)
model_finetune = SomeTorchForecastingModel(...,  # use identical parameters & values as in original model
                                           lr_scheduler_cls=torch.optim.lr_scheduler.ExponentialLR,
                                           lr_scheduler_kwargs={"gamma": 0.09})
# load the weights from a manual save
model_finetune.load_weights("/your/path/to/save/model.pt")
将模型导出为 ONNX 格式用于推理¶
还可以将模型权重导出为 ONNX 格式以在轻量级环境中运行推理。以下示例适用于除 RNNModel 之外的任何 TorchForecastingModel,并可选择使用过去、未来和/或静态协变量。请注意,所有系列和协变量相对于目标 series 的末尾,必须向过去(input_chunk_length)和未来(output_chunk_length)延伸足够远。如果不实现自回归逻辑,将无法预测范围 n > output_chunk_length。
model = SomeTorchForecastingModel(...)
model.fit(...)
# make sure to have `onnx` and `onnxruntime` installed
onnx_filename = "example_onnx.onnx"
model.to_onnx(onnx_filename, export_params=True)
现在,加载模型并预测系列结束后的步骤
from typing import Optional
import onnx
import onnxruntime as ort
import numpy as np
from darts import TimeSeries
from darts.utils.onnx_utils.py import prepare_onnx_inputs
onnx_model = onnx.load(onnx_filename)
onnx.checker.check_model(onnx_model)
ort_session = ort.InferenceSession(onnx_filename)
# use helper function to extract the features from the series
past_feats, future_feats, static_feats = prepare_onnx_inputs(
    model=model,
    series=series,
    past_covariates=ts_past,
    future_covariates=ts_future,
)
# extract only the features expected by the model
ort_inputs = {}
for name, arr in zip(['x_past', 'x_future', 'x_static'], [past_feats, future_feats, static_feats]):
    if name in [inp.name for inp in list(ort_session.get_inputs())]:
        ort_inputs[name] = arr
# output has shape (batch, output_chunk_length, n components, 1 or n likelihood params)
ort_out = ort_session.run(None, ort_inputs)
回调¶
回调是一种强大的方式,可以在训练过程中监控或控制模型的行为。一些示例包括
- 性能监控:计算额外的指标(除了默认损失之外) 
- 提前停止:一旦模型收敛就停止训练 
- ... 
使用回调,您可以在预定义的点/挂钩处向现有过程添加自定义代码。一旦过程执行到达相应的挂钩,代码就会被触发。一些示例挂钩包括
- 训练开始/结束 
- 训练/验证步骤的开始/结束 
- ... 
一些有用的预定义 PyTorch Lightning 回调可以在这里找到。
提前停止示例¶
提前停止是一种避免过拟合和减少训练时间的有效方法。一旦验证损失在几个周期内没有显著改善,它将退出训练过程。
您可以将提前停止与任何 TorchForecastingModel 一起使用,利用 PyTorch Lightning 的 EarlyStopping 回调
import pandas as pd
from pytorch_lightning.callbacks import EarlyStopping
from torchmetrics import MeanAbsolutePercentageError
from darts.dataprocessing.transformers import Scaler
from darts.datasets import AirPassengersDataset
from darts.models import NBEATSModel
# read data
series = AirPassengersDataset().load()
# create training and validation sets:
train, val = series.split_after(pd.Timestamp(year=1957, month=12, day=1))
# normalize the time series
transformer = Scaler()
train = transformer.fit_transform(train)
val = transformer.transform(val)
# any TorchMetric or val_loss can be used as the monitor
torch_metrics = MeanAbsolutePercentageError()
# early stop callback
my_stopper = EarlyStopping(
    monitor="val_MeanAbsolutePercentageError",  # "val_loss",
    patience=5,
    min_delta=0.05,
    mode='min',
)
pl_trainer_kwargs = {"callbacks": [my_stopper]}
# create the model
model = NBEATSModel(
    input_chunk_length=24,
    output_chunk_length=12,
    n_epochs=500,
    torch_metrics=torch_metrics,
    pl_trainer_kwargs=pl_trainer_kwargs)
# use validation set for early stopping
model.fit(
    series=train,
    val_series=val,
)
要在超参数优化的上下文中使用提前停止和剪枝,请查看本指南。
用于存储损失的自定义回调示例¶
训练和验证损失可以通过 tensorboard 自动记录。激活后,Darts 默认会将日志存储到当前工作目录下的 darts_logs 文件夹中。您可以通过模型参数 work_dir 和 model_name 来更改此设置。
model = SomeTorchForecastingModel(..., log_tensorboad, save_checkpoints=True)
model.fit(...)
安装 tensorboard 库后,您可以从命令行可视化日志
tensorboad --log_dir darts_logs
让我们看看如何实现一个自定义回调,以便在 Python 中访问模型损失。
from pytorch_lightning.callbacks import Callback
class LossLogger(Callback):
    def __init__(self):
        self.train_loss = []
        self.val_loss = []
    # will automatically be called at the end of each epoch
    def on_train_epoch_end(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
        self.train_loss.append(float(trainer.callback_metrics["train_loss"]))
    def on_validation_epoch_end(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
        self.val_loss.append(float(trainer.callback_metrics["val_loss"]))
loss_logger = LossLogger()
model = SomeTorchForecastingModel(
    ...,
    nr_epochs_val_period=1,  # perform validation after every epoch
    pl_trainer_kwargs={"callbacks": [loss_logger]}
)
# fit must include validation set for "val_loss"
model.fit(...)
注意:由于模型训练器在训练开始前会执行验证健全性检查,因此回调将在 loss_logger.val_loss 中提供一个额外的元素。
性能建议¶
本节回顾了训练和使用基于 Torch 的模型时影响性能的主要因素。
使用 32 位数据构建你的 TimeSeries¶
Darts 中的模型会动态转换自身(为 64 位或 32 位)以遵循 TimeSeries 中的 dtype。当所有内容(数据和模型)都使用 float32 时,通常可以获得显着的性能和内存提升。为此,只需使用 dtype 为 np.float32 的数组(或 Dataframe 支持的数组)构建您的 TimeSeries,或者简单地调用 my_series32 = my_series.astype(np.float32)。调用 my_series.dtype 会返回您的 TimeSeries 的 dtype。
使用 GPU¶
在许多情况下,使用 GPU 会比使用 CPU 带来显著的加速。它也可能产生一些开销(用于在 GPU 之间传输数据),因此通常需要进行一些测试和调优。关于如何通过 PyTorch Lightning 设置 GPU(或 TPU)的更多信息,请参考我们的GPU/TPU 指南。
调整批处理大小¶
更大的批处理大小倾向于加速训练,因为它减少了每个周期的反向传播次数,并且有可能更好地并行化计算。然而,它也会改变训练动态(例如,您可能需要更多周期,并且收敛动态会受到影响)。此外,更大的批处理大小会增加内存消耗。因此,在这里也需要进行一些测试。
调整 num_loader_workers¶
Darts 中的所有深度学习模型在其 fit() 和 predict() 函数中都有一个参数 dataloader_kwargs,用于配置 PyTorch DataLoaders。可以通过 dataloader_kwargs 字典中的 num_workers 键设置 PyTorch DataLoaders 的 num_workers 参数。设置 num_workers > 0 将使用额外的 worker 来加载数据。这通常会产生一些开销(特别是增加内存消耗),但在某些情况下也能显着提高性能。理想值取决于许多因素,例如批处理大小、是否使用 GPU、可用的 CPU 核数以及加载数据是否涉及 I/O 操作(如果序列存储在磁盘上)。
先从小模型开始¶
当然,影响性能的主要因素之一是模型大小(参数数量)以及前向/反向传播所需的运算次数。Darts 中的模型可以进行调优(例如层数、注意力头、宽度等),这些超参数对性能影响很大。开始时,最好先构建适中大小的模型。
内存中的数据和 I/O 瓶颈¶
如果可能的话,最好提前将所有 TimeSeries 加载到内存中。Darts 提供了在任何 Sequence[TimeSeries] 上训练模型的可能性,这意味着对于大型数据集,您可以编写自己的 Sequence 实现,并从磁盘延迟读取时间序列。然而,这通常会产生高昂的 I/O 成本。因此,在训练多个系列时,首先尝试提前构建一个简单的 List[TimeSeries],看看它是否能保存在计算机内存中。
不要使用 所有 可能的子序列进行训练¶
默认情况下,调用 fit() 时,Darts 中的模型将构建适合您正在使用的模型的 TrainingDataset 实例(例如,PastCovariatesTorchModel、FutureCovariatesTorchModel 等)。默认情况下,这些训练数据集通常包含每个 TimeSeries 中存在的所有可能的连续(输入、输出)子序列。如果您的 TimeSeries 很长,这可能导致大量的训练样本,从而直接(线性地)影响训练模型一个周期所需的时间。您有两个选项来限制此情况
- 为 - fit()函数指定一些- max_samples_per_ts参数。这将只使用每个- TimeSeries中最新的- max_samples_per_ts个样本进行训练。
- 如果此选项无法满足您的需求,您可以实现自己的 - TrainingDataset实例,并自行定义如何为训练对您的- TimeSeries进行切片。我们建议您查看此子模块,以查看如何实现的示例。
基准测试示例¶
作为示例,我们展示了在能源数据集(darts.datasets.EnergyDataset)的前 80% 数据上训练一个周期所需的时间。该数据集包含一个长度为 28050 个时间步长、维度为 28 的多元系列。我们训练了两个模型:NBEATSModel 和 TFTModel,使用默认参数,input_chunk_length=48 和 output_chunk_length=12(这在默认顺序训练数据集下产生 27991 个训练样本)。对于 TFT 模型,我们还设置了参数 add_cyclic_encoder='hour'。测试在一台配置为 Intel CPU i9-10900K @ 3.70GHz、Nvidia RTX 2080s GPU、32 GB RAM 的机器上进行。所有 TimeSeries 都预先加载到内存中并作为列表提供给模型。
| 模型 | 数据集 | dtype | CUDA | 批处理大小 | worker 数量 | 每周期时间 | 
|---|---|---|---|---|---|---|
| 
 | 能源 | 64 | 否 | 32 | 0 | 283s | 
| 
 | 能源 | 64 | 否 | 32 | 2 | 285s | 
| 
 | 能源 | 64 | 否 | 32 | 4 | 282s | 
| 
 | 能源 | 64 | 否 | 1024 | 0 | 58s | 
| 
 | 能源 | 64 | 否 | 1024 | 2 | 57s | 
| 
 | 能源 | 64 | 否 | 1024 | 4 | 58s | 
| 
 | 能源 | 64 | 是 | 32 | 0 | 63s | 
| 
 | 能源 | 64 | 是 | 32 | 2 | 62s | 
| 
 | 能源 | 64 | 是 | 1024 | 0 | 13.3s | 
| 
 | 能源 | 64 | 是 | 1024 | 2 | 12.1s | 
| 
 | 能源 | 64 | 是 | 1024 | 4 | 12.3s | 
| 
 | 能源 | 32 | 否 | 32 | 0 | 117s | 
| 
 | 能源 | 32 | 否 | 32 | 2 | 115s | 
| 
 | 能源 | 32 | 否 | 32 | 4 | 117s | 
| 
 | 能源 | 32 | 否 | 1024 | 0 | 28.4s | 
| 
 | 能源 | 32 | 否 | 1024 | 2 | 27.4s | 
| 
 | 能源 | 32 | 否 | 1024 | 4 | 27.5s | 
| 
 | 能源 | 32 | 是 | 32 | 0 | 41.5s | 
| 
 | 能源 | 32 | 是 | 32 | 2 | 40.6s | 
| 
 | 能源 | 32 | 是 | 1024 | 0 | 2.8s | 
| 
 | 能源 | 32 | 是 | 1024 | 2 | 1.65 | 
| 
 | 能源 | 32 | 是 | 1024 | 4 | 1.8s | 
| 
 | 能源 | 64 | 否 | 32 | 0 | 78s | 
| 
 | 能源 | 64 | 否 | 32 | 2 | 72s | 
| 
 | 能源 | 64 | 否 | 32 | 4 | 72s | 
| 
 | 能源 | 64 | 否 | 1024 | 0 | 46s | 
| 
 | 能源 | 64 | 否 | 1024 | 2 | 38s | 
| 
 | 能源 | 64 | 否 | 1024 | 4 | 39s | 
| 
 | 能源 | 64 | 是 | 32 | 0 | 125s | 
| 
 | 能源 | 64 | 是 | 32 | 2 | 115s | 
| 
 | 能源 | 64 | 是 | 1024 | 0 | 59s | 
| 
 | 能源 | 64 | 是 | 1024 | 2 | 50s | 
| 
 | 能源 | 64 | 是 | 1024 | 4 | 50s | 
| 
 | 能源 | 32 | 否 | 32 | 0 | 70s | 
| 
 | 能源 | 32 | 否 | 32 | 2 | 62.6s | 
| 
 | 能源 | 32 | 否 | 32 | 4 | 63.6 | 
| 
 | 能源 | 32 | 否 | 1024 | 0 | 31.9s | 
| 
 | 能源 | 32 | 否 | 1024 | 2 | 45s | 
| 
 | 能源 | 32 | 否 | 1024 | 4 | 44s | 
| 
 | 能源 | 32 | 是 | 32 | 0 | 73s | 
| 
 | 能源 | 32 | 是 | 32 | 2 | 58s | 
| 
 | 能源 | 32 | 是 | 1024 | 0 | 41s | 
| 
 | 能源 | 32 | 是 | 1024 | 2 | 31s | 
| 
 | 能源 | 32 | 是 | 1024 | 4 | 31s |