ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于CNN-LSTM的短期负荷预测:原理、数据处理与PyTorch实现

基于CNN-LSTM的短期负荷预测:原理、数据处理与PyTorch实现 简介本资源是《电网技术》2019年刊出的一篇电力系统短期负荷预测论文PDF适合电力调度、智能电网及深度学习应用方向的研究人员与工程师阅读。文中针对负荷数据非线性、强时序性等特点提出CNN与LSTM结合的混合预测模型将历史负荷、气象、日期和峰谷电价等数据按滑动窗口构造连续特征图先由CNN提取局部与非连续特征间的潜在关联再以时序方式输入LSTM完成预测并通过江苏某地区实际负荷数据验证效果。实验结果对比表明该方法在预测精度上优于传统ARIMA、BP神经网络、随机森林及标准LSTM模型。资源为单文件PDF压缩包大小1.22MB便于批量下载与离线查阅。目前已有3998人学习可作为短期负荷预测方向的方法参考与复现依据。1. 短期负荷预测为什么盯上CNN-LSTM一张时间表里的两个问题电网调度大厅里每天上午的例行工作之一是把次日96个点的负荷曲线报出来。报高了多开一台机组备用一晚上多烧燃料报低了实际负荷一旦冲上去就得临时调用高价电。短期负荷预测未来数小时到数天的负荷预测就是这么一项直接算经济账的工作。基于CNN-LSTM混合神经网络模型的短期负荷预测方法是近年在工程上被反复验证的一条路径CNN负责从历史负荷序列里提取局部形态早晚双峰、午间凹陷LSTM负责捕捉跨时段的先后依赖昨天的此时段与今天的此时段高度相关。这篇文章沿数据清洗、滑窗构造、模型搭建、训练评估的顺序给出可复现的落地路径适合正在做电力数据分析的算法工程师以及从统计模型转向深度学习的研究生。2. CNN和LSTM各自解决什么问题混合神经网络的选型逻辑2.1 一维卷积在负荷序列里提取的是“形态”不是“趋势”负荷曲线不是白噪声。工作日的负荷曲线有非常稳定的形态早上8点到11点爬升、午间12点到14点下落、傍晚18点到21点冲到高位。这种形态跨日重复是卷积神经网络最擅长抓取的模式。把负荷序列看作一维信号卷积核就像一个滑动的模板在序列上反复比对“这一段像不像早高峰的陡升”。这种特征提取模式让CNN在负荷预测里天然比全连接网络多一个优势它不受“某个时刻的负荷值”精确位置的束缚只要局部形态存在就能激活对应的卷积核。import torch.nn as nn conv nn.Conv1d( in_channels1, # 输入通道单变量负荷序列 out_channels32, # 卷积核数量即提取32种局部形态 kernel_size3, # 每次看3个连续时刻 stride1, padding1 # 保持序列长度不变 )这段定义了一个最基础的一维卷积层。in_channels1表示输入是单变量负荷序列out_channels32意味着有32个卷积核并行扫描每个卷积核学习一种局部形态kernel_size3相当于每次只看3个连续时刻对应15分钟粒度下的45分钟窗口。padding1让输出长度和输入长度一致方便后续接LSTM时对齐时间步。注意这里说的是“局部形态”而不是“整体趋势”。卷积核的感受野有限一个kernel_size3的卷积核看不到“昨天和今天的关联”这是CNN的边界。因此它只做特征提取器不做序列建模器。真正承担跨时段记忆任务的是LSTM这也是CNN-LSTM结构里两者分工的起点。2.2 LSTM门控机制解决的是“跨时段的记忆”负荷序列里有一类依赖是CNN看不见的三天前开始持续高温空调负荷逐日累积抬升昨天晚高峰的负荷水平会影响今天晚高峰的起点。这些依赖的时间跨度从数小时到数天需要模型具备“记住关键状态、遗忘无关信息”的能力。LSTM的遗忘门、输入门、输出门就是为了这个目的设计的。遗忘门决定昨天的负荷状态保留多少输入门决定当前时段的观测写入记忆多少输出门决定当前隐藏状态释放多少给下一层。在CNN-LSTM结构里LSTM看到的不是原始负荷而是CNN压缩后的特征序列这大大减轻了LSTM的记忆负担——它不需要从原始波动里自己找形态只需要学形态之间的前后承接关系。这里有个容易误解的点LSTM的hidden_size不是“记住几天”而是“用多少维向量表示当前状态”。hidden_size设得越大模型表达能力越强但过拟合越快。负荷预测任务我一般从32起步最大到64超过128几乎必过拟合。另一个常见误区是盲目堆LSTM层数——负荷序列的依赖结构远没有自然语言那么复杂单层LSTM在大多数情况下已经够用两层以上只会让训练变慢、泛化变差。2.3 为什么是“CNN-LSTM”而不是纯LSTM、纯CNN或Transformer纯LSTM的问题是序列太长时梯度在时间步间传递容易衰减。模型局部特征提取长程依赖建模样本量要求训练成本纯CNN强弱中低纯LSTM弱强中中CNN-LSTM强强中中Transformer强强高高把672个时间步直接喂给LSTM模型需要从原始负荷里同时完成形态识别和依赖建模两件事而原始负荷里大量局部细节对长程记忆是噪声这会让LSTM的遗忘门负担过重。纯CNN的问题是感受野有限想覆盖“三天前的同一时段”需要堆很多层或加很大的卷积核计算量上不划算而且卷积本身没有“保留跨天状态”的机制。Transformer在自然语言处理上很强但负荷预测的样本量通常只有一两年的小时级数据几千个样本很难喂饱注意力机制且注意力对序列顺序的建模需要额外位置编码超参又多工程上调起来极费时间。实践里CNN-LSTM是一种“计算效率与建模能力折中”的方案CNN把672个时间步压缩成更短的特征序列LSTM在这个压缩序列上建模依赖全连接层输出未来24点。什么情况下不建议用CNN-LSTM如果负荷规律非常平稳比如工厂的恒定生产线负荷、样本量不足三个月简单模型昨天同时刻、ARIMA往往更稳。混合神经网络的价值体现在负荷形态丰富、受气温和节假日影响的场景里这是选型前要想清楚的前提——不是所有预测任务都值得上深度模型先跑通一个naive baseline再做结构选型是更稳妥的工程习惯。3. 把原始负荷数据变成训练样本滑窗构造与归一化的几个关键细节3.1 数据清洗缺失值、异常值和节假日的三步处理负荷数据从SCADA系统导出后几乎不可能直接可用。遥测中断导致整段缺失、某台变压器检修导致负荷跳零、节假日负荷水平骤降40%——这些都要在构造样本之前处理干净。我一般按三条规矩做缺失值用前后两小时同时刻的均值补线性插值在负荷突变段容易失真异常值用滑动MAD准则先粗筛再人工核对跳变点节假日做独立特征不参与连续日模式的学习。import pandas as pd import numpy as np def clean_load_data(df, load_colload): # 1. 缺失值线性插值兜底连续缺失超6小时用前7天同时刻均值 df[load_col] df[load_col].interpolate(methodlinear, limit24) long_missing df[load_col].isna() if long_missing.any(): df.loc[long_missing, load_col] ( df[load_col].shift(96).rolling(7*96, min_periods1).mean() ) # 2. 异常值偏离7天滑动中位数超过3倍MAD的替换为中位数 median df[load_col].rolling(7*96, centerTrue).median() mad (df[load_col] - median).abs().rolling(7*96).median() mask (df[load_col] - median).abs() 3 * 1.4826 * mad df.loc[mask, load_col] median[mask] return df这里比较关键的是MAD中位数绝对偏差而不是3σ。负荷数据的尖峰往往不是正态分布用均值和标准差容易被真实尖峰本身带偏MAD对尖峰更稳健。1.4826这个常数是把MAD折算到标准差尺度用的。shift(96)是取前一天同一时刻这是15分钟粒度下的一个自然周期rolling(7*96)是取过去7天同时刻的窗口也是同样的周期逻辑。清洗这步没有统一标准核心是让异常样本不要进入训练集。跳变点如果确实存在比如某天某个工厂启停且跟业务相关可以保留但要做好标记不要一刀切地抹掉——负荷预测模型需要学习“正常波动”的形态把这些真实业务事件也抹了预测结果反而会失真。3.2 滑窗构造输入过去7天预测未来24点样本构造的核心问题是用多长的历史窗口预测未来多少个点。窗口太长样本数量减少训练数据不够窗口太短模型看不到跨日规律。我常用的是输入过去7天672个点输出未来24点6小时用于滚动日内预测如果目标是次日96点全曲线输出对应改为96点。滑窗构造时要注意步长——训练集里步长可以设为1测试集里步长必须等于预测长度否则就成了“用上一步的预测结果作弊”。def make_windows(data, in_len7*96, out_len24, step1): X, y [], [] for i in range(0, len(data) - in_len - out_len 1, step): X.append(data[i : i in_len]) y.append(data[i in_len : i in_len out_len]) return np.array(X), np.array(y)这个循环做的事情是从序列起点开始每次截取in_len个点作为输入、紧接着的out_len个点作为标签然后向后滑动step个点。step1时样本量最大但相邻样本高度重叠模型容易把“记忆序列位置”当成“学习负荷规律”测试时我强制stepout_len让每次预测都基于全新的历史段这样评估出来的误差才是真实误差。特征通道上还有一个可扩展的做法把温度、节假日标记作为额外通道拼到负荷序列后面让Conv1d的in_channels变成3或更多。多通道输入让CNN能在提取负荷形态的同时参考外部因素对夏季高温日、长假这类特殊场景有明显改善。但额外通道会显著增加训练时间如果样本量不足宁可不加。3.3 归一化先fit训练集再transform其余数据集归一化是数据泄漏的高发区。MinMaxScaler如果对整个数据集包括测试集统一fit测试集的最大值就被模型提前看到了验证误差会系统性偏低。正确做法是只在训练集上fit然后用同一组min/max去transform验证集和测试集。from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler() # 只在训练段上学习min/max train_2d train_raw.reshape(-1, 1) scaler.fit(train_2d) train_norm scaler.transform(train_2d).reshape(-1) valid_norm scaler.transform(valid_raw.reshape(-1, 1)).reshape(-1)注意reshape成(-1,1)再transform因为MinMaxScaler要求输入是二维。顺序错一步后面的模型评估全部失真。我见过一个项目验证集MAPE显示2.3%上线后实际误差8%——原因就是归一化泄漏模型学到的“规律”里包含了未来的幅值信息验证时自然好看。注意归一化泄漏是负荷预测项目里最隐蔽的坑。任何涉及全量数据的统计量min/max、均值/方差都有可能造成泄漏不只是归一化。另外反归一化也要留个心眼预测值是归一化空间里的评估时要先反归一化再算RMSE和MAPE。如果忘了这一步MAPE会严重失真因为分母是0到1之间的归一化值算出来的百分比完全不对。4. 用PyTorch实现CNN-LSTM负荷预测核心代码与参数调优4.1 模型结构从Conv1d到LSTM的维度转换是第一个卡点CNN-LSTM结构里最容易被维度转换卡住的地方Conv1d期望输入是(batch, channels, seq_len)LSTM期望输入是(seq_len, batch, input_size)中间需要一次permute。另外池化层会压缩序列长度这个压缩后的长度直接影响LSTM的时间步数需要显式计算。import torch import torch.nn as nn class CNNLSTM(nn.Module): def __init__(self, in_channels1, seq_len672, out_len24, cnn_out32, lstm_hidden32): super().__init__() self.conv nn.Sequential( nn.Conv1d(in_channels, cnn_out, kernel_size3, padding1), nn.ReLU(), nn.MaxPool1d(kernel_size2), # 序列长度减半672 - 336 ) self.lstm nn.LSTM( input_sizecnn_out, hidden_sizelstm_hidden, num_layers1, batch_firstTrue, ) self.fc nn.Linear(lstm_hidden, out_len) def forward(self, x): # x: (batch, seq_len) - (batch, 1, seq_len) x x.unsqueeze(1) x self.conv(x) # (batch, cnn_out, seq_len//2) x x.permute(0, 2, 1) # (batch, seq_len//2, cnn_out) out, _ self.lstm(x) # (batch, seq_len//2, lstm_hidden) # 取最后一个时间步的隐藏状态 out out[:, -1, :] # (batch, lstm_hidden) return self.fc(out) # (batch, out_len) model CNNLSTM(in_channels1, seq_len672, out_len24) print(model)几个关键参数说明in_channels设为1表示单变量负荷如果加入温度、节假日特征可以设为3kernel_size3的卷积核配合padding1不改变序列长度MaxPool1d把序列长度从672压缩到336这336就是LSTM的时间步数——这个数仍然不小所以LSTM只用了1层单层在负荷预测里往往比多层更稳。取out[:, -1, :]是拿最后一个时间步的隐藏状态做预测因为我们要预测的是“看完整个历史窗口之后的未来”。如果想让模型利用整个序列的信息而不是只取最后一步可以改成对全部时间步的隐藏状态做平均池化mean pooling在部分场景里能小幅提升精度但参数量不变、代码多几行收益不一定明显。模型结构这块没有标准答案但有一个通用原则CNN的输出通道数乘以序列长度压缩比决定了LSTM看到的特征粒度。输出通道太多LSTM的input_size变大训练变慢输出通道太少特征信息不够。32到64是一个经过验证的常见区间。4.2 训练配置损失函数用MSE学习率从1e-3开始调负荷预测的损失函数工程里用MSE最常见原因有两个它天然惩罚大幅偏差负荷预测偏大偏小都要额外花钱且梯度计算简单。MAE对尖峰不敏感更适合做评估指标而不是训练目标。优化器用Adam学习率1e-3是通用起点验证loss连续5个epoch不降就用ReduceLROnPlateau把学习率降一半。import torch.optim as optim from torch.optim.lr_scheduler import ReduceLROnPlateau loss_fn nn.MSELoss() optimizer optim.Adam(model.parameters(), lr1e-3) scheduler ReduceLROnPlateau(optimizer, modemin, factor0.5, patience5, min_lr1e-5) for epoch in range(epochs): model.train() train_losses [] for xb, yb in train_loader: optimizer.zero_grad() pred model(xb) loss loss_fn(pred, yb) loss.backward() optimizer.step() train_losses.append(loss.item()) model.eval() with torch.no_grad(): val_pred model(X_val) val_loss loss_fn(val_pred, y_val).item() scheduler.step(val_loss)训练循环里有几个细节值得说model.eval()和torch.no_grad()必须成对出现前者关闭Dropout和BatchNorm的随机行为后者关闭梯度追踪省内存且防止验证时反向传播误改参数。scheduler.step(val_loss)要传验证loss而不是训练loss因为我们要监控的是泛化性能min_lr1e-5设个下限防止学习率降到0导致后期完全不更新。epoch数量怎么定我习惯看验证loss曲线而不是固定跑几百轮。最常见的现象是训练loss持续下降、验证loss先降后升这就是过拟合的信号。此时应该做的是减少epoch或增大dropout而不是继续训练。另一个被忽略的参数是batch size16到64之间对负荷预测影响不大但batch越小、梯度噪声越大模型越容易跳出差的局部最优batch太大则训练不稳定。4.3 评估指标MAPE适合汇报RMSE适合优化训练结束后的评估我习惯同时算两个指标RMSE均方根误差用于优化和对比模型MAPE平均绝对百分比误差用于向业务方汇报。RMSE对大幅偏差更敏感两个模型RMSE一样时MAPE更低的那个在低谷时段表现更好而低谷时段的绝对负荷低、经济影响小所以汇报时两者要分开讲。def evaluate_metrics(y_true, y_pred): rmse np.sqrt(np.mean((y_true - y_pred) ** 2)) mape np.mean(np.abs((y_true - y_pred) / y_true)) * 100 return rmse, mape负荷数据量纲大几十兆瓦RMSE直接看好坏不明显一般看相对值——RMSE除以平均负荷得到CV-RMSE变异系数小于5%算优秀5%到10%可接受超过10%就要回头查数据或模型结构了。这里的y_true是反归一化之后的真实值不是训练用的归一化值否则MAPE会严重失真。评估时还有一个容易漏掉的时间维度把一天分成峰段、平段、谷段分别算误差。峰段的绝对误差大但百分比误差小谷段反过来。如果只看全天平均模型在峰段表现差的问题会被谷段掩盖调度员最关心的恰恰是峰段精度。5. 训练CNN-LSTM常见的翻车现场从数据泄漏到节假日的避坑记录5.1 验证集好得离谱上线就翻车归一化泄漏现象验证集MAPE只有2%部署后实际运行MAPE高达8%。原因MinMaxScaler对全量数据含未来做了fit模型在训练时见过了未来的最大值和最小值相当于考试时偷看了答案。解决严格按时间顺序划分数据集只对训练集fit scaler验证集和测试集用transform。新数据进来时增量更新scaler的min/max也要小心别把未来数据混进去。5.2 长假预测整体偏移节假日没有独立建模现象春节、国庆期间预测值普遍偏高偏差最大的一天出现在假期第三天。原因模型只学了“连续日模式”长假期间负荷水平骤降且假期内不存在“工作日/休息日”的交替节律模型把周末模式套在了假期上整体曲线形状对了幅值却差一截。解决把节假日做成独立特征通道is_holiday、距离假期开始的天数或者在滑窗构造时对跨越节假日的样本降权。更简单的做法是长假预测直接改用同类历史假期日作为参考基准不强行依赖CNN-LSTM。5.3 滑窗步长设置不当测试误差失真现象测试集上用step1构造样本RMSE很低改成stepout_len后RMSE翻倍。原因step1的测试样本高度重叠模型在训练时见过的窗口与测试窗口只有几个点的差异相当于把训练集内容泄露到了测试阶段。解决测试集滑窗step强制等于out_len。评估滚动预测能力时用“预测未来24点 → 把真实值并入历史 → 再预测下一个24点”的方式模拟真实部署流程。5.4 LSTM对初始权重敏感同一份数据跑出两个结果现象同样的代码和参数跑两次验证loss差3倍。原因LSTM的权重初始化对梯度流影响很大PyTorch默认的初始化在负荷这类平滑序列上有时会陷入差的局部最优说是玄学也不过分。解决固定随机种子。def seed_everything(seed42): import random random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed)这段代码要在模型初始化之前调用。torch.manual_seed同时控制CPU和GPU上的随机数生成器如果用了DataLoader的shuffle还要给DataLoader传generator参数才能完全复现。固定种子只能保证单个项目可复现换机器或换PyTorch小版本后仍可能有微小的数值差异这是深度学习的常态不是bug。5.5 预测未来96点时误差随时间步迅速累积现象预测未来24点时第一小时误差很小第24小时误差是第1小时的2倍预测96点时后半段曲线几乎失去形状。原因负荷预测的误差随预测时域延长而累积是固有规律但CNN-LSTM里全连接层直接输出24个点模型倾向于把后半段的输出压平到均值附近回归到均值导致曲线形状丢失。解决按时间段分段评估第1-6小时、第7-12小时先确认误差是均匀分布还是集中在远端如果后半段明显拉胯把输出长度缩短到6点用滚动外推方式做96点预测虽然总误差可能更大但曲线形状保得住调度上反而更实用。6. 用滚动式时间序列验证检验CNN-LSTM一个值得养成的评估习惯很多入门项目犯的第一个错误随机打乱数据集做K折交叉验证。负荷序列只要一打乱时间上的连续依赖就被破坏了验证结果没有任何参考价值。时间序列预测的正确验证方式是滚动式验证rolling origin validation固定一个训练起点预测接下来一段时间然后把真实值纳入历史窗口向前滚动再预测下一段。def rolling_validate(model, data, in_len, out_len, start, n_steps): errors [] for i in range(n_steps): train_part data[: start i * out_len] test_part data[start i * out_len : start (i 1) * out_len] # 在实际项目中这里需要重训或增量更新模型 pred model(data[train_part[-in_len:]]) errors.append(evaluate_metrics(test_part, pred)) return errors这个验证过程里每一次预测的起点都在向后移动且每次预测用的都是“真实的历史”不会出现“用预测值当真实值”的自欺欺人。滚动验证的步数n_steps一般取5到10太少看不出稳定性差异太多每次都要重新训练模型、成本太高。我实际项目中一般让滚动验证跨过一个完整周确保工作日和周末都被覆盖。还有一个更实用的基线检查把“昨天同时刻的负荷”作为平凡预测naive baseline你的模型至少要比这个基线好20%以上才算有工程价值。我第一次做CNN-LSTM时验证误差比“昨天同时刻”还高一度以为是模型结构有问题查了两天发现是滑窗构造时样本顺序没有按时间排导致训练集里混入了未来数据。从那以后我把平凡基线检查当成每个模型上线前的固定动作先跑baseline再跑模型免得在错误的数据上白费力气。希望这个滚动验证和基线检查的习惯能让你的CNN-LSTM负荷预测模型少走一段弯路帮到你。本文还有配套的精品资源点击获取
返回列表