3. 特征工程:时间特征、滑动窗口、差分、归一化与标准化方法
各位同学,咱们接着聊。上一章我们把原始数据清洗干净了,但说实话,原始数据就像一块没加工的木头,直接扔进模型里,效果往往不理想。特征工程,就是给这块木头雕花、上漆的过程。我个人习惯把这一步叫做「喂给模型之前,帮它把题目标出来」。
发电量预测里,特征工程尤其重要。为什么?因为电力数据有很强的时间依赖性,还有周期性。你想想看,今天的发电量跟昨天、上周同一天、甚至去年同一天都有关系。模型自己可不懂这些,得靠我们帮它提炼出来。
核心观点:特征工程做得好,模型就成功了一半。剩下的另一半,是调参和运气(笑)。
3.1 时间特征:让模型「看懂」时间
时间特征,说白了就是把日期时间拆成模型能理解的东西。我见过不少新手直接把时间戳扔进模型,结果模型一头雾水。你想想看,模型怎么理解「2024-03-15 14:30:00」这个字符串?它只能当成一个数字或者类别来处理,但时间背后的周期性信息就全丢了。
常用的时间特征包括:
- 小时:0-23,捕捉日内负荷变化。中午和傍晚通常是高峰。
- 星期几:0-6,区分工作日和周末。周末工厂停工,用电量明显下降。
- 月份:1-12,反映季节性变化。夏天开空调,冬天取暖。
- 是否节假日:0或1,这个特征在光伏预测里特别重要。节假日工厂停工,但居民用电可能增加。
- 一年中的第几天:1-365/366,捕捉长期趋势。
我的小技巧:对于小时、星期这类循环特征,我习惯用正弦和余弦编码。比如小时,sin(2π*h/24) 和 cos(2π*h/24),这样23点和0点之间的距离就不是23,而是1。模型能更好地理解「午夜前后」的连续性。
举个例子,我在做某个光伏电站的预测时,发现模型在下午4点到6点之间总是高估发电量。后来一查,原来那段时间是下班高峰期,电网负荷变化剧烈。加上「是否高峰时段」这个特征后,误差直接降了8%。
3.2 滑动窗口:让模型「记住」历史
滑动窗口,也叫滞后特征。它的核心思想是:用过去的数据来预测未来。比如,用过去3小时的发电量来预测下一小时的发电量。
常见的滑动窗口特征:
- 滞后值:t-1, t-2, ..., t-n 时刻的发电量。
- 滚动统计量:过去n小时的均值、标准差、最大值、最小值。
- 差分值:当前时刻与上一时刻的差值,反映变化趋势。
我记得有一次,一个学员问我:「窗口大小怎么选?」我的回答是:没有标准答案,但有个经验法则——窗口大小至少覆盖一个完整周期。对于小时级数据,至少24小时;对于日级数据,至少7天。当然,最终还是要靠交叉验证来调。
# Python 示例:生成滑动窗口特征
import pandas as pd
def create_window_features(df, target_col='power', window=24):
"""
生成滑动窗口特征
window: 窗口大小,默认24小时
"""
df = df.copy()
# 滞后特征
for i in range(1, window+1):
df[f'lag_{i}'] = df[target_col].shift(i)
# 滚动统计量
df['rolling_mean'] = df[target_col].rolling(window=window).mean()
df['rolling_std'] = df[target_col].rolling(window=window).std()
df['rolling_max'] = df[target_col].rolling(window=window).max()
df['rolling_min'] = df[target_col].rolling(window=window).min()
# 注意:shift和rolling会产生NaN,需要处理
df = df.dropna()
return df
# 使用示例
# df_with_features = create_window_features(df, window=24)
避坑指南:我曾经犯过一个错误——在生成滑动窗口特征时,不小心用到了未来的数据。比如用t+1时刻的数据来预测t时刻。这在训练时看起来效果很好,但一到线上就崩了。记住:永远不要用未来信息预测过去。shift()函数里的正负号一定要搞对。
3.3 差分:让数据「平稳」下来
差分,说白了就是「今天比昨天多了多少」。为什么要做差分?因为很多时间序列模型(比如ARIMA)要求数据是平稳的。发电量数据通常有趋势和季节性,差分可以去除这些成分。
常见的差分方法:
- 一阶差分:Δy_t = y_t - y_{t-1},去除线性趋势。
- 季节性差分:Δy_t = y_t - y_{t-24}(对于小时级数据),去除日周期性。
- 二阶差分:在一阶差分的基础上再做一次差分,去除二次趋势。
你可能会问:「差分后的数据怎么还原?」嗯,这个问题问得好。预测完成后,需要做逆差分才能得到真实的发电量。比如,如果你做了一阶差分,预测结果是Δy,那么真实值就是 y_t = y_{t-1} + Δy_t。
# Python 示例:差分与逆差分
import numpy as np
def difference(series, period=1):
"""一阶差分"""
return series.diff(periods=period)
def inverse_difference(original_series, diff_series, period=1):
"""逆差分"""
# 注意:original_series 是差分前的序列,需要提供初始值
return original_series.shift(period) + diff_series
# 示例
# diff_power = difference(df['power'], period=1)
# original_power = inverse_difference(df['power'], diff_power, period=1)
我的经验:在光伏预测中,我通常只做一阶差分。因为光伏发电有明显的日周期性,但季节性差分(比如t-24)会丢失太多信息。而且,现在的深度学习模型(如LSTM)本身就能处理非平稳数据,不一定非要差分。具体用不用,取决于你选什么模型。
3.4 归一化与标准化:让特征「公平」竞争
归一化和标准化,是特征工程里最容易理解但也最容易出错的一步。它们的目的是把不同量纲的特征映射到同一个尺度上,避免某些特征因为数值大而主导模型。
三种常用方法:
| 方法 | 公式 | 输出范围 | 适用场景 |
|---|---|---|---|
| Min-Max 归一化 | X' = (X - X_min) / (X_max - X_min) | [0, 1] | 数据有明确边界,如温度、湿度 |
| Z-score 标准化 | X' = (X - μ) / σ | 均值0,标准差1 | 数据近似正态分布,或有异常值 |
| RobustScaler | X' = (X - median) / IQR | 无固定范围 | 数据有较多异常值 |
我的建议:对于发电量预测,我一般先用Z-score标准化。因为发电量数据通常有异常值(比如设备故障导致的突降),Z-score对异常值不那么敏感。如果异常值特别多,我会改用RobustScaler。Min-Max归一化我用的比较少,因为它对异常值太敏感了。
这里有个关键点:一定要先拆分训练集和测试集,再用训练集的参数去归一化测试集。我曾经见过有人把整个数据集一起归一化,然后才拆分。这样做会导致数据泄露——测试集的信息提前被模型「看到」了,评估结果会虚高。
# Python 示例:正确的归一化流程
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
# 假设 X 是特征矩阵,y 是目标变量
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False)
# 用训练集拟合 scaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
# 用训练集的参数转换测试集
X_test_scaled = scaler.transform(X_test)
# 注意:y(目标变量)通常不需要归一化,除非模型要求
避坑指南:我曾经在做一个风电预测项目时,把风速和发电量一起归一化了。结果模型预测的发电量总是偏小。后来才发现,风速和发电量虽然相关,但它们的分布不同。归一化后,模型把风速的波动也「传染」给了发电量。从那以后,我坚持特征和目标变量分开处理。
3.5 特征工程的完整流程
好了,上面讲了四个核心方法。在实际项目中,它们通常是组合使用的。我一般按这个顺序来:
- 先做时间特征:提取小时、星期、月份等。
- 再做滑动窗口:生成滞后值和滚动统计量。
- 然后做差分:如果模型需要平稳数据。
- 最后做归一化/标准化:把所有特征映射到同一尺度。
你可能会问:「顺序重要吗?」嗯,重要。比如,如果你先归一化再做差分,差分后的数据就不再是归一化的了。所以,归一化一定要放在最后一步。
另外,特征工程不是一次性的。我习惯先做一个基线模型,然后逐步添加特征,观察模型效果的变化。如果某个特征加了之后效果反而变差,那就果断去掉。特征不是越多越好,有时候「少即是多」。
好了,这一章的内容就到这里。特征工程是个细活,需要耐心和实验。下一章我们会讲模型选择,到时候会用到今天讲的特征。记得多动手写代码,光看是学不会的。