机器学习时间特征处理:循环编码(Cyclical Encoding)与其在预测模型中的应用

简介: 处理时间特征时,直接使用线性数值会引发“午夜悖论”,导致模型在时间断点处表现失真。本文详解如何用正弦和余弦函数将时间映射为循环特征,解决23:59与00:01的断裂问题,提升模型对周期性模式的理解,适用于小时、星期、月份等场景,显著优化预测效果。

做过电力负荷预测或者交通预测朋友,大概率都处理过时间特征。这里最直接的做法通常是把时间(比如分钟或小时)直接扔进模型里。这看起来逻辑自洽,但存在这一个大坑,就是“午夜悖论”。

比如说你的模型面对两个时间点:23:59(一天的第1439分钟) 和 00:01(一天的第1分钟)。在我们的认知里,这俩只差两分钟,但在模型的逻辑里1439 和 1 可是不一样的。大多数机器学习算法(线性回归、KNN、SVM 甚至神经网络)在处理数值时,默认遵循线性逻辑:数值越大,代表的量级越高。它们理解不了“时间是循环的”这个概念。对它们来说午夜不是终点回到起点的闭环,而是一个断崖。

这就是为什么你加了时间特征,模型却在日期变更线附近表现拉胯的根本原因。

传统编码方式的局限性

处理时间特征,最常见的路数无非两种,但这两种都有硬伤。

整数编码(Integer Encoding)

把 0 到 23 点编码成数字 0-23。这就人为制造了一个断层:23 到 0 的跳跃,被模型视作全天最大的波动。但实际上,晚上 11 点到午夜的变化,跟晚上 9 点到 10 点有什么本质区别吗?完全没有。

下面是这种线性模式下,时间特征在数据层面的表现。

 # Generate data
date_today = pd.to_datetime('today').normalize()
datetime_24_hours = pd.date_range(start=date_today, periods=24, freq='h')
df = pd.DataFrame({'dt': datetime_24_hours})
df['hour'] = df['dt'].dt.hour

# Calculate Sin and Cosine
df["hour_sin"] = np.sin(2 * np.pi * df["hour"] / 24)
df["hour_cos"] = np.cos(2 * np.pi * df["hour"] / 24)

# Plot the Hours in Linear mode
plt.figure(figsize=(15, 5))
plt.plot(df['hour'], [1]*24, linewidth=3)
plt.title('Hours in Linear Mode')
plt.xlabel('Hour')
plt.xticks(np.arange(0, 24, 1))
plt.ylabel('Value')
 plt.show()

线性模式下的小时表示。

独热编码(One-hot Encoding)

既然连续数字有问题,那拆成 24 个独立的列呢?断层是没了但丢失了更重要的东西:邻近性(Proximity)。在独热编码下,凌晨 2 点和 3 点的距离,跟它和晚上 10 点的距离是一样的。模型失去了“时间相邻”这个上下文信息,更别提这会让特征维度爆炸,树模型处理起来效率低,线性模型跑起来也费劲。

解决方案:三角函数映射(Trigonometric Mapping)

解决这个问题的核心在于思维视角的转换:不要把时间看作一条直线,而要看作一个圆。

24小时是一个闭环,我们的编码方式也得闭环。把每一个小时想象成圆周上均匀分布的点,要确定圆上一个点的位置单靠一个数值是不够的,我们需要两个坐标: x and y

这就是正弦(Sine)和余弦(Cosine)发挥作用的地方。

几何原理

圆周上的任意角度都可以通过正弦和余弦映射到一个唯一的坐标点。这种映射赋予了模型一个平滑、连续的时间表示。

 plt.figure(figsize=(5, 5))
 plt.scatter(df['hour_sin'], df['hour_cos'], linewidth=3)
 plt.title('Hours in Cyclical Mode')
 plt.xlabel('Hour')

经过正弦和余弦转换后的循环模式。

计算公式非常简单:

2 * π * hour / 24

:先把小时数值转化成弧度角度。在这个体系下,午夜和晚上 11 点的角度非常接近,通过

sin

cos

将角度投影到两个坐标轴上。

这两个值结合在一起唯一确定了当前的小时,23:00 和 00:00 在特征空间里的距离就被拉得很近了,这正是我们想要的效果。

这套逻辑同样适用于分钟、星期、月份等任何具有周期性的特征。

代码实战

我们拿 UCI 的 Appliances Energy Prediction 数据集来跑个对比实验。模型选用随机森林回归器(Random Forest Regressor)。

Candanedo, L. (2017). Appliances Energy Prediction [Dataset]. UCI Machine Learning Repository. https://doi.org/10.24432/C5VC8G. Creative Commons 4.0 License.

 # Imports
 from sklearn.ensemble import RandomForestRegressor
 from sklearn.model_selection import train_test_split
 from sklearn.metrics import root_mean_squared_error
 from ucimlrepo import fetch_ucirepo

获取数据:

 # fetch dataset 
appliances_energy_prediction = fetch_ucirepo(id=374) 

# data (as pandas dataframes) 
X = appliances_energy_prediction.data.features 
y = appliances_energy_prediction.data.targets 

# To Pandas
df = pd.concat([X, y], axis=1)
df['date'] = df['date'].apply(lambda x: x[:10] + ' ' + x[11:])
df['date'] = pd.to_datetime(df['date'])
df['month'] = df['date'].dt.month
df['day'] = df['date'].dt.day
df['hour'] = df['date'].dt.hour
df.head(3)

先建立一个基准模型(Baseline),使用未处理的线性时间特征。

 # X and y
# X = df.drop(['Appliances', 'rv1', 'rv2', 'date'], axis=1)
X = df[['hour', 'day', 'T1', 'RH_1', 'T_out', 'Press_mm_hg', 'RH_out', 'Windspeed', 'Visibility', 'Tdewpoint']]
y = df['Appliances']

# Train Test Split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Fit the model
lr = RandomForestRegressor().fit(X_train, y_train)

# Score
print(f'Score: {lr.score(X_train, y_train)}')

# Test RMSE
y_pred = lr.predict(X_test)
rmse = root_mean_squared_error(y_test, y_pred)
print(f'RMSE: {rmse}')

基准结果如下:

 Score: 0.9395797670166536
 RMSE: 63.60964667197874

接下来我们对

hour

day

进行循环编码,替换掉原来的线性特征然后重新训练模型。

 # Add cyclical hours sin and cosine
df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24)
df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24)
df['day_sin'] = np.sin(2 * np.pi * df['day'] / 31)
df['day_cos'] = np.cos(2 * np.pi * df['day'] / 31)

# X and y
X = df[['hour_sin', 'hour_cos', 'day_sin', 'day_cos','T1', 'RH_1', 'T_out', 'Press_mm_hg', 'RH_out', 'Windspeed', 'Visibility', 'Tdewpoint']]
y = df['Appliances']

# Train Test Split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Fit the model
lr_cycle = RandomForestRegressor().fit(X_train, y_train)

# Score
print(f'Score: {lr_cycle.score(X_train, y_train)}')

# Test RMSE
y_pred = lr_cycle.predict(X_test)
rmse = root_mean_squared_error(y_test, y_pred)
print(f'RMSE: {rmse}')

结果显示,Score 提升了约 1%,RMSE 下降了 1 个点。

 Score: 0.9416365489096074
 RMSE: 62.87008070927842

看着提升不大?这只是一个简单的 Toy Example,也没做任何精细的数据清洗。但这里的提升完全来自于正弦和余弦变换带来的特征表达能力的增强。

本质上这让模型“看懂”了现实世界中电力需求的连续性,它并不会因为时钟跳过 0 点就突然归零。

为什么 Sin 和 Cos 缺一不可?

很多人可能会想,只用 Sin 这一列行不行?还能省点特征维度。答案是不行。这会破坏对称性。在一个 24 小时的圆周上,早上 6 点和晚上 6 点的 Sine 值可能是一样的。如果只给模型一个值,它就会混淆这两个截然不同的时间段(比如早高峰和晚高峰)。必须同时使用 Sin 和 Cos就像定位必须要有经度和纬度一样。只有这样圆上的每一个小时才能拥有唯一的“特征指纹”。

实际应用中的收益

这套方法在不同模型下的收益是不一样的:

  • 基于距离的模型(KNN, SVMs):这是最大的受益者。循环编码消除了边界上的伪“长距离”,让数据点之间的距离计算回归真实。
  • 神经网络(Neural Networks):平滑的特征空间有助于网络更快的收敛和更稳定的训练表现,消除了午夜那种剧烈的数值跳变。
  • 树模型(Tree-based models):虽然像 XGBoost 或 LightGBM 这种强力模型最终也能通过不断分裂学到这种模式,但提供循环编码特征相当于给了它们一个极佳的先验知识(Inductive Bias),在追求极致性能和解释性时非常有用。

适用场景

使用这套方法的判断标准很简单,问自己一个问题:这个特征是循环往复的吗?如果是,那就试试。常见的例子包括:

  • 一天中的小时(0-23)
  • 一周中的星期(1-7)
  • 一年中的月份(1-12)
  • 风向(0-360度)

总结

时间在数据科学里不应该只是一个冰冷的数字,它本质上是圆周上的坐标。如果你执意把它当直线处理,模型在周期边界处跌倒是迟早的事。使用正弦和余弦进行循环编码,是一种优雅且低成本的修正手段。它保留了数据的邻近性,消除了人工伪影,能让模型学得更快、更准。下次如果你的模型预测曲线在日期交界处出现诡异的跳变,不妨试试这个方法。

https://avoid.overfit.cn/post/5fea3ffcb7ac4b27a3a0d7bb55b9bd39

作者:Gustavo Santos

目录
相关文章
|
机器学习/深度学习 算法 Unix
循环编码:时间序列中周期性特征的一种常用编码方式
循环编码是深度学习中处理周期性数据的一种技术,常用于时间序列预测。它将周期性特征(如小时、日、月)转换为网络可理解的形式,帮助模型识别周期性变化。传统的one-hot编码将时间特征转换为分类特征,而循环编码利用正弦和余弦转换,保持时间顺序信息。通过将时间戳转换为弧度并应用sin和cos,每个原始特征只映射到两个新特征,减少了特征数量。这种方法在神经网络中有效,但在树模型中可能需谨慎使用。
2433 5
|
8月前
|
机器学习/深度学习 传感器 监控
基于 YOLOv8 的智能火灾识别系统设计与实现— 从数据集训练到 PyQt5 可视化部署的完整工程实践
本项目设计并实现了一款基于YOLOv8的智能火灾识别系统,融合深度学习与计算机视觉技术,支持图片、视频、摄像头等多源输入。采用PyQt5开发图形界面,具备高精度、实时性强、易部署等优点,适用于智慧消防、工业巡检等场景,提供完整代码与模型权重,真正实现开箱即用。
697 5
基于 YOLOv8 的智能火灾识别系统设计与实现— 从数据集训练到 PyQt5 可视化部署的完整工程实践
|
8月前
|
消息中间件 人工智能 NoSQL
AgentScope x RocketMQ:打造企业级高可靠 A2A 智能体通信基座
Apache RocketMQ 推出轻量级通信模型 LiteTopic,专为 AI 场景设计,结合 A2A 协议与 AgentScope 框架,实现多智能体高效、可靠协作,支持海量会话持久化、断点续传与动态订阅,重塑企业级 AI 应用架构。
461 28
|
8月前
|
存储 缓存 NoSQL
阿里云 Tair 联手 SGLang 共建 HiCache,构建面向“智能体式推理”的缓存新范式
针对智能体式推理对KVCache的挑战,阿里云Tair KVCache团队联合SGLang社区推出HiCache技术,通过多级存储卸载与全局共享机制,实现缓存命中率翻倍、TTFT降低56%、QPS提升2倍,构建面向长上下文、高并发、多智能体协作的下一代推理缓存基础设施。
1486 27
阿里云 Tair 联手 SGLang 共建 HiCache,构建面向“智能体式推理”的缓存新范式
|
机器学习/深度学习 存储 Dart
100天搞定机器学习|Day63 彻底掌握 LightGBM
100天搞定机器学习|Day63 彻底掌握 LightGBM
100天搞定机器学习|Day63 彻底掌握 LightGBM
|
机器学习/深度学习 编解码 TensorFlow
EfficientNet代码复现--ICML2019
在本文中,我们系统地研究了模型缩放,并确定仔细平衡网络深度、宽度和分辨率可以带来更好的性能。基于这一观察,我们提出了一种新的缩放方法,该方法使用简单但高效的复合系数统一缩放深度/宽度/分辨率的所有维度。我们证明了这种方法在扩展 MobileNets 和 ResNet 方面的有效性。
847 0
EfficientNet代码复现--ICML2019
|
机器学习/深度学习 数据采集 人工智能
机器学习基础知识——基本原理、常用算法与评估指标
机器学习基础知识——基本原理、常用算法与评估指标
2474 0
|
机器学习/深度学习 自然语言处理 PyTorch
使用Transformer 模型进行时间序列预测的Pytorch代码示例
时间序列预测是一个经久不衰的主题,受自然语言处理领域的成功启发,transformer模型也在时间序列预测有了很大的发展。本文可以作为学习使用Transformer 模型的时间序列预测的一个起点。
1627 2
|
数据采集 开发工具 Python
海康威视工业相机SDK+Python+PyQt开发数据采集系统(支持软件触发、编码器触发)
该系统基于海康威视工业相机SDK,使用Python与PyQt开发,支持Gige与USB相机设备的搜索及双相机同时显示。系统提供软件触发与编码器触发模式,并可在数据采集过程中实时保存图像。此外,用户可以调节曝光时间和增益,并进行信息输入,这些信息将被保存至配置文件以便下次自动加载。参数调节与实时预览等功能进一步增强了系统的实用性。
2374 1

热门文章

最新文章