百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

独家 时间信息编码为机器学习模型特征的三种方法(附链接)

toyiye 2024-04-27 03:47 23 浏览 0 评论


作者:Eryk Lewinson
翻译:张睿毅校对:张睿毅
本文约4200字,建议阅读10分钟本文我们主要使用非常知名的Python包,以及依赖于一个相对不为人知的scikit-lego包。


标签:数据帧, 精选, 机器学习, Python, 技术演练



设置和数据


在本文中,我们主要使用非常知名的Python包,以及依赖于一个相对不为人知的scikit-lego包,这是一个包含许多有用功能的库,这些功能正在扩展scikit-learn的功能。我们导入所需的库,如下所示:


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import date
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import FunctionTransformer
from sklearn.metrics import mean_absolute_error
from sklego.preprocessing import RepeatingBasisFunction


为了简单起见,我们使用自生成数据。在此示例中,我们使用人工时间序列。我们首先创建一个空的数据帧,其索引跨越四个日历年(我们使用pd.date_range)。然后,我们创建两列:


  • day_nr – 表示时间流逝的数字索引
  • day_of_year – 一年中的第一天


最后,我们必须创建时间序列本身。为此,我们将两条变换的正弦曲线和一些随机噪声结合起来。用于生成数据的代码基于scikit-lego文档中包含的代码。


# 避免重复
np.random.seed(42)


# 生成日期的数据格式
range_of_dates = pd.date_range(start="2017-01-01",
                           End="2020-12-30")
X = pd.DataFrame(index=range_of_dates)


# 创建日期数据的序列
X["day_nr"] = range(len(X))
X["day_of_year"] = X.index.day_of_year


# 生成目标成分
signal_1 = 3 + 4 * np.sin(X["day_nr"] / 365 * 2 * np.pi)
signal_2 = 3 * np.sin(X["day_nr"] / 365 * 4 * np.pi + 365/2)
noise = np.random.normal(0, 0.85, len(X))


# 合并获取目标序列
y = signal_1 + signal_2 + noise


# 画图
y.plot(figsize=(16,4), title="Generated time series");

图 1:生成的时间序列。


然后,我们创建一个新的 DataFrame,在其中存储生成的时间序列。此数据帧将用于比较使用不同特征工程方法的模型性能。


results_df = y.to_frame()
results_df.columns = ["actuals "]


创建与时间相关的要素


在本节中,我们将介绍生成时间相关特征的三种老练方法。


在我们深入研究之前,我们应该定义一个评估框架。我们的模拟数据包含四年的观察结果。我们将使用生成的前 3 年的数据作为训练集,并将在第4年进行评估。我们将使用平均绝对误差 (MAE) 作为评估指标。


下面我们定义一个变量,该变量将用于分割两个集合:


TRAIN_END = 3 * 365


方法1:虚拟变量


我们从您很可能已经熟悉的东西开始,至少在某种程度上是这样。对时间相关信息进行编码的最简单方法是使用虚拟变量(也称为单热编码)。



让我们看一个示例。


X_1 = pd.DataFrame(
data=pd.get_dummies(X.index.month, drop_first=True, prefix="month")
)


在下面,您可以看到我们操作的输出。


表 1:带有月份假人的数据帧。


首先,我们从 DatetimeIndex 中提取有关月份的信息(编码为 1 到 12 范围内的整数)。然后,我们使用pd.get_dummies函数来创建虚拟变量。每列都包含有关观测值(行)是否来自给定月份的信息。


您可能已经注意到,我们已经降低了一个级别,现在只有11列。我们这样做是为了避免在使用线性模型时可能出现的臭名昭著的虚拟变量陷阱(完美的多重共线性)问题。


在我们的示例中,我们使用虚拟变量方法来获取观测值的月份。其实也可以使用相同的方法获取来自 DatetimeIndex 的一系列其他信息。例如,一年中的日/周/季度,给定一天是否为周末的标志,一个周期的第一天/最后一天等等。可以找到一个列表,其中包含所有可能的从pandas文档索引中提取的功能,可在 pandas.pydata.org找到。


额外提示:以下已经不属于简单练习的范围了,但在现实生活中,我们还可以使用有关特殊日子的信息(想想国定假日,圣诞节,黑色星期五等)来创建功能。holidays是一个不错的Python库,包含每个国家/地区特殊日子的信息,无论过去和未来。


如简介中所述,特征工程的目标是将复杂性从模型转移到特征集。这就是为什么我们将使用最简单的ML模型之一 -线性回归 – 展示一下拟合时间序列的程度,在我们仅使用创建的虚拟数据下。


model_1 = LinearRegression().fit(X_1.iloc[:TRAIN_END], y.iloc[:TRAIN_END])


results_df["模型_1"] = model_1.predict(X_1)
results_df[["actuals","model_1"]].plot(figsize=(16,4),title="用月虚拟变量拟合")
plt.axvline(date(2020, 1, 1), c="m", linestyle="--");


图 2:使用月份假人进行拟合。垂直线将训练集和测试集分开。


我们可以看到,拟合线已经很好地遵循了时间序列,尽管它有点锯齿状(类似阶梯) - 这是由虚拟特征的不连续性引起的。因此我们将尝试通过接下来的两种方法解决此问题。


但在继续之前,值得一提的是,当使用非线性模型(例如决策树(或其集合))时,别将诸如月份,或一年中的某天等特征显式编码设为随机数。这些模型能够学习序数输入特征与目标之间的非单调关系。


方法#2:具有正弦/余弦变换的循环编码


正如我们前面所看到的,拟合的线类似于步骤。这是因为每项虚拟数据都是单独处理的,没有连续性。然而,例如时间等变量存在明显的周期连续性。这意味着什么呢?


想象一下,我们正在处理购买者的数据。当我们纳入观察到的购买者消费月份的信息时,如果连续两个月之间存在更强的联系,是有道理的。按照这个逻辑,12月和1月之间以及1月和2月之间的联系很强。相比之下,1月和7月之间的联系就并不那么紧密。这道理同样适用于其他与时间相关的信息。


那么,我们如何将这些知识融入特征工程中呢?三角函数啊。我们可以使用以下正弦/余弦变换将循环时间特征编码为两个特征。


def sin_transformer(period):
return FunctionTransformer(lambda x: np.sin(x / period * 2 * np.pi))


def cos_transformer(period):
return FunctionTransformer(lambda x: np.cos(x / period * 2 * np.pi))


在下面的代码片段中,我们复制初始 DataFrame,添加带有月份数字的列,然后 使用正弦/余弦变换对月份和day_of_year两项进行编码。然后,我们绘制两对曲线。


X_2 = X.copy()
X_2["月"] = X_2.index.month


X_2["月_sin"] = sin_transformer(12).fit_transform(X_2)[" 月"]
X_2["月_cos"] = cos_transformer(12).fit_transform(X_2)[" 月"]


X_2["日_sin"] = sin_transformer(365).fit_transform(X_2)["每年的日"]
X_2["日_cos"] = cos_transformer(365).fit_transform(X_2)[" 每年的日"]


fig, ax = plt.subplots(2, 1, sharex=True, figsize=(16,8))
X_2[["月_sin", "月_cos"]].plot(ax=ax[0])
X_2[["日_sin", "日_cos"]].plot(ax=ax[1])
plt.suptitle("用正余弦变换循环编码");


图 3:基于每月和每日频率的正弦/余弦变换。


如图 3 所示,我们可以从转换后的数据中得出两个知识。首先,我们可以很容易地看到,当使用月份进行编码时,曲线是阶跃的,但是当使用每日频率时,曲线要平滑得多;其次,我们也可以理解为什么我们必须使用两条曲线而不是一条曲线。由于曲线的重复性,如果在绘图中绘制一条单年水平直线,则会在两个地方穿过曲线。这还不足以让模型了解观测值的时间点。但是有了这两条曲线,就没有这样的问题,用户可以识别出每一个时间点。让大家看得更明白点,我们在散点图上绘制正弦/余弦函数的值。在图 4 中,我们可以看到一个圆的模式,没有重叠。


图 4:正弦和余弦变换的散点图。


让我们仅使用来自每日频率的新创建要素来拟合相同的线性回归模型。


X_2_daily = X_2[["day_sin", "day_cos"]]
model_2 = LinearRegression().fit(X_2_daily.iloc[:TRAIN_END],y.iloc[:TRAIN_END])
results_df["model_2"] = model_2.predict(X_2_daily)
results_df[["actuals", "model_2"]].plot(figsize=(16,4), title="使用正弦/余弦特征拟合")
plt.axvline(date(2020, 1, 1), c="m", linestyle="--");

图 5:使用正弦/余弦变换拟合。垂直线将训练集和测试集分开。


图 5 显示,该模型能够拾取数据的总体趋势,识别具有较高和较低的周期。但是,预测的幅度似乎不太准确,乍一看,这种拟合似乎比使用第一种方法,虚拟变量,实现的拟合更差(图 2)。


在我们讨论第三种特征工程技术之前,值得一提的是,这种方法存在一个严重的缺点,尤其会在使用基于树的模型时,缺点很明显。最初设计,基于树的模型就是基于当时的单个特征进行拆分。正如我们之前提到的,正弦/余弦特征应该同时考虑,以便正确识别一段时间内的时间点。


方法#3:径向基函数


最后一种方法使用径向基函数。我们不会详细介绍它们的实际情况,但您可以在此处阅读有关该主题的更多信息。从本质上讲,我们再次希望解决第一种方法遇到的问题,即我们的时间特征具有连续性。


我们使用方便的scikit-lego库,它提供了RepeatmentBasisFunction类,并指定了以下参数:


  • 我们要创建的基函数的数量(我们选择:12个)。
  • 用于为 径向基函数(RBF)编制索引的列。我们这里采用的列是,该观测值来自一年中的哪一天。
  • 输入范围 – 我们这里,范围是从1到365。
  • 如何处理数据帧的其余列,我们将使用这些数据帧来拟合估计器。"drop"将仅保留创建的 RBF 功能,"passthrough "将保留旧功能和新功能。


rbf = RepeatingBasisFunction(n_periods=12,
                         column="day_of_year",
                         input_range=(1,365),
                         remainder="drop")
rbf.fit(X)
X_3 = pd.DataFrame(index=X.index,
               data=rbf.transform(X))


X_3.plot(subplots=True, figsize=(14, 8),
     sharex=True, title="Radial Basis Functions",
     legend=False);


图 6:12 个径向基函数。


图 6 展示,我们使用日数作为输入,创建了 12 个径向基函数。每条曲线都包含有关多靠近本年某一天的信息(在选择此列的情况下)。例如,第一条曲线测量从1月1日开始的距离,因此它在每年的第一天达到峰值,在之后和当初升高的幅度对称地减小。


根据设计,基函数在输入范围内的间距相等。我们选择了12,因为我们希望RBF类似于月份。这样,每个函数都会显示到月份第一天的距离(由于月份的长度不相等)。


与前面的方法类似,让我们使用 12 个RBF 特征去拟合线性回归模型。


model_3 = LinearRegression().fit(X_3.iloc[:TRAIN_END], y.iloc[:TRAIN_END])


results_df["model_3"] = model_3.predict(X_3)
results_df[["actuals", "model_3"]].plot(figsize=(16,4), title="使用RBF特征拟合")
plt.axvline(date(2020, 1, 1), c="m", linestyle="--");


图 7:使用径向基函数拟合。垂直线将训练集和测试集分开。


图 7 显示,当使用 RBF 功能时,该模型能够准确地捕获真实数据。


使用径向基函数时,我们可以调整两个关键参数:


  • 径向基函数的数目,
  • 钟形曲线的形状 – 可以使用 RepeatingBasis 函数的宽度参数对其进行修改。


调整这些参数值的一种方法是使用网格搜索来确定给定数据集的最佳值。


最终比较


我们可以执行以下代码段,以生成编码时间相关信息的不同方法的数字比较。


results_df.plot(title="对比不同时间特征的拟合",figsize=(16,4), color = ["c", "k", "b", "r"])
plt.axvline(date(2020, 1, 1), c="m", linestyle="--");


图 8:使用基于不同时间特征获得的模型,比较拟合。垂直线分开的是训练集和测试集


图 8 表明,径向基函数与所考虑的方法最接近。正弦/余弦特征允许模型拾取主要模式,但不足以完全捕获系列的动态。


使用下面的代码段,我们计算每个模型在训练集和测试集上的平均绝对误差。我们预计训练集和测试集的分数之间非常相似,因为生成的序列几乎完全是周期性的 - 年份之间的唯一区别是随机分量。


当然,在现实生活中情况并非如此,在现实中,随着时间的推移,我们会在同一时期之间遇到更多的变化。但是,在这种情况下,我们还会使用许多其他特征(例如趋势,或时间流逝的某种度量)来解释这些变化。


score_list = []
for fit_col in ["model_1", "model_2", "model_3"]:
scores = {
    "model": fit_col,
    "train_score": mean_absolute_error(
        results_df.iloc[:TRAIN_END]["actuals"],
        results_df.iloc[:TRAIN_END][fit_col]
    ),
    "test_score": mean_absolute_error(
        results_df.iloc[TRAIN_END:]["actuals"],
        results_df.iloc[TRAIN_END:][fit_col]
    )
}
score_list.append(scores)


scores_df = pd.DataFrame(score_list)
scores_df


与之前一样,我们可以看到使用RBF特征的模型产生了最佳拟合,而正弦/余弦特征的表现最差。我们对训练集和测试集所得分数之间的相似性假设也得到了证实。

表 2:训练/测试集的分数 (MAE) 比较。


总结


  • 我们展示了三种将时间相关信息编码为机器学习模型特征的方法。
  • 除了最流行的虚拟编码之外,还有一些方法更适合编码时间的循环性质。
  • 使用这些方法时,时间间隔的粒度对于新创建的要素的形状非常重要。
  • 使用径向基函数,我们可以决定要使用的函数的数量,以及钟形曲线的宽度。


您可以在我的GitHub上找到本文中使用的代码。如果您有任何反馈,我很乐意在Twitter上讨论。


引用


[1] 时间相关的特征工程

https://scikit-learn.org/stable/auto_examples/applications/plot_cyclical_feature_engineering.html

[2] 预处理

https://scikit-lego.readthedocs.io/en/latest/preprocessing.html

[3] 时间/日期因素

https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#time-date-components


关于作者



Eryk Lewinson是一位具有定量金融背景的数据科学家。在他的职业生涯中,他曾在两家咨询公司工作,一家是金融科技规模的扩大公司,最近一次是在荷兰最大的在线零售商。在他的工作中,他使用机器学习为公司生成可操作的见解。目前,他将精力集中在时间序列预测领域。Eryk还出版了一本书 - Python for Finance Cookbook - 他在书中探讨了现代数据科学解决方案在定量金融领域的各种应用。他的书的第二版计划于2022年出版。在业余时间,他喜欢玩电子游戏,与女朋友一起旅行,并撰写与数据科学相关的主题。他的文章已被浏览超过250万次。


原文标题:

Three Approaches to Encoding Time Information as Features for ML Models

原文链接:

https://developer.nvidia.com/blog/three-approaches-to-encoding-time-information-as-features-for-ml-models/

相关推荐

Python 可视化工具包(python常见的可视化工具)

喜欢用Python做项目的小伙伴不免会遇到这种情况:做图表时,用哪种好看又实用的可视化工具包呢?本文将介绍一些常用的Python可视化包,包括这些包的优缺点以及分别适用于什么样的场景。这篇文章...

Python的GPU编程实例——近邻表计算

目录技术背景...

python算法体验-3.python实现欧式距离的三种方式

欧式距离也称欧几里得距离,是最常见的距离度量,衡量的是多维空间中两个点之间的绝对距离。欧式距离源自N维欧氏空间中两点...

python实现Lasso回归分析(特征筛选、建模预测)

实现功能:...

python语言检测模块langid、langdetect使用

本文首发地址:https://blog.csdn.net/Together_CZ/article/details/86678423欢迎关注我的博客【Together_CZ】,我是沂水寒城!之前使用数据...

7天学会Python最佳可视化工具Seaborn(一):可视化变量间的关系

众所周知,Seaborn“可能”是Python下最友好、易用的可视化工具了,可视化效果也非常好。但是截止目前,并没有一份中文教程供广大国内Python使用者查阅学习。怎么能因为语言的问题,让大家错过这...

在Python中使用K-Means聚类和PCA主成分分析进行图像压缩

各位读者好,在这篇文章中我们尝试使用sklearn库比较k-means聚类算法和主成分分析(PCA)在图像压缩上的实现和结果。压缩图像的效果通过占用的减少比例以及和原始图像的差异大小来评估。图像压...

OpenCV-Python 相机校准 | 四十九

目标在本节中,我们将学习由相机引起的失真类型,如何找到相机的固有和非固有特性如何根据这些特性使图像不失真基础一些针孔相机会给图像带来明显的失真。两种主要的变形是径向变形和切向变形。径向变形会导致直线出...

python数据预处理技术(python 数据预处理)

在真实世界中,经常需要处理大量的原始数据,这些原始数据是机器学习算法无法理解的。为了让机器学习算法理解原始数据,需要对数据进行预处理。我们运行anaconda集成环境下的“jupyternotebo...

【Python可视化系列】一文教你绘制不同类型散点图(理论+源码)

这是...

OpenCV-Python 特征匹配 | 四十四

目标在本章中,我们将看到如何将一个图像中的特征与其他图像进行匹配。我们将在OpenCV中使用Brute-Force匹配器和FLANN匹配器Brute-Force匹配器的基础蛮力匹配器很简单。它使用第一...

实战python中Random模块使用(python中的random模块)

一、random模块简介Python标准库中的random函数,可以生成随机浮点数、整数、字符串,甚至帮助你随机选择列表序列中的一个元素,打乱一组数据等。要在Python中使用random模块,只需要...

Python随机模块22个函数详解(python随机函数的应用)

随机数可以用于数学,游戏,安全等领域中,还经常被嵌入到算法中,用以提高算法效率,并提高程序的安全性。平时数据分析各种分布的数据构造也会用到。random模块,用于生成伪随机数,之所以称之为伪随机数,是...

说冲A就冲A,这个宝藏男孩冯俊杰我pick了

爱奇艺新上架了一部网剧叫《最后一个女神》。有个惊人的发现,剧里男三居然是《青春有你》的训练生冯俊杰。剧组穷,戏服没几件,冯俊杰几乎靠一件背背佳撑起了整部剧。冯俊杰快速了解一下。四川人,来自觉醒东方,人...

唐山打人嫌犯陈继志去医院就医的背后,隐藏着三个精心设计的步骤

种种迹象表明,陈继志这帮人对处理打人之后的善后工作是轻车驾熟的,他们想实施的计划应该是这样的:首先第一步与伤者进同一家医院做伤情鉴定,鉴定级别最好要比对方严重,于是两位女伤者被鉴定为轻伤,他们就要求医...

取消回复欢迎 发表评论:

请填写验证码