4. 特征工程基础:时间特征提取、滞后特征与滚动窗口统计
各位同学,咱们今天聊点实在的。
特征工程这东西,说白了就是「喂给模型什么数据」。你想想看,同样的算法,有人跑出SOTA,有人跑出笑话,差距往往就在特征上。我个人习惯把特征工程分成三块:时间特征、滞后特征、滚动统计特征。今天咱们一个一个拆开讲。
4.1 时间特征提取:把时钟变成模型能懂的语言
风电功率预测,本质上是和时间打交道。但模型不认识「2025年3月15日下午3点」这种格式。你得帮它翻译成数字。
核心思路:把时间戳拆解成周期性分量和趋势性分量。
4.1.1 小时特征
小时是24小时循环的。但直接扔个0~23的数字进去,模型会以为23和0离得很远——其实它们只差1小时。我建议用正弦余弦编码:
import numpy as np
import pandas as pd
def encode_hour(df, col='timestamp'):
"""对小时做正弦余弦编码"""
hour = df[col].dt.hour
df['hour_sin'] = np.sin(2 * np.pi * hour / 24)
df['hour_cos'] = np.cos(2 * np.pi * hour / 24)
return df
嗯,这里要注意:正弦余弦要一起用,单用一个会丢失相位信息。我在项目里吃过这个亏,只用了正弦,结果模型把凌晨3点和下午3点搞混了——因为sin(3)和sin(15)数值一样。
4.1.2 季节与月份特征
风电功率有明显的季节性。冬天风大,夏天风小,这是常识。但月份也是循环的,12月和1月挨着。同样用正弦余弦:
def encode_month(df, col='timestamp'):
month = df[col].dt.month
df['month_sin'] = np.sin(2 * np.pi * month / 12)
df['month_cos'] = np.cos(2 * np.pi * month / 12)
return df
如果你觉得月份粒度太粗,可以加一个「季节」标签:春季(3-5)、夏季(6-8)、秋季(9-11)、冬季(12-2)。我个人习惯两者都保留,让模型自己学哪个更重要。
小技巧:还可以提取「一年中的第几天」作为特征。这个值从1到365,能反映更细粒度的季节变化。我一般在做长期预测(72小时以上)时才会加这个。
4.2 滞后特征构建:让模型看到「过去」
风电功率有很强的自相关性。说白了,现在的风速和几分钟前的风速关系很大。滞后特征就是把这些「过去的值」拿过来当特征。
4.2.1 什么是滞后特征?
假设当前时刻是t,那么t-1时刻的功率值就是滞后1步的特征。t-2就是滞后2步,以此类推。
def create_lag_features(df, col='power', lags=[1, 2, 3, 6, 12, 24]):
"""构建滞后特征"""
for lag in lags:
df[f'lag_{lag}'] = df[col].shift(lag)
return df
我曾经犯过一个错误:对全数据集直接shift,导致训练集里混进了未来的信息。记住,一定要在分组后、按时间顺序shift。比如按风机ID分组:
df['lag_1'] = df.groupby('turbine_id')['power'].shift(1)
4.2.2 滞后步长怎么选?
这个没有标准答案。我一般会先做自相关分析(ACF图),看看哪些滞后步长相关性高。通常:
- 短期预测(1-4小时):滞后1、2、3、6步就够了
- 中期预测(24小时):需要滞后12、24、48步
- 长期预测(72小时以上):滞后步长可以稀疏一些,比如24、48、72
避坑指南:滞后特征越多,模型越容易过拟合。我曾经在一个项目里加了30个滞后特征,训练集R²高达0.98,测试集直接崩到0.6。后来砍到6个滞后特征,测试集反而升到0.85。记住:少即是多。
4.3 滚动窗口统计特征:捕捉局部趋势
滞后特征只给了单个时间点的值,但风电功率的「趋势」也很重要。滚动窗口统计特征就是计算过去一段时间内的统计量,比如均值、标准差、最大值、最小值。
4.3.1 常用滚动统计量
def create_rolling_features(df, col='power', windows=[3, 6, 12, 24]):
"""构建滚动窗口统计特征"""
for w in windows:
df[f'rolling_mean_{w}'] = df.groupby('turbine_id')[col].transform(
lambda x: x.rolling(w, min_periods=1).mean()
)
df[f'rolling_std_{w}'] = df.groupby('turbine_id')[col].transform(
lambda x: x.rolling(w, min_periods=1).std()
)
df[f'rolling_max_{w}'] = df.groupby('turbine_id')[col].transform(
lambda x: x.rolling(w, min_periods=1).max()
)
df[f'rolling_min_{w}'] = df.groupby('turbine_id')[col].transform(
lambda x: x.rolling(w, min_periods=1).min()
)
return df
你想想看,滚动均值能反映「最近一段时间的平均水平」,滚动标准差能反映「波动剧烈程度」。这两个特征在风电预测中特别有用——风大的时候波动也大,模型需要知道这个规律。
4.3.2 窗口大小怎么定?
| 预测场景 | 推荐窗口大小 | 说明 |
|---|---|---|
| 超短期(15分钟-1小时) | 3、6、12 | 捕捉分钟级波动 |
| 短期(1-6小时) | 6、12、24 | 捕捉小时级趋势 |
| 中期(6-24小时) | 12、24、48 | 捕捉日周期变化 |
| 长期(24-72小时) | 24、48、72 | 捕捉多日趋势 |
个人经验:滚动窗口不要太多,3~4个不同尺度的窗口就够了。我一般选一个短窗口(比如3步)、一个中窗口(12步)、一个长窗口(24步)。再多的话特征维度爆炸,收益却不大。
4.4 特征工程整体流程
好了,咱们把三个部分串起来。下面这张图展示了完整的特征工程流程:
流程很简单:原始时间序列进来,分三条路走。时间特征负责「现在是什么时候」,滞后特征负责「刚才发生了什么」,滚动统计负责「最近一段时间的趋势」。最后合并成一张大特征表,喂给模型。
4.5 实战中的避坑指南
最后,我把自己踩过的坑总结一下:
- 数据泄露问题:滞后特征和滚动特征都要在分组后、按时间顺序计算。千万别用未来的数据算过去的特征。
- 特征冗余:滞后24步和滚动均值24步,信息有重叠。我一般会做特征重要性排序,去掉冗余的。
- 缺失值处理:滚动窗口刚开始时会有NaN。用
min_periods=1可以缓解,但前几个样本的特征值不太准。我习惯直接丢掉前几个样本。 - 特征标准化:时间特征(正弦余弦)天然在[-1,1]区间,但滞后特征和滚动特征的量级可能很大。记得做标准化或归一化。
一句话总结:时间特征告诉模型「什么时候」,滞后特征告诉模型「刚才怎样」,滚动特征告诉模型「趋势如何」。三者结合,风电功率预测的准确率能提升10%~20%。
好了,特征工程的基础就这些。下一节咱们聊聊数值天气预报特征怎么融合进来——那又是另一个故事了。
公众号:蓝海资料掘金营,微信deep3321