1. 为什么偏偏是西班牙电力市场:可再生能源高渗透下的价格困局
西班牙电力市场这几年几乎是全球电价预测研究者的“天然实验室”。原因不复杂:这个市场的可再生能源装机比例非常高,风电和光伏出力在总发电量中经常能占到一半以上,而这两类电源又天然具有极强的间歇性——晴天、大风天和阴天、静风天的发电曲线完全是两回事。电价在这种供需结构里变得极其敏感,波动幅度远超传统火电主导的市场。
我最早接触这个课题时,印象最深的是2022年到2023年那段时间的极端行情。批发电价一度冲到每兆瓦时200欧元以上,而某些光伏大发的中午时段又会瞬间跌到接近地板价,甚至偶尔出现负电价。这种“一天之内冰火两重天”的走势,给传统统计模型(ARIMA、GARCH这类)带来了近乎毁灭性的打击——它们假设价格序列具有某种平稳性或至少可预测的波动结构,但西班牙市场的数据告诉我,这个假设十次有八次不成立。
那深度学习模型为什么能扛住这种波动?核心在于它不依赖对数据生成过程的强假设。LSTM、TCN这类模型只需要大量的历史样本,就能自动学习到“光照强→光伏出力大→午间电价被打压”这类隐含的因果关系,哪怕是滞后若干小时、嵌套了复杂的天气耦合效应,也能通过多层非线性变换逼近。加上电价预测本质上是一个时间序列回归问题,输入特征可以做得非常丰富——历史负荷、历史电价、新能源预测出力、日历变量、燃料价格,甚至相邻法国市场的电价——这对数据驱动模型是天然的利好。
这篇文章要分享的,就是我在“西班牙电价预测”这个研究方向上完整的实操链路:从数据获取与清洗、特征工程、模型选型与训练,到SHAP可解释性分析的落地写法。目标读者是那些已经跑过几个简单回归模型、想进一步把深度学习真正用在实际电力市场数据上的朋友。我会把踩过的坑、试错过但最终放弃的方案、以及“为什么要这么做”的底层逻辑一起讲清楚,不绕弯子,直接上干货。
2. 数据准备环节:ENTSO-E透明平台与特征工程中的隐藏陷阱
2.1 用公开API拿到靠谱的市场数据
做电价预测,第一步最难的不是建模,而是拿数据。西班牙电力市场的数据源主要分两类:一类是西班牙电网运营商的公开平台,直接提供日前市场(Day-Ahead Market)的清算价、系统负荷、新能源实际出力等字段;另一类是欧洲范围的电力数据透明平台ENTSO-E,覆盖面更全,还能同时拉取邻近市场的价格。我的建议是两个源都接,以ENTSO-E为主,以本土平台作交叉校验。
从实操角度,ENTSO-E的API走的是RESTful接口,需要先注册申请API Key,申请理由填学术研究或市场分析一般都能过审。拉数据有一个很烦的机制:大部分数据不是实时开放,而是延迟三天到一周才完整归档,所以要做历史回测或者训练集构建,需要耐心把时间窗口拉开。我当时写了一个封装脚本,用requests逐月拉取,遇到限流就sleep几秒再继续,整个历史数据集大概花了一晚上跑完。
字段选择上我做了这样的裁剪:
| 变量类别 | 具体字段 | 时间粒度 |
|---|---|---|
| 目标变量 | 日前市场清算价(€/MWh) | 1小时 |
| 负荷类 | 系统总负荷、可控负荷 | 1小时 |
| 新能源类 | 风电实测出力、光伏实测出力 | 1小时 |
| 机组组合类 | 燃煤/燃气/核电出力 | 1小时 |
| 市场关联类 | 葡萄牙市场电价、法国市场电价 | 1小时 |
| 日历类 | 星期、节假日标记、小时序数 | 1小时 |
这里有个很典型的坑:ENSTO-E返回的新能源出力数据是“实测值”,而电价预测真正想要的是“预测值”——因为预测时点上,未来几个小时的风光出力我们是不知道的,只能用天气预报或新能源功率预测结果作为输入。如果直接用实测值训练,模型无意中“偷看”了未来信息,回测分数会非常漂亮,但上线表现直接崩塌。这个错误我在早期版本中踩过,后来解决方案是:用滞后几小时的新能源出力作为特征,模拟预测输出的时间滞后性,或者干脆引入公开的气象再分析数据(比如ERA5)自己算理论出力。
2.2 时间对齐与缺失值处理:看似简单实则致命
电力数据天然带着时区和夏令时的坑。西班牙使用欧洲中部时间(CET/CEST),每年三月和十月要切换夏令时,切换当天会多一小时或少一小时。很多公开数据源在夏令时切换日的索引是错位的,如果直接pandas对齐,会出现“同一时刻两条记录”或者“某时刻为空”。我当时用的方法是:统一转成UTC时间戳存储,建模时再映射回本地时间并把小时序号单独做成特征。
缺失值方面,电网数据整体质量不错,但还是会有小块的空洞——比如某天的某个采集通道故障,或者极端天气导致上报链路中断。一般两小时以内的空洞我用线性插值补,超过两小时的直接剔除样本,不硬填。尤其注意不要在目标变量(电价)上做暴力插值,因为电价序列的尖峰和跳跃本身就是模型要学习的信号,插值会把这些信号抹平。
特征工程里还有一个小细节值得展开说一下。除了原始的历史负荷和新能源出力,我加了几组构造特征:
- 滚动统计特征:过去24小时的峰值、谷值、均值、标准差,捕捉近期价格波动的状态;
- 滞后差分类特征:比如“当前时刻负荷与昨日同刻负荷之差”,能反映日内变化趋势;
- 日历编码:把星期、小时用正弦余弦编码,避免星期几或小时序号被模型当成连续值处理;
- 差分电价特征:过去1小时、3小时、6小时的电价变动量。
为什么这些手工特征在深度学习里还有必要?虽然LSTM理论上可以自己学时序依赖,但给模型提供显式的变化率特征能显著降低学习难度,尤其在训练数据量有限(我用了三年逐小时数据,约两万多条样本)的情况下,特征质量直接决定过拟合程度。实测中,加入滚动统计和差分特征后验证集MAPE下降了大约1.5个百分点,这个收益在电价预测里已经非常可观了。
3. 深度学习模型选型与训练细节:从LSTM到TCN的摸索过程
3.1 为什么最终选了TCN而不是更火的Transformer
我一开始是按照惯性直接上了LSTM,毕竟处理序列问题它是老牌选手。但在实际调参过程中,我逐渐发现LSTM的两个痛点:第一,训练速度确实慢,尤其在序列长度拉长到96步(四天窗口)之后,batch内的时间步展开非常吃显存;第二,梯度消失问题虽然通过门控机制缓解了,但深层堆叠时训练稳定性仍然不够理想,偶尔会遇到损失不下降或剧烈震荡的情况。
后来我换成了时序卷积网络(TCN)。TCN的核心思想是用因果膨胀卷积替代循环结构。因果卷积保证了每个时刻的输出只依赖当前时刻及之前的输入,不会“偷看未来”;膨胀卷积则通过指数级增大感受野的方式,使模型可以用很浅的层数覆盖很长的历史窗口。相比LSTM,TCN的训练是并行化的,速度至少快三到四倍,而且没有循环结构带来的BPTT梯度问题,训练曲线平稳得多。
至于Transformer,我也试过。在序列长度不算特别长(96步以内)的设定下,Transformer的注意力机制和绝对位置编码并没有发挥出碾压性优势,反而因为参数量大、数据量不够而更容易过拟合,训练时间也更长。电力市场数据的样本量与NLP领域的海量语料完全不是一个量级,模型复杂度必须克制。所以最终落地的是TCN + 双向LSTM的混合结构:先用TCN提取局部时序模式,再喂给一个只堆两层的双向LSTM捕捉更长的依赖关系。这个组合在验证集上比单用LSTM降低大约0.8%的MAPE,比单用TCN也略有提升。
3.2 训练配置与超参数:给一个能直接复现的基线
如果直接抄我的最终配置,大致是这样的:
- 输入特征维度:27维(历史电价、负荷、新能源出力、日历编码、滚动统计、差分特征等)
- 时间窗口:7天×24小时 = 168步预测未来24小时,即输出24个连续小时的价格
- TCN层:4层,初始通道数64,每层翻倍到512,卷积核大小5,膨胀系数[1, 2, 4, 8]
- BiLSTM层:2层,隐藏单元128,dropout 0.2
- 输出层:全连接 + 线性激活,映射到24个值
- 损失函数:HuberLoss(δ=1.0),对尖峰不那么敏感,比MSE稳
- 优化器:AdamW,初始学习率3e-4,配合CosineAnnealingLR调度器衰减
- Batch大小:128,早停轮数15,最大训练轮数80
训练集、验证集、测试集按时间顺序切分:前两年半训练,接下来三个月验证,最后三个月测试。切分时千万不能随机打乱,时间序列必须保持顺序,否则会造成数据泄漏——模型见过“未来的数据”再来预测“过去的时刻”,评估结果没有任何参考意义。
Loss曲线在HuberLoss下比较平稳,不像MSE那样被个别极端尖峰带着剧烈跳变。我截图跑完的best epoch大概在第47轮,验证Loss稳定在0.32左右。测试集上的具体指标是:MAE约4.36 €/MWh,MAPE约9.8%。考虑到测试集中包含了几次价格大幅波动的事件段,这个表现我认为是比较扎实的。
3.3 训练过程中踩过的两个ESL坑
第一个坑是归一化方式。电价序列的分布是典型的重尾分布——大部分时间价格在30到80欧元之间徘徊,但偶尔会冲到150甚至300。如果直接做MinMax归一化,异常尖峰会把正常区间的数值压缩到很小的范围,模型几乎是在用放大镜看“正常情况”。我最后用的是RobustScaler(基于中位数和四分位距),对极端值有很强的鲁棒性。训练时目标变量也做了同样的缩放,预测输出再反变换回去。
第二个坑是训练-预测分布漂移。西班牙市场在2022年经历了能源价格飙升,批发电价均值从2021年的60多欧涨到130多欧。拿2021年数据训练的模型,直接去预测2022年的电价,MAPE会恶化到20%以上。解决办法是滚动再训练:每两周用最新一个月的真实数据微调一次模型权重(把学习率降到原学习的十分之一,只训练5轮)。这个策略跟金融里处理波动率聚集的思路如出一辙——不可能有一个静态的“万能模型”能应对持续变化的市场结构,动态更新才是唯一的出路。
4. SHAP可解释性分析:让黑盒模型开口说话
4.1 SHAP原理速通:从Shapley值到实际的Python实现
深度学习模型的准确性有了,但研究项目停在“预测得准”是不够的,尤其是电力市场这种对决策敏感的场景,“为什么预测出这个价”和“预测出什么价”同等重要。评审和业务方都会追问:你模型觉得这个价格由什么驱动?这就要引入可解释性分析。
SHAP(SHapley Additive exPlanations)是目前最常用的模型解释工具,理论基础是合作博弈论中的Shapley值。它的思路是这样:假设有一个包含N个特征的合作博弈,模型的预测值是这些特征共同作用的结果。要计算每个特征“公平”的贡献值,需要考察在所有可能特征集合中,加入该特征前后预测值变化的加权平均。直观讲,就是把所有特征按不同顺序排列组合,逐个“加入游戏”,看每个特征在每种排列下带来的边际贡献,然后对这些边际贡献做加权平均,权重与子集大小有关。
从实现角度看,SHAP库对深度学习模型的支持已经相当成熟。对于PyTorch模型,常用的有两种explainer:一种是DeepExplainer,专门针对神经网络,利用链式法则和误差反向传播思想计算近似SHAP值,速度快但精确度略低;另一种是GradientExplainer,同样适用于深度学习模型,基于期望梯度方式。如果模型本身是梯度提升树(XGBoost、LightGBM),那就直接用TreeExplainer,那是精确计算,性能极好。我在这篇研究里用的是DeepExplainer,因为模型规模不大,计算成本完全可以接受。
4.2 SHAP可视化的四种核心图,以及我实际解读出来的结论
SHAP的输出不是一张图就完事,它是一整套分析体系。我实际使用过程中,最有价值的四种可视化方式做个梳理:
- 全局特征重要性(Bar Plot):把所有样本各特征的|SHAP值|取平均后排序,反映“平均而言谁对预测值的绝对影响最大”。
- 特征影响方向(Bee Swarm Plot,即蜂群图):每个样本是蜂群图上的一个点,横轴是SHAP值(正负代表推高/拉低预测电价),颜色代表特征值高低,能直观看到“特征值越大越推高价格”还是“特征值越极端影响越强”这类非线性关系。
- 单样本解释(Waterfall Plot,瀑布图):针对某一个具体预测时刻,从上到下展示各特征如何从基准预测值一步步加加减减得到最终预测值。
- 交互作用分析(Interaction Values):两个特征的交互贡献,例如“光伏出力”和“时刻”之间的相互作用——相同光伏出力水平,在午间和傍晚对电价的影响方向截然不同。
我跑完整个测试集后,全局特征重要性的排序结果非常有说服力。不出意外,历史同小时电价(前一天同一时刻的清算价)和系统负荷排在前两位,两者合计贡献了超过50%的全局影响。这符合电力市场的基本规律:电价具有很强的自相关性和负荷驱动性。真正让我意外的是光伏出力的SHAP模式——它在午间时段是负贡献(拉低价格),但在晚上7点到9点的峰荷时段,光伏出力特征的SHAP值反而出现了正向偏移。原因其实不复杂:晚高峰时光伏几乎不发电,所以当模型检测到那一刻的光伏出力“异常地高于凌晨水平”(比如夏季长日照导致傍晚还有残余出力),它会推断电力供需紧张程度比往年同期要低,反而降低了价格预测。这个“特征值与影响方向不一致”的现象,恰恰是线性模型完全无法表达、而SHAP能拆解出来的非线性关系。
负荷类特征的表现也很有意思。系统总负荷的SHAP值呈现明显的U型关系——低负荷时段(凌晨三四点)和高负荷时段(晚上八九点)都会强烈影响最终预测值,但方向相反。区别在于,凌晨低负荷往往对应着风电大发(夜间风大是欧洲常见的天气特征),电价被压到很低的水平,这里“负荷低”本身就是一个强信号;而晚间高负荷时即使价格冲刺到高峰,模型给出的预测不确定性也更大,因为这时候天然气价格、碳排放成本的传导效应更强,这些外部因素在我的特征集里没有直接覆盖,模型只能通过历史同期的价格模式间接推断。
4.3 用SHAP反哺特征工程的几个实例
可解释性分析不只是用来“画几张图放报告里”,它的更实际价值是反哺特征工程和模型迭代。我举两个例子说明怎么用SHAP做特征筛选和矛盾识别。
第一,我在初始特征集合里放了一个“节假日标记”的0/1变量。SHAP的全局重要性排序里,它的|SHAP|均值排在倒数几位,几乎不起作用。进一步看它的蜂群图,发现节假日和非节假日的SHAP分布几乎没有重叠,说明模型确实没有从该特征学到差异化信息。分析原因后发现,西班牙的节假日分布与北欧国家不同,很多假日是宗教节日,并不一定伴随工商业用电的显著下降(比如圣诞节期间虽然工商业放假,但家庭取暖用电反而上升)。所以我后来把这个布尔标记改成“非工作日连续天数”和“假期前/后一天”两个连续特征,再跑一遍,SHAP重要性明显上升,验证集MAPE降了0.6个百分点。
第二,利用SHAP交互值发现了“风电出力”和“葡萄牙电价”之间的冗余关系。初始特征里我同时放了西班牙本土的风电出力和葡萄牙市场的日前电价。交互分析显示这两个特征的联合SHAP值分布几乎与“仅使用葡萄牙电价”时的效果重叠,说明葡萄牙电价已经隐式包含了西班牙风电大量出力时向葡萄牙输电导致的互联价格走低信息。结论是把风电出力从特征集里移除,模型验证精度不变,但特征维度从27降到26,训练时间缩短了8%。这种“特征去冗余”单靠相关性分析看不出来,只有SHAP交互分析能把信息重叠暴露得这么清楚。
5. 结果分析、误差透视与业务层面的落地建议
5.1 误差不是均匀分布的:分时段误差分析
只看平均指标会给决策者造成错觉。我把测试集按小时拆分,逐一计算每个预测时刻的MAE,发现了非常明显的时段差异:
| 预测时刻(当地) | MAE(€/MWh) | MAPE(%) | 典型场景 |
|---|---|---|---|
| 0:00 - 4:00 | 2.81 | 6.1 | 低负荷,风电主导,价格平稳 |
| 4:00 - 8:00 | 3.97 | 9.0 | 负荷爬坡启动,燃气机组开始竞价 |
| 8:00 - 12:00 | 5.12 | 11.3 | 光伏爬坡,价格被压缩但波动加大 |
| 12:00 - 16:00 | 3.66 | 9.5 | 光伏大发,午间低谷价格 |
| 16:00 - 20:00 | 4.84 | 10.2 | 光伏衰减,价格抬升,启动晚高峰 |
| 20:00 - 24:00 | 6.73 | 15.8 | 晚高峰,不确定性最大 |
晚间时段的误差显著偏高,原因不难理解:这个时段的价格不仅受西班牙国内供需影响,还深受法国市场的跨区输电态势左右——法国核电晚高峰的可用性、跨境联络线容量约束,都可能成为价格跳变的触发器。但这些信息在我当前的特征集里仅有一小部分通过“法国日前电价”体现出来,远远不够。如果后续要提升精度,需要引入联络线可用容量、法国负荷高峰预测、以及更细粒度的天气变量(风速预报的空间分布,而非全国平均)。
从误差分布形态看,模型在价格尖峰时刻的系统性低估现象也比较明显。测试集里出现了两天价格超过180 €/MWh,模型在这两天的预测值分别只有131和117,低估幅度超过35%。归根结底,极端尖峰往往由突发事件驱动(比利时核电机组跳闸、法国核电站检修叠加寒潮、天然气供应扰动),而这些事件在训练历史中的出现频率极低,任何数据驱动模型都难以提前捕捉。这是模型天花板的问题,不是调参能解决的。
5.2 落地场景差异:交易策略、电网运营和风险管理的不同用法
同一个预测模型,不同角色的用法完全不同。
对于能源交易团队,核心关心的是方向性准确率和价格区间边界。我的实测经验是,模型在“判断明天午间价格低于清晨价格”这类相对排序问题上,准确率超过92%,这比绝对价格预测的精度更有交易价值。交易员完全可以根据预测价格曲线设计日前市场的购电策略——在预测低价时段购入,高价时段卖出或减少购电,配合储能充放电策略,能有效规避尖峰时段的购电成本。
对于电网调度部门,预测的价值更多体现在场景多样性上。一个点预测不够用,他们更需要的是分位数预测或区间预测。我在后期测试中把模型的输出层改成24个分位数头(用分位数损失函数训练),生成10%、50%、90%三个分位数区间,用于评估“价格走势是否存在极端尾部风险”。比如在预测到晚间负荷压力较大的日子,90%分位数价格比中位数高40%以上,这就是一个强烈的风险提示信号,调度可以提前预留更多的储备容量。
站在研究复现的角度,我的建议是:不要一上来就追求“全网最精准”的SOTA模型,先把完整的“数据整理→特征工程→深度模型→SHAP解释”的闭环跑通。这个闭环里最值钱的能力,不是某个特定模型结构或某层参数,而是对数据特征与市场价格形成机制之间关系的理解能力——理解为什么光伏负贡献、为什么晚间误差大、为什么节假日特征无效,这些能力可以迁移到任何其他电力市场,比如法国市场、北欧市场,或者国内某些省份的现货试点市场。
根据我个人在这些踩坑过程中的体会,SHAP分析不仅在论文和报告中好看,最大的作用是逼着我去重新审视那些“想当然合理”的特征。没有SHAP,我的光伏出力特征和节假日标记很可能就一直糊涂地放在训练集里,模型也能收敛,指标也不难看,但你知道这里面有多少是碰运气。模型精度重要,但只有配合可靠的归因逻辑,才能让研究结论经得住推敲,也才能在真实业务场景里形成有效的决策支撑。