简介:这份资源是GEFCOM2014能源负荷预测项目的配套数据集,面向电力行业研究者、数据科学从业者以及使用R语言进行时间序列建模的学习者,用于探索小时级能源消耗模式并构建预测模型。压缩包为zip格式,整体约111.53MB,上游未提供文件总数与类型明细,但内容围绕多地区历史负荷数据展开,可支撑从数据清洗到建模评估的完整流程。已有1425人学习下载,适合希望借助真实赛题数据练习预测技能的读者。借助R生态中的forecast、caret、ggplot2等工具,读者可实践时间序列分解、ARIMA与状态空间建模、随机森林等机器学习方法,并完成特征工程、交叉验证、参数调优与集成学习。资源还便于对比预测值与实际负荷、绘制残差图与散点图,从而直观评估模型性能,是理解能源消耗规律、积累预测项目经验的实用素材。
1. 能源负荷预测的硬骨头:GEFCOM2014 到底在比什么
GEFCOM2014 是能源负荷预测领域被反复引用的一个基准赛事,全称 Global Energy Forecasting Competition 2014。它不像很多比赛只给一条曲线让你拟合,而是把电力负荷、电价、风电、光伏四个赛道放在一起,每个赛道又分概率预测和点预测,考察的是模型在真实时间序列上的泛化能力。很多人第一次接触它,是冲着“负荷预测”四个字来的,结果打开数据发现:小时级负荷、多区域、温度变量、节假日效应全搅在一起,缺失值和异常值还不少。这个标题里的 gefcom2014-epfl,通常指的是一套围绕该赛事负荷赛道整理出来的实验代码或复现框架,名字里的 epfl 多半是某实验室或某课程项目留下的标记。它要解决的问题很具体:给定历史负荷和温度,预测未来一段时间的电力需求,并且要能给出概率区间,而不只是一个点估计。适合谁?做电力调度、能源管理、时序预测的工程师,以及想拿真实数据练手概率预测的学生。如果你只做过 Kaggle 上那种干净的单变量时序,这个数据集会让你重新认识“脏数据”三个字。
2. 把 GEFCOM2014 负荷数据读明白:结构、粒度与切分逻辑
2.1 数据长什么样:区域、温度与负荷的三角关系
GEFCOM2014 负荷赛道的数据通常按区域组织,每个区域有若干年的小时级负荷记录,同时附带对应气象站的温度。负荷受温度影响很大,夏天降温负荷、冬天取暖负荷都会让曲线出现双峰或尖峰。原始文件一般是 CSV 或文本格式,列名可能是英文缩写,比如 zone_id、timestamp、load、temp。时间戳有的带时区,有的不带,这是第一个容易翻车的地方。我一般会先做三件事:统一时间戳为 UTC 或本地标准时间、检查每个区域的时间跨度是否一致、把温度列按区域对齐到负荷时间轴上。如果温度是多个站点,还要决定是取平均还是选最近站点。这一步不做,后面模型学到的就是错位的因果关系。
2.2 训练/验证切分:为什么不能随机打乱
时序预测最忌讳随机切分。GEFCOM2014 的赛制本身就是滚动预测,所以复现时也要按时间顺序切。常见做法是:用前 N 年做训练,接着几个月做验证,最后一段做测试。比如训练到某年年底,验证下一年前三个月,测试再往后三个月。这样切出来的验证集和测试集才模拟了真实部署场景。如果你用 sklearn 的 train_test_split 直接 shuffle,模型会看到未来的负荷水平,验证分数虚高,上线就崩。我习惯写一个按时间索引切分的函数,确保训练集的最大时间戳小于验证集的最小时间戳。
import pandas as pd def time_split(df, train_end, val_end): """ df: 含 timestamp 列,已按时间排序 train_end: 训练集截止时间字符串 val_end: 验证集截止时间字符串 """ train = df[df['timestamp'] <= pd.Timestamp(train_end)].copy() val = df[(df['timestamp'] > pd.Timestamp(train_end)) & (df['timestamp'] <= pd.Timestamp(val_end))].copy() test = df[df['timestamp'] > pd.Timestamp(val_end)].copy() return train, val, test逻辑说明:这个函数只做时间过滤,不改变行内顺序。参数 train_end 和 val_end 需要根据数据实际跨度来定,比如数据从某年 1 月到某年 12 月,可以设 train_end 为第三年 12 月 31 日,val_end 为第四年 3 月 31 日。注意时间戳格式要统一,如果原始数据是字符串,先转成 pandas Timestamp。
2.3 缺失值与异常值:别急着插值
负荷数据里常见两种脏:一种是传感器掉线导致的连续缺失,一种是尖峰异常。很多人上来就 fillna(method='ffill'),结果把夜间低负荷填成了白天高负荷。我的做法是:先统计每个区域的缺失比例,低于 1% 的用线性插值,高于 5% 的考虑整段丢弃或单独建模。异常值用滚动中位数绝对偏差检测,超过阈值三倍的标记为 NaN,再插值。温度缺失同理,但温度变化平滑,插值风险小一些。注意:插值只能在训练集内部做,验证集和测试集的缺失要用训练集的统计量或前向填充,不能引入未来信息。
3. 从点预测到概率预测:模型选型与特征工程
3.1 为什么 GEFCOM2014 强调概率预测
电力系统调度需要知道“明天最大负荷有 90% 概率不超过多少”,而不是一个孤零零的数字。GEFCOM2014 的评分规则也偏向概率预测,比如用 pinball loss 或 Winkler score。所以复现时不能只训一个回归模型输出点值,要输出分位数或分布参数。常见做法有两类:一类是分位数回归,直接预测多个分位点;另一类是残差建模,先训点预测,再对残差拟合分布。我一般先用 LightGBM 做点预测,再用分位数回归补概率区间,因为树模型对特征交互友好,训练也快。
3.2 特征工程:时间、温度与滞后项
负荷预测的特征无非几类:时间特征(小时、星期、月份、是否节假日)、温度特征(当前温度、滑动平均、温差)、滞后特征(前 1 小时、前 24 小时、前 168 小时负荷)。GEFCOM2014 里温度对负荷的影响是非线性的,夏天高温和冬天低温都会推高负荷,所以我会加温度的平方项和分段哑变量。滞后特征要注意:预测未来 24 小时时,前 24 小时负荷是已知的,但前 1 小时负荷在滚动预测中也是已知的,可以用。但不要用未来时刻的负荷做特征,那是标签泄漏。
import numpy as np def add_features(df): df = df.copy() df['hour'] = df['timestamp'].dt.hour df['dayofweek'] = df['timestamp'].dt.dayofweek df['month'] = df['timestamp'].dt.month df['is_weekend'] = (df['dayofweek'] >= 5).astype(int) # 温度非线性 df['temp_sq'] = df['temp'] ** 2 # 滞后负荷 df['load_lag1'] = df['load'].shift(1) df['load_lag24'] = df['load'].shift(24) df['load_lag168'] = df['load'].shift(168) # 滑动平均温度 df['temp_ma24'] = df['temp'].rolling(24).mean() return df.dropna()逻辑说明:shift 操作会产生 NaN,dropna 会丢掉前 168 行,这是可接受的。参数方面,滞后阶数 24 和 168 分别对应日周期和周周期,如果数据粒度不是小时,要相应调整。温度滑动平均窗口 24 小时是为了平滑短期波动,窗口大小可以根据区域气候调整。
3.3 分位数回归的实现与参数
LightGBM 支持 objective='quantile',指定 alpha 为分位点。比如要预测 0.1、0.5、0.9 三个分位点,就训三个模型,或者用同一个模型不同 alpha。训练时注意样本权重,GEFCOM2014 不同区域负荷量级差异大,可以按区域归一化或加权。参数上,num_leaves 控制在 31 到 127 之间,learning_rate 设 0.05 左右,n_estimators 用早停确定。分位数回归的损失函数对异常值不敏感,但收敛慢一些,可以适当增加迭代次数。
import lightgbm as lgb def train_quantile_model(X_train, y_train, X_val, y_val, alpha): params = { 'objective': 'quantile', 'alpha': alpha, 'metric': 'quantile', 'num_leaves': 63, 'learning_rate': 0.05, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 5, 'verbose': -1 } dtrain = lgb.Dataset(X_train, y_train) dval = lgb.Dataset(X_val, y_val, reference=dtrain) model = lgb.train( params, dtrain, valid_sets=[dval], num_boost_round=2000, callbacks=[lgb.early_stopping(50)] ) return model逻辑说明:alpha 是目标分位点,比如 0.1 表示预测下界。early_stopping 防止过拟合。注意分位数模型不能直接用默认的 rmse 早停,要用 quantile 指标。如果验证集损失震荡,可以降低 learning_rate 或增大 early_stopping 轮数。
4. 避坑与排查:GEFCOM2014 复现中最容易翻车的五个点
4.1 时间戳时区不一致导致特征错位
现象:训练时分数正常,验证时误差突然变大,检查发现温度滞后负荷 6 小时。原因:负荷时间戳是本地时间,温度是 UTC,没统一。解决:所有时间列先转成同一时区,再合并。我一般用 pandas 的 tz_localize 和 tz_convert,如果原始无时区,按数据说明指定。
4.2 滞后特征在预测未来多步时失效
现象:预测未来 24 小时,模型用了 load_lag1,但实际预测时未来 1 小时负荷未知。原因:训练时 shift 产生的滞后特征在滚动预测中不可得。解决:多步预测要改成递归预测或直接多输出,或者只用截止预测起点已知的滞后。常见做法是训练时用 teacher forcing,预测时递归填充。
4.3 分位数交叉
现象:预测的 0.1 分位点大于 0.5 分位点,违反常识。原因:三个分位点模型独立训练,没有约束。解决:训练后做后处理,按分位点排序调整,或者用联合分位数回归。我一般用简单的排序修正,保证单调性。
4.4 区域归一化导致量级信息丢失
现象:所有区域一起训练,模型对大负荷区域预测偏小。原因:做了全局归一化,小负荷区域主导了损失。解决:按区域分别归一化,或者用区域 ID 做特征让模型自己学。GEFCOM2014 区域差异大,我倾向于分区域建模再合并。
4.5 验证集泄露
现象:验证分数异常高,测试分数暴跌。原因:特征工程用了全量数据的统计量,比如全局均值归一化。解决:所有统计量只在训练集上计算,再应用到验证和测试。滚动窗口的均值和方差也要用历史窗口。
5. 进阶技巧:用递归多步与集成提升概率预测稳定性
5.1 递归多步预测的实现
GEFCOM2014 要求预测未来多个小时,直接多输出模型容易忽略时序依赖。递归预测是训一个单步模型,预测下一步后把预测值当作已知滞后填回去,再预测下一步。缺点是误差累积,但配合概率输出可以给出区间。实现时注意:每次递归都要重新计算时间特征和温度特征,温度可以用预报值或持久性假设。
def recursive_forecast(model, last_known, steps, feature_cols): """ last_known: 包含最近 168 小时数据的 DataFrame steps: 预测步数 feature_cols: 模型输入特征列 """ preds = [] history = last_known.copy() for _ in range(steps): # 构造下一时刻特征 next_time = history['timestamp'].iloc[-1] + pd.Timedelta(hours=1) row = {} row['hour'] = next_time.hour row['dayofweek'] = next_time.dayofweek row['month'] = next_time.month row['is_weekend'] = int(next_time.dayofweek >= 5) row['temp'] = history['temp'].iloc[-1] # 持久性假设 row['temp_sq'] = row['temp'] ** 2 row['load_lag1'] = history['load'].iloc[-1] row['load_lag24'] = history['load'].iloc[-24] row['load_lag168'] = history['load'].iloc[-168] row['temp_ma24'] = history['temp'].iloc[-24:].mean() X_next = pd.DataFrame([row])[feature_cols] y_next = model.predict(X_next)[0] preds.append(y_next) # 更新历史 new_row = history.iloc[-1:].copy() new_row['timestamp'] = next_time new_row['load'] = y_next new_row['temp'] = row['temp'] history = pd.concat([history, new_row], ignore_index=True) return preds逻辑说明:这个函数假设温度用持久性预测,实际可以替换成温度预报。feature_cols 要和训练时一致。递归步数一般不超过 24,再长误差太大。注意 history 要保留至少 168 行,否则 lag168 取不到。
5.2 集成多个分位点模型
单模型分位数可能不稳定,可以把不同随机种子、不同特征子集的模型集成。简单平均或加权平均,权重用验证集 pinball loss 的倒数。集成后分位数交叉概率降低,区间更平滑。我一般训 5 个模型,每个用不同的 bagging_seed 和 feature_fraction,然后对每个分位点分别平均。
5.3 验证概率预测的指标
点预测看 MAE 或 RMSE,概率预测看 pinball loss 和覆盖率。覆盖率指真实值落在预测区间内的比例,比如 80% 区间应该有约 80% 的样本落入。如果覆盖率远低于名义值,说明区间太窄;远高于则太宽。我习惯画一张可靠性图,横轴是名义分位点,纵轴是实际覆盖率,理想是对角线。这张图能快速暴露模型是系统性高估还是低估不确定性。
| 指标 | 含义 | 理想值 |
|---|---|---|
| Pinball Loss | 分位数损失,越小越好 | 接近 0 |
| 覆盖率 | 真实值落入区间的比例 | 接近名义分位点 |
| 区间宽度 | 预测区间的平均宽度 | 在覆盖率达标下越窄越好 |
5.4 一个具体技巧:用温度预报替代持久性假设
递归预测里温度用持久性假设会累积误差,如果能有温度预报,哪怕粗糙的,也能显著提升。我一般用滑动平均温度作为预报,或者用历史同期温度。如果数据里有未来温度列,直接用;没有的话,用前 24 小时温度均值加一个日周期修正。这个技巧在 GEFCOM2014 负荷赛道上很实用,因为温度是外生变量,比负荷本身好预测。
最后说个血泪经验:我最早做这个数据集时,为了刷高验证分数,把未来温度也当特征用了,结果测试集一塌糊涂。后来养成习惯,任何特征先问一句“预测时刻真的能拿到吗”。这个习惯比任何模型都值钱。希望帮到你。
本文还有配套的精品资源,点击获取