去年我接手了一个区域电力负荷短期预测的小项目数据拿到的第一时间我直接按照惯常思路搭了一个LSTM网络——结果训练集上表现还说得过去一到测试集上MAPE直接高到没法看预测曲线整体滞后早上八点的负荷爬坡它硬是慢了一拍。后来把结构换成CNN和RNN的混合网络前面用卷积层抽取局部特征后面接LSTM建模时序依赖效果才真正稳下来。这篇文章就把整个链路完整写一遍从电力负荷数据的特性分析、CNN和RNN的分工逻辑到样本构造、训练阶段最容易踩的坑以及最后怎么用Matlab把预测结果画成能直接放进报告里的图。适合正在做电力负荷时间预测、或者想了解CNN-RNN混合网络怎么落地的同学参考。1. 电力负荷数据到底特殊在哪为什么不能拿通用时间序列模型硬套1.1 负荷曲线的三类“规律”与两类“突变”电力负荷数据表面上看就是一个时间序列但它的结构比大多数公开数据集要复杂得多。先说规律我总结为三类第一是日周期。绝大多数地区的负荷曲线都有明显的“双峰”特征早高峰和晚高峰凌晨低谷。这个规律来自人的作息基本稳定。第二是周周期。工作日和周末的负荷形态差异非常明显周五晚高峰和周六同时段的负荷能差出不少因为工厂停工、商业负荷变化、人们出行模式改变。第三是季节趋势。夏天降温负荷和冬天采暖负荷会整体抬升春秋两季相对平缓而且不同季节的日峰值出现时刻还会漂移。再说不规律的部分。第一类突变是节假日。春节、国庆这种长假期间城市负荷可能整体下降两到三成变化幅度远大于正常波动而且节前节后的过渡日形态同样特殊。第二类突变是极端天气和临时事件比如持续高温导致空调负荷集中释放、大风降温、大型活动散场后的短时高峰。这些特性带出了一个关键结论负荷数据并不是“平稳随时间变化”的序列而是“强周期、带突变、受外部变量调制”的混合信号。如果直接拿通用时间序列模型硬套模型很可能只学到了周期成分遇到突变瞬间失效。1.2 选型前先定好评价指标否则后面没法对比模型做出来好不好不能靠眼睛看曲线“差不多”就下结论。我建议在动手之前就把评价指标定下来常用的是这三个指标计算公式特点说明MAEmean(|y_true - y_pred|)平均绝对误差直观对异常值不敏感RMSEsqrt(mean((y_true - y_pred)^2))对大误差惩罚更重能体现“灾难性预测”MAPEmean(|y_true - y_pred| / y_true) × 100%相对误差便于跨场景汇报但小负荷值会放大数字有个坑必须提醒MAPE在凌晨负荷低的时段会变得非常敏感。比如凌晨两点真实负荷是120MW预测误差10个MWMAPE就是8%以上同样10个MW误差发生在晚高峰800MW的时刻MAPE只有1.25%两者实际工程量级完全不同。所以搞负荷预测我通常同时报RMSE和MAPE而且一定要画真实值和预测值的对比图防止被单个指标误导。2. CNN与RNN的分工逻辑混合网络的每个部件都在干什么2.1 CNN不是“看图”是抓局部模式很多人一听到CNN就想到图像分类但在负荷预测里CNN完全不是这个用法。这里用的是一维卷积卷积核沿着时间轴滑动每个卷积核相当于一个局部模式探测器。举个例子一个卷积核可能学到“连续三个点快速上升”另一个学到“负荷开始见顶回落”这些局部形态就是负荷曲线里的“短语”。多个卷积核叠加就能抽取出不同类型的短时变化特征比如早高峰的陡峭爬坡、午间的平缓过渡、晚高峰的双肩形态。为什么非要用CNN抓这个因为LSTM这类循环网络擅长的是捕捉长期依赖但它对“局部形态”的建模效率并不高尤其是当序列很长时LSTM容易把早期的局部细节“压扁”到记忆里。CNN先把局部特征提炼出来LSTM后面处理的就是“精炼后的信息”而不是原始毛刺数据。我用一个生活类比CNN像是先快速扫描全文把关键词标出来LSTM像是通读全文理解上下文关系。两者分工前者管“看到什么”后者管“记住什么意味着什么”。2.2 LSTM处理的是依赖不是顺序RNN系列网络在时间序列任务里的核心能力不是“按照顺序读数据”而是建立跨时间的依赖关系。LSTM通过输入门、遗忘门、输出门三个门控结构决定哪些历史信息要保留、哪些要丢弃。回到负荷预测场景LSTM需要捕捉的依赖包括今天上午九点的负荷跟昨天上午九点有关跟七个整天前的工作日形态有关还跟最近几小时的趋势有关。这些依赖的时间跨度从几小时到几天不等。单一LSTM的问题在于它把这些依赖关系和原始数值细节混在一起学训练效率低而且对突变的响应偏慢。CNN先做特征提取之后LSTM的输入维度下降需要建模的“语义空间”更紧凑收敛更快泛化也更稳。2.3 三种常见混合结构以及我的选型CNN和RNN的组合方式业界没有唯一标准我整理一下常见的三种结构方案连接方式适用场景我的评价串联式A输入 → Conv1D → LSTM → Dense单变量或少量变量时序特征明显最常用结构简单适合多数场景串联式B每个时间步先过CNN提取特征再按时间顺序进LSTM多变量且每个时刻的变量间空间关系重要变量多时效果好但计算量更大并行融合CNN分支和LSTM分支分别提取特征后拼接同时需要强局部特征和长依赖数据量充足效果好但参数多需要更多调参精力我最终采用的是串联式A的变体输入序列先经过两层Conv1D中间插一个MaxPooling降维然后接一个LSTM层最后通过全连接层输出预测值。核心考虑两点一是项目数据量不大结构太复杂容易过拟合二是这个结构的参数数量可控训练时间在可接受范围内。关键参数我给了个基准线Conv1D的卷积核大小选3第一层32个卷积核第二层64个池化窗口2LSTM隐层单元数64Dropout设0.2。这个配置不是最优的但作为起点非常稳后面可以根据验证集表现微调。3. 样本构造与数据预处理的完整链路3.1 缺失值和异常数值处理顺序很重要拿到原始负荷数据第一件事不是建模而是把数据质量搞干净。顺序错了后面全白费。先处理缺失值。时间序列的缺失值不能像表格数据那样直接删行因为负荷曲线有连续性删掉一段会导致序列断裂。我习惯的做法是缺口小于3个点用线性插值缺口较大用前后同类型日的均值填充。比如早上八点的数据缺了就用前一个工作日和后一个工作日早上八点的平均值。再处理异常值。异常值通常来自采集设备故障或者数据传输错误表现为突然跳变、归零、或者超出物理合理范围。我的做法是先画一遍全量曲线直观地看再用滚动窗口加阈值检测计算每个点附近前后2小时的中位数如果当前点偏离中位数超过三倍标准差就标记为异常用中位数替换。这个步骤我吃了不少亏。刚开始只看了统计量没画图结果一个连续三天的“假高峰”混进训练集模型学了个莫名其妙的模式测试集上每到那段时间误差就增大。所以劝大家预处理阶段一定要可视化眼睛扫一遍比什么算法都可靠。3.2 滑动窗口怎么切窗口长度、步长与数据划分模型不能直接吃整条时间序列要把序列切成“样本”。一个样本包含两部分输入特征过去一段时间的负荷和预测目标未来某个时刻的负荷。窗口长度我的经验是取24的倍数因为负荷有日周期窗口至少要覆盖一个完整日周期。项目里我试过24、48、72、168一周最终选了48——覆盖两个完整日循环信息量足够又不会太长导致样本数量过少。这里有一个特别重要的原则划分训练集、验证集和测试集时必须按时间顺序切绝对不允许随机打乱。日志类数据用随机划分会泄漏未来信息模型在验证集上表现虚高一到真实场景就崩。我的切法是前70%做训练中间15%做验证最后15%做测试保证测试集在时间上完全晚于训练集。3.3 归一化方法的选择与反归一化的坑CNN和LSTM对输入尺度非常敏感负荷数值动辄几百上千MW不归一化直接训练会导致梯度更新非常混乱。我用的方法是最小最大值归一化把数据压缩到[0,1]区间x_scaled (x - x_min) / (x_max - x_min)这里有个关键细节归一化的参数min和max只能从训练集计算然后应用到验证集和测试集。为什么因为测试集是模拟未来的未知数据如果用了全局的min和max就把未来信息泄漏到训练过程中了。真实部署时新的数据点可能超出训练集的max需要做截断处理。反归一化同样是高发翻车点。模型的输出是[0,1]区间的小数必须通过逆变换还原成真实负荷单位再做误差分析。我见过有人直接把归一化的预测值去算MAPE得到的结果严重失真。正确流程是先反归一化再计算所有误差指标。这个顺序错一步后面的分析全部作废。4. 训练阶段的参数选择与常见故障排查4.1 超参数基准线从一套可以复用的配置出发超参数组合网上有无数种说法但靠谱的做法是先套一套基准线然后围绕结果做单因素调整。我常用的基准配置如下超参数基准值调整方向优化器Adamloss震荡时换SGD或降学习率初始学习率0.001收敛慢则升到0.003震荡则降到0.0003Batch Size64显存/内存不足时可降为32LSTM隐藏单元64数据量大可升到128Dropout0.2过拟合时升到0.3或0.4Early Stopping Patience15根据验证集变化频率调整最大Epoch100一般用不到早停会先触发训练过程中要同时盯训练损失和验证损失两个指标。如果训练损失持续下降但验证损失停滞甚至回升说明过拟合了优先加大Dropout其次考虑减小LSTM单元数。如果两个损失都不下降学习率可能太低或者模型结构有bug先确认代码逻辑再调参。4.2 “预测曲线整体滞后一拍”一个高频问题的根因分析训练过程中我遇到的最典型问题也是很多做时序预测的人都会撞上的测试集上的预测曲线形状完全正确但整体比真实值晚了一个时刻。早高峰的上升段预测还在低位爬晚高峰的拐点预测还在涨。根因在于模型在MSE损失下学出了一个“偷懒策略”对当前时刻的预测最保守最不容易出错的做法就是参考上一时刻的真实值稍微偏移一点点。因为下一时刻的真实值和上一时刻非常接近把预测值设成“跟上一步差不多”的误差非常小远小于冒险预测一个拐点可能带来的大误差。尤其当外部特征不足时模型更容易走这条路。我的解决思路按优先级排列如下加入外部特征把温度、湿度、节假日标志、星期几编码拼进输入向量。模型有了外部信息就不再只依赖上一步取值。扩大窗口长度从24扩大到48或72让模型看到更长的上下文减少对最后一点的过度依赖。改多步预测不预测下一个单点而是预测未来24个点。这样模型必须学会规划一整段曲线很难再用“复制上一步”蒙混过关。调整损失函数在MSE基础上加入相邻点差分的惩罚项明确告诉模型“我不仅要绝对值准确趋势变化也要准”。这个方案有效但实现复杂度高我建议先试前三条。最后我通过加入温度特征和天气类型修正把滞后问题基本解决。测试集上的MAPE从原来的9%左右降到了5%以内提升非常明显。4.3 早停策略与最优模型保存训练深度模型最常见的调参错误是“跑到最后再保存”但实际上最后一轮往往不是最优的。我在项目里设置了早停机制每个epoch结束计算验证集MAPE连续15个epoch没有改善就停止训练并保存验证集表现最好的那一次权重。还有一个经验验证集的评估标准要和最终业务目标一致。如果业务方关心峰值时段的预测准确性验证指标就应该重点看晚高峰时段的RMSE而不是全天的平均MAPE。我一开始全时段统一指标结果模型在中段调得很准峰值反而偏了后来调整了评估口径才对齐。5. Matlab作图实操把预测结果变成能放进报告的一页图5.1 从模型输出到数据还原第一步别急着画Matlab作图是整个流程的最后一公里也是很多人做得最糙的一步。我的建议是先整理数据再动笔画图。模型训练完成后把测试集上的预测结果导出和对应的真实值、时间戳一起保存成CSV。Matlab里直接用readtable读取data readtable(test_predictions.csv); time data.time; true_value data.true_value; pred_value data.pred_value;这一步看起来简单但有个容易错的点模型内部的预测值是归一化后的必须先做反归一化再存CSV或者存完之后在Matlab里反归一化。我建议在Python端训练时就把反归一化做完Matlab里直接读物理量单位。这样能避免两边公式不一致导致的隐性错误。整理好的数据至少包含四列时间戳、真实值、预测值、绝对误差。有了这四列后面所有的图都能顺畅生成。5.2 主图设计真实值与预测值的双段展示预测主图的标准做法是把训练段和测试段都画出来中间用一条分界线隔开让看图的人一眼看清哪些是训练拟合、哪些是真正预测。我常用的画法如下figure(Color,w,Position,[100 100 1200 500]); % 画训练段 idx_train time test_start_time; plot(time(idx_train), true_value(idx_train), Color, [0.2 0.2 0.2], LineWidth, 0.8); hold on; % 画测试段的真实值和预测值 idx_test ~idx_train; h1 plot(time(idx_test), true_value(idx_test), Color, [0 0.45 0.75], LineWidth, 1.2); h2 plot(time(idx_test), pred_value(idx_test), Color, [0.85 0.2 0.1], LineWidth, 1.2, LineStyle, --); % 标记测试集起点 xline(datenum(test_start_time), :, 测试集起点, LabelOrientation, horizontal, FontSize, 10); legend([h1 h2], {真实值, 预测值}, Location, best); xlabel(时间); ylabel(负荷 (MW)); grid on; set(gca, FontSize, 11, FontName, Helvetica);几个作图细节真实值用实线预测值用虚线线宽1.2以上颜色用高对比度的两色避开红绿色盲人群分不清的搭配。x轴时间格式要提前用datetick设置好否则X轴显示的是一串序列数别人根本看不懂。坐标轴标注必须带物理单位负荷单位用MW或kW不要光写一个数字。图上永远不要出现归一化值这是报告图的底线。5.3 误差分析图组把“整体还行”拆成“哪里不行”主图只能说明趋势拟合得好不好真正给项目收尾做评估、或者写分析报告需要一组误差图。我常用的三件套是绝对误差曲线、误差分布直方图、分时段箱线图。绝对误差曲线直接反映每个时间点的预测偏差figure; error true_value(idx_test) - pred_value(idx_test); plot(time(idx_test), error, Color, [0.3 0.3 0.3], LineWidth, 0.8); yline(0, --, 零误差线); xlabel(时间); ylabel(绝对误差 (MW)); title(测试集逐点绝对误差);误差分布直方图帮你看误差的整体形态figure; histogram(error, 50, FaceColor, [0.4 0.6 0.8]); xlabel(误差 (MW)); ylabel(频数); title(预测误差分布);分时段箱线图是我个人很推荐的一张图把一天的24小时分桶画出每个小时的误差分布你能直接看出哪个时段预测最差。做法是把测试集时间拆出小时数然后用boxplot按小时分组hours hour(time(idx_test)); figure; boxplot(error, hours, Labels, 0:23); xlabel(小时); ylabel(绝对误差 (MW)); title(各小时预测误差分布);实测中这组图往往能告诉我们误差最大的时段通常集中在清晨低谷和晚高峰突变段前者是量级问题后者是形态问题两个问题的改进方向不一样。另外还可以画一张预测值vs真实值的散点图并叠加yx参考线。如果散点越贴近对角线说明预测越准如果明显偏离还能看出是系统性偏高还是偏低figure; scatter(true_value(idx_test), pred_value(idx_test), 15, [0.3 0.6 0.5], filled); hold on; axis_range [min(true_value) max(true_value)]; plot(axis_range, axis_range, k--, LineWidth, 1.2); xlabel(真实值 (MW)); ylabel(预测值 (MW)); title(测试集预测值与真实值散点对比);5.4 让图“经得起审”的几条经验最后分享几条关于Matlab出图的实操经验都是吃了亏之后总结的第一输出分辨率直接设300dpi以上。用exportgraphics导出比print更好控制尺寸和清晰度exportgraphics(gcf, load_forecast_result.png, Resolution, 300);如果汇报材料需要矢量图可以导出.emf格式Word和PowerPoint里缩放不失真。第二字体样式统一。中文标签用宋体或微软雅黑英文和数字用Helvetica或Times New Roman别混用导致整体观感杂乱。坐标轴字号不要小于10标题字号不要大于14图例字号和坐标轴一致。第三一页图里信息要克制。我见过有人把真实值、预测值、误差、上下界、标注全塞进一张图结果什么都看不清。我的原则是一张图最多三条信息线看图的重点要突出。第四配色选低饱和度的颜色。默认的纯红纯蓝在屏幕上还行打印出来刺眼。建议使用接近灰度系的深灰、深蓝、暗红搭配既专业又不抢戏。写在最后这套CNN-RNN混合网络加Matlab作图的流程我自己跑通之后最大的体会是模型的改进是加分项数据和特征工程才是基本盘。同样的网络结构把数据处理好、特征构建好效果可能比换一个更复杂的模型还要明显。如果你也在做电力负荷预测或者类似的时序预测项目建议先花大力气把数据预处理做扎实模型选型反而可以更务实一些。另外一个小建议把Matlab的画图脚本整理成一套固定模板项目结束后保存下来下次换一个数据集、换一个模型只要改几处变量名就能直接复用。我后来做其他时间序列项目出图效率至少快了一倍。预测这件事指标会波动图不会骗人把图和误差分析做细项目的问题和亮点都会浮出水面。