第四章 特征工程:让数据开口说话

特征工程,说白了就是给模型喂“好料”。

我做了这么多年风电功率预测,最深的体会就是:模型再牛,也架不住特征太烂。你想想看,如果输入的都是噪音,输出能准吗?

这一章,我们就聊聊怎么从原始数据里挖出真正有用的特征。我个人习惯把这部分工作分成四大块:时间特征、气象特征、历史功率特征、滚动特征。咱们一个一个来。

4.1 时间特征:捕捉周期规律

风电功率有明显的日周期和季节周期。白天风大还是晚上风大?夏天和冬天风速分布一样吗?这些规律,得靠时间特征来告诉模型。

4.1.1 小时特征

小时特征不是简单地把“1-24”扔进去。我建议做正弦-余弦编码。为什么?因为23点和0点其实很近,但数值上差了很多。模型会以为它们很远。

# 小时的正弦-余弦编码
import numpy as np

hour = np.array([0, 1, 2, ..., 23])
hour_sin = np.sin(2 * np.pi * hour / 24)
hour_cos = np.cos(2 * np.pi * hour / 24)

这样处理后,23点和0点在二维空间里就是相邻的。嗯,这里要注意:所有周期性特征都建议这么处理,包括月份、星期几。

4.1.2 季节特征

季节对风电的影响太大了。我在北方某风电场做过项目,冬天平均风速比夏天高出30%以上。季节特征我一般用两种方式:

  • 硬编码:直接标1-4,或者用one-hot编码
  • 连续编码:用月份的正弦-余弦,这样季节过渡更平滑

我个人更倾向第二种。因为季节是渐变的过程,不是突然从春天跳到夏天的。

小技巧:如果你用月份的正弦-余弦,记得把月份从1-12映射到0-2π。我见过有人直接用月份数值,结果模型把12月和1月当成最远的两个点——这显然不对。

4.2 气象特征:核心驱动力

风速、风向、温度、气压、湿度,这五个是气象特征的核心。但直接扔进去?不行。得加工。

4.2.1 风速特征

风速是功率预测最重要的特征,没有之一。但要注意:风速和功率不是线性关系。我建议做以下处理:

  • 风速的平方、立方:因为功率和风速的三次方近似成正比
  • 风速的指数变换:比如取对数,让分布更接近正态
  • 风速的差分:风速变化率,能反映天气系统的移动
# 风速特征扩展
wind_speed = df['wind_speed'].values
df['wind_speed_sq'] = wind_speed ** 2
df['wind_speed_cu'] = wind_speed ** 3
df['wind_speed_log'] = np.log1p(wind_speed)  # 避免0值问题
df['wind_speed_diff'] = np.diff(wind_speed, prepend=wind_speed[0])

4.2.2 风向特征

风向也是周期特征,同样需要正弦-余弦编码。但这里有个坑:风向对功率的影响不是对称的。比如,有些风电场北风时功率高,南风时功率低,但东风和西风可能差不多。

我曾经遇到过一个项目,模型预测偏差很大。排查了很久,发现是风向编码的问题。后来我加了一个风向分段特征:把360度分成8个扇区,每个扇区一个二进制特征。效果立竿见影。

避坑指南:我曾经把风向直接作为数值特征输入,结果模型学到的规律全是错的。因为0度和360度是同一个方向,但数值差360。一定要用正弦-余弦或者分段编码。

4.2.3 温度、气压、湿度

这三个特征相对简单,但也有一些门道:

特征 常见处理 我的经验
温度 标准化或归一化 注意昼夜温差大的地区,建议用滑动平均平滑
气压 直接使用或取差分 气压变化率比绝对值更有用,能指示天气系统变化
湿度 标准化 湿度高时往往风速低,这个交互特征值得关注

4.3 历史功率特征:让模型记住过去

风电功率有很强的自相关性。现在的功率和过去几分钟、几小时的功率高度相关。所以,历史功率特征是必加的。

4.3.1 滞后特征

滞后特征就是过去时刻的功率值。我一般会取以下滞后:

  • 短时滞后:t-1, t-2, t-3(分钟级)
  • 中时滞后:t-10, t-30(十分钟到半小时级)
  • 长时滞后:t-60, t-120(小时级)

具体取哪些滞后,要看你的预测步长和数据频率。我习惯先做自相关分析,看看哪些滞后相关性高,再决定。

4.3.2 统计特征

除了原始值,我还会计算滑动窗口内的统计量:

# 滑动窗口统计特征
window = 10  # 过去10个时间步
df['power_mean_10'] = df['power'].rolling(window=window).mean()
df['power_std_10'] = df['power'].rolling(window=window).std()
df['power_max_10'] = df['power'].rolling(window=window).max()
df['power_min_10'] = df['power'].rolling(window=window).min()

这些统计特征能反映功率的波动情况。比如,标准差大说明功率不稳定,模型可能需要更保守的预测。

4.4 滚动特征:捕捉动态变化

滚动特征和滑动窗口统计有点像,但更强调变化趋势。我个人觉得这是特征工程里最容易被忽视的部分。

4.4.1 差分特征

一阶差分:当前值减去上一个值。二阶差分:一阶差分的差分。差分能反映变化速度。

df['power_diff_1'] = df['power'].diff(1)
df['power_diff_2'] = df['power'].diff(2)
df['wind_speed_diff_1'] = df['wind_speed'].diff(1)

4.4.2 比率特征

有时候相对变化比绝对变化更有意义。比如:

  • 当前风速 / 过去1小时平均风速
  • 当前功率 / 过去1小时平均功率
  • 功率变化率:(当前功率 - 过去功率) / 过去功率

这些比率特征能消除量纲影响,让模型更关注相对变化。

4.4.3 趋势特征

我还会用线性回归拟合过去N个时间步的斜率,作为趋势特征。比如,过去10分钟风速是上升还是下降?上升速度有多快?

# 计算趋势斜率
from scipy import stats

def calc_trend(series):
    x = np.arange(len(series))
    slope, _, _, _, _ = stats.linregress(x, series)
    return slope

df['power_trend_10'] = df['power'].rolling(window=10).apply(calc_trend)
核心要点:特征工程不是越多越好。我见过有人一口气造了200多个特征,结果模型过拟合得一塌糊涂。记住:质量比数量重要。每个特征都要问自己:这个特征和功率预测有关系吗?它会不会引入噪音?

4.5 特征工程知识体系

下面这张图,是我对本章知识体系的总结。你可以把它当作一个检查清单,做特征工程时对照着来。

特征工程知识体系 时间特征 • 小时正弦-余弦 • 季节连续编码 • 星期几/月份 • 节假日标记 气象特征 • 风速:平方/立方/对数 • 风向:正弦-余弦/分段 • 温度:平滑处理 • 气压:差分/变化率 • 湿度:交互特征 历史功率特征 • 短时滞后(t-1,t-2) • 中时滞后(t-10,t-30) • 长时滞后(t-60,t-120) • 滑动窗口统计 滚动特征 • 一阶/二阶差分 • 比率特征 • 趋势斜率 • 变化率 核心原则:质量 > 数量,每个特征都要有物理意义 💡 我的经验之谈 特征工程占整个项目60%以上的工作量 不要一次性造太多特征,先加10个,看效果,再加 和现场工程师聊聊,他们知道哪些气象条件影响最大

好了,特征工程这部分就聊到这儿。记住:好的特征能让模型事半功倍。下一章我们聊聊特征选择,看看怎么从一堆特征里挑出最精华的部分。


公众号:蓝海资料掘金营,微信deep3321