在电力市场交易和电网运营中,电价预测一直是个既基础又关键的课题。传统单变量时序预测方法(如ARIMA、LSTM等)虽然能捕捉价格自身的时间依赖性,但往往忽略了影响电价的众多外部因素。这就像试图仅通过昨天的天气来预测今天的温度——虽然有一定相关性,但显然忽略了气压、湿度、季节等更丰富的环境信息。
我最近完成的一个电力交易系统项目中,就深刻体会到了这一点。当我们仅用历史电价数据训练模型时,预测误差始终徘徊在12-15%之间;而引入负荷、时段特征等多元变量后,误差直接降到了8%以下。这个实战案例促使我系统梳理了多变量电价预测的方法论,特别是随机森林在这一场景下的独特优势。
在决定采用随机森林之前,我们团队其实评估过多种方案:
python复制候选模型 = [
LSTM网络,
梯度提升树(如XGBoost),
传统ARIMA,
随机森林
]
最终选择随机森林主要基于以下判断:
我们做了组对照实验很有意思:在同一组特征下,比较LSTM和随机森林的表现:
| 指标 | LSTM | 随机森林 | 优势方 |
|---|---|---|---|
| 训练时间 | 2.3小时 | 9分钟 | 随机森林 |
| 测试集MAE | 8.2 | 7.8 | 随机森林 |
| 特征解释性 | 差 | 优秀 | 随机森林 |
| 超参敏感度 | 高 | 低 | 随机森林 |
这个结果让不少团队成员感到意外——在tabular数据上,传统机器学习方法往往比深度学习更高效。
我们的特征矩阵包含三大类信息:
实时负荷数据
load_mw:当前总负荷(MW)load_change_rate:相比上一时段的负荷变化率(x - μ) / σ时间特征工程
python复制def time_encoder(t):
hour_sin = np.sin(2*np.pi*t.hour/24)
hour_cos = np.cos(2*np.pi*t.hour/24)
return hour_sin, hour_cos
is_workday = (t.weekday() < 5)市场规则特征
python复制def get_period_type(hour):
if 8 <= hour < 11: return 'peak'
elif 19 <= hour < 22: return 'peak'
else: return 'off_peak'
我们通过网格搜索确定了最优滞后组合:
| 特征类型 | 窗口大小 | 计算方式 |
|---|---|---|
| 滞后特征 | [1, 4, 8, 16, 96] | price[t-lag] |
| 滚动均值 | [4, 16, 96] | mean(price[t-w:t]) |
| 滚动标准差 | [4, 16, 96] | std(price[t-w:t]) |
| 变化率 | [1, 4] | (price[t]-price[t-1])/price[t-1] |
关键经验:滞后96(即24小时周期)对电价预测特别重要,这反映了日周期规律
递推预测的核心逻辑如下:
python复制def recursive_predict(model, init_features, steps=96):
predictions = []
current_features = init_features.copy()
for _ in range(steps):
# 预测下一时刻
pred = model.predict([current_features])[0]
predictions.append(pred)
# 更新特征:用预测值替代真实值
current_features['price_lag_1'] = pred
current_features = update_rolling_stats(current_features, pred)
return predictions
在实践中我们发现三个典型问题:
我们的解决方案:
脚本中直接使用未来负荷数据是不现实的。我们的改进方案:
code复制负荷历史数据 → 负荷预测模型 → 预测负荷值
↓
电价预测模型 ← 合并其他已知特征
单次尾部测试容易过拟合,我们改为:
python复制def rolling_backtest(data, model, window_size=30):
metrics = []
for i in range(len(data)-window_size):
train = data[:i]
test = data[i:i+window_size]
model.fit(train)
pred = model.predict(test)
metrics.append(calc_metrics(test, pred))
return metrics
部署后必须监控:
我们开发了特征健康度看板:
code复制特征名 当前均值 历史均值 Z-score 状态
load_mw 452.3 438.1 2.14 ▲
price_lag_1 58.2 62.3 -1.05 ▼
当监测到性能下降时,触发以下流程:
我们发现80%的预测能力来自20%的关键特征:
load_mw)period_type)price_lag_96)price_roll_std_4)通过特征置换重要性测试确认:
| 特征 | 重要性分数 | 剔除后MAE变化 |
|---|---|---|
| load_mw | 0.32 | +42% |
| period_type | 0.18 | +15% |
| price_lag_96 | 0.15 | +12% |
| price_lag_1 | 0.08 | +5% |
随机森林的关键参数及其影响:
n_estimators:树的数量(我们设为200,超过后收益递减)max_depth:控制过拟合(最佳值通常在15-25之间)min_samples_leaf:对电价预测很关键(设为3效果最好)调参代码示例:
python复制param_grid = {
'n_estimators': [100, 200, 300],
'max_depth': [10, 15, 20],
'min_samples_leaf': [1, 3, 5]
}
grid_search = GridSearchCV(
estimator=RandomForestRegressor(),
param_grid=param_grid,
cv=TimeSeriesSplit(n_splits=3)
)
优秀的预测模型最终要服务于业务决策。我们开发了以下应用场景:
交易策略优化:
发电计划调整:
python复制if predicted_price > operating_cost:
turn_on_additional_generators()
风险控制看板:
错误做法:
正确做法:
python复制scaler.fit(train_data) # 仅用训练数据拟合
test_data = scaler.transform(test_data) # 应用相同变换
低效方式:
推荐方案:
python复制period_type_encoder = OneHotEncoder()
X_train = encoder.fit_transform(X_train[['period_type']])
不充分做法:
完整评估:
python复制metrics = {
'MAE': mean_absolute_error,
'RMSE': lambda y, p: np.sqrt(mean_squared_error(y,p)),
'MAPE': lambda y, p: np.mean(np.abs((y-p)/y)),
'Peak_Accuracy': peak_hit_rate # 自定义指标
}
对于希望进一步提升的团队,建议:
特征层面:
模型层面:
系统层面:
经过三个月的迭代,我们总结出几点核心经验:
这个项目的完整代码库包含:
对于希望复现或拓展的团队,建议从核心特征工程开始,逐步添加业务特定的变量。记住:没有放之四海而皆准的特征组合,最好的特征永远来自对业务的深刻理解。