简介:这份 GEFCOM2014-EPFL 能源负荷预测资料面向电力行业数据分析人员、高校师生及 R 语言实践者,围绕小时级、多地区的历史负荷数据,帮助读者理解能耗模式并完成从时间序列探索到预测建模的完整练习,可作为课程设计、竞赛练习或课题复现的参考材料。内容覆盖 ts 对象与趋势、季节性分解,ARIMA、状态空间模型及随机森林、支持向量机、神经网络等预测方法,也涉及温度、节假日、天气预报等特征工程,以及 MSE、RMSE、MAE、R² 等评估指标、网格搜索调参、集成学习和残差图、预测对比图等可视化环节。压缩包约 111.53MB,文件总数显示为 0、类型明细暂缺,具体目录需以下载后的实际内容为准。已有 1425 人学习,适合具备一定 R 基础、希望借助 forecast、caret、ggplot2、dplyr 等工具链系统复现负荷预测流程,并积累调参、评估与结果解读经验的中级读者。
1. 一道让多数人答不上来的题:你的负荷预测模型为什么输给"昨天同一时刻"
GEFCOM14 的能源负荷预测题在面试里最常被拿来开场:给你 20 个分区、11 个气象站的多年逐小时负荷与温度记录,预测未来一天的逐小时负荷,你的基线模型选什么。多数人张口就是线性回归或者 LSTM,面试官通常不接话,只回一句"和昨天同一时刻比呢"。这一问考的不是模型新旧,而是你知不知道负荷序列的强周期性,以及温度响应为什么有滞后——这正是 GEFCOM14 这套数据最想筛出来的东西。能把"基线为什么是 naive"讲清楚的人,后面讲分位数回归、讲层级聚合才有人愿意听;讲不清的人,模型再花哨也只是堆名词。
2. 考点分布:五块考点与 pinball loss 这条隐形分数线
这套数据看起来像一个回归问题,实际是五块考点叠在一起:数据规格、特征工程、概率预测、层级一致性、评分与汇报。面试官问的顺序基本也是这个顺序,从"你拿到数据先干什么"往下推。真正拉开差距的是后两块,因为前两块大部分人能背,后两块才是分水岭。先把分值心里有个数:数据规格与清洗约占两成,特征工程约占三成,概率预测与模型约占三成,层级一致性与评分汇报各占一成。你不可能样样满分,但要保证不会因为最基础的那两成被直接判出局。
2.1 数据长什么样:20 个分区、11 个气象站与一条强周期曲线
先把字段关系摆清楚。负荷侧是 20 个分区(zone)的逐小时电量,时间戳对齐到整点;气象侧是 11 个站点的逐小时温度,站点和分区不是一一对应,需要通过空间加权把温度映射到分区上。这个"多对多"的映射关系是第一道坎,很多人在这里就开始拍脑袋,直接取算术平均,然后在温度敏感的月份集体翻车。
数据里能一眼看出来的结构有三层。第一层是日内周期,早晚两个峰,峰谷比在夏季和冬季都不小;第二层是周周期,工作日和周末的曲线形态不同,周一和周五还各自有特点;第三层是年周期加节假日,年末年初那几天的形状完全脱离常规。面试时如果只答"有周期性和趋势",等于没答,要具体到"日、周、年三个尺度分别用什么项去拟合"。
还有一个容易被忽略的点:分区之间的负荷不是独立的。某个分区温度骤降,相邻分区往往同步上升,这种空间相关性在层级聚合题里会变成考点。你在特征表里加不加邻区温度,加不加总量负荷的滞后项,都是可以展开讲两分钟的内容。
提示:面试里被问数据规格时,先说清"对象、粒度、时间跨度、缺失情况"四件事,再谈建模,顺序反了会显得你没碰过真实数据。
2.2 评分口径:pinball loss 怎么算,为什么中位数不是重点
这类赛题的评分不是 RMSE,而是分位数损失,也就是 pinball loss。对分位水平 τ 和预测值 q:
- 若实际值 y ≥ q,损失为 τ × (y − q)
- 若实际值 y < q,损失为 (1 − τ) × (q − y)
对所有样本和所有分位水平取平均,就是总分。这个式子看着简单,含的信息量很大:它奖励"该保守时保守、该激进时激进",而不是一味压低平均误差。所以你交上去的 99 条分位线必须是单调的,τ=0.99 的预测值不能低于 τ=0.95 的预测值,否则模型内部就自相矛盾。
面试官最想听的是你对"分布形状"的理解。负荷的预测分布在高峰时段往往是右偏的,上分位数拉得比下分位数开;到了深夜低谷,分布变窄,你还按同一套宽度去给区间,就会被评分规则惩罚。能说出"不同时段、不同季节的分位宽度应该不一样,我是怎么让它不一样的",这一问基本就过了。
反过来说,只盯中位数(τ=0.5)是本末倒置。中位数误差好看,不代表 5% 和 95% 的尾部靠谱,而尾部恰好是电网调度最关心的部分——尖峰时段预测偏低一点,代价远比平时大。
2.3 真题演练:基线模型为什么选 naive 和气候态
面试题:如果只允许你用一行代码做预测,你选什么?为什么?
标准答法是选两个,不是选一个:一个是"昨天同一时刻"(naive,滞后 24 小时),一个是"去年同期同一时刻"或者"同月同小时的历史均值"(气候态基线)。前者抓住日周期,后者抓住年周期。回答时要把两者的失效场景讲出来:
naive 在平稳的日子几乎打不过,但遇到节假日、极端天气、温度骤变,它会把前一天的所有异常原样搬过来,误差爆炸。气候态在季节切换时稳,但对连续的阴雨天反应迟钝,因为它把每一天都当成"平均水平"。
所以正确的表述是"先用这两个基线划一条线,任何模型必须先稳定赢过它们,再谈复杂度"。这时候面试官通常会追问"如果模型只赢 3% 呢",你要答"先看赢在哪几个时段、哪几个分位,如果只在深夜低负荷段占优,那这 3% 意义不大"。
这里有个实战细节值得讲:把验证段按月份切开,逐月对比基线和模型的差距,画一张小表。如果某个月模型突然输给 naive,八成是那个月有节假日或者寒潮,属于特征没覆盖,而不是模型不行。
| 基线 | 抓什么 | 典型失效场景 | 是否建议保留 |
|---|---|---|---|
| 滞后 24 小时 | 日周期、近期水平 | 节假日、温度骤变 | 保留,作为下限 |
| 历史同月同小时均值 | 年周期、季节水平 | 连续异常天气、趋势漂移 | 保留,作为长期锚 |
| 上周同日 | 周周期 | 跨季节、长假 | 可选,做补充 |
3. 高频考点:三张特征表决定你能不能过技术面
负荷预测的模型部分,公开资料里讲得都差不多,真正区分人的是特征怎么造。面试官心里通常有三张表:温度表、日历表、滞后表。你把这三张表讲明白,模型用什么反而是次要的。这一章就按这三张表拆,每一张都给出可复现的做法和对应的追问。
3.1 温度:平滑温度、度日与分段线性响应
负荷和温度的关系不是一条直线,而是一条 U 形或者 V 形:低温段随温度上升负荷下降(采暖需求减弱),高温段随温度上升负荷上升(制冷需求增强),中间有一段相对平坦的舒适区。直接丢一个原始温度进线性模型,等于强行假设单调关系,必然欠拟合。
第二个问题是滞后。建筑有热惯性,人对温度的响应也不是瞬时的,今天的负荷受过去十几到几十小时温度的影响。所以常见做法是先对温度做滑动平均,再造度日变量(cooling degree days、heating degree days),用两个分段项去逼近那条 U 形曲线。
# 温度特征:滞后平滑 + 分段线性(度日) library(dplyr) build_temp_features <- function(weather, k = 24, base_c = 18, base_h = 15) { weather %>% arrange(datetime) %>% mutate( # 过去 k 小时滑动平均:模拟建筑热惯性,sides = 1 表示只用历史 temp_smooth = stats::filter(temp, rep(1 / k, k), sides = 1), # 制冷度日:高于基准温度的部分才计入 cdd = pmax(temp_smooth - base_c, 0), # 采暖度日:低于基准温度的部分才计入 hdd = pmax(base_h - temp_smooth, 0) ) }代码里有三个参数值得在面试时主动解释。k 控制平滑窗口,取 24 意味着"过去一天的平均温度",取 48 到 72 更贴近墙体蓄热的实际时间常数,具体取值要靠验证段试。base_c 和 base_h 是两个拐点温度,不同分区因为气候和建筑结构不同,拐点会漂移,所以这两个值最好不要全分区共用一个,而是按分区各自标定,或者干脆让模型自己学。
还有一个进阶做法可以说:把温度按分位数分箱,每箱一个哑变量,等价于非参数的响应曲线。这个方法在答辩里很好用,因为你可以直接画出"分箱后的负荷均值曲线"给面试官看,形状对不对一目了然。
注意:滑动平均必须只用当前时刻及之前的数据,用居中窗口会把未来温度泄漏进来,验证分数虚高,上线就崩。
3.2 日历:傅里叶项、节假日哑变量与桥接日
日历特征最容易被做浅。只放"星期几"和"月份"两个因子,等于告诉模型"每个月的每一天都一样",这在负荷预测里是明显不成立的。比较完整的做法是三套东西并存。
第一套是傅里叶项,用几组正弦余弦去拟合年内和日内的平滑周期。优点是参数少、不会过拟合,适合捕捉缓慢的季节过渡。第二套是类别哑变量,星期几、是否周末、是否月初月末,这些是硬切换,用傅里叶项拟合不干净。第三套是节假日及其前后各一两天,也就是所谓的"桥接日",因为负荷在假期前一天就开始往下掉,假期结束后一两天才恢复。
# 日历特征:傅里叶项 + 星期哑变量 + 节假日窗口 build_calendar_features <- function(df, K = 2) { df %>% mutate( hour = as.integer(format(datetime, "%H")), wday = factor(format(datetime, "%u")), # 1-7,周一为 1 # 年内周期的傅里叶项,K 越大拟合越细,2 到 4 比较稳 sin_y = sin(2 * pi * K * yday / 365), cos_y = cos(2 * pi * K * yday / 365), # 日内周期,日内只有一个主峰一个次峰,取 1 到 2 足够 sin_d = sin(2 * pi * hour / 24), cos_d = cos(2 * pi * hour / 24) ) }参数 K 是傅里叶阶数,年周期取 2 到 4 通常够,取太大就会把节假日那种尖峰也当成周期去拟合,反而让模型学出"每年这个时候都该低"的错误规律。日内周期相对稳定,一阶两阶就行。
节假日那部分,面试时最好能说出"我用的是哪一类日历",比如法定假日、调休工作日、寒暑假分段。这类信息的处理方式比模型选择更能体现你对业务的理解。
3.3 面试题演练:一张温度-负荷散点图能问出五个点
面试题:屏幕上是某分区全年温度对负荷的散点图,形状像一个散开的"V"。请你现场解释这个形状,并给出建模方案。
这题的正确打开方式不是直接讲模型,而是先描述现象。第一句说"这是典型的 U 形响应,两端分别是采暖和制冷主导",第二句说"散开的原因是把不同小时、不同星期的点混在一起了",第三句才是"所以我按小时分层看,形状会更清晰"。
接下来讲方案,分三层递进。第一层是分段线性,用两个度日变量把 V 形的两条边分别拟合,中间平坦区由两个 pmax 自动处理。第二层是样条或者分箱,用更灵活的函数逼近曲线,代价是参数多、需要更多正则。第三层是让模型自己学,把平滑温度连同小时哑变量一起丢进树模型,靠分裂点找拐点。三层都要说,但要说清各自的代价,只说第三层会显得你只会调库。
追问通常有两个。一个问"为什么温度要用平滑值而不是瞬时值",答案回到热惯性。另一个问"如果某分区全年没有明显高温,V 形的右边几乎没数据,你怎么处理",这题考的是外推,正确答法是承认这一段不可辨识,要么借邻区数据,要么在模型里加约束,而不是硬拟合。
4. 概率预测真题:从单点到分位数,R 里怎么落地
到了这一步,点预测已经不够用了,题目要的是分布。这一章的三个小节对应三种典型追问:怎么拟合多条分位线、怎么保证分区和总量对得上、为什么集成在这类赛题里特别管用。全部用 R 落地,代码可以直接改造。
4.1 分位数回归:一次拟合 99 条分位线
分位数回归的直觉是:把损失函数换成不对称的绝对值损失,不同的 τ 就对应不同的"偏向"。τ 小的时候低估的惩罚更重,模型倾向给低值;τ 大的时候反过来。一次拟合多个 τ,就得到一整条分布。
library(quantreg) taus <- c(0.01, 0.05, 0.25, 0.5, 0.75, 0.95, 0.99) # rq 对每个 tau 单独求解,返回对象里按 tau 分槽存放系数 fit <- rq(load ~ cdd + hdd + factor(hour) + factor(wday) + lag24, tau = taus, data = train) # 预测结果是一个矩阵:行是样本,列是分位水平 qhat <- predict(fit, newdata = test)代码后有三件事要讲清。第一,rq 对每个 τ 独立求解,所以不同分位线的系数可能互相打架,出现分位交叉(τ=0.9 的预测低于 τ=0.5)。解决办法是拟合完做一次重排,或者直接用保序的联合估计方法。第二,特征里我保留了 lag24 和小时哑变量,这两项在分位数回归里很关键,因为它们承担了大部分周期性解释力。第三,分位数回归是线性的,温度的非线性要靠前面造的 cdd、hdd 和分箱来补,不要指望它自己学出 U 形。
实际比赛里更常见的做法是梯度提升树配合分位损失,或者对每个分位单独训一个模型。树模型的优势是自动处理交互,代价是尾部容易过拟合,需要在验证段按分位逐个看误差。
提示:分位交叉在答辩时是个高频追问点,主动提出来并给出处理办法,比被问出来强得多。
4.2 层级聚合:从 zone 到总量的一致性与对账
这套数据的层级结构是:各分区负荷加总等于系统总负荷。如果你对 20 个分区各自建模,再把预测值加起来,得到的"总量预测"和对总量直接建模的结果通常不一致。这不是小问题,调度侧看到两个口径不一样会直接质疑你的结果。
处理办法有两条路。自下而上是最简单的,各分区分别预测再求和,优点是分区细节保住了,缺点是误差会累积,分区越多总量越飘。自上而下是先预测总量,再按历史占比拆到各分区,优点是总量稳,缺点是分区的个性化特征被抹平。
更好用的是最优组合:先分别算出两组预测,再按验证段的表现给权重,权重可以按分位数分别标定。这个方法的表述要点是"我不是选一条路,而是让数据决定权重",面试官听到这句基本就认可了。
对账这一步千万别省。每次出结果,做三件事:各分区预测加总与总量预测比一次,和历史同期的分区占比比一次,和基线的分区占比比一次。三次对账能捞出一大半的低级错误。
4.3 追问演练:集成为什么在这类比赛里特别管用
面试题:你最终提交的是单模型还是集成?为什么?
正确答法是"集成,但不是因为集成分数高,而是因为误差结构互补"。负荷预测的误差有很强的时段性:线性模型在平稳段表现好,树模型在含交互的极端天气段更好,分位数回归在尾部更稳。把这几种按分位、按时段组合,往往比单一模型稳定。
接着要讲组合方式。最简单的是等权平均,作为起点;好一点的是按验证段的 pinball loss 反比加权;再好一点是按小时、按季节分别给权重,但要注意样本量,分得太细会过拟合验证集。这里可以说一句实话:分时段加权在验证集上提升明显、在测试集上回吐的情况太常见,所以权重至少要留一段独立数据来确认。
最后补一句风险控制:集成成员之间相关性太高时,集成就没有意义。判断办法是看各模型残差的相关矩阵,如果几个模型残差几乎同步波动,那它们本质上是同一个模型的不同写法。
5. 易错点:五个高频失分点,每个都有人栽过
这一章全是血泪经验。下面五条是面试和答辩里最常被抓住的,按"错在哪、为什么错、怎么答对"三段来说,建议对着自己的项目逐条自查。
5.1 建模环节的三个坑
坑一:用居中窗口做温度平滑。错在用了未来信息,filter默认双侧,一不小心就把后 12 小时的温度算进来了。为什么错得很隐蔽:验证分数会明显变好,你以为是特征有用,其实是数据泄漏。怎么答对:所有滚动计算一律sides = 1,并在时间序列切分上严格按时间先后划分训练、验证、测试,绝不打乱。
坑二:把节假日当成普通工作日。错在日历特征只放了星期几。为什么错:假期前后的负荷形状会整体平移,模型把这部分残差当成噪声,导致那几天的分位数全线偏移。怎么答对:节假日单独建哑变量,并把前后各一到两天标记为桥接日;如果样本里假日太少,就做相似日匹配,用历史上形状相近的日子替代建模。
坑三:全分区共用一个温度拐点。错在假设 20 个分区气候和建筑结构一致。为什么错:拐点偏一点,度日变量就整体偏移,温度敏感月份的系统性误差会一直存在。怎么答对:拐点按分区标定,或者干脆用分箱代替解析式,让每个分区自己的数据决定形状。
5.2 汇报环节的两个坑
坑四:只报总分,不报分位和分时段。错在把一个平均值当成结论。为什么错:pinball loss 的平均值会把尾部的糟糕表现掩盖掉,而尾部恰恰是业务最关心的。怎么答对:汇报时至少给三张表——按分位水平的误差、按小时的误差、按月份的误差,然后指出模型在哪个格子最弱,以及你打算怎么补。
坑五:层级对不上还硬讲。错在分区预测加总与总量预测不一致,被追问时说不清。为什么错:两条建模路径各自独立,误差不共享。怎么答对:主动交代你用的是自下而上、自上而下还是最优组合,给出对账结果,并说明残差怎么分摊。承认"我这里做了近似"比含糊过去强。
注意:以上五条里,第一条和第五条最致命。数据泄漏会让所有结论作废,层级对不上会让所有数字失去可信度,其他三条还能补救。
6. 答题技巧:把整套流程压成一张可以背的答题卡
面试时间有限,你不可能把上面所有内容都倒出来。我的做法是准备一张六行的答题卡,每行一句话,被问到哪里就从那一行展开。第一行讲数据:20 个分区、11 个气象站、逐小时、强日周双周期。第二行讲基线:naive 加气候态,先划线下限。第三行讲特征:平滑温度加度日、傅里叶加节假日窗口、滞后项。第四行讲模型:分位数回归打底,树模型补交互,按分位组合。第五行讲一致性:层级对账三遍,权重由验证段定。第六行讲评估:分位、分时、分月三张表,主动交代最弱的一格。
答题卡的价值在于顺序。很多人一上来就讲模型结构,讲了五分钟面试官还没听到你怎么处理温度,评价自然上不去。把顺序固定成"数据 → 基线 → 特征 → 模型 → 一致性 → 评估",每一段控制在一分钟内,剩下的时间留给追问。
压轴的那一问通常是"如果给你更多时间,你会做什么"。这时候别答"试试更深的网络",要答可验证的方向:第一,把温度的空间加权从算术平均换成距离加权或者学出来的权重,看温度敏感月份的分位误差有没有下降;第二,把分位数从稀疏的几个点扩到完整的网格,看尾部是否更稳;第三,做一次跨年的滚动验证,确认模型在季节切换时不掉链子。三个方向都要带上"怎么判断有没有用",也就是评价口径,否则听起来像空想。
最后留一个自查的小脚本,答辩前跑一遍,能挡掉大部分口径问题。
# 提交前自查:单调性、覆盖率、层级对账 check_submission <- function(qmat, y, total_pred, zone_pred) { # 1. 分位是否单调:每行做一次差分,出现负值即为交叉 cross <- apply(qmat, 1, function(r) any(diff(r) < 0)) # 2. 区间覆盖率:实际值落在 5% 到 95% 之间的比例,应接近 90% cover <- mean(y >= qmat[, "0.05"] & y <= qmat[, "0.95"]) # 3. 层级对账:分区加总与总量预测的相对偏差 gap <- mean(abs(rowSums(zone_pred) - total_pred) / total_pred) list(cross_rate = mean(cross), coverage = cover, hier_gap = gap) }三个返回值对应三类问题:cross_rate不为零说明分位线打架,先重排再提交;coverage明显偏离 0.9 说明区间过窄或过宽,回到分位标定那一步;hier_gap超过千分之几就要解释清楚,别让它成为答辩现场的黑匣子。这套自查我踩过坑之后才加上,从那以后每次出结果,哪怕只是改了一个特征,我都强制走一遍,宁可多花十分钟,也不要在现场被问住。希望帮到你。
本文还有配套的精品资源,点击获取