简介:面向电力系统负荷预测与智能电网研究场景,提供基于变分模态分解、卷积神经网络和长短期记忆网络组合模型的Python完整实现。该方案可处理负荷数据非平稳、强波动问题,适用于科研复现、算法对比与工程实践。压缩包共9个文件,包括5个Python脚本、2个Jupyter交互笔记、1个Excel数据文件、1个CSV历史负荷数据,整体仅2.82MB,结构紧凑。脚本涵盖VMD分解、CNN-LSTM、LSTM及BiLSTM/BiGRU变体等算法,覆盖数据预处理、模态分解、特征提取、时序建模、四分折训练与结果评估等环节。内置样例数据,下载后配置Python环境即可运行。目前已有712人学习浏览,适合希望快速开展负荷预测实验的研究者与学生使用。
1. 方案选型:单一LSTM不够用,为什么非要组合拳
先说结论:电力负荷预测这件事,越到后面越发现,单纯靠一个LSTM或者Transformer硬扛,很难同时兼顾“趋势”和“波动”两个维度。负荷序列天生就是非平稳的,白天峰谷、工作日与周末、季节性的冷热负荷,这些信号混叠在一起,你把原始序列直接丢给LSTM,模型会花大量容量去拟合那些高频毛刺,真正重要的位置关系反而被淹没了。
VMD-CNN-LSTM这套组合的核心理念,就是“先分解,再逐个击破”。VMD把原始信号按频率从高到低拆成若干个模态分量,相当于把一团乱麻先梳理成几股线;CNN负责从每一股线里抽取局部形状特征,比如某个时段突然爬坡的形态;LSTM再基于这些特征去建模前后时间步的依赖关系。三层分工明确,各干各的活,最后把每个模态的预测结果叠起来,输出最终负荷值。
这套方案适合谁?一种是电力系统相关专业的学生和研究人员,拿来发论文或者毕业设计都很常见;另一种是电力工程领域的算法工程师,手上刚好有历史负荷数据,希望用一个稳定、可控、可解释的模型做短期负荷运营分析。比起端到端的纯深度学习模型,VMD-CNN-LSTM多了一个人为可控的分解环节,出问题的时候你能定位到具体是哪个分量出了问题,这一点在实际工作里非常重要。
我最早也是在单模型LSTM上碰了钉子才转向这个方案的。当时用的数据集来自国内某地级市的公开负荷数据,采样间隔15分钟一天96个点。LSTM单独跑,MAPE大概能到4%左右,听起来还行,但一到日照变化大的季节或者长假前后,误差直接飙到7%往上。后来加了VMD前置分解,误差曲线明显稳定下来。这篇文章就围绕这套实现展开,代码层面我会按照最容易复现的方式来写,环境、依赖、步骤都会讲清楚。
2. 核心原理与参数选择的底层逻辑
2.1 VMD:把复杂负荷信号“提纯”的关键一步
VMD(变分模态分解)是一种完全非递归的信号分解方法,它的目标是把输入信号分解为K个有限带宽的模态分量,每个分量围绕一个中心频率震荡。你把它理解成一台“光谱分离机”,原始信号是混合光,经过VMD之后就变成了K道单色光,每道光都有自己的频率范围。
对比EMD(经验模态分解),VMD最大的优势在于数学基础牢固,不会出现EMD那种由于递归筛选导致的端点效应和模态混叠问题。尤其是负荷数据两端往往存在不完整的升降沿,用EMD容易在端点处长出假分量,VMD在处理这类数据时更稳。实际负荷序列里,你能拆出明显的趋势分量、日周期分量、周周期分量,剩下的高频部分就是随机波动和噪声,这些成分分开建模比混在一起建模要轻松得多。
VMD在Python里一般用vmdpy库来实现,核心参数有三个:
- K:模态个数,这个最关键,太小会欠分解,趋势和周期混在一起;太大会过分解,出现中心频率几乎重叠的假模态。
- alpha:惩罚项系数,它控制模态带宽的约束强度,alpha越大,各模态带宽越窄,频率分离越干净,但过大会让模态失真。
- tau:噪声容忍度,一般设为0,表示完全信任输入数据的保真度。
我在负荷场景下常规的起点是K=6、alpha=2000、tau=0。K的选择没有绝对标准,但你可以在分解之后把各模态的中心频率打出来,如果第K个和第K+1个模态的中心频率非常接近,那就是K设大了,往回减一个就好。这个经验比任何理论公式都好用。
2.2 CNN与LSTM:一个管特征,一个管记忆
CNN在时序任务里经常被当成特征提取器来用。一维卷积沿着时间轴滑动,卷积核能够捕捉到短时间窗口内的局部模式,比如负荷连续三个点快速上升、凌晨时段持续走平这类形态。它的好处是翻译不变性:无论这种爬坡形态出现在凌晨还是傍晚,卷积核都能以同样方式识别出来。
LSTM则负责长距离依赖。负荷预测有一个非常明显的特性——今天的同一时刻与昨天的同一时刻高度相关,前面的卷积层负责把每个时间步的局部形状编码成特征向量,LSTM则在这些特征向量之间建立顺序记忆,让模型知道“前几天下午这个时段开始降温,今天下午也有降温趋势”。遗忘门、输入门、输出门这三个门控机制,本质上就是在决定什么历史信息该丢、什么新信息该写进状态、什么信息该输出给下一层。
在CNN-LSTM的组合方式上,我的做法是Conv1D层加MaxPooling1D层反复堆叠,把序列长度逐步压缩,最后接LSTM层。卷积层不是让LSTM输入更长更丰富的序列,而是帮它把信息浓缩压缩。直接给LSTM灌入96个原始时间点也是一种方案,但效果通常不如先卷积压缩再进LSTM的结构。前提是你数据量不能太少,比如只有几千条样本的话,参数规模大的组合结构反而容易过拟合。
3. Python代码实现与踩坑实录
3.1 环境准备与数据预处理
我用的是Python 3.9环境,深度学习框架选择TensorFlow 2.x的Keras接口,VMD分解用vmdpy库。你在安装的时候要注意,vmdpy只依赖numpy和scipy,安装非常快,不会遇到什么环境冲突问题,TensorFlow则建议用2.10以上版本搭配2.9以上的Keras,这两个版本匹配关系算是比较稳定的组合。
数据预处理包含这么几件事:
- 缺失值处理:负荷数据偶尔会出现空值,我一般用前一天同一时刻的值做线性插值,而不是用全局均值,因为每天同一时刻的负荷相关性极强。
- 异常值剔除:明显超出正常范围的点,比如某时刻负荷突然变成0又恢复,多半是采集设备故障,直接剔除。
- 归一化:这一步必须做。VMD分解本身不要求数据一定在某个区间内,但后续喂给CNN和LSTM时,未经归一化的输入很容易让训练过程震荡或者收敛过慢。我用MinMaxScaler把所有数据压到0到1之间,注意先拟合训练集的scaler再变换测试集,不能用全量数据拟合,否则会出现信息泄漏。
数据划分上有个重要细节:负荷预测这类时间序列问题,不能用随机切分的方式划分训练集和测试集,必须按照时间顺序来。我用前80%的数据做训练集,后20%按时间顺序做测试集。如果你随机打乱,模型相当于偷偷看到了未来的信息,测试指标会虚高到没有任何参考价值。
划分完成后还需要构造监督学习样本。用滑动窗口的方式,设定一个时间步长比如step=48,也就是过去两天(每个点15分钟)的历史数据来预测下一个点。构造出来的每个样本形状是(step, 1),经过CNN一维卷积需要变成(step, 1)或者(step, feature_dim),特征维度看你要不要加入温度、湿度这类外部变量。这里我先不加入外部特征,纯粹用负荷自身的历史序列做预测。
3.2 VMD分解与模态预测的完整流程
VMD分解的调用方式非常直接:
from vmdpy import VMD import numpy as np # data是一维负荷序列,已经是归一化后的 alpha = 2000 # 惩罚项 tau = 0 # 噪声容忍度 K = 6 # 模态数量 DC = 0 # 不强制第一个模态是趋势项 init = 1 # 均匀初始化中心频率 tol = 1e-7 u, u_hat, omega = VMD(data, alpha, tau, K, DC, init, tol)u就是分解出来的模态分量,形状是(K, len(data))。拿到u之后,需要做一个标准化处理——每个模态分量分别进行归一化,保持数据尺度一致,否则高频分量的振幅可能只有低频分量的百分之一,模型很难同时兼顾。需要注意一个问题:VMD在边界处的模态会有轻微的波动放大效应,所以分解前我在数据序列两端各延长了一小段反射信号,分解完再裁剪掉。用反射镜像延拓比零填充要自然很多,能显著减少端点处的畸变。这个细节是我看了几次分解图之后试出来的,一开始在端点处分解效果总是模糊,加了延拓之后清晰多了。
每个模态都单独训练一个CNN-LSTM模型,结构如下:
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense, Dropout def build_cnn_lstm(step=48, filters=64, lstm_units=32): model = Sequential() model.add(Conv1D(filters=filters, kernel_size=3, activation='relu', input_shape=(step, 1))) model.add(MaxPooling1D(pool_size=2)) model.add(Conv1D(filters=filters//2, kernel_size=3, activation='relu')) model.add(MaxPooling1D(pool_size=2)) model.add(LSTM(lstm_units, return_sequences=False)) model.add(Dropout(0.2)) model.add(Dense(1)) model.compile(optimizer='adam', loss='mse') return model这里有个容易被忽略的点:卷积核和池化的尺寸要匹配你的步长。step=48经过两次池化后序列长度变成12,如果step改成其他值,可能不够两次池化压缩,你就得减少卷积层数或者调整pool_size。
训练过程我用的是Adam优化器,初始学习率0.001,batch_size=32,epoch上线100,同时加EarlyStopping,监控验证集损失,patience=10就停。K个模态训出来的模型是独立的,虽然结构一样,输入输出各自独立,最后把所有模态的预测结果相加就得到最终负荷值。
为什么要每个模态分开建模而不是把所有模态拼起来送进一个多通道模型?两种做法都有人做,但我实测下来,分开模型训练更稳定。因为各模态的频率特性差异很大,低频趋势分量在时间上的惯性极强,高频噪声分量几乎就是白噪声,强行塞进同一个模型会让梯度更新方向被高频分量带偏。分开建模之后,你可以单独看每个模态的预测误差,定位改善空间,这也是这套组合模型好调优的原因。
3.3 反归一化与预测结果组装
每个模态的预测结果都是0到1之间的归一化值,需要先还原到该模态的真实尺度,再把所有模态加总。这里有一个容易犯的错误:你不能先加总归一化预测值再去反归一化,因为每个模态的scaler是各自拟合的,必须先用各自的scaler做逆变换,再相加,顺序不能反。
# preds_imf是一个列表,每个元素是shape (n_test,)的归一化预测值 total_pred = np.zeros_like(preds_imf[0]) for i, pred in enumerate(preds_imf): total_pred += scaler_imf[i].inverse_transform(pred.reshape(-1, 1)).flatten() # 最后再和真实值对比组装完成后,就可以计算误差指标了。还有一个细节:因为VMD分解时用了端点延拓,我把每个模态的延拓部分对应的预测结果也一并裁剪掉了,确保预测值和真实值在时间轴上一一对应。
4. 效果评估与指标解读
这套模型训练完之后,我跟基线模型的对比结果很有代表性。这里给出我跑某地级市公开数据集的一个典型结果,测试集覆盖了连续60天的负荷数据:
| 模型 | MAE (MW) | RMSE (MW) | MAPE (%) |
|---|---|---|---|
| LSTM | 45.6 | 63.8 | 4.21 |
| CNN-LSTM | 43.2 | 60.9 | 3.95 |
| VMD-CNN-LSTM | 36.8 | 51.2 | 3.36 |
可以看到,加了VMD之后MAPE降低了接近0.6个百分点。从相对值来看好像不是很夸张,但负荷预测是一个靠规模效应体现价值的场景,一个中等规模的区域配电网日负荷峰值有几百上千兆瓦,0.6个百分点折算下来就是每天好几兆瓦时的误差偏差,长期累加起来非常可观。
我更关注的是误差分布的变化。单一LSTM在高频波动时段经常出现局部预测失效的情况,而VMD-CNN-LSTM把高频部分单独分配给了一个模态模型,低频趋势部分的预测曲线非常平滑,即使在负荷突变段,误差也主要集中在小幅超调或者小幅滞后,不会出现那种完全脱离真实轨迹的“飞点”。
判断模型好坏的时候,不要只看指标均值。我习惯把预测残差按小时维度做统计,看看哪些时段最容易误差放大。实测下来,早上六七点的起床负荷爬坡段和晚上八九点的负荷回落段是最容易出偏差的位置,说明这两处形态变化快,模型在局部特征提取上有改进空间。这也是CNN能在这套组合里发挥作用的地方——卷积核算出来的特征图可以拿去可视化,看看模型到底关注了哪些时段的形态特征。
如果你要写论文,建议把各模态的预测曲线和叠加后的最终曲线都画出来,放在一张图里。审稿人和老师看到模态分解图后基本上能一眼看懂你的整个思路,远比你用大段文字去描述“该方法能够有效提取特征”之类的空话管用得多。
5. 常见问题与排查技巧实录
5.1 模态个数怎么选,为什么分解结果看着不对劲?
K值选的不好是最常见的问题。K太小,你会看到某个模态里同时包含了缓慢趋势和明显的日周期性波动,后续无论怎么调网络结构都压不住误差;K太大,会出现两个模态的中心频率挤在一起,分解出的波形高度相似,白白增加了计算量。
我的经验是:先设K=5跑一次,打印每个模态的中心频率。如果第5个和第4个频率差距很小,把K调成4再跑;如果发现最后的低频模态中心频率接近0,这个就是趋势项,低频模态不会再分离出更多信息,就应该停止增加K。负荷数据我绝大多数时候用K=5~8之间,还没见过超过8的需求。
5.2 训练时loss不下降或者震荡,优先查什么
排除法顺序:先确认输入数据没有NaN,再确认数据已经归一化,然后检查网络结构和学习率是否匹配。我遇到最多的场景是学习率太大导致loss来回震荡,直接用0.001跑还正常,但如果你把CNN层数加多了,初始学习率需要适当调低到0.0005。
此外,LSTM很容易受到序列长度影响。step设得越长,模型需要记忆的时间跨度就越大,训练难度也随之提升。如果你的step超过了96(一天的数据),建议先把序列压缩到更低维度再接LSTM,比如让CNN先把96个点压缩到24个特征时间步。
5.3 预测曲线总是滞后真实值,怎么处理
滞后问题在负荷预测里非常典型,尤其是采用滑动窗口用过去预测未来时,模型学到的最简单策略就是把上一时刻的值复制过来,因为负荷曲线具有强自相关性,这个“偷懒”策略往往loss还很低。
我亲测有效的改善手段有两种:一是把训练样本中的中间部分随机遮掉一部分,迫使模型不能完全依赖最近时刻的值;二是把预测目标从“下一个单点”改成“未来多步序列”,让模型必须建立一个更全局的时间关系。对于短期负荷预测来说,第一种方法更简单直接,改动成本最少。我在同样数据集上试过,使用mask技巧之后滞后现象能减轻不少。
5.4 Python环境相关的坑
跑这套代码时最常见的问题集中在vmdpy和TensorFlow的兼容性上。vmdpy是老库,个别新版本numpy去掉了某些旧接口,运行时会报module 'numpy' has no attribute 'float'之类的错误。解决办法是把numpy降级到1.23.x版本,或者直接给numpy.float做个兼容别名,我一般选择降级,省得改代码。TensorFlow部分建议装CPU版就够了,负荷预测的数据量不大,用GPU跑反而有额外的显存管理和CUDA配置变量要处理。
6. 后续可做的扩展方向
这套VMD-CNN-LSTM结构本身是一个足够稳的骨架,后续很多优化都是在这个骨架上的“追加”。如果你手头有外部气象数据,比如温度、湿度、风速,可以在每个模态的模型输入层那里叠加这些外部特征,而不是把它们强行拼接进负荷序列里。因为外部特征对负荷的影响是非线性的,比如温度对夏季空调负荷的影响主要集中在高频波动分量上,把它加到对应模态的输入里效果会更好。
另一个值得尝试的方向是把高频模态的预测从单值输出改成概率分布输出。比如最后一个Dense层输出一个高斯分布的均值和方差,用NLL损失函数训练。这样不仅给出预测值,还能给出预测置信区间,在实际调度场景里置信区间比点预测更实用,要知道电网运营人员面对的不确定性不光来自负荷,还包括新能源出力的波动。
如果你的业务场景需要做未来多步预测,建议把最后一个LSTM改成return_sequences=True并接一个时序Dense层,或者采用Seq2Seq结构。单点预测模型在滚动预测时会不断累积误差,滚动步数越长,误差发散越明显,这类多步结构的优势会非常突出。
我在实际工程里最终更看重的是计算效率和可维护性。VMD分解一次性完成,K个模态模型可以并行训练,并不比单一LSTM模型慢多少。整个流程拆得开、看得清,出了问题能迅速定位到某个模块,这套方案从研究到落地的路径是最短的。
本文还有配套的精品资源,点击获取