4、特征工程:滞后特征、滚动窗口统计量、时间编码
特征工程,说白了就是给模型喂「对的食物」。
我见过太多人,模型选得再好,调参再努力,最后效果不行——回头一看,特征全是原始值,连个滞后项都没加。嗯,这就像让一个厨师只用盐做菜,能好吃才怪。
今天咱们聊三个核心操作:滞后特征、滚动窗口统计量、时间编码。这三板斧,基本覆盖了90%的时序特征需求。
4.1 滞后特征:让模型「看见」过去
滞后特征,就是把过去某个时刻的值,搬到当前时刻来用。
举个例子:你想预测明天的销量,那今天的销量、昨天的销量、甚至上周同一天的销量,都是极好的特征。
我个人习惯,先做1阶滞后,再看业务周期。如果是日数据,我会做lag-1、lag-7、lag-30。为什么?因为日周期、周周期、月周期,这三个节奏基本覆盖了大部分场景。
lag_k(t) = value(t - k)
代码实现很简单,用pandas的shift方法就行:
import pandas as pd
# 假设df是时序数据,按日期排序
df['lag_1'] = df['value'].shift(1)
df['lag_7'] = df['value'].shift(7)
df['lag_30'] = df['value'].shift(30)
我曾经踩过一个坑:做滞后特征时忘了排序。数据是乱序的,shift出来的值根本不是「过去」,而是「随便某个时间点」。结果模型训练时看起来不错,上线后直接崩了。所以记住:先排序,再shift。
4.2 滚动窗口统计量:捕捉「趋势」和「波动」
滞后特征只能看到「某一个点」,但很多时候,我们需要看「一段区间」的统计量。
比如:过去7天的平均销量、过去30天的销量标准差、过去14天的最大值……这些就是滚动窗口统计量。
为什么需要它?因为模型需要理解「趋势」和「波动」。你想想看,如果今天销量突然暴涨,模型光看昨天的值可能反应不过来,但如果它知道「过去7天平均销量很低」,就能判断出「今天是个异常点」。
常用的滚动统计量包括:
- 均值:反映趋势
- 标准差:反映波动
- 最大值/最小值:反映极值
- 中位数:抗噪性强
- 偏度/峰度:反映分布形态(高阶用法)
# 滚动窗口统计量示例
df['rolling_mean_7'] = df['value'].rolling(window=7).mean()
df['rolling_std_7'] = df['value'].rolling(window=7).std()
df['rolling_max_14'] = df['value'].rolling(window=14).max()
这里有个细节:滚动窗口默认是「左闭右开」的,也就是说,当前时刻的值也会被算进去。如果你想要「纯历史」的统计量,记得用shift再偏移一下:
# 纯历史均值(不含当前时刻)
df['rolling_mean_7_pure'] = df['value'].shift(1).rolling(window=7).mean()
嗯,这个坑我踩过不止一次。有一次做实时预测,模型总是「提前知道」当前值,导致线上效果远差于离线。后来才发现,滚动窗口默认包含了当前时刻。
4.3 时间编码:让模型理解「时间」本身
时间本身也是一种特征。模型不懂「2024年3月15日」是什么意思,但你可以把它拆解成:年、月、日、星期几、第几周、是否节假日……
我建议至少做以下几类时间编码:
| 编码类型 | 示例 | 说明 |
|---|---|---|
| 年 | 2024 | 捕捉年度趋势 |
| 月 | 1-12 | 捕捉季节性 |
| 日 | 1-31 | 捕捉月度内模式 |
| 星期几 | 0-6 | 捕捉周周期 |
| 是否周末 | 0/1 | 二值化,简单有效 |
| 第几周 | 1-52 | 捕捉周级别周期 |
| 一年中的第几天 | 1-365 | 连续型,适合线性模型 |
# 时间编码示例
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day'] = df['date'].dt.day
df['weekday'] = df['date'].dt.weekday # 0=周一, 6=周日
df['is_weekend'] = (df['weekday'] >= 5).astype(int)
df['week_of_year'] = df['date'].dt.isocalendar().week
df['day_of_year'] = df['date'].dt.dayofyear
周期性编码怎么做?举个例子:
# 周期性编码示例(以月份为例)
import numpy as np
df['month_sin'] = np.sin(2 * np.pi * df['month'] / 12)
df['month_cos'] = np.cos(2 * np.pi * df['month'] / 12)
这样,12月和1月在sin/cos空间里就是相邻的点了。我一般在做深度学习模型时必加这个,树模型倒不一定需要。
4.4 本章知识体系
下面这张图,帮你理清三个特征工程操作的关系:
4.5 实战建议与避坑指南
最后,分享几个我自己的经验:
- 不要一次性生成太多滞后特征。我见过有人生成lag-1到lag-100,结果模型过拟合得一塌糊涂。先做业务分析,再选关键滞后阶数。
- 滚动窗口的边界处理要小心。尤其是预测时,你只能用到「历史数据」,不能「偷看」未来。我建议在训练时就用shift模拟预测场景。
- 时间编码不是越多越好。年、月、日、星期几这四个基本够用。加太多细粒度特征(比如小时、分钟),反而容易引入噪声。
- 特征工程完成后,记得做相关性分析。如果两个特征高度相关(比如lag-1和lag-2),考虑只保留一个,或者做PCA降维。
嗯,特征工程这部分就聊到这儿。记住一句话:好的特征,比好的模型更重要。你花80%的时间做特征,20%的时间调模型,这个比例是合理的。
公众号:蓝海资料掘金营,微信deep3321