简介:基于LSTM模型的电价预测源代码,是一套面向电力市场分析、能源交易决策及量化研究场景的深度学习时序预测项目,适合高校学生、数据科学初学者与相关领域工程师参考使用。压缩包共33个文件,包括11个Python脚本、8个编译缓存、4个XML工程配置、4个CSV样本数据、1个训练好的模型权重、2张可视化图片及说明文档,整体仅540KB,代码组织清晰,可直接运行与二次开发。项目覆盖了从数据清洗、归一化、训练集/测试集划分,到LSTM网络搭建、参数迭代、评估与预测结果输出的完整链路,并自带示例数据与模型参数,便于对照复现;代码模块化程度较高,具备良好迁移性,除电价预测外也可迁移至股票价格等相似时间序列任务。该资源已有202人学习,体量虽小但信息密度高,既能帮助理解LSTM内部机制,又能作为快速上手的实战模板。 做电价预测这个方向有一段时间了,最初我也没有直接上LSTM,而是先试了ARIMA、XGBoost这些常见套路。结果怎么说呢,能出结果,但峰谷位置的误差始终压不下来。后来把重心切到LSTM,才发现电价这种数据天生就适合交给带记忆机制的循环网络去处理——它不只是学一个"当前特征到价格的映射",更关键的是它能记住过去一段时间的价格走势,然后基于走势外推。这篇文章就围绕"基于LSTM模型的电价预测源代码"展开,把整个项目从数据清洗、样本构造、模型搭建到训练评估的完整链路讲清楚,最后给出可以照着改的源码结构。想入时间序列预测这个坑、手里正好有历史电价数据但不知道怎么下手的同学,可以认真看一下。
1. 电价数据有什么特殊之处,凭什么它适合用LSTM建模
在动手写代码之前,得先想明白一个问题:为什么电价预测这个任务,LSTM会比普通回归模型更合适。这不是赶时髦,而是从数据本身的性质推导出来的结论。
电价序列有几个非常明显的特征。一是强周期性,一天之内通常是"凌晨低谷、午前小高峰、晚间主高峰"这样的形态,工作日和周末的曲线又有差异,不同季节的用电规模也不一样。二是高度非线性,极端天气、发电机组临时检修、负荷突变都会让价格瞬间跳变。三是强依赖历史状态,明天的早高峰价格,往往和今天、前几天同时间段的价格存在强相关,这种依赖不是某个固定公式能表达的,需要模型自己去学。
普通的前馈神经网络在处理这类数据时的短板很明显:它对每个时间点的输入是独立看待的,完全没有"记忆"的概念。而LSTM内部有输入门、遗忘门、输出门三个门控结构,通过门控机制决定哪些历史信息要保留、哪些要丢弃。说得直白一点,它就像一个人读一条时间序列的时候,边读边记笔记,碰到重要的信息就写进本子,碰到不重要的就划掉,最后靠这本子里的浓缩记忆来预测下一个时刻的数值。
当然我也对比过GRU和Transformer。GRU结构更轻,训练速度快一些,在样本量不是特别大的时候也是个不错的选择,但我最终保留LSTM版本的原因有两个:一是LSTM在电价这种中等长度序列上的表现足够稳定,二是LSTM的tuning经验在社区里更丰富,出了问题能搜到大量现成案例。至于Transformer,它在长序列全局依赖上确实强,但电价预测这种任务用过去24小时或者168小时的数据去预测未来1小时,序列长度并不算长,Transformer的全局注意力优势发挥不出来,反而容易因为训练数据量不够而过拟合。
所以,选LSTM不是因为它最先进,而是因为它在"效果可接受、调试成本低、社区资料多"这三个维度上最均衡。这个道理适用于绝大多数实际项目,选模型永远不要只看排行榜,要看数据和任务的真实需求。
2. 数据清洗与样本构造:九成的预测误差其实出在这一步
很多人在网上跑通现成代码之后,换到自己数据集上效果立刻崩掉,问题多半不在模型,在数据处理。原始电价数据的质量问题比想象中严重,最常见的有三类:缺失值、异常尖峰、时间不连续。
2.1 缺失值与异常值的处理策略
电价数据在采集过程中经常会出现整点数据丢失,或者某个时段因为系统故障记了一堆0。我的处理原则是:连续缺失不超过两个点就用线性插值,超过两个点就对齐相邻日期的同一时刻进行填充。这是因为电价有极强的日内周期性,用前一天同一时刻的价格做参考比单纯靠相邻点插值可靠得多。
异常尖峰要分情况讨论。如果尖峰出现在极端天气日或者重大节假日,那它本身是有效信息,不能随便去掉。如果尖峰是因为采集设备故障导致的,比如价格突然跳到几万块一兆瓦时然后又落回正常范围,这时候需要设置一个合理的阈值,比如超过正常区间3倍以上且持续时间只有一个采样点,就判定为异常值并做平滑处理。
2.2 时间特征构造:模型不认识日历,但我们得喂给它
LSTM本身只感知到"连续的数值序列",它并不知道今天到底是周一还是周六,也没办法自动理解凌晨2点和下午2点有什么区别。所以我们必须把时间信息转成特征喂进去。
我通常会构造以下几列特征:
- 小时的sin/cos编码:避免23点和0点之间的数值跳变
- 星期几的独热编码:让模型区分工作日与周末
- 节假日标记:非工作日特征,直接给0/1
- 历史价格滞后项:如滞后1小时、24小时、168小时的价格
- 滑动均值与滑动标准差:反映近期价格水平和波动程度
这里有一个容易被忽略的细节:做时间滑窗的时候,一定要避免未来数据泄漏。就是说,预测t时刻的价格时,只能用t时刻之前的数据构造特征,绝对不能把t时刻之后的信息提前混进来,否则训练时指标漂亮,一上真实环境就原形毕露。
2.3 归一化与滑动窗口样本生成
LSTM对输入数值的尺度很敏感。电价的范围可能在几十到几千之间浮动,这种量级差异会让训练过程极不稳定。我的做法是使用MinMaxScaler把价格缩放到0到1之间,注意:统计缩放参数只能用训练集去拟合,验证集和测试集都要复用训练集的scaler来转换,否则又犯了数据泄漏的毛病。
滑动窗口的样本生成是整个预处理里最关键的一步。假设我们设置lookback窗口为24小时,意思就是拿过去24个小时的价格和特征数据,去预测下一个小时的价格。用代码表示就是:
def create_sequences(features, target, lookback=24): X, y = [], [] for i in range(len(features) - lookback): X.append(features[i:i + lookback]) y.append(target[i + lookback]) return np.array(X), np.array(y)窗口长度这个超参数后面还会详细聊,它直接影响模型能"看到多远的过去"。对于电价预测,我实测下来24小时和168小时是两个常见的窗口档位,前者适合做次日小时级预测,后者因为涵盖完整一周的周期,适合做含周规律的预测任务。
2.4 数据集划分:顺序切分,不要乱序
这一点必须单独拿出来强调。时间序列数据的划分和普通机器学习不一样,不能用随机打乱的方式切分训练集和测试集,因为样本之间存在时序依赖。正确做法是按时间顺序,比如前70%的数据作为训练集,接着15%作为验证集,最后15%作为测试集。测试集必须是最新一段时间的真实数据,这样评估出来的结果才有说服力。
之前见过有人拿随机抽样切分方式训练LSTM,测试集里的样本时间点比训练集还早,结果模型"偷看"了未来的信息,验证指标异常地好,上线之后完全不是那么回事。这种错误不细想根本发现不了,但一旦部署,代价非常大。
3. 网络怎么搭:LSTM层数、隐层维度与关键超参数的实测权衡
模型结构这部分,业界没有统一标准,网上各种配置都有。我分享一下在电价预测场景下实测下来比较稳定的一组配置,以及每个参数的调优逻辑。
3.1 基线网络结构
我用的是Keras实现,整体结构非常简洁:
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau model = Sequential([ LSTM(units=64, return_sequences=True, input_shape=(lookback, n_features)), Dropout(0.2), LSTM(units=32, return_sequences=False), Dropout(0.2), Dense(units=16, activation='relu'), Dense(units=1) ]) model.compile(optimizer='adam', loss='mse', metrics=['mae'])这个结构里有几个点值得展开说。
3.2 单层还是双层LSTM
我试过一层、两层、三层LSTM堆叠。一层LSTM在数据量只有几千条的时候训练速度快,但拟合能力有限,对电价这种复杂非线性模式的刻画出不太够用。三层LSTM表达能力上去了,但训练时间明显拉长,而且容易过拟合,在小规模数据集上尤其明显。最后稳定在两层:第一层return_sequences设为True,是为了把完整的隐藏状态序列传给第二层;第二层return_sequences设为False,只输出最后一个时间步的隐藏状态,再接全连接层做预测。
3.3 隐层维度:64还是128
隐层维度说的是LSTM内部状态向量的宽度。64对比128,在测试集上的差异并没有想象中大,提升大概在2%到3%的MAPE之间,但训练时间却增加了近一倍。所以数据量没有大到一定程度的时候,64是一个性价比很高的起点。如果后续数据量扩充了,再往上加到128也不迟。
3.4 lookback窗口、batch size与学习率
lookback窗口的选择可以直接实验对比,我用过一个笨办法:分别用12、24、48、168跑几组实验,对比验证集loss。实测下来24小时窗口在小时级预测任务上的表现最均衡,12小时因为缺少跨天信息导致夜间预测偏差大,168小时窗口数据维度高、训练慢,但MAPE并没有明显下降。
batch size我一般设为64或128。过小会导致梯度更新方向抖动剧烈,过大则内存占用高而且容易收敛到平坦但泛化差的区域。学习率初始值用0.001,配合指数衰减或者ReduceLROnPlateau,训练后期自动降学习率来精细收敛。实测中,ReduceLROnPlateau的触发效果比固定指数衰减更可控,因为它只有在loss停滞时才会降低学习率,避免过早衰减导致收敛不充分。
4. 训练过程翻车记录:Loss震荡、过拟合和早停策略实操
代码写出来跑通只是第一步,真正让人挠头的是训练过程中出现的各种异常。我把实际踩过的坑和对应的处理方法整理一下,这些经验在教科书上很难找到。
4.1 Loss震荡不收敛:先查数据再查学习率
我遇到过最迷惑的情况是训练集loss在一开始就不降反升,甚至出现NaN。排查链路是这样的:先检查归一化是否出了问题,比如某列特征没有做缩放,数值直接爆掉;接着检查序列构造是否存在泄漏,比如滑窗内混入了目标值自身。排除这些之后,如果正常训练但仍然震荡,那就需要降低学习率,从0.001降到0.0003试试。
还有一次很隐蔽的问题:我在构造特征时,把"目标值的小时标记"误当成了特征列,训练时模型走了捷径,验证集效果一塌糊涂。这个坑提醒我,特征工程做完之后一定要进行一次"未来信息"自检,一行一行地过特征逻辑,别贪多。
4.2 过拟合的直观信号与应对手段
过拟合最直接的信号是训练loss不断下降,但验证loss在某个epoch之后开始回升。这时我一般先看训练集和验证集的差距有多大:如果训练集MAE已经压到极低,而验证集远高于它,基本可以确定过拟合了。
应对手段按优先级排序:第一,增加Dropout比率,我会从0.2提到0.3或者0.4;第二,缩小LSTM隐层维度,从64降到32;第三,增加训练数据量,或者用更短的lookback窗口来减少模型容量;第四,加入L2正则化。注意Dropout和L2不是加得越多越好,过大之后模型会欠拟合,表现为训练集和验证集的误差同时上升。
4.3 早停策略:不要执着于固定的epoch数
训练LSTM的时候设置一个固定的epochs数,比如100或者500,这其实是一个非常粗糙的做法。我现在的习惯是配合EarlyStopping,监控验证集loss,当它在连续15到20个epoch内没有下降时,就提前终止训练,并且把验证集loss最低的那个epoch对应的权重保存下来。
early_stop = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True) lr_scheduler = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5)EarlyStopping的好处不只是省时间,它本质上就是"在模型泛化能力最强的那一刻截断训练"。如果不设早停,训练到后期虽然训练loss还在微降,验证loss已经开始恶化,保存下来的模型反而不是最优的。
4.4 梯度裁剪:处理Loss突然爆掉的小技巧
LSTM在训练过程中偶尔会遇到loss突然飙到一个离群值的情况,这与梯度爆炸有关。在Keras里可以通过clipnorm参数来控制梯度范数的上限,比如设置clipnorm=1.0。这个参数平时用不上,但如果你的loss曲线偶尔出现尖峰,加上它往往能让训练过程稳定不少。
5. 结果评估别只看MAE:峰谷电价捕捉能力才是关键
模型训练完之后,评估环节最容易出现的问题,是只看一个平均误差指标就下结论。我之前也犯过这样的错误,后来被实际场景教育了:某些时段预测的误差被平均指标"稀释"了,但那些时段恰恰是电力交易中最需要精准预测的时刻。
5.1 指标不是越多越好,但要选对
电价预测常见的指标有MAE,RMSE,MAPE。我一般会同时打印这三个值,因为它们的评价视角不同:MAE反映平均绝对偏差,RMSE对大幅偏差更敏感,MAPE反映相对误差的大小。还有一点需要注意的是,当电价出现接近0的低谷值时,MAPE会被分母拉得很离谱,所以如果数据里存在大量低价时段,MAPE的值要大打折扣看待。
5.2 按时段拆分误差:比总指标更有洞察力
我在评估阶段会把测试集的预测结果按小时维度拆分,分别计算早上7点到10点、晚上18点到21点这两个用电高峰段的误差指标,再计算其他时段的误差指标。为什么要单独拆峰谷?因为这两个时间段的价格绝对值高、波动大,预测偏差直接影响交易策略的盈亏。很多时候总平均MAE看起来还行,拆开一看发现高峰时段的MAE是其他时段的两倍不止,这种信息只看总指标是永远发现不了的。
具体实现也很简单,就是把预测结果和真实值按时间标签存储,然后用pandas按小时分组求MAE。最后画出一张24小时的误差分布图,哪个时段预测弱,一眼就能看出来。
5.3 反归一化:一个极其容易翻车的细节
测试集预测出来的结果是通过scaler归一化过的,评估之前必须反归一化回真实电价尺度。很多人在这里图省事,直接把归一化后的误差当成最终指标,结果发现MAE只有0.05就觉得模型很完美,实际换算成真实电价完全不是一回事。正确做法是先用scaler.inverse_transform把预测值和真实值都还原,再计算误差指标。
反归一化之后还有一个小细节:如果预测值出现了负值,这在电价场景下实际上是不合理的,需要做截断处理,把负值置为0或者一个很小的正数。虽然LSTM输出层用的是线性激活,不排除个别点会拟合出负值,但这种点要单独处理,否则下游交易策略会把负价格当成真实信号。
5.4 可视化对比:预测曲线和真实曲线叠加
评估的时候我习惯把测试集最后7天的真实电价和预测电价画在同一个图上。这个图能直观告诉我的信息比任何指标都多:如果预测曲线整体滞后于真实曲线,说明模型对短时波动的响应偏慢,可能需要调整lookback窗口;如果峰谷趋势跟上了但幅度偏小,说明模型存在过度平滑的问题,可能是Dropout率太高或者模型容量不足。
6. 源码工程化:从单个脚本到可维护预测工具的整理思路
很多开源项目里的LSTM预测代码是一个大而全的脚本,从上到下依次是导入库、读数据、处理、训练、评估。这种结构在一个固定数据集的演示场景下没有问题,但一旦要接新数据、换参数、部署到生产环境,就会非常痛苦。所以我建议把项目按功能模块拆分,一个清晰的目录结构本身就是项目质量的一部分。
6.1 推荐的项目目录结构
electricity_price_lstm/ ├── data/ │ ├── raw/ # 原始数据 │ ├── processed/ # 清洗后的数据 │ └── scaler.pkl # 归一化参数 ├── src/ │ ├── data_loader.py # 数据读取与清洗 │ ├── features.py # 特征工程与序列生成 │ ├── model.py # LSTM模型定义 │ ├── train.py # 训练脚本 │ ├── evaluate.py # 评估脚本 │ └── utils.py # 公共工具函数 ├── configs/ │ └── config.yaml # 超参数配置 ├── models/ │ └── lstm_model.h5 # 训练好的模型权重 └── notebooks/ └── eda.ipynb # 探索性数据分析这个结构里最重要的就是config.yaml,把所有超参数集中管理。换数据集或者调参的时候,只需要改配置文件,不需要动代码。对于做实验来说,这个改动省下来的时间非常可观。
6.2 模型保存、加载与推理封装
训练完成后把权重和配置一起保存,这样别人拿到的不是一个黑盒模型文件,而是一整套可复现的实验配置。
model.save('models/lstm_model.h5') # 推理时加载 from tensorflow.keras.models import load_model model = load_model('models/lstm_model.h5')在部署的时候,我会在推理模块里做一个很小的封装函数,输入的是原始特征DataFrame和lookback窗口,内部完成标准化和序列拼接,输出的是反归一化之后的真实价格。用户不需要关心内部细节,这样的接口设计在实际使用中体验好很多。
6.3 后续扩展:从单步预测到多步预测
这篇文章里讨论的是单步预测,也就是用过去24小时预测未来1小时。实际业务里,我们往往需要预测未来24小时甚至72小时的价格曲线。扩展方向有两种:一种是递归预测,把预测出来的值作为输入的一部分,继续预测下一个时刻,这种方式实现简单但误差会随着预测步长累积;另一种是seq2seq结构,用编码器-解码器架构直接输出未来24个小时的价格序列,误差累积问题会好一些,但模型结构更复杂,训练时间更长。我个人的建议是先做好单步预测,把整个数据链路打磨稳定,再扩展多步,这个顺序能少踩很多坑。
6.4 代码质量与可复现性的几个经验
写这类项目代码时,有几点是我现在非常在意的:所有随机操作设置固定的随机种子,保证实验可复现;每个处理函数写清楚输入输出格式,方便自己隔一段时间回来看也能快速上手;重要步骤加注释说明"为什么这样做",而不是只写"做了什么"。
最后再分享一个实操中容易忽视的细节:当你要发布这套源代码的时候,记得把数据脱敏,尤其是数据里可能包含特定区域、特定交易机构的信息,尽量只保留时间序列本身。这样既安全,又方便别人用任意一份电价数据替换测试,真正发挥开源源码的价值。
本文还有配套的精品资源,点击获取