快速傅里叶变换预测模型 (FFT)

以下是 FFT 预测模型的简要演示。此模型特别适用于具有非常强季节性的数据。本演示选择的数据集也相应地进行了选择。

[1]:
# fix python path if working locally
from utils import fix_pythonpath_if_working_locally

fix_pythonpath_if_working_locally()
[2]:
%load_ext autoreload
%autoreload 2
%matplotlib inline
import warnings

import pandas as pd

from darts.datasets import AirPassengersDataset, EnergyDataset, TemperatureDataset
from darts.metrics import mae
from darts.models import FFT, ExponentialSmoothing, Theta
from darts.utils.missing_values import fill_missing_values

warnings.filterwarnings("ignore")
import logging

logging.disable(logging.CRITICAL)
Importing plotly failed. Interactive plots will not work.

读取和格式化

这里我们简单读取包含每日温度的 CSV 文件,并将数值转换为所需的格式。

[3]:
ts = TemperatureDataset().load()

构建用于训练和验证的 TimeSeries 实例

[4]:
train, val = ts.split_after(pd.Timestamp("19850701"))
train.plot(label="train")
val.plot(label="val")
../_images/examples_03-FFT-examples_6_0.png

基本 FFT 模型

[5]:
model = FFT(required_matches=set(), nr_freqs_to_keep=None)
model.fit(train)
pred_val = model.predict(len(val))

下面的图表显示,简单的 DFT(使用随机的训练-测试分割)很可能会导致糟糕的结果。仔细观察可以发现,预测结果(紫色)只是简单地重复了训练集(蓝色)。这是 DFT 的标准行为,就其本身而言是相当无用的,因为重复训练集可以更有效地完成。对这种方法进行了三项改进。

[6]:
train.plot(label="train")
val.plot(label="val")
pred_val.plot(label="prediction")
print("MAE:", mae(pred_val, val))
MAE: 5.424526892430279
../_images/examples_03-FFT-examples_10_1.png

改进 1:裁剪训练集

第一个改进是在将训练集输入到 FFT 算法之前对其进行裁剪,使得裁剪后序列的第一个时间戳在季节性方面与要预测的第一个时间戳匹配,即具有相同的月份、日期、工作日、一天中的时间等。我们可以通过将可选参数 required_matches 传递给 FFT 构造函数来实现这一点,该参数明确告诉模型哪些时间戳属性是相关的。但实际上,如果我们不手动设置,模型将尝试自动找到相关的 pd.Timestamp 属性并相应地裁剪训练集(我们在这里就这样做)。

[7]:
model = FFT(nr_freqs_to_keep=None)
model.fit(train)
pred_val = model.predict(len(val))

我们可以看到结果显示预测的季节性与验证集的季节性完美对齐。然而,我们仍然只是重复训练集,包括所有的噪声。查看误差,我们可以看到这仍然是一个相当糟糕的预测。

[8]:
train.plot(label="train")
val.plot(label="val")
pred_val.plot(label="predict")
print("MAE:", mae(pred_val, val))
MAE: 3.0995766932270916
../_images/examples_03-FFT-examples_14_1.png

改进 2:过滤掉低振幅波

DFT 分解到频域允许我们选择性地过滤掉低振幅的波。这使我们能够保留强的季节性趋势,同时丢弃一些噪声。在 FFT 模型中,通过传递可选参数 nr_freqs_to_keep 来实现这一点。此参数表示将保留的频率总数。例如,如果传递的值为 20,则只会使用振幅最高的 20 个频率。默认值设置为 10。

[9]:
model = FFT(nr_freqs_to_keep=20)
model.fit(train)
pred_val = model.predict(len(val))

我们得到了噪声较少的信号。根据数据集的不同,这可能是一个更好的预测。查看误差指标,我们可以看到此模型的性能显著优于之前的模型。

[10]:
train.plot(label="train")
val.plot(label="val")
pred_val.plot(label="predict")
print("MAE:", mae(pred_val, val))
MAE: 2.2941917142812893
../_images/examples_03-FFT-examples_18_1.png

改进 3:去趋势

让我们尝试一个具有全局上升趋势的不同数据集

[11]:
ts_2 = AirPassengersDataset().load()
train, val = ts_2.split_after(pd.Timestamp("19551201"))
train.plot(label="train")
val.plot(label="val")
../_images/examples_03-FFT-examples_20_0.png
[12]:
model = FFT()
model.fit(train)
pred_val = model.predict(len(val))

显然,我们的模型完全未能纳入上升趋势。由于趋势的存在,我们的模型也未能识别月度季节性。

[13]:
train.plot(label="train")
val.plot(label="val")
pred_val.plot(label="prediction")
../_images/examples_03-FFT-examples_23_0.png

这个问题可以通过将可选的趋势参数设置为 'poly' 或 'exp' 来解决,这会拟合数据的多项式或指数函数,并在进行 DFT 之前将其减去。进行预测时,趋势会再次添加回去。

[14]:
model = FFT(trend="poly")
model.fit(train)
pred_val = model.predict(len(val))

我们现在得到了一个更好的预测结果。

[15]:
train.plot(label="train")
val.plot(label="val")
pred_val.plot(label="prediction")
../_images/examples_03-FFT-examples_27_0.png

新数据:每小时核能发电量

[16]:
ts_3 = EnergyDataset().load()
ts_3 = fill_missing_values(ts_3, "auto")
ts_3 = ts_3["generation nuclear"]
train, val = ts_3.split_after(pd.Timestamp("2017-07-01"))
train.plot(label="train")
val.plot(label="val")
../_images/examples_03-FFT-examples_29_0.png

除了简单地查看 FFT 模型的性能外,我们还可以看看其他一些预测模型在此新数据集上的 MAE 表现。令人惊讶的是,在这个数据集上,FFT 模型优于所有其他模型(至少在使用默认参数的情况下)。诚然,选择此数据集是由于其高度季节性的性质。然而,这表明 FFT 具有用例。此外,FFT 模型的运行时间比其他模型短得多!

[17]:
models = [ExponentialSmoothing(), Theta(), FFT()]

for model in models:
    model.fit(train)
    pred_val = model.predict(len(val))
    print(str(model) + " MAE: " + str(mae(pred_val, val)))
Prophet MAE: 653.8682864837639
Exponential smoothing MAE: 1667.805755244887
Theta(2) MAE: 944.0038491197353
FFT(nr_freqs_to_keep=10, trend=None) MAE: 643.337489093281