在上一章中,我们开始将机器学习(ML)作为解决时间序列预测问题的工具。我们还讨论了一些技术,如时间延迟嵌入和时间嵌入,这些技术将时间序列预测问题转化为机器学习范式中的经典回归问题。本章,我们将详细介绍这些技术,并在实践中通过我们在本书中使用的数据集来进行操作。
本章我们将涵盖以下主题:
- 理解特征工程
- 避免数据泄漏
- 设置预测时间范围
- 时间延迟嵌入
- 时间嵌入
技术要求
你需要设置Anaconda环境,按照本书前言中的说明进行,以便获得一个包含所有库和数据集的工作环境,这些库和数据集是本书中代码所需的。在运行笔记本时,任何额外的库都会被自动安装。
在使用本章代码之前,你需要运行以下笔记本:
- 来自第2章的 02-Preprocessing_London_Smart_Meter_Dataset.ipynb
- 来自第4章的 01-Setting_up_Experiment_Harness.ipynb
本章的代码可以在以下链接找到:github.com/PacktPublis…
理解特征工程
特征工程,顾名思义,是从数据中提取特征的过程,主要使用领域知识,以使学习过程更加顺利和高效。在典型的机器学习(ML)设置中,工程化好的特征对于从任何机器学习模型中获得良好的表现至关重要。特征工程是机器学习中的一个高度主观的部分,每个问题都有不同的解决路径——这些路径是为该问题专门手工设计的。假设你有一个房价数据集,并且你有一个特征“Year Built”,它告诉你房屋建造的年份。现在,为了让信息更加清晰,我们可以从“Year Built”特征创建另一个特征“House Age”。这可能会给模型提供更好的信息,这就是特征工程。
当我们将时间序列问题转化为回归问题时,有一些标准技术可以应用。这是过程中的一个关键步骤,因为一个机器学习模型在理解时间上的效果取决于我们如何设计特征来捕捉时间。我们在第4章《设置强基线预测》中讨论的基线方法是为特定的时间序列预测用例创建的,因为这些模型本身就已经包含了时间的处理。例如,ARIMA模型不需要任何特征工程来理解时间,因为它本身就包含了时间的处理。然而,标准回归模型没有显式的时间理解,因此我们需要创建良好的特征来嵌入时间的方面。
在上一章(第5章《时间序列预测作为回归》)中,我们讨论了两种主要的方式来在回归框架中编码时间:时间延迟嵌入和时间嵌入。尽管我们从高层次上提到了这些概念,但现在是时候深入探讨并实际应用它们了。
笔记本提醒:
要跟随完整代码,请使用第6章文件夹中的 01-Feature_Engineering.ipynb 笔记本。
我们已经将我们使用的数据集分割为训练、验证和测试数据集。然而,由于我们生成的特征是基于之前的观察值,因此在实际操作中,将训练、验证和测试数据集合并在一起会更好。为什么这么做更清晰,稍后就会明白,但现在我们暂时相信这一点并继续前进。现在,让我们将这三个数据集合并起来:
# 读取缺失值填充和训练测试数据集
train_df = pd.read_parquet(preprocessed / "selected_blocks_train_missing_imputed.parquet")
val_df = pd.read_parquet(preprocessed / "selected_blocks_val_missing_imputed.parquet")
test_df = pd.read_parquet(preprocessed / "selected_blocks_test_missing_imputed.parquet")
# 添加训练、验证和测试标签以便区分它们
train_df['type'] = "train"
val_df['type'] = "val"
test_df['type'] = "test"
full_df = pd.concat([train_df, val_df, test_df]).sort_values(["LCLid", "timestamp"])
del train_df, test_df, val_df
现在,我们有了一个full_df,它将训练、验证和测试数据集合并在一起。你们中的一些人可能已经在脑海中响起了警铃,担心合并训练集和测试集的问题。这会不会导致数据泄漏呢?让我们来检查一下。
避免数据泄漏
数据泄漏发生在模型使用一些在预测时不可用的信息进行训练时。通常,这会导致训练集的高性能,但在未见过的数据上表现很差。数据泄漏有两种类型:
- 目标泄漏:当关于目标的信息(我们想要预测的内容)泄漏到模型的一些特征中时,导致模型对这些特征的过度依赖,从而导致泛化能力差。这包括任何使用目标信息的特征。
- 训练-测试污染:当训练集和测试集之间存在一些信息泄漏时,发生这种情况。这可能是因为数据拆分不当或处理不小心。但也可以通过一些更微妙的方式发生,例如在拆分训练集和测试集之前对数据进行缩放。
当我们处理时间序列预测问题时,最大且最常见的错误就是目标泄漏。我们需要仔细考虑每一个特征,确保不使用在预测时不可用的数据。下面的图表将帮助我们记住并内化这一概念:
为了让这个概念在时间序列预测的背景下更加清晰和相关,我们来看一个例子。假设我们在预测洗发水的销量,并且将护发素的销量作为一个特征。我们开发了模型,在训练数据上训练,并在验证数据上测试,模型表现得非常好。然而,一旦我们开始进行未来的预测,就会发现一个问题:我们也不知道未来护发素的销量是什么。虽然这个例子很直观,但有时候这个问题并不那么明显。这就是为什么在创建特征时我们需要非常小心,并且始终从“这个特征在预测时是否可用?”的角度来评估特征的原因。
最佳实践:
除了认真思考特征外,还有很多方法可以识别目标泄漏:
- 如果你构建的模型表现得过于完美,那很可能存在数据泄漏问题。
- 如果某个特征在模型特征重要性中占据过高的权重,这个特征可能存在泄漏问题。
- 重新检查与目标高度相关的特征。
虽然我们在本书的前面部分生成了预测,但我们从未明确讨论过预测范围(forecast horizon)。这是一个重要的概念,对于我们接下来讨论的内容至关重要。让我们花点时间来理解预测范围。
设置预测范围
预测范围是指我们希望在任何给定时间预测的未来时间步数。例如,如果我们想要预测我们在电力消耗数据集上使用的未来24小时的情况,那么预测范围就是48(因为数据是半小时一次)。在第5章《时间序列预测作为回归》中,我们生成了基准预测时,我们一次性预测了整个测试数据。在这种情况下,预测范围就等于测试数据的长度。
直到现在我们都不需要担心这个问题,因为在经典的统计预测方法中,这个决定是与建模过程解耦的。如果我们训练了一个模型,我们可以用它来预测任何未来的点,而不需要重新训练。但在时间序列预测作为回归的问题中,我们有了对预测范围的约束,而这个约束与数据泄漏有关。现在可能还不太清楚这个概念,所以我们会在学习完特征工程技巧后再回到这个问题。现在,让我们先看一下单步预测。在我们所使用的数据集的背景下,这意味着我们将回答以下问题:下一个半小时的能耗是多少?我们将在第4部分《预测的机制》中讨论多步预测和其他预测机制。
现在我们已经设定了一些基本规则,让我们开始了解不同的特征工程技巧。要跟随Jupyter笔记本进行学习,请前往第6章文件夹并使用01-Feature_Engineering.ipynb文件。
时间延迟嵌入
时间延迟嵌入的基本思想是通过最近的观察来嵌入时间。在第5章《时间序列预测作为回归》中,我们讨论了将时间序列的先前观察值作为滞后值来纳入预测(第5.6节“时间延迟嵌入”中的图示)。
然而,使用这一概念,还有一些其他的方法可以捕捉最近的信息和季节性的信息:
- 滞后值
- 滚动窗口聚合
- 季节性滚动窗口聚合
- 指数加权移动平均
让我们逐一看一下这些方法。
滞后值或回退
假设我们有一个包含时间步长的时间序列,Y_L。假设当前时刻为T,我们有一个历史长度为L的时间序列。所以我们的时间序列会有y_T作为最新的观察值,接着是y_T-1、y_T-2,依此类推,随着时间的推移。正如在第5章《时间序列预测作为回归》中所解释的,滞后值是包含时间序列中前几个观察值的特征,如下图所示:
我们可以通过包含比当前时间步长早的观察值(yT-a)来创建多个滞后值;我们将其称为滞后a。在前面的图示中,我们展示了Lag 1、Lag 2和Lag 3。然而,我们可以根据需要添加任意数量的滞后值。现在,让我们学习如何在代码中实现这一点:
df["lag_1"] = df["column"].shift(1)
记得我们之前合并了训练集和测试集,并且我让你信任这个步骤吗?现在是回报那个信任的时候了。如果我们考虑滞后操作(或任何自回归特征),它依赖于时间轴上的连续表示。如果我们考虑测试集,对于前几行(或最早的日期),滞后值会缺失,因为它们是训练集的一部分。所以,通过将这两个数据集合并,我们创建了一个沿时间轴的连续表示,使得像shift这样的标准pandas函数可以轻松高效地用于创建这些特征。
就这么简单,但我们需要分别对每个LCLid执行滞后操作。我们在src.feature_engineering.autoregressive_features中提供了一个有用的方法add_lags,可以快速高效地为每个LCLid添加所需的所有滞后值。让我们看看如何使用它。
我们将导入这个方法,并使用它的一些参数来配置滞后操作:
from src.feature_engineering.autoregressive_features import add_lags
# 创建前5个滞后,然后是前5个滞后值,分别来自前一天和前一周,以捕捉季节性
lags = (
(np.arange(5) + 1).tolist()
+ (np.arange(5) + 46).tolist()
+ (np.arange(5) + (48 * 7) - 2).tolist()
)
full_df, added_features = add_lags(
full_df, lags=lags, column="energy_consumption", ts_id="LCLid", use_32_bit=True
)
现在,让我们看看我们在上面的代码片段中使用的参数:
- lags:该参数接受一个整数列表,表示我们需要创建的所有滞后值特征。
- column:需要滞后的列名。在我们的案例中,这一列是
energy_consumption。 - ts_id:包含时间序列唯一ID的列名。如果为None,则假定DataFrame仅包含一个时间序列。在我们的案例中,
LCLid是该列的名称。 - use_32_bit:该参数本身没有功能作用,但它使得DataFrame在内存中变得更小,以牺牲浮动点数的精度为代价。
该方法返回一个包含滞后值的DataFrame,以及一个包含新添加特征列名的列表。
滚动窗口聚合
通过滞后,我们将当前时刻的数据点与过去的单一数据点连接起来,但通过滚动窗口特征,我们将当前时刻与过去一段时间窗口的聚合统计量连接起来。我们不再仅仅看过去时刻的观测值,而是看过去三个时间步长的观测值的平均值。请看下面的图示,以便更好地理解这一点:
我们可以使用不同的窗口来计算滚动统计量,每个窗口会捕获历史的不同方面。在前面的图示中,我们可以看到一个窗口为3和一个窗口为4的示例。当我们处于时间步T时,滚动窗口大小为3会包含yT - 3、yT - 2、yT - 1作为过去观测值的向量。一旦得到这些数据,我们可以应用任何聚合函数,例如均值、标准差、最小值、最大值等。一旦通过聚合函数得到一个标量值,我们就可以将其作为特征包含在时间步t中。
我们不包括yT在过去观测值的向量中,因为这会导致数据泄漏。
让我们看看如何用pandas来实现这一点:
# 我们通过向前移动一个时间步来确保不会发生数据泄漏
df["rolling_3_mean"] = df["column"].shift(1).rolling(3).mean()
与滞后操作类似,我们需要为每个LCLid列单独执行此操作。我们在src.feature_engineering.autoregressive_features中包含了一个名为add_rolling_features的实用方法,可以快速有效地为每个LCLid添加所有所需的滚动特征。让我们看看如何使用它。
我们将导入此方法,并使用它的一些参数来配置滚动操作:
from src.feature_engineering.autoregressive_features import add_rolling_features
full_df, added_features = add_rolling_features(
full_df,
rolls=[3, 6, 12, 48], # 需要计算聚合统计量的窗口大小
column="energy_consumption", # 要进行滚动的列
agg_funcs=["mean", "std"], # 需要进行的聚合函数
ts_id="LCLid", # 时间序列的唯一ID列
use_32_bit=True, # 是否使用32位存储
)
现在,让我们看看在上面的代码片段中使用的参数:
- rolls: 此参数接受一个整数列表,表示我们需要计算聚合统计量的所有窗口大小。
- column: 要进行滚动的列的名称。在我们的例子中是
energy_consumption。 - agg_funcs: 这是一个我们希望对每个滚动窗口应用的聚合函数的列表。允许的聚合函数包括
{mean, std, max, min}。 - n_shift: 在进行滚动操作之前需要移动的时间步数。此参数可以避免数据泄漏。虽然我们在这里使用的是1步,但有时我们需要移动多个时间步,通常在多步预测中使用,我们将在第4部分《预测机制》中讨论。
- ts_id: 包含时间序列唯一ID的列名。如果为None,则假设DataFrame只有一个时间序列。在我们的例子中,
LCLid是该列的名称。 - use_32_bit: 该参数没有功能上的作用,但可以使DataFrame在内存中更小,牺牲浮动点数值的精度。
此方法返回一个已添加滚动特征的DataFrame,以及一个包含新添加的特征列名的列表。
季节性滚动窗口聚合
季节性滚动窗口聚合与滚动窗口聚合非常相似,但与其在窗口中使用过去n个连续的观测值,不如采用季节性窗口,即在窗口中的每个项目之间跳过一定数量的时间步。以下图示将帮助我们更清楚地理解这一点:
季节性滚动窗口聚合与滚动窗口聚合非常相似,但不同之处在于,它不会在窗口中使用过去n个连续的观测值,而是采用季节性窗口,即在窗口中的每个项目之间跳过固定数量的时间步。以下图示将有助于理解这一点:
关键参数是季节性周期,通常称为M。这个参数表示我们预期季节性模式重复的时间步数。当我们处于时间步T时,普通的滚动窗口大小为3的窗口会包含yT - 3、yT - 2、yT - 1作为过去的观测值向量。而季节性滚动窗口则会跳过m个时间步,窗口中的观测值将是yT - M、yT - 2M、yT - 3M。与通常情况一样,一旦得到窗口向量,我们只需应用聚合函数以获得一个标量值,并将其作为特征。
为了避免数据泄漏,我们不将yT包含在季节性滚动窗口向量中。
这个操作在pandas中不易高效地实现。我们将使用来自github.com/jmoralez/window_ops/的实现,该实现使用NumPy和Numba来加速操作并提高效率。
和之前看到的特征一样,我们需要为每个LCLid单独执行此操作。我们在src.feature_engineering.autoregressive_features中提供了一个有用的方法add_seasonal_rolling_features,可以快速有效地为每个LCLid添加所有季节性滚动特征。让我们看看如何使用它。
我们将导入该方法,并使用一些方法参数配置我们希望的季节性滚动操作:
from src.feature_engineering.autoregressive_features import add_seasonal_rolling_features
full_df, added_features = add_seasonal_rolling_features(
full_df,
rolls=[3],
seasonal_periods=[48, 48 * 7],
column="energy_consumption",
agg_funcs=["mean", "std"],
ts_id="LCLid",
use_32_bit=True,
)
现在,让我们看看在之前的代码片段中使用的参数:
- seasonal_periods: 这是一个季节性周期的列表,应在季节性滚动窗口中使用。在有多个季节性模式的情况下,我们可以包括所有季节性的滚动特征。
- rolls: 该参数接受一个整数列表,表示我们需要在每个窗口上计算的聚合统计量。
- column: 要进行滞后处理的列的名称。在我们的例子中是
energy_consumption。 - agg_funcs: 这是一个聚合函数列表,我们希望对在
rolls中声明的每个窗口进行操作。允许的聚合函数有 {mean, std, max, min}。 - n_shift: 这是在进行滚动操作之前需要的季节性时间步数的偏移量。此参数可防止数据泄漏。
- ts_id: 包含时间序列唯一ID的列名。如果为
None,则假设DataFrame只包含一个时间序列。在我们的例子中,LCLid是该列的名称。 - use_32_bit: 此参数在功能上不做任何更改,但通过牺牲浮动数字的精度,使DataFrame在内存中变得更小。
像往常一样,该方法返回包含季节性滚动特征的DataFrame,以及包含新添加的特征列名的列表。
指数加权移动平均(EWMA)
在滚动窗口均值操作中,我们计算了窗口的平均值,这与简单的移动平均(MA)是同义的。EWMA 是移动平均的一个更智能的变种。虽然移动平均考虑了一个滚动窗口,并且窗口中的每个项在计算平均值时权重相等,EWMA 则在窗口中执行加权平均,并且权重按照指数速率衰减。衰减速率由一个参数 α\alphaα 控制。由于这一点,我们可以将所有可用的历史数据视为一个窗口,并让 α\alphaα 参数决定在 EWMA 中包含多少近期数据。
这个过程可以简洁且递归地表示如下:
其中,y^t\hat{y}ty^t 是时间点 ttt 的预测值,yty_tyt 是实际观测值,y^t−1\hat{y}{t-1}y^t−1 是前一个时刻的预测值, α\alphaα 是平滑因子(介于0和1之间)。
可以看到,α\alphaα 值越大,平均值越偏向近期值(见下图6.6,能够直观地了解权重如何变化)。如果展开递归,每个项的权重会被计算为:
其中 kkk 是距离时间点 TTT 的时间步数。如果我们绘制权重图,我们可以看到它们呈指数衰减,α\alphaα 控制衰减的速率。另一种理解 α\alphaα 的方式是通过跨度(span)。跨度是指在指数加权衰减的权重接近零的周期数(这不是严格的数学定义,而是直观的理解)。α\alphaα 和跨度之间通过以下公式关联:
这个概念在下图中会变得更加清晰,我们已经绘制了不同 α\alphaα 值下的权重衰减情况。
在这里,我们可以看到,权重在达到跨度(span)时变得很小。
直观地说,我们可以将EWMA看作是时间序列整个历史的平均值,但通过像 α\alphaα 和跨度(span)这样的参数,我们可以使历史的不同时间段在平均值中占据更具代表性的地位。如果我们定义一个60周期的跨度,可以认为最近的60个时间周期对平均值的贡献最大。因此,使用不同跨度或 α\alphaα 值生成的EWMA为我们提供了能够捕捉不同历史时间段的代表性特征。
整个过程在下图中得到了展示:
现在,让我们看看如何在 pandas 中实现这一点:
df["ewma"] = df['column'].shift(1).ewm(alpha=0.5).mean()
像我们之前讨论的其他特征一样,EWMA 也需要为每个 LCLid 分别处理。我们在 src.feature_engineering.autoregressive_features 中提供了一个便捷的方法 add_ewma,它可以快速有效地为每个 LCLid 添加所有需要的 EWMA 特征。让我们看看如何使用它。
我们将导入这个方法,并使用其中的一些参数来配置我们想要的 EWMA:
from src.feature_engineering.autoregressive_features import add_ewma
full_df, added_features = add_ewma(
full_df,
spans=[48 * 60, 48 * 7, 48],
column="energy_consumption",
ts_id="LCLid",
use_32_bit=True,
)
现在,让我们来看一下我们在上面的代码片段中使用的参数:
- alphas:这是我们需要计算 EWMA 特征的所有 α\alphaα 值的列表。
- spans:或者,我们可以使用此参数列出所有我们需要计算 EWMA 特征的跨度。如果使用此功能,则会忽略
alphas。 - column:要进行滞后处理的列名。在我们的案例中,这是
energy_consumption。 - n_shift:这是在进行滚动操作之前需要移动的时间步数。此参数用于避免数据泄漏。
- ts_id:包含时间序列唯一 ID 的列名。如果为
None,则假设数据帧只包含单个时间序列。在我们的案例中,LCLid是该列的名称。 - use_32_bit:此参数没有实际功能,但通过牺牲浮动点数值的精度来显著减小数据帧在内存中的大小。
和往常一样,这个方法会返回包含 EWMA 特征的 DataFrame,以及一个列出新添加特征的列名的列表。
这些是将时间延迟嵌入到机器学习模型中的一些标准方式,但您并不局限于这些。正如我们所说,特征工程是一个不受规则约束的领域,我们可以根据需要发挥创意,并将领域知识注入到模型中。除了我们已经看到的特征外,我们还可以包括作为自定义滞后的滞后差异,等等。在大多数实际案例中,我们最终会使用多种时间延迟嵌入方式来构建我们的模型。滞后特征在大多数情况下是最基础和最重要的,但我们确实会通过季节性滞后、滚动特征等方式编码更多信息。就像机器学习中的一切一样,没有万灵药。每个数据集都有其独特性,这使得特征工程在每个案例中都非常重要且不同。
现在,让我们看看我们可以通过时间嵌入添加的另一类特征。
时间嵌入
在第五章《时间序列预测作为回归》中,我们简要讨论了时间嵌入的过程,即我们尝试将时间嵌入到机器学习模型可以利用的特征中。如果我们稍微思考一下时间,我们可以发现,在时间序列预测的背景下,时间的两个方面对我们来说是非常重要的——时间的流逝和时间的周期性。
为了帮助机器学习模型捕捉这些方面,我们可以添加一些特征:
- 日历特征
- 已经过的时间
- 傅里叶项
让我们逐一来看。
日历特征
我们可以提取的第一类特征是基于日历的特征。虽然时间序列的严格定义是按时间顺序收集的一组观察值,但通常我们会有这些收集到的观察值的时间戳。我们可以利用这些时间戳并提取日历特征,如月份、季度、年份中的日期、小时、分钟等。这些特征能够捕捉时间的周期性,并帮助机器学习模型很好地捕捉季节性。只有比时间序列频率更高的日历特征才有意义。例如,在一个具有周频率的时间序列中,小时特征没有意义,但月份特征和周特征是有意义的。我们可以利用 pandas 内置的 datetime 功能来创建这些特征,并将其作为类别特征处理在模型中。
已经过的时间
这是另一个捕捉时间流逝的特征。随着时间的推移,这个特征单调递增,从而使机器学习模型能够感知时间的流逝。创建这个特征的方法有很多种,但最简单且高效的方法之一是使用 NumPy 中的日期整数表示形式:
df['time_elapsed'] = df['timestamp'].values.astype(np.int64)/(10**9)
我们在 src.feature_engineering.temporal_features 中提供了一个有用的方法,叫做 add_temporal_features,它可以自动添加所有相关的时间特征。让我们看看如何使用它。
我们将导入这个方法,并使用它的一些参数来配置和创建时间特征:
full_df, added_features = add_temporal_features(
full_df,
field_name="timestamp",
frequency="30min",
add_elapsed=True,
drop=False,
use_32_bit=True,
)
现在,让我们来看一下我们在前面代码片段中使用的参数:
field_name: 这是包含应用于创建特征的日期时间的列名。frequency: 我们应该提供时间序列的频率作为输入,以便方法自动提取相关特征。这些是标准的 pandas 频率字符串。add_elapsed: 该标志用于开启或关闭已过时间特征的创建。use_32_bit: 这个参数功能上没有任何作用,但可以使 DataFrame 在内存中占用更小的空间,通过牺牲浮动精度来实现。
与我们之前讨论的其他方法一样,这个方法也会返回包含添加的时间特征的新 DataFrame,并返回包含新添加特征的列名的列表。
傅里叶项
之前,我们提取了一些日历特征,如月份、年份等,并讨论了将它们作为类别变量用于机器学习模型。另一种可以表示相同信息的方法,但使用连续的尺度,是通过使用傅里叶项。我们在第三章《分析和可视化时间序列数据》中讨论过傅里叶级数。简而言之,傅里叶级数的正弦-余弦形式如下所示:
在这里,SNS_NSN 是信号 SSS 的 N 项近似。当 N 无穷大时,得到的近似值等于原始信号。P 是周期的最大长度,ana_nan 和 bnb_nbn 分别是扩展中第 n 项的余弦项和正弦项的系数,a0a_0a0 是截距。
我们可以创建这些余弦和正弦函数作为特征来表示季节性周期。如果我们对月份进行编码,我们知道它从 1 到 12,然后重复。因此,在这种情况下,P 将是 12,x 将是 1,2,...,12。因此,对于每个 x,我们可以计算余弦和正弦项,并将它们作为特征添加到机器学习模型中。直观地讲,我们可以认为模型将基于数据推断系数,从而帮助模型更容易地预测时间序列。
下图展示了月份在顺序尺度和作为傅里叶级数表示之间的区别:
前面的图仅展示了一个傅里叶项;我们可以添加多个傅里叶项来帮助捕捉更复杂的季节性。
我们不能简单地说季节性的连续表示比类别表示更好,因为这取决于你使用的模型类型和数据集。这是一个我们需要通过经验来验证的事情。
为了简化添加傅里叶特征的过程,我们在 src.feature_engineering.temporal_features 中提供了一些易于使用的方法,在 bulk_add_fourier_features 文件中,这些方法可以自动为我们想要的所有日历特征添加傅里叶特征。让我们看看如何使用它。
我们将导入该方法,并使用它的一些参数来配置和创建基于傅里叶级数的特征:
python
复制代码
full_df, added_features = bulk_add_fourier_features(
full_df,
["timestamp_Month", "timestamp_Hour", "timestamp_Minute"],
max_values=[12, 24, 60],
n_fourier_terms=5,
use_32_bit=True,
)
现在,让我们看看在前面的代码片段中使用的参数:
- columns_to_encode: 这是我们需要使用傅里叶项进行编码的日历特征列表。
- max_values: 这是日历特征的季节性周期的最大值列表,按
columns_to_encode中给定的顺序排列。例如,对于月份编码,我们给定 12 作为相应的最大值。如果没有给出,max_value将由系统推断。推荐仅在数据中包含至少一个完整的季节性周期时使用此功能。 - n_fourier_terms: 这是要添加的傅里叶项的数量。这与之前提到的傅里叶级数公式中的 n 是同义词。
- use_32_bit: 该参数在功能上没有任何作用,但可以使 DataFrame 在内存中变得更小,牺牲浮动数字的精度。
与我们之前讨论的方法一样,这也返回一个新的 DataFrame,其中包含了傅里叶特征,并且返回一个包含新添加特征列名的列表。
在执行第 6 章中的 01-Feature_Engineering.ipynb 笔记本后,我们将得到以下已生成特征的文件,保存到磁盘中:
selected_blocks_train_missing_imputed_feature_engg.parquetselected_blocks_val_missing_imputed_feature_engg.parquetselected_blocks_test_missing_imputed_feature_engg.parquet
在本节中,我们查看了几种流行且有效的生成时间序列特征的方法。但还有许多其他方法,具体取决于你的问题和领域,其中许多方法都与之相关。
额外信息:
特征工程的领域非常广阔,市面上有一些开源库可以帮助我们更容易地探索这个领域。以下是其中一些库:Nixtla/tsfeatures,tsfresh,DynamicsAndNeuralSystems/catch22。Ben D. Fulcher 的预印本《Feature-based time-series analysis》(链接) 也很好地总结了这一领域。
一个较新的库叫做 functime (链接),它提供了快速的特征工程例程,使用 Polars 编写,值得一看。书中讨论的许多特征工程方法都可以通过 functime 和 Polars 进行加速。
总结
在上一章简要概述了用于时间序列预测的机器学习范式之后,本章我们实践性地探讨了如何准备数据集并提取必要的特征以开始使用这些模型。我们回顾了一些针对时间序列的特征工程技术,如滞后特征、滚动特征和季节性特征。我们在本章中学习的所有技术都是工具,可以帮助我们快速进行实验,以找出哪些方法适用于我们的数据集。然而,我们这里只讨论了特征工程,它影响的是标准回归方程(y = mX + c)的一侧。方程的另一侧,即我们预测的目标(y),同样也非常重要。在下一章,我们将探讨一些概念,如平稳性以及一些影响目标的转换方法。