全局预测模型

在前几章中,我们了解了如何将现代机器学习模型应用于时间序列预测问题,基本上取代了诸如ARIMA或指数平滑之类的传统模型。然而,在此之前,我们是孤立地看待任何数据集中的不同时间序列(例如伦敦智能电表数据集中的家庭),就像传统模型所做的那样。

然而,我们现在将探索一种不同的建模范式,即使用单个机器学习模型来同时预测一组时间序列。正如我们将在本章中了解到的,从计算和准确性的角度来看,这种范式带来了许多好处。

本章涵盖以下主要主题:

技术要求

你需要按照本书《前言》中的说明设置Anaconda环境,以获得一个包含本书代码所需所有库和数据集的可用环境。任何额外的库将在运行notebooks时安装。

在使用本章代码之前,你需要运行以下notebooks:

本章的相关代码可在 https://github.com/PacktPublishing/Modern-Time-Series-Forecasting-with-Python-/tree/main/notebooks/Chapter10 找到。

为什么需要全局预测模型?

我们在第5章《时间序列预测作为回归》中简要讨论了全局模型,当时提到了相关数据集。我们可以想到许多会遇到相关时间序列的场景。我们可能需要预测零售商所有产品的销量、城市不同区域出租车服务的请求次数,或特定区域所有家庭的能耗(这正是伦敦智能电表数据集所做的)。我们将这些称为相关时间序列,因为数据集中所有不同的时间序列可能有许多共同因素。例如,零售产品中可能出现的年度季节性可能存在于大部分产品中,或者温度等外部因素影响能耗的方式可能对大量家庭相似。因此,无论以何种方式,相关时间序列数据集中的不同时间序列之间共享属性。

传统上,我们曾将每个时间序列视为独立的时间序列;换句话说,每个时间序列被假设为来自不同的数据生成过程。ARIMA和指数平滑等经典模型是针对每个时间序列单独训练的。然而,我们也可以将数据集中的所有时间序列视为来自同一个数据生成过程,随后的建模方法将是训练单个模型来预测数据集中的所有时间序列。后者就是我们所说的全局预测模型GFM)。GFM是设计用于处理多个相关时间序列的模型,允许在这些时间序列之间进行共享学习。相比之下,传统方法被称为局部预测模型LFM)。

虽然我们在第5章《时间序列预测作为回归》中简要讨论了LFM的缺点,但让我们更具体地总结一下,看看GFM如何帮助我们克服其中许多缺点。

样本量

在大多数实际应用(尤其是业务预测)中,我们需要预测的时间序列并不长。采用完全数据驱动的方法对这种小时间序列进行建模是有问题的。用少量数据点训练高度灵活的模型会导致模型记住训练数据,从而过拟合。

传统上,这是通过为预测模型施加强先验或归纳偏置来克服的。归纳偏置大致指内置于模型中的一组假设或约束,应帮助模型预测训练时未遇到的特征组合。例如,双指数平滑对季节性和趋势有强假设。该模型不允许从数据中学习任何其他更复杂的模式。因此,利用这些强假设,我们将模型搜索限制在假设空间的一小部分。虽然这有助于低数据场景,但另一方面,这些假设可能会限制准确性。

机器学习领域的最新发展毫无疑问地告诉我们,在大型训练集上采用数据驱动方法(具有更少的假设或先验)将使我们训练出更好的模型。然而,传统统计智慧告诉我们,数据点的数量至少需要是试图从这些数据点中学习的参数数量的10到100倍。

因此,如果坚持使用LFM,能够采用完全数据驱动方法的场景将非常罕见。这正是GFM大放异彩的地方。GFM能够利用数据集中所有时间序列的历史来训练模型,学习一组适用于数据集中所有时间序列的参数。借用第5章《时间序列预测作为回归》中引入的术语,我们增加了数据集的宽度,同时保持长度不变(请回顾图5.2)。这种单个模型可用的历史信息的激增使我们能够在时间序列数据集上使用完全数据驱动技术。

交叉学习

GFM通过设计促进了数据集中不同时间序列之间的交叉学习。想象一个时间序列相当新,没有足够丰富的历史来教导模型——例如,新推出的零售产品的销量或某个地区新家庭的用电量。如果孤立地考虑这些时间序列,我们需要等待一段时间才能从训练它们的模型中获得合理的预测,但GFM通过启用交叉学习使这一过程更容易。GFM隐含了不同时间序列之间相似性的感知,并且能够利用在具有丰富历史的相似时间序列中看到的模式来对新时间序列进行预测。

交叉学习的另一种帮助方式是,在估计季节性等共同参数时充当正则化器。例如,零售场景中相似产品表现出的季节性最好在聚合级别上估计,因为每个单独的时间序列都会有一些噪声可能渗入季节性提取中。通过强制多个产品具有共同的季节性,我们实际上正则化了季节性估计,从而使季节性估计更加稳健。GFM的好处在于,它们采用数据驱动的方法来定义哪些产品的季节性应该一起估计,哪些产品具有不同的模式。如果不同产品具有不同的季节性模式,GFM可能难以一起建模。然而,当提供足够的信息来区分不同产品时,GFM也能够学习这种差异。

多任务学习

GFMs可以被视为多任务学习范式,其中单个模型被训练以学习多个任务(因为预测每个时间序列是一个单独的任务)。多任务学习是一个活跃的研究领域,使用多任务模型有许多好处:

工程复杂性

对于大型数据集,LFMs在工程方面也带来了挑战。如果我们要预测数千或数百万个时间序列,训练和管理这些LFM的生命周期将变得越来越困难。在第8章《使用机器学习模型预测时间序列》中,我们仅为数据集中的一部分家庭训练了LFMs。为所有150个家庭训练机器学习模型花费了近20到30分钟,并且我们使用了默认的超参数。在正常的机器学习工作流程中,我们会训练多个机器学习模型并进行超参数调优以找到最佳配置。然而,为数据集中的数千个时间序列执行所有这些步骤将变得越来越复杂和耗时。

同样,还存在管理这些模型生命周期的问题。所有这些单独的模型需要部署到生产环境,需要监控它们的性能以检查模型和数据漂移,并且需要按固定频率重新训练。随着我们需要预测的时间序列越来越多,这变得越来越复杂。

然而,通过转向GFM范式,我们大大减少了训练和管理机器学习模型在其整个生命周期中所需的时间和精力。正如我们将在本章中看到的,针对这150个家庭训练一个GFM仅需要训练LFMs所需时间的一小部分。

尽管GFMs有诸多优点,但它们也并非没有缺点。主要的缺点是我们假设数据集中的所有时间序列都是由一个单一的数据生成过程DGP)生成的。这个假设可能不成立,这可能导致GFM对数据集中代表性不足的某些特定类型的时间序列模式欠拟合。

另一个未解决的问题是GFM是否适用于不相关的任务或时间序列。对此尚无定论,但Montero-Manso等人证明,使用GFM建模不相关的时间序列也有收益。Oreshkin等人从另一个角度提出了相同的发现,他们在M4数据集(一组不相关的数据集)上训练了一个全局模型,并获得了最先进的性能。他们将其归因于模型的元学习能力。

尽管如此,相关性确实有助于GFM,因为这样学习任务变得更容易。我们将在本章后续部分看到这一点的实际应用。

从更大的角度来看,我们从GFM范式获得的好处远超过其缺点。在大多数任务上,GFMs的表现与局部模型相当或更好。Montero-Manso等人也在理论上证明,在最坏的情况下,GFM学习到的函数与局部模型相同。我们在后续部分将要训练的模型中会清楚地看到这一点。最后,随着转向GFM范式,训练时间和工程复杂性大幅下降。

既然我们已经解释了为什么GFM是一种值得采用的范式,那么让我们来看看如何训练一个GFM。

创建GFM

训练GFM非常简单。在《第8章:使用机器学习模型进行时间序列预测》中,我们训练LFM时,遍历了伦敦智能电表数据集中的不同家庭,并为每个家庭训练了一个模型。然而,如果我们只是将所有家庭的数据合并到一个数据框中(我们的数据集已经是这样),然后在其上训练一个单一模型,就得到了一个GFM。我们需要记住的一点是,要确保数据集中所有时间序列的频率相同。换句话说,如果在训练这些模型时混合了日度时间序列和周度时间序列,性能下降将非常明显——尤其是当我们使用时变特征和其他基于时间的信息时。对于纯自回归模型,以这种方式混合时间序列的影响要小得多。

笔记本提示:

要跟随完整代码,请使用Chapter10文件夹中的名为01-Global_Forecasting_Models-ML.ipynb的笔记本。

我们在《第8章:使用机器学习模型进行时间序列预测》中开发的标准框架足够通用,也适用于GFM。因此,正如该章所做的那样,我们在01-Global_Forecasting_Models-ML.ipynb笔记本中定义了FeatureConfigMissingValueConfig。我们还略微调整了用于训练和评估机器学习的Python函数,使其适用于所有家庭。详细信息和精确函数可以在笔记本中找到。

现在,我们不遍历不同的家庭,而是将整个训练数据集输入到get_X_y函数中:

# Define the ModelConfig
from lightgbm import LGBMRegressor
model_config = ModelConfig(
    model=LGBMRegressor(random_state=42),
    name="Global LightGBM Baseline",
    # LGBM is not sensitive to normalized data
    normalize=False,
    # LGBM can handle missing values
    fill_missing=False,
)
# Get train and test data
train_features, train_target, train_original_target = feat_config.get_X_y(
    train_df, categorical=True, exogenous=False
)
test_features, test_target, test_original_target = feat_config.get_X_y(
    test_df, categorical=True, exogenous=False
)

既然有了数据,就需要训练模型。训练模型也完全与我们在《第8章:使用机器学习模型进行时间序列预测》中看到的一样。我们只需要选择LightGBM,这是表现最好的LFM模型,并使用我们之前定义的函数来训练模型并评估结果:

y_pred, feat_df = train_model(
        model_config,
        _feat_config,
        missing_value_config,
        train_features,
        train_target,
        test_features,
    )
agg_metrics, eval_metrics_df = evaluate_forecast(
    y_pred, test_target, train_target, model_config
)

现在,在y_pred中,我们将获得所有家庭的预测值,feat_df将获得特征重要性。agg_metrics将获得所有选定家庭的聚合指标。

让我们看看我们的GFM模型表现如何:

图10.1 – 基线GFM的聚合指标

图10.1: 基线GFM的聚合指标

在指标方面,我们没有超越最好的LFM(第一行)。然而,需要注意的是模型训练所需的时间——约30秒。而针对所有选定住户的LFM训练需要约30分钟。训练时间的大幅减少使我们能够更灵活地迭代不同的特征和技术。

话虽如此,现在让我们来看一些可以提高GFM准确性的技术。

提升GFM的策略

GFM已被广泛应用于Kaggle及其他地方的许多预测竞赛中。它们经过了实证检验,尽管从理论角度研究其为何如此有效的文献很少。Montero-Manso和Hyndman(2020)有一篇工作论文,标题为《时间序列群体预测的原理与算法:局部性与全局性》,该论文对GFM以及数据科学界共同开发的许多技术进行了深入的理论和实证研究。在本节中,我们将尝试纳入改进GFM的策略,并尽可能给出其为何有效的理论依据。

参考文献检查:

Montero-Manso和Hyndman(2020)的研究论文在参考文献中被引用为参考文献1

在论文中,Montero-Manso和Hyndman使用机器学习中关于泛化误差的基本结果进行理论分析,值得花一些时间至少从高层次上理解它。Generalization error,如我们所知,是样本外误差与样本内误差之间的差异。Yaser S Abu-Mostafa有一门免费的在线大规模开放在线课程MOOC)以及一本配套书籍(两者链接均在延伸阅读部分)。这是一门关于机器学习的短期课程,我向机器学习领域的任何人推荐该课程,以便为我们所做的事情建立更扎实的理论和概念基础。该课程和书籍提出的重要概念之一是使用概率论中的霍夫丁不等式来推导学习问题的边界。让我们快速看一下这个结果以加深理解:

$$E_{out} \lt E_{in} + \sqrt{\frac{\log(|H|) + \log\left(\frac{2}{\delta}\right)}{2N}}$$

其概率至少为1-$\delta$.

Ein是样本内平均误差,而Eout是期望的样本外误差。N是数据集中用于学习的样本总数,而H是模型的假设类,它是一个可能拟合数据的有限函数集。|H|的大小表示H的复杂度。虽然边界公式看似吓人,但我们简化看待它的方式以建立必要的理解。

我们希望Eout尽可能接近Ein,为此,我们需要平方根下的项尽可能小。平方根下有两个我们“可控”的项——N和|H|。因此,为了使泛化误差(Ein-Eout)尽可能小,我们需要要么增加N(拥有更多数据),要么减少|H|(使用更简单的模型)。这个结果适用于所有机器学习,但Montero-Manso和Hyndman通过一些假设将其也应用于时间序列模型。正是这一结果为他们工作论文中的论点提供了理论支持。

Montero-Manso和Hyndman将霍夫丁不等式应用于LFM和GFM以进行比较。我们可以在这里看到结果(如需完整的数学和统计学理解,请参考参考文献中的原始论文):

$$E_{out}^{Local} \lt E_{in}^{Local} + \sqrt{\frac{log(\prod_{i=1}^{K} |H_i|) + log\left(\frac{2}{\delta}\right)}{2NK}}$$

$$E_{out}^{Global} \lt E_{in}^{Global} + \sqrt{\frac{log(|J|) + log\left(\frac{2}{\delta}\right)}{2NK}}$$

$E_{out}^{Local}$ 和 $E_{out}^{Global}$ 分别是使用局部方法和全局方法时所有时间序列的平均样本内误差。 $E_{out}^{Global}$ $E_{in}^{Global}$ 分别是局部方法和全局方法下的样本外期望。Hi 是第 i 个时间序列的假设类,而 J 是全局方法的假设类(全局方法只拟合一个函数,因此只有一个假设类)。

由此得出的一个最有趣的结果是,LFM的复杂度项($\log(\prod_{i=1}^{K} |H_i|)$)随着数据集的大小而增长。数据集中时间序列的数量越多,复杂度越高,泛化误差越差;而对于GFM,复杂度项(log(|J|))保持恒定。因此,对于中等规模的数据集,无论GFM多么复杂,LFM(如指数平滑)的整体复杂度都可能远高于单个GFM。作为推论,我们也可以认为,利用现有的数据集(NK),我们可以负担得起训练一个比LFM模型复杂度高得多的模型。有许多方法可以增加模型的复杂度,我们将在下一节中看到。

现在,让我们回到我们正在训练的GFM。我们发现,与最佳LFM(LightGBM)相比,我们训练的GFM性能并不理想,但优于基线及其他尝试的模型,因此我们立即知道训练的GFM并非糟糕。接下来,我们看看一些提高模型性能的方法。

增加记忆

正如我们在第5章《时间序列预测作为回归》中讨论的,本书中讨论的机器学习模型是有限记忆模型或马尔可夫模型。诸如指数平滑之类的模型在预测时会考虑时间序列的整个历史,但我们讨论的任何机器学习模型都仅利用有限记忆进行预测。在有限记忆模型中,我们允许模型访问的记忆量称为记忆大小(M)或自回归阶数(来自计量经济学)。

向模型提供更大的记忆量会增加模型的复杂度。因此,提高GFM性能的方法之一是增加模型可访问的记忆量。增加记忆量有多种方法。

添加更多滞后特征

如果你之前接触过ARIMA模型,你会知道自回归AR)项的使用是谨慎的。我们通常看到AR模型只有个位数的滞后。没有什么能阻止我们运行具有更大滞后的ARIMA模型,但由于我们是在LFM范式下运行ARIMA,模型必须使用有限数据学习所有滞后的参数,因此实践中从业者通常选择较小的滞后。然而,当我们转向GFM时,我们可以负担得起更大的滞后。Montero-Manso和Hyndman通过实证展示了向GFM添加更多滞后的好处。对于高度季节性的时间序列,观察到一种奇特现象:精度随滞后增加而提高,但随后饱和并突然恶化(当滞后等于季节周期时)。进一步增加滞后超过季节周期后,精度显示出巨大提升。这可能是由于季节性导致的过拟合。模型很容易偏向季节性滞后,因为它在样本中效果很好,所以最好在季节周期的正侧额外添加几个滞后。

添加滚动特征

另一种增加模型记忆的方法是将滚动平均值作为特征包含进来。滚动平均值从扩展的记忆窗口中提取信息,并通过描述性统计量(如均值或最大值)对这些信息进行编码。这是一种包含记忆的有效方式,因为我们可以使用非常大的记忆窗口,并将信息作为单个特征包含在模型中。

添加EWMA特征

一个指数加权移动平均EWMA)是一种在有限记忆模型中包含无限记忆的方法。EWMA本质上取整个历史的平均值,但根据我们设置的$\alpha$进行加权。因此,根据不同的$\alpha$值,我们得到不同类型的记忆,同样编码为单个特征。包含不同的EWMA特征在实践中也被证明是有益的。

我们已经在特征工程(第6章时间序列预测的特征工程)中包含了这些特征,它们是我们训练的基线GFM的一部分,所以让我们继续讨论提高GFM准确性的下一个策略。

使用时间序列元特征

先前在创建全球预测模型(GFM)部分训练的基线GFM包含了滞后特征、滚动特征和EWMA特征,但我们没有提供任何帮助模型区分数据集中不同时间序列的特征。基线GFM学习了一个通用函数,根据特征生成预测。这对于同质数据集(所有时间序列性质非常相似)可能效果不错,但对于异质数据集,模型区分每个时间序列的信息就很有用了。

因此,关于时间序列本身的信息我们称之为元特征。在零售场景中,它可以是产品ID、产品类别、门店编号等。在我们的数据集中,有stdorToUAcornAcorn_groupedLCLid等特征,它们提供了一些关于时间序列本身的信息。在GFM中包含这些元特征将提高模型的性能。

但有一个问题——这些元特征通常是类别型的。当特征中的值只能取离散值时,该特征就是类别型的。例如,Acorn_grouped只能取三个值之一——AffluentComfortableAdversity。大多数机器学习模型不能很好地处理类别特征。scikit-learn(Python生态中最流行的机器学习库)中的所有模型根本不允许使用类别特征。为了在机器学习模型中包含类别特征,我们需要将它们编码为数值形式,并且有许多方法可以编码类别列。让我们回顾几个流行的选项。

序数编码和独热编码

最流行的类别特征编码方法是序数编码和独热编码,但它们并不总是最佳选择。让我们快速回顾这些技术以及它们何时适用。

序数编码是最简单的。我们只需为类别中的每个唯一值分配一个数值代码,然后用该数值代码替换类别值。要编码数据集中的Acorn_grouped特征,我们需要分配代码,例如将1赋给Affluent2赋给Comfortable3赋给Adversity,并将所有类别值实例替换为我们分配的代码。虽然这很容易,但这种编码会给类别值引入我们可能有意或无意赋予的含义。当我们分配数值代码时,我们隐式地表示获得代码2的类别值优于获得代码1的类别值。这种编码仅适用于有序特征(其类别值在含义上具有内在等级感),并且应谨慎使用。另一种思考问题的方式是距离。当我们进行序数编码时,ComfortableAffluent之间的距离可能大于ComfortableAdversity之间的距离,这取决于编码方式。

独热编码是表示无顺序含义的类别特征的更好方式。它本质上将类别特征编码到更高的维度,在该空间中使类别值等距。编码类别值所需的维度大小等于类别变量的基数。基数是指类别特征中唯一值的数量。让我们看一下示例数据如何在独热编码方案中编码:

图10.2 – 类别特征的独热编码

图10.2:分类特征的一热编码

我们可以看到,生成的编码将具有分类特征中每个唯一值的一列,并且该值由列中的 1 指示。例如,第一行是 Comfortable,因此,除了 Comfortable 列之外的每一列都将有 0,而 Comfortable 列将有 1。如果我们计算任何两个分类值之间的欧几里得距离,我们可以看到它们是相同的。

然而,这种编码有三个主要问题,所有这些问题在高基数分类变量中都会成为问题:

有一种略有不同的一热编码方式,其中我们丢弃其中一个维度,称为哑变量编码。这还有一个好处是使编码线性无关,这反过来又有一些优点,特别是对于普通线性回归。延伸阅读部分有一个链接,如果你想了解更多。

由于我们必须编码的分类列具有高基数(至少其中一些),我们不会进行这种编码。相反,让我们看看一些可以更好地处理高基数分类变量的编码技术。

频率编码

频率编码是一种不会增加问题维度的编码方案。它接收单个分类数组并返回单个数值数组。逻辑非常简单——它将分类值替换为该值在训练数据集中出现的次数。虽然它并不完美,但效果相当好,因为它允许模型根据类别出现的频率来区分不同的类别

有一个流行的库 category_encoders,它实现了许多不同的编码方案,采用标准的 scikit-learn 风格估计器,我们将在实验中使用它。我们在第8章《使用机器学习模型进行时间序列预测》中开发的标准框架也有几个我们没有使用的功能——encode_categoricalcategorical_encoder

因此,让我们使用它们并现在训练模型:

from category_encoders import CountEncoder
from lightgbm import LGBMRegressor
#Define which columns names are categorical features
cat_encoder = CountEncoder(cols=cat_features)
model_config = ModelConfig(
    model=LGBMRegressor(random_state=42),
    name="Global LightGBM with Meta Features (CountEncoder)",
    # LGBM is not sensitive to normalized data
    normalize=False,
    # LGBM can handle missing values
    fill_missing=False,
    # Turn on categorical encoding
    encode_categorical=True,
    # Pass the categorical encoder to be used
    categorical_encoder=cat_encoder
)

过程的其余部分与我们在创建全局预测模型(GFM)一节中看到的相同,并使用编码后的元特征得到预测。

图10.3 – 使用元特征(频率编码)的GFM的聚合指标

图10.3:使用元特征(频率编码)的GFM的聚合指标

立即可以看出误差有所减小,尽管幅度很小。我们还可以看到训练时间几乎翻倍。这可能是因为现在除了训练机器学习模型外,还增加了编码分类特征的步骤。

频率编码的主要问题在于它不适用于数据集中均匀分布的特征。例如,LCLid 特征(只是每个家庭的唯一代码)在数据集中均匀分布,当我们使用频率编码时,所有 LCLid 特征几乎会得到相同的频率,因此机器学习模型将它们视为几乎相同。

现在让我们看看一种略有不同的方法。

目标均值编码

目标均值编码在其最基础的形式中是一个非常简单的概念。它是一种有监督方法,使用训练数据集中的目标变量来编码分类列。让我们看一个例子:

图10.4 – 目标均值编码

图10.4:目标均值编码

基础目标均值编码存在一些局限性。它增加了过拟合训练数据的风险,因为我们直接使用目标均值,从而以某种方式将目标泄漏到模型中。该方法的另一个问题是,当分类值分布不均匀时,可能会出现样本量很小的分类值,因此均值估计变得嘈杂。将这个问题推向极端,我们还会遇到测试数据中出现未见过的分类值的情况。基础版本也不支持这种情况。因此,在实践中,这种简单版本几乎从不使用,但该概念稍加复杂的版本被广泛使用,并且是编码分类特征的有效策略。

category_encoders 中,这个概念有许多变体,但这里我们来看两个流行且有效的变体。

2001年,Daniele Micci-Barreca提出了均值编码的一种变体。如果我们将目标视为二元变量(例如1和0),均值(即1的个数或样本数)也是出现1的概率。利用对均值的这种解释,Daniele 提出将两个概率——先验概率和后验概率——混合作为分类特征的最终编码。

参考文献检查:

研究论文Daniele Micci-Barreca在参考文献中被引用为文献2

先验概率定义如下:

$$P_{prior} = \frac{n_y}{n_{TR}}$$

这里,ny 是满足条件 目标 = 1 的案例数,并且 nTR 是训练数据中的样本数。

对类别 i 的后验概率定义如下:

$$P_{posterior}^{i} = \frac{n_{iY}}{n_i}$$

这里,niY 是满足条件 类别 = iY = 1 的数据集样本数,并且 ni 是满足条件 类别 = i 的数据集样本数。

现在,对类别 i 的最终编码如下:

$$S_i = \lambda(n_i) \times P_{posterior}^{i} + (1 - \lambda(n_i)) \times P_{prior}$$

这里,$\lambda$ 是权重因子,它是关于 ni 的单调递增函数,取值介于0和1之间。因此,随着样本数量的增加,该函数赋予后验概率更大的权重。

将其适应到回归设置中,概率变为期望值,因此公式变为:

$$S_i = \lambda(n_i) \times \frac{\sum_{k \in TR_i} Y_k}{n_i} + (1 - \lambda(n_i)) \left\{ \frac{\sum_{k \in TR} Y_k}{n_{TR}} \right\}$$

这里,TRi 是所有满足 category = 1 的行,$\sum_{k \in TR} Y_k$ 是 TRiY 的总和。$\sum_{k \in TR} Y_k$ 是训练数据集中所有行中 Y 的总和。与二元变量一样,我们混合了给定 category = iY 的期望值(E[[Y||category = i])和 Y 的期望值(E[[Y])用于最终的类别编码。

我们可以用于 $\lambda$ 的函数有很多。Daniele 提到一个非常常见的函数形式(sigmoid):

$$\lambda(n_i) = \frac{1}{1 + e^{-\frac{n_i - k}{f}}}$$

这里,ni 是数据集中 category = i 的样本数量,kf 是可调超参数。k 决定了我们完全信任估计的最小样本量的一半。如果 k = 1,那么意味着我们信任仅有两个样本的类别的后验估计。f 决定了 sigmoid 在两个极端之间过渡的速度。随着 f 趋于无穷,过渡变为先验概率和后验概率之间的硬阈值。TargetEncoder 中的 category_encoders 实现了此 $\lambda$。k 参数称为 min_samples_leaf,默认值为 1,f 参数称为 smoothing,默认值为 1。让我们看看这种编码在我们的问题上是如何工作的。在我们使用的框架中使用不同的编码器,只需将不同的 cat_encoder(初始化的类别编码器)传递给 ModelConfig

from category_encoders import TargetEncoder
cat_encoder = TargetEncoder(cols=cat_features)

其余代码完全相同。我们可以在相应的笔记本中找到完整代码。让我们看看新的编码效果如何:

图10.5 – 使用元特征(目标编码)的GFM的聚合指标

图10.5:使用元特征(目标编码)的GFM的聚合指标

效果不是很好,对吧?就像机器学习模型一样,没有免费午餐定理NFLT)也适用于类别编码。没有任何一种编码方案始终有效。虽然与主题不直接相关,但如果你想了解更多关于NFLT的信息,请参阅扩展阅读部分。

对于所有这些监督式类别编码技术,例如目标均值编码,我们必须非常小心,避免引入数据泄漏。编码器应使用训练数据进行拟合,而不使用验证或测试数据。另一种非常流行的技术是使用交叉验证生成类别编码,并利用样本外编码来完全避免数据泄漏或过拟合。

还有更多的编码方案,例如 MEstimateEncoder(使用加性平滑作为 $\lambda$)、HashingEncoder 等,都在 category_encoders 中。另一种非常有效的类别特征编码方法是使用深度学习的嵌入。扩展阅读部分有关于此操作的教程链接。

在此之前,所有这些分类编码都是建模前的单独步骤。现在,让我们来看一种在模型训练中原生考虑分类特征的技术。

LightGBM 对分类特征的原生处理

有少数机器学习模型实现原生处理分类特征,尤其是梯度提升模型。CatBoost 和 LightGBM,两个最流行的 GBM 实现,开箱即用地处理分类特征拓展阅读部分提供了如何完成这种编码的进一步信息链接。category_encoders 实现了这一逻辑,如同CatBoostEncoder,这样我们也可以将这种编码用于任何机器学习模型。

虽然 CatBoost 处理这种到数值特征的内部转换,但 LightGBM 采用更原生方式处理分类特征。LightGBM 在生长和分裂树时将分类特征视为原样。对于一个具有 k 个唯一值的分类特征(基数为 k),有 2k-1-1 个可能的分割。这很快就变得不可处理,但对于回归树,Walter D. Fisher 早在 1958 年提出了一种技术,使得找到最优分割的复杂度大大降低。该方法的本质是利用每个分类值的平均目标统计量对它们进行排序,然后在排序后的分类值中找到最优分割。

参考文献检查:

Fisher 的研究论文在 参考文献 中被引用为引用为 3

LightGBM 的 scikit-learn API 通过接受一个参数支持此特性,categorical_feature,该参数包含一个分类特征名称列表,在 fit 期间。我们可以使用 fit_kwargs 参数在我们定义的MLModel 的 fit 中,该 MLModel 定义于在 第 8 章使用机器学习模型进行时间序列预测,来传入此参数。让我们看看如何做到这一点:

from lightgbm import LGBMRegressor
model_config = ModelConfig(
    model=LGBMRegressor(random_state=42),
    name="Global LightGBM with Meta Features (NativeLGBM)",
    # LGBM is not sensitive to normalized data
    normalize=False,
    # LGBM can handle missing values
    fill_missing=False,
    # We are using inbuilt categorical feature handling
    encode_categorical=False,
)
# Training the model and passing in fit_kwargs
y_pred, feat_df = train_model(
    model_config,
    _feat_config,
    missing_value_config,
    train_features,
    train_target,
    test_features,
    fit_kwargs=dict(categorical_feature=cat_features),
)

y_pred 具有具有预测,我们像往常一样评估。让我们也看看结果:

图 10.6 – 使用元特征的 GFM 的聚合指标(原生 LightGBM)

图 10.6:使用元特征的 GFM 的聚合指标(原生 LightGBM)

我们可以观察到 MAE 的显著降低以及 meanMASE 的显著降低,通过原生处理分类特征。我们还可以看到总训练时间的减少,因为我们没有单独的分类特征编码步骤。经验上,原生处理分类特征在大多数情况下效果更好。

既然我们已经对分类特征进行了编码,让我们看看另一种提高准确性的方法。

调优超参数

超参数是一种控制机器学习模型训练方式的设置,但它并非从数据中学习得到。相比之下,模型参数是在训练过程中从数据中学习得到的。例如,在梯度提升决策树GBDT)中,模型参数是每棵树中的决策阈值,从数据中学习得到。而超参数如树的数量学习率树的深度,则在训练前设定,控制模型的结构及其学习方式。参数会随数据调整,但超参数必须通过外部调优。

尽管超参数调优是机器学习中的常见做法,但在LFM范式下,由于模型数量庞大,我们一直无法进行调优。现在我们有了一个能在30秒内完成训练的GFM,超参数调优变得可行。从理论角度看,我们还发现GFM能够承受更大的复杂度,因此可以评估更多函数以选出最佳模型而不会过拟合。

数学优化定义为:从一组可用候选中,根据某个标准选择最佳元素。大多数情况下,这涉及从一组候选(搜索空间)中寻找某个函数(目标函数)的最大值或最小值,并受限于一些条件(约束条件)。搜索空间可以是离散变量、连续变量或两者的混合,目标函数可以是可微的或不可微的。针对这些不同情况,已有大量研究成果。

你可能在想,我们现在为什么讨论数学优化?超参数调优本身就是一个数学优化问题。这里的目标函数是不可微的,返回的是我们优化的指标——例如平均绝对误差MAE)。

搜索空间包括我们调优的不同超参数——例如树的数量或树的深度。它可以是连续变量和离散变量的混合,而约束条件则是我们对搜索空间施加的任何限制——例如,某个超参数不能为负,或某个超参数组合不能出现。因此,了解数学优化中使用的术语将有助于我们的讨论。

尽管超参数调优是标准机器学习概念,但我们将快速回顾三种主要调优技术(除了手动试错法)。

网格搜索

网格搜索可以视为一种暴力方法:我们在搜索空间上定义一个离散网格,检查网格中每个点的目标函数值,然后选取网格中的最佳点。网格被定义为一组离散点,对应我们选择调优的每个超参数。网格定义后,评估所有网格交点以搜索最佳目标值。如果我们要调优5个超参数,每个参数的网格有20个离散值,那么网格搜索的试验总数将是3,200,000(205)。这意味着需要训练并评估模型320万次。这变得非常受限,因为大多数现代机器学习模型都有许多超参数。例如,LightGBM有100多个超参数,其中至少20个在调优时影响很大。因此,使用网格搜索这种暴力方法迫使我们把搜索空间设得非常小,以便在合理时间内完成调优。

对于我们的情况,我们定义了一个非常小的网格,仅包含27次试验,通过将搜索空间限制得非常小。来看看我们是怎么做的:

from sklearn.model_selection import ParameterGrid
grid_params = {
    "num_leaves": [16, 31, 63],
    "objective": ["regression", "regression_l1", "huber"],
    "random_state": [42],
    "colsample_bytree": [0.5, 0.8, 1.0],
}
parameter_space = list(ParameterGrid(grid_params))

我们只调优三个超参数(num_leavesobjectivecolsample_bytree),每个参数只有三个选项。完成网格搜索只需遍历参数空间,评估每个超参数组合下的模型:

scores = []
for p in tqdm(parameter_space, desc="Performing Grid Search"):
    _model_config = ModelConfig(
        model=LGBMRegressor(**p, verbose=-1),
        name="Global Meta LightGBM Tuning",
        # LGBM is not sensitive to normalized data
        normalize=False,
        # LGBM can handle missing values
        fill_missing=False,
    )
    y_pred, feat_df = train_model(
        _model_config,
        _feat_config,
        missing_value_config,
        train_features,
        train_target,
        test_features,
        fit_kwargs=dict(categorical_feature=cat_features),
    )
    scores.append(ts_utils.mae(
                test_target['energy_consumption'], y_pred
            ))

这大约需要15分钟完成,并给出了最佳MAE为0.73454,这已经比未调优的GFM有了很大改进。

然而,这让我们思考是否还存在一个在我们定义的网格中未覆盖的更好解决方案。一种选择是扩展网格并重新运行网格搜索。但这会使试验次数呈指数级增长,很快变得不可行。

让我们看看另一种方法,它可以在相同试验次数下探索更大的搜索空间。

随机搜索

随机搜索采用了略有不同的路线。在随机搜索中,我们也定义搜索空间,但不是离散地定义空间中的特定点,而是在我们想要探索的范围内定义概率分布。这些概率分布可以是均匀分布(表示范围内任何点出现的可能性相同)、高斯分布(中间有熟悉的峰值),或任何其他深奥的分布,例如伽马分布或贝塔分布。只要我们能从分布中采样,就可以将其用于随机搜索。一旦定义了搜索空间,我们就可以从分布中采样点,并评估每个点以找到最佳超参数。

对于网格搜索,试验次数是所定义搜索空间的函数,而对于随机搜索,试验次数是用户输入,因此我们可以自行决定需要为超参数调优投入多少时间或计算预算,正因如此,我们还可以搜索更大的搜索空间。

有了这种新的灵活性,让我们为问题定义一个更大的搜索空间,并使用随机搜索:

import scipy
from sklearn.model_selection import ParameterSampler
random_search_params = {
    # A uniform distribution between 10 and 100, but only integers
    "num_leaves": scipy.stats.randint(10,100),
    # A list of categorical string values
    "objective": ["regression", "regression_l1", "huber"],
    "random_state": [42],
    # List of floating point numbers between 0.3 and 1.0 with a resolution of 0.05
    "colsample_bytree": np.arange(0.3,1.0,0.05),
    # List of floating point numbers between 0 and 10 with a resolution of 0.1
    "lambda_l1":np.arange(0,10,0.1),
    # List of floating point numbers between 0 and 10 with a resolution of 0.1
    "lambda_l2":np.arange(0,10,0.1)
}
# Sampling from the search space number of iterations times
parameter_space = list(ParameterSampler(random_search_params, n_iter=27, random_state=42))

这同样运行了大约15分钟,但我们探索了更大的搜索空间。然而,报告的最佳MAE仅为 0.73752,低于网格搜索的结果。也许如果我们运行更多迭代次数,会得到更好的分数,但这只是瞎猜。讽刺的是,随机搜索基本上也在做同样的事:它闭上眼睛,随机向飞镖盘投掷飞镖,希望能命中靶心。

数学优化中有两个术语:探索(exploration)和利用(exploitation)。探索确保优化算法到达搜索空间的不同区域,而利用则确保我们在给出更好结果的区域进行更多搜索。随机搜索纯粹是探索性的,在评估不同试验时对正在发生的事情毫无察觉。

让我们看看最后一种试图在探索与利用之间取得平衡的技术。

贝叶斯优化

贝叶斯优化与随机搜索有很多相似之处。两者都将搜索空间定义为概率分布,并且在这两种技术中,用户决定需要评估多少次试验,但它们的区别正是贝叶斯优化的关键优势。随机搜索从搜索空间中随机采样,而贝叶斯优化则智能地进行采样。贝叶斯优化能够感知其过去的试验以及这些试验产生的目标值,从而调整未来的试验,以利用那些出现更好目标值的区域。在高层次上,它通过构建目标函数的概率模型并利用该模型将试验集中在有前景的区域来实现这一点。该算法的细节值得了解,我们在进一步阅读中提供了一些资源,以帮助你继续学习。

现在,让我们使用一个流行的库 optuna,在我们一直训练的GFM上实现贝叶斯优化以进行超参数调优。

过程相当简单。我们需要定义一个函数,该函数接受一个名为trial的参数。在函数内部,我们从trial对象中采样我们想要调整的不同参数,训练模型,评估预测,并返回我们想要优化的指标(MAE)。让我们快速实现:

def objective(trial):
    params = {
        # Sample an integer between 10 and 100
        "num_leaves": trial.suggest_int("num_leaves", 10, 100),
        # Sample a categorical value from the list provided
        "objective": trial.suggest_categorical(
            "objective", ["regression", "regression_l1", "huber"]
        ),
        "random_state": [42],
        # Sample from a uniform distribution between 0.3 and 1.0
        "colsample_bytree": trial.suggest_uniform("colsample_bytree", 0.3, 1.0),
        # Sample from a uniform distribution between 0 and 10
        "lambda_l1": trial.suggest_uniform("lambda_l1", 0, 10),
        # Sample from a uniform distribution between 0 and 10
        "lambda_l2": trial.suggest_uniform("lambda_l2", 0, 10),
    }
    _model_config = ModelConfig(
        # Use the sampled params to initialize the model
        model=LGBMRegressor(**params, verbose=-1),
        name="Global Meta LightGBM Tuning",
        # LGBM is not sensitive to normalized data
        normalize=False,
        # LGBM can handle missing values
        fill_missing=False,
    )
    y_pred, feat_df = train_model(
        _model_config,
        _feat_config,
        missing_value_config,
        train_features,
        train_target,
        test_features,
        fit_kwargs=dict(categorical_feature=cat_features),
    )
    # Return the MAE metric as the value
    return ts_utils.mae(test_target["energy_consumption"], y_pred)

一旦定义了目标函数,我们需要初始化一个采样器。optuna有许多采样器,例如GridSamplerRandomSamplerTPESampler。对于所有标准用例,应使用TPESamplerGridSampler执行网格搜索,RandomSampler执行随机搜索。在定义树结构Parzen估计器TPE)采样器时,有两个参数值得我们注意:

对于最常见的用例,其余参数最好保持默认。

现在,我们创建一个study对象,它是运行试验并存储所有试验细节的对象:

# Create a study
study = optuna.create_study(direction="minimize", sampler=sampler)
# Start the optimization run
study.optimize(objective, n_trials=27, show_progress_bar=True)

这里,我们定义了优化方向,并传入了之前初始化的采样器。定义study后,我们需要调用optimize方法,并传入我们定义的目标函数、需要运行的试验次数以及其他一些参数。optimize方法的完整参数列表请参阅此处——https://optuna.readthedocs.io/en/stable/reference/generated/optuna.study.Study.html#optuna.study.Study.optimize

这运行的时间稍长,可能是由于生成新试验需要额外的计算,但对于27次试验仍然只花了大约20分钟。正如预期,这找到了另一组超参数组合,其目标值为0.72838(到目前为止最低的)。

为了充分说明这三者之间的区别,让我们比较一下三种技术如何分配它们的计算预算:

图10.7 – 计算资源分配分布(网格搜索对比随机搜索对比贝叶斯优化)

图10.7:计算资源分配分布(网格搜索对比随机搜索对比贝叶斯优化)

我们可以看到,贝叶斯优化在低值侧有一个长尾,表明它将其大部分计算预算用于评估和利用搜索空间中的最优区域。

让我们看看,随着优化过程的推进,使用这些技术的不同试验表现如何。

笔记本中包含了这三种技术的更详细比较和评论。

归根结底,如果我们拥有无限的计算资源,那么使用定义明确且细粒度高的网格进行网格搜索是最佳选择;但如果我们重视计算效率,则应选择贝叶斯优化。

让我们看看新参数的效果如何:

图10.8 – 带有元特征且经过调优的GFM的汇总指标

图10.8:带有元特征且经过调优的GFM的汇总指标

我们在MAEmeanMASE上取得了巨大改进,这主要是因为我们在超参数调优时针对MAE进行了优化。MAE和MSE的侧重点略有不同,我们将在第4部分《预测机制》中详细讨论。运行时间也增加了,因为新参数构建的树叶子比之前更多,且比默认参数更复杂。

现在,让我们看看另一种提升GFM性能的策略。

分区

在迄今为止讨论的所有策略中,这是最反直觉的一个,尤其是如果你来自标准的机器学习或统计学背景。通常,我们会期望模型在更多数据上表现更好,但将数据集划分或分割成多个几乎相等的部分已被(经验上)证明可以提高模型的准确性。虽然这种现象已被经验观察到,但其原因尚不完全清楚。一种解释是,GFM在训练于相似实体的子集时,学习任务略微简化,从而可以学习针对相似实体子集的特定函数。Montero-Manso和Hyndman(参考文献1)提出了另一种解释。他们认为,划分数据是增加复杂性的另一种形式,因为复杂性项不再是log(|J|),而是

$$log\left(\prod_{i=1}^{P} |H_i|\right)$$

其中P是分区的数量。基于这个理由,LFM是P等于数据集中时间序列数量的特殊情况。

划分数据的方式有很多种,每种方式的复杂度各不相同。

随机划分

最简单的方法是将数据集随机拆分为P个相等的分区,并为每个分区训练单独的模型。该方法严格遵循Montero-Manso和Hyndman提供的解释,因为我们随机拆分数据集,不考虑不同家庭之间的相似性。让我们看看如何做到这一点:

# Define a function which splits a list into n partitions
def partition (list_in, n):
    random.shuffle(list_in)
    return [list_in[i::n] for i in range(n)]
# split the unique LCLids into partitions
partitions = partition(train_df.LCLid.cat.categories.tolist(), 3)

然后,我们只需遍历这些分区,并为每个分区训练单独的模型。具体代码可在笔记本中找到。让我们看看随机划分的效果如何:

图10.9 – 使用调优后的GFM(含元特征)和随机划分的聚合指标

图10.9:使用调优后的GFM(含元特征)和随机划分的聚合指标

我们可以看到MAEmeanMASE有所下降,即使是随机划分。运行时间也有所减少,因为每个模型处理的数据更少,因此训练更快。

现在,让我们看看另一种划分方式,即考虑不同时间序列之间的相似性。

判断划分

判断划分是指我们利用时间序列的某些属性来拆分数据集,之所以称为判断,是因为这通常取决于建模人员的判断。实现方式有很多种。我们可以使用某些元特征,或者利用时间序列的某些特征(如规模、变异性、间歇性或其组合)来划分数据集。

让我们使用一个名为Acorn_grouped的元特征来划分数据集。同样,我们只需遍历Acorn_grouped中的唯一值,并为每个值训练一个模型。我们也将不使用Acorn_grouped作为特征。具体代码在笔记本中。让我们看看这种划分的效果如何:

图10.10 – 使用调优后的GFM(含元特征)和Acorn_grouped划分的聚合指标

图10.10:使用元特征和Acorn_grouped分区的调优GFM的聚合指标

这比随机分区效果更好。我们可以假设每个分区(AffluentComfortableAdversity)具有某种相似性,这使学习更容易,从而获得更好的准确性。

现在,让我们看看另一种基于相似性来划分数据集的方法。

算法分区

在判断分区中,我们选择一些元特征或时间序列特征来划分数据集。我们选择少数几个维度来划分数据集,因为我们是依靠思维进行划分,而我们的心智能力无法很好地处理超过两三个维度,但我们可以将此分区视为无监督聚类方法,这种方法称为算法分区。

有两种方法可以对时间序列进行聚类:

tslearn是一个开源Python库,它实现了基于时间序列之间距离的几种时间序列聚类方法。在扩展阅读中有一个链接,可以了解更多关于该库的信息以及如何将其用于时间序列聚类。

在我们的示例中,我们将使用第一种方法,即推导出一些时间序列特征,并用它们进行聚类。有许多来自统计和时间序列文献的特征,例如自相关、均值、方差、熵和峰峰值距离,我们可以从时间序列中提取这些特征。

我们可以使用另一个名为时间序列特征提取库tsfel)的开源Python库来简化过程。

该库有许多特征类别——统计域、时间域和频谱域——我们可以从中选择,其余部分由库处理。让我们看看如何生成这些特征并创建一个数据框来执行聚类:

import tsfel
cfg = tsfel.get_features_by_domain("statistical")
cfg = {**cfg, **tsfel.get_features_by_domain("temporal")}
uniq_ids = train_df.LCLid.cat.categories
stat_df = []
for id_ in tqdm(uniq_ids, desc="Calculating features for all households"):
    ts = train_df.loc[train_df.LCLid==id_, "energy_consumption"]
    res = tsfel.time_series_features_extractor(cfg, ts, verbose=False)
    res['LCLid'] = id_
    stat_df.append(res)
stat_df = pd.concat(stat_df).set_index("LCLid")

数据框大致如下所示:

图10.11 – 从不同时间序列中提取的特征

图10.11: 从不同时间序列中提取的特征

现在我们已经有了数据框,其中每一行代表一个具有不同特征的时间序列,理想情况下,我们可以应用任何聚类方法,如k-means、k-medoids或HDBSCAN,并找到聚类。然而,在高维空间中,许多距离度量(包括欧氏距离)的效果并不理想。Charu C. Agarwal等人2001年的一篇开创性论文探讨了这个问题。当我们增加空间的维度时,我们的常识(它概念化三维空间)不再适用,因此常见的距离度量如欧氏距离在高维空间中效果不佳。我们提供了一篇博客的链接(在延伸阅读中)和论文本身(参考文献5),它们使概念更清晰。因此,处理高维聚类的一种常见方法是先进行降维,然后使用常规聚类方法。

主成分分析PCA)是该领域的首选工具,但由于PCA在降维时仅捕捉和描述线性关系,如今另一类技术开始变得更受欢迎——流形学习。

t分布随机邻域嵌入t-SNE)是该类别中的一种流行技术,在高维可视化中非常受欢迎。这是一种非常巧妙的技术,它将高维空间中的点投影到较低维度,同时使原始空间中的距离分布尽可能接近低维空间中的距离分布。这里有很多内容可以学习,但超出了本书的范围。在延伸阅读部分有一些链接可以帮助您入门。

简而言之,我们将使用t-SNE降维数据集,然后在降维后的数据集上进行聚类。如果您真的想对时间序列进行聚类并以其他方式使用这些聚类,我不建议使用t-SNE,因为它不保留点之间的距离和点的密度。延伸阅读中的distil.pub文章进一步阐述了这个问题。但在我们的案例中,我们只是将聚类用于训练另一个模型的分组,因此这种近似效果不错。让我们看看如何做到这一点:

from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from src.utils.data_utils import replace_array_in_dataframe
from sklearn.manifold import TSNE #T-Distributed Stochastic Neighbor Embedding
# Standardizing to make distance calculation fair
X_std = replace_array_in_dataframe(stat_df, StandardScaler().fit_transform(stat_df))
#Non-Linear Dimensionality Reduction
tsne = TSNE(n_components=2, perplexity=50, learning_rate="auto", init="pca", random_state=42, metric="cosine", square_distances=True)
X_tsne = tsne.fit_transform(X_std.values)
# Clustering reduced dimensions into 3 clusters
kmeans = KMeans(n_clusters=3, random_state=42).fit(X_tsne)
cluster_df = pd.Series(kmeans.labels_, index=X_std.index)

由于我们将维度降到了二维,我们还可以可视化形成的聚类:

图10.12 – t-SNE降维后的时间序列聚类

图10.12: t-SNE降维后的时间序列聚类

我们形成了三个定义明确的聚类,现在我们将使用这些聚类为每个集群训练一个模型。像往常一样,我们遍历这三个聚类并训练模型。让我们看看我们是如何做到的:

图10.13 – 使用经过调整的GFM及元特征和聚类划分的聚合指标

图10.13:使用带有元特征和聚类划分的调优GFM的聚合指标

看起来这是我们所有实验中见过的最佳MAE,但三种划分技术的MAE非常相似。仅通过观察单个留出集,我们无法看出哪种方法更优。为了稳妥起见,我们可以使用测试数据集运行这些预测,使用01a-Global_Forecasting_Models-ML-test.ipynb文件夹中的Chapter08笔记本。让我们看看测试数据集中聚合指标的情况:

图10.14 – 测试数据的聚合指标

图10.14:测试数据的聚合指标

正如预期,聚类划分仍然是这种情况下表现最佳的方法。

第8章《使用机器学习模型进行时间序列预测》中,我们花费了8分20秒为数据集中所有住户训练一个LFM。现在,采用GFM范式,我们在57秒内(在最坏情况下)完成了模型训练。训练时间减少了777%,同时MAE降低了8.78%。

我们选择使用LightGBM进行这些实验。这并不意味着LightGBM或其他任何梯度提升模型是GFM的唯一选择,但它们是一个相当不错的默认选择。一个经过良好调优的梯度提升树模型是一个非常难以击败的基线,但正如机器学习中一贯的做法,我们应该通过设计良好的实验来检查哪种方法效果最佳。

尽管对于何时GFM比LFM更合理没有硬性规定或阈值,但随着数据集中时间序列数量的增加,GFM在准确性和计算方面都变得更有优势。

尽管我们使用GFM取得了良好的结果,但在此范式中表现良好的复杂模型通常是黑箱。让我们来看一些打开黑箱、更好理解和解释模型的方法。

可解释性

可解释性可以定义为人类理解决策原因的程度。在机器学习和人工智能中,这转化为人们理解算法及其预测的方式和原因的程度。看待可解释性有两种方式:透明性和事后解释。

透明性是指模型本身简单,可以通过人类认知进行模拟或思考。人类应能完全理解模型的输入以及将输入转换为输出的过程。这是一个非常严格的条件,几乎所有的机器学习或深度学习模型都无法满足。

这时,事后解释技术就大显身手了。有各种各样的技术利用模型的输入和输出来理解模型为何做出这样的预测。

有许多流行的技术,例如排列特征重要性Shapley值LIME。所有这些都是通用的解释技术,可以用于任何机器学习模型,包括我们正在讨论的GFMs。让我们从高层次来讨论其中几种。

平均不纯度减少:

这是我们从基于树的模型中直接获得的常规“特征重要性”。该技术衡量特征在决策树中用于分裂节点时降低不纯度(如分类中的基尼不纯度或回归中的方差)的程度。不纯度降低越多,特征被认为越重要。然而,它偏向于连续特征或基数高的特征。它速度快,在scikit-learn等库中容易获得,但如果特征具有不同尺度或许多类别,则可能给出误导性结果。

删除列重要性(留一协变量法LOCO):

该方法通过每次移除一个特征并重新训练模型来评估特征重要性。相对于基线模型的性能下降表明了该特征的重要性。它是模型无关的,并捕捉特征之间的交互作用,但计算成本高,因为需要为每个移除的特征重新训练模型。如果存在共线性特征,也可能给出误导性结果,因为模型可能通过其他特征补偿被移除的特征。

排列重要性:

排列重要性衡量当单个特征的值被随机打乱、破坏其与目标的关系时模型性能的下降。该技术直观且模型无关,不需要重新训练模型,计算效率高。然而,它可能高估相关特征的重要性,因为模型可以依赖相关特征来补偿被排列的特征。

部分依赖图(PDP)和个体条件期望图(ICE):

PDP可视化特征对模型预测的平均效应,显示目标变量如何随特征值的变化而变化,而ICE图显示特征对单个实例的效应。这些图有助于理解特征-目标关系,但假设特征之间独立,这可能在存在相关变量时导致误导性解释。

局部可解释模型无关解释(LIME):

LIME是一种模型无关的技术,通过使用更简单的可解释模型(如线性回归)在局部近似复杂模型来解释单个预测。它通过生成数据点的扰动并拟合局部模型来工作。该方法直观且广泛适用于结构化和非结构化数据(文本和图像),但对于表格数据,定义合适的扰动局部性可能具有挑战性。

SHapley加法解释(SHAP):

SHAP将多种解释方法(包括Shapley值和LIME)统一到一个单一的框架中,该框架以模型无关的方式归因特征重要性。SHAP提供局部和全局解释,并受益于基于树的模型(TreeSHAP)的快速实现。它结合了

每种技术都有其优势和权衡,但SHAP因其坚实的理论基础以及有效连接局部和全局解释的能力而脱颖而出。关于这些技术的更全面介绍,我在延伸阅读中提供了一些链接。本人撰写的系列博客和Christoper Molnar的免费书籍是帮助您快速入门的优秀资源(更多关于可解释性的内容请参见第17章)。

恭喜您完成了本书的第二部分!这是一个相当密集的部分,我们涵盖了大量理论和实践课程,希望您现在能够熟练使用机器学习进行时间序列预测。

小结

为了圆满结束本书的第二部分,我们详细探讨了GFM,并了解了它们为何重要以及为何它们是时间序列预测中令人兴奋的新方向。我们了解了如何使用机器学习模型来使用GFM,并回顾了许多提高GFM性能的技术,其中大多数在竞赛和行业用例中非常常用。我们还高层次地了解了可解释性技术。现在我们已经完成了本书的机器学习部分,将在下一章转向一种特定类型的机器学习——深度学习——这在过去几年中已变得广为人知。——在下一章。

参考文献

以下是我们在本章中引用的资料来源:

  1. Montero-Manso, P., Hyndman, R.J. (2020), 预测时间序列组的原理与算法:局部性与全局性. arXiv:2008.00444[cs.LG]: https://arxiv.org/abs/2008.00444..
  2. Micci-Barreca, D. (2001), 分类和预测问题中高基数分类属性的预处理方案. SIGKDD Explor. Newsl. 3, 1 (2001年7月), 27–32: https://doi.org/10.1145/507533.507538..
  3. Fisher, W. D. (1958). 关于最大化同质性的分组. Journal of the American Statistical Association, 53(284), 789–798: https://doi.org/10.2307/2281952..
  4. Fisher, W.D. (1958), 分类和预测问题中高基数分类属性的预处理方案. SIGKDD Explor. Newsl. 3, 1 (2001年7月), 27–32.
  5. Aggarwal, C. C., Hinneburg, A., 和 Keim, D. A. (2001). 关于高维空间中距离度量的惊人行为.第8届数据库理论国际会议论文集(ICDT '01). Springer-Verlag, 柏林, 海德堡, 420–434: https://dl.acm.org/doi/10.5555/645504.656414..
  6. Oreshkin, B. N., Carpov D., Chapados N., 和 Bengio Y. (2020). N-BEATS: 用于可解释时间序列预测的神经基扩展分析. 第8届学习表征国际会议, ICLR 2020: : https://openreview.net/forum?id=r1ecqn4YwB..

延伸阅读

以下是一些可供深入学习的资源:

留下评论!

感谢您从Packt Publishing购买本书——希望您喜欢!您的反馈非常宝贵,有助于我们改进和成长。阅读完成后,请花一点时间在亚马逊上留下评论;只需一分钟,但对像您这样的读者意义重大。

扫描二维码或访问链接以免费获取您选择的一本电子书。

https://packt.link/NzOWQ

第3部分

时间序列深度学习

在本部分中,我们将聚焦于激动人心的深度学习领域以解决时间序列问题。本部分首先对必要概念进行了良好介绍,然后逐步构建适合处理时间序列数据的各种专门架构。同时,还讨论了深度学习中的全局模型以及一些使其效果更好的策略。最后,我们深入探讨了概率预测的生成,这在当今预测领域中高度相关。

本部分包含以下章节: