Press "Enter" to skip to content

LSTM模型在时间序列中的五个实际应用,附带代码

如何在多个不同的时间序列环境中实现高级神经网络模型

Andrew Svk在Unsplash上的照片

当我在2022年1月写下《探索LSTM时间序列神经网络模型》一文时,我的目标是展示如何使用我开发的时间序列库scalecast在Python中轻松实现高级神经网络模型,以便促进我的工作和项目。我从未想过这篇文章会被观看成千上万次,并且在我发布后的一年多时间里,当我搜索“lstm预测python”时,它仍然是谷歌搜索的第一条结果(今天我检查时,它仍然排在第二位)。

我并没有试图引起太多关注那篇文章,因为我认为它并不是很好。它从来不是一篇关于实现LSTM模型的最佳方法的指南,而是对其在时间序列预测中的实用性的简单探索。我试图回答以下问题:使用默认参数运行模型时会发生什么,根据不同方式调整参数会发生什么,它在某些数据集上可以被其他模型轻松超越,等等。然而,根据我看到的博客文章、Kaggle笔记本甚至是Udemy课程,它们都使用了那篇文章中的代码,这表明很多人将这篇文章当作前一种价值而不是后一种。现在我明白了,我没有清楚地表达我的意图。

今天,为了扩展那篇文章,我想展示如何应用LSTM神经网络模型,或者至少是我会如何应用它,以充分发挥其在时间序列预测问题中的价值。自从我写下第一篇文章以来,我们已经能够在scalecast库中添加许多新的创新功能,使使用LSTM模型变得更加无缝,我将利用这个空间来探索其中一些我最喜欢的功能。我认为使用该库的LSTM有五个应用领域,它们都能够出色地工作:单变量预测、多变量预测、概率预测、动态概率预测和迁移学习。

在开始之前,请确保在终端或命令行中运行以下命令:

pip install --upgrade scalecast

本文所开发的完整笔记本位于此处。

最后一点说明:在每个示例中,我可能会使用“RNN”和“LSTM”这些术语互换使用。或者,LSTM预测的某个图表上可能显示为RNN。长短期记忆(LSTM)神经网络是一种具有额外记忆相关参数的递归神经网络(RNN)。在scalecast中,rnn模型类可用于在从tensorflow移植的模型中适配简单的RNN和LSTM单元。

1. 单变量预测

使用LSTM模型进行简单的单变量预测问题是最常见、也是最明显的方法。尽管模型适配了许多参数,应该足够复杂,能够有效地学习任何给定时间序列中的趋势、季节性和短期动态,但我发现它在处理平稳数据(不具有趋势或季节性的数据)时表现更好。因此,对于可在Kaggle上以开放数据库许可证获取的空乘客流量数据集,如果我们简单地去趋势和去季节化数据,我们可以轻松创建准确可靠的预测:

transformer = Transformer(    transformers = [        ('DetrendTransform',{'poly_order':2}),        'DeseasonTransform',    ],)

我们还要确保在完成后将结果恢复到原始水平:

reverter = Reverter(    reverters = [        'DeseasonRevert',        'DetrendRevert',    ],    base_transformer = transformer,)

现在,我们可以指定网络参数。对于这个示例,我们将使用18个滞后值、一层,tanh激活函数和200个epochs。随意探索您自己的更好参数!

def forecaster(f):    f.set_estimator('rnn')    f.manual_forecast(        lags = 18,        layers_struct = [            ('LSTM',{'units':36,'activation':'tanh'}),        ],        epochs=200,        call_me = 'lstm',    )

将所有内容组合到一个流水线中,运行模型并以可视化方式查看结果:

pipeline = Pipeline(    steps = [        ('转换',transformer),        ('预测',forecaster),        ('还原',reverter),    ])f = pipeline.fit_predict(f)f.plot()plt.show()
作者提供的图像

足够好,比我在其他文章中展示的任何内容都要好得多。要扩展这个应用程序,您可以尝试使用不同的滞后阶数,在模型中添加傅立叶项的季节性,找到更好的系列转换,并使用交叉验证调整模型的超参数。如何做到这一点将在后续章节中进行演示。

2. 多变量预测

假设我们有两个我们希望一起移动的系列。我们可以创建一个LSTM模型,在进行预测时同时考虑这两个系列,以提高模型的整体准确性。这就是多变量预测。

在这个例子中,我将使用Avocados数据集,该数据集在Kaggle上以开放数据库许可证提供。它以每周为单位测量了美国不同地区的鳄梨的价格和销售数量。我们从经济理论中知道,价格和需求密切相关,因此使用价格作为先导指标,我们可能能够比仅使用历史需求进行单变量预测时更准确地预测销售的鳄梨数量。

我们要做的第一件事是转换每个系列。我们可以通过运行以下代码来搜索“最佳”的转换集(即在样本外得分的转换):

data = pd.read_csv('avocado.csv')# 需求vol = data.groupby('Date')['Total Volume'].sum()# 价格price = data.groupby('Date')['AveragePrice'].sum()fvol = Forecaster(    y = vol,    current_dates = vol.index,    test_length = 13,    validation_length = 13,    future_dates = 13,    metrics = ['rmse','r2'],)transformer, reverter = find_optimal_transformation(    fvol,    set_aside_test_set=True, # 防止泄漏,以便我们可以公平地对比生成的模型    return_train_only = True, # 防止泄漏,以便我们可以公平地对比生成的模型    verbose=True,    detrend_kwargs=[        {'loess':True},        {'poly_order':1},        {'ln_trend':True},    ],    m = 52, # 一个季度有多少个周期?    test_length = 4,)

从这个过程中推荐的转换是季节性调整,假设52个周期是一个季度,以及鲁棒的尺度(对异常值鲁棒的缩放)。然后,我们可以将该转换拟合到系列上,并调用一个单变量LSTM模型来对多变量模型进行基准测试。这次,我们将使用一个超参数调整过程,通过生成可能的激活函数、层大小和丢失值的网格:

rnn_grid = gen_rnn_grid(    layer_tries = 10,    min_layer_size = 3,    max_layer_size = 5,    units_pool = [100],    epochs = [25,50],    dropout_pool = [0,0.05],    callbacks=EarlyStopping(      monitor='val_loss',      patience=3,    ),    random_seed = 20,) # 创建一个超参数值的网格,以调整LSTM模型

这个函数提供了一种很好的方式来将一个可管理的网格输入到我们的对象中,同时具有足够的随机性,以获得一些好的参数选择。现在我们拟合单变量模型:

fvol.add_ar_terms(13) # 模型将使用13个系列滞后fvol.set_estimator('rnn')fvol.ingest_grid(rnn_grid)fvol.tune() # 使用13周期的验证集fvol.auto_forecast(call_me='lstm_univariate')

要将其扩展到多变量上下文,我们可以使用与其他系列相同的转换集来转换价格时间系列。然后,我们将13个价格滞后项输入到Forecaster对象中,并拟合一个新的LSTM模型:

fprice = Forecaster(    y = price,    current_dates = price.index,    future_dates = 13,)fprice = transformer.fit_transform(fprice)fvol.add_series(fprice.y,called='price')fvol.add_lagged_terms('price',lags=13,drop=True)fvol.ingest_grid(rnn_grid)fvol.tune()fvol.auto_forecast(call_me='lstm_multivariate')

我们还可以对一个朴素的模型进行基准测试,并在原始系列级别上绘制结果,同时还有一个样本外测试集:

# 用于基准测试的朴素预测fvol.set_estimator('naive')fvol.manual_forecast()fvol = reverter.fit_transform(fvol)fvol.plot_test_set(order_by='TestSetRMSE')plt.show()
作者提供的图片

从三个模型在视觉上聚集在一起的方式来看,导致这个特定系列的大部分准确性的是应用的转换方法 —— 这就是为什么朴素模型最终和两个LSTM模型相当可比的原因。不过,LSTM模型是一种改进,多变量模型的R平方得分为38.37%,而单变量模型为26.35%,相比基准线的-6.46%。

作者提供的图片

可能导致LSTM模型在这个系列上表现不佳的一个因素是它的长度较短。只有169个观测值,这可能不足以让模型充分学习到模式。然而,任何对朴素或简单模型的改进都可以被认为是一种成功。

3. 概率预测

概率预测是指模型不仅能够进行点预测,还能够提供预测可能偏离的方向的估计。概率预测类似于带有置信区间的预测,这个概念已经存在很长时间。一种快速出现的产生概率预测的方法是将一个符合性置信区间应用于模型,使用校准集来确定实际未来点的可能分散程度。这种方法的优点是适用于任何机器学习模型,无论该模型对其输入或残差的分布作出了哪些假设。它还提供了某些非常有用的覆盖保证,对于任何机器学习从业者来说都非常有用。我们可以将符合性置信区间应用于LSTM模型,以生成概率预测。

对于这个例子,我们将使用FRED上可用的每月住房开始数据集,FRED是一个开源的经济时间序列数据库。我将使用从1959年1月到2022年12月的数据(768个观测值)。首先,我们将再次搜索最佳的转换集合,但这次使用一个具有10个epochs的LSTM模型来评估每个转换尝试:

transformer, reverter = find_optimal_transformation(    f,    estimator = 'lstm',    epochs = 10,    set_aside_test_set=True, # 防止泄漏,以便我们可以公平地基准测试结果模型    return_train_only = True, # 防止泄漏,以便我们可以公平地基准测试结果模型    verbose=True,    m = 52, # 一个季节周期是什么?    test_length = 24,    num_test_sets = 3,    space_between_sets = 12,    detrend_kwargs=[        {'loess':True},        {'poly_order':1},        {'ln_trend':True},    ],)

我们将再次随机生成一个超参数网格,但这次我们可以使其搜索空间非常大,然后在模型稍后拟合时手动将其限制为10个尝试,以便在合理的时间内交叉验证参数:

rnn_grid = gen_rnn_grid(    layer_tries = 100,    min_layer_size = 1,    max_layer_size = 5,    units_pool = [100],    epochs = [100],    dropout_pool = [0,0.05],    validation_split=.2,    callbacks=EarlyStopping(      monitor='val_loss',      patience=3,    ),    random_seed = 20,) # 构建一个非常大的网格,然后手动限制它

现在我们可以构建并拟合流水线:

def forecaster(f,grid):    f.auto_Xvar_select(        try_trend=False,        try_seasonalities=False,        max_ar=100    )    f.set_estimator('rnn')    f.ingest_grid(grid)    f.limit_grid_size(10) # 随机将大网格减小为10    f.cross_validate(k=3,test_length=24) # 三折交叉验证    f.auto_forecast()pipeline = Pipeline(    steps = [        ('Transform',transformer),        ('Forecast',forecaster),        ('Revert',reverter),    ])f = pipeline.fit_predict(f,grid=rnn_grid)

因为在Forecaster对象中设置了足够大的测试集,所以结果会自动给出每个点估计的90%概率分布:

f.plot(ci=True)plt.show()
作者提供的图像

4. 动态概率预测

上述示例提供了静态的概率预测,其中每个预测路径上的上界和下界与其他点的上界和下界相距相等。当预测未来时,直观上来说,越远的预测将会导致误差的分散越大,而静态区间无法捕捉到这种细微差别。通过使用回测(backtesting),可以实现更动态的概率预测。

回测是指反复重新拟合模型,对不同的预测时间范围进行预测,并在每次迭代中测试其性能的过程。让我们以上一个示例中指定的流程进行10次回测。我们至少需要10次回测迭代才能在90%置信水平上构建置信区间:

backtest_results = backtest_for_resid_matrix(    f,    pipeline=pipeline,    alpha = .1,    jump_back = 12,    params = f.best_params,)backtest_resid_matrix = get_backtest_resid_matrix(backtest_results)

我们可以通过可视化每次迭代的残差的绝对值来分析:

作者提供的图像

这个特定示例中有趣的是,最大的误差通常不会出现在预测的最后几步,而是在第14-17步。这可能发生在具有奇数季节模式的时间序列中。异常值的存在也可能影响到这种模式。无论如何,我们现在可以使用这些结果将静态置信区间替换为每步都符合的动态置信区间:

overwrite_forecast_intervals(    f,    backtest_resid_matrix=backtest_resid_matrix,    alpha=.1, # 90%置信区间)f.plot(ci=True)plt.show()
作者提供的图像

5. 迁移学习

当我们希望在模型适应的上下文之外使用模型时,迁移学习非常有用。下面我将演示两种特定情况下的迁移学习的用途:在给定时间序列中有新数据可用时进行预测,以及在具有相似趋势和季节性的相关时间序列上进行预测。

情景1:相同系列的新数据

我们可以使用与前两个示例中相同的房屋数据集,但假设经过一段时间后,我们现在有了截至2023年6月的数据。

df = pdr.get_data_fred(    'CANWSCNDW01STSAM',    start = '2010-01-01',    end = '2023-06-30',)f_new = Forecaster(    y = df.iloc[:,0],    current_dates = df.index,    future_dates = 24, # 2年预测时间范围)

我们将使用相同的转换重新创建流程,但这次使用迁移预测而不是常规的比例预测过程,该过程也会拟合模型:

def transfer_forecast(f_new,transfer_from):    f_new = infer_apply_Xvar_selection(infer_from=transfer_from,apply_to=f_new)    f_new.transfer_predict(transfer_from=transfer_from,model='rnn',model_type='tf')pipeline_can = Pipeline(    steps = [        ('Transform',transformer),        ('Transfer Forecast',transfer_forecast),        ('Revert',reverter),    ])f_new = pipeline_can.fit_predict(f_new,transfer_from=f)

尽管相关函数的名称仍然是fit_predict(),但实际上在流水线中并没有拟合,只有预测。这大大减少了我们重新拟合和重新优化模型所需的时间。然后我们查看结果:

f_new.plot()plt.show('Housing Starts Forecast with Actuals Through June, 2023')plt.show()
作者提供的图像

场景2:具有类似特征的新时间序列

对于第二种情况,我们可以使用希望使用在美国房屋动态训练的模型来预测加拿大的住房开工情况的假设情景。免责声明:我不知道这是否真的是一个好主意——这只是我想到的一个场景,用来演示如何完成这个任务。但我想象它可能会有用,并且涉及的代码可以转移到其他情况(也许是对于您已经拟合了表现良好模型的较长序列具有相似动态的较短序列的情况)。在这种情况下,代码实际上与场景1的代码完全相同;唯一的区别是我们加载到对象中的数据:

df = pdr.get_data_fred(    'CANWSCNDW01STSAM',    start = '2010-01-01',    end = '2023-06-30',)f_new = Forecaster(    y = df.iloc[:,0],    current_dates = df.index,    future_dates = 24, # 2-year forecast horizon)def transfer_forecast(f_new,transfer_from):    f_new = infer_apply_Xvar_selection(infer_from=transfer_from,apply_to=f_new)    f_new.transfer_predict(transfer_from=transfer_from,model='rnn',model_type='tf')pipeline_can = Pipeline(    steps = [        ('Transform',transformer),        ('Transfer Forecast',transfer_forecast),        ('Revert',reverter),    ])f_new = pipeline_can.fit_predict(f_new,transfer_from=f)f_new.plot()plt.show('Candian Housing Starts Forecast')plt.show()
作者提供的图像

我认为这个预测看起来足够可信,可以作为LSTM迁移学习的有趣应用。

结论

对于许多预测用例,LSTM模型可以是一个有趣的解决方案。在本文中,我演示了如何使用Python代码将LSTM模型应用于五个不同的目的。如果您觉得有用,请在GitHub上给scalecast一个星,并确保在VoAGI上关注我,获取最新和最好的软件包信息。如有反馈、建设性批评或对本代码有任何问题,请随时发送电子邮件至:mikekeith52@gmail.com。

Leave a Reply

Your email address will not be published. Required fields are marked *