前阵子在帮一家制造企业做能源管理平台优化时,我给 MyEMS(开源能源管理系统)接入了一套基于 LSTM 神经网络的电力负荷预测模块,最终在连续两周的测试中把预测准确率稳定在了 95% 上下。很多朋友一听“AI 负荷预测”就以为要重搭平台、新建设备,实际落地时你会发现,主要工作量根本不在于模型本身有多高级,而在于数据清洗、特征工程,以及怎么把模型结果顺畅地嵌进现有系统的业务流程里。这篇文章把我从 0 到 1 的做法完整复盘一遍,包括模型原理、数据准备、训练评估和上线后的坑,适合正在做能管平台、配电运维、碳管理和综合能源服务的工程师参考。
1. 项目背景:当用能单位的数据积累到一定程度,预测就会成为刚需
1.1 MyEMS 是什么,为什么它适合做预测能力的底座
MyEMS 是一套开源能源管理系统,很多工厂、园区和商业楼宇都在用它做电表、水表、气表的数据采集、能耗计量、分项统计和成本分析。它的架构比较清晰,底层用 MySQL 存历史数据,上层通过 REST API 提供数据查询和页面展示,二次开发门槛不算高。我在很多项目里看到的情况是:客户已经部署了 MyEMS,也积累了一年以上的表计数据,库表里面躺着的负荷数据从几千条到几百万条都有,但大家只拿它看报表、做计费,很少有人想过去挖掘这些数据更深层的价值。
之所以说 MyEMS 适合做预测能力的底座,是因为负荷预测离不开连续、干净、可追溯的历史数据。MyEMS 天然把计量点、采集时间、数值、费率时段这些信息统一管理起来了,省去了我自己从零搭采集链路的麻烦。换句话说,你不需要再买一套“AI 能源平台”,在原有的 MyEMS 基础上加一个预测模块,数据链路是现成的,组织层面也更容易接受。
1.2 负荷预测的几个真实使用场景
我这次做的项目,客户是一个日峰值负荷在 8000 kW 左右的制造企业,内部有多条生产线,同时还有空压站和中央空调这类大功率设备。负荷预测对他们不是锦上添花,而是有几个很具体的业务诉求。
第一个是基本电费优化。国内很多地区对大工业用户按变压器容量或者最大需量收取基本电费,如果企业能提前知道未来 24 小时的负荷峰值,就可以在峰值时段主动错峰、压限部分可中断负荷,避免被高额容量费罚款。第二个是电力市场化交易场景下的购电计划。现在不少省份已经允许工商业用户直接参与市场化交易,偏差电量要考核,负荷预测越准,偏差越小,真金白银的收益越明显。第三个是现场配电设备和储能的协调控制。我接触的不少项目都配了储能,储能充放电策略必须依赖未来几小时到几十小时的负荷曲线,尤其是光伏发电和负荷同时波动的情况下,提前 24 小时的负荷预测直接影响储能的经济性。
这些场景有一个共同点:预测的对象不是下一个 5 分钟,而是未来 24 小时甚至更长时间段的逐时负荷曲线。这就要求模型不仅要有短期瞬态记忆,还要能捕捉每天的周期性规律,比如白班、夜班切换,工作日和周末的差异,这些特点正好是 LSTM 这类循环神经网络擅长处理的。
1.3 传统时序方法为什么在这里会失灵
在没有上 LSTM 之前,我尝试过几种常规方法来对比。第一个是 ARIMA 模型,对平稳时间序列效果还行,但电力负荷受生产计划、天气、节假日影响很大,属于典型的非平稳序列,ARIMA 需要手动做差分和定阶,还要不断重新拟合,遇到突发工况就崩。第二个是多元线性回归,我当时把温度、湿度、日类型、历史负荷都放进去做了个回归模型,训练倒是快,但变量之间的非线性关系拟合得很差,尤其是上午和傍晚负荷陡升的阶段,残差非常明显。
最让我不满意的是移动平均和指数平滑这类“无脑外推”方法。它们不理解“今天周二,上周二的负荷对今天有很强的参考性”这种事,只会拿最近几个点做加权平均,一旦负荷曲线从低谷快速拉升,预测值就会慢半拍。实测下来,这些方法在未来 1 小时时段的平均误差普遍在 8% 到 15%,稍微遇到天气突变或者生产线调整,误差能冲到 20%。所以我才决定采用 LSTM,把负荷预测真正当成一个“序列到序列”的问题来解决。
2. LSTM 在负荷预测中的定位与技术原理
2.1 从普通 RNN 到 LSTM,改变的核心是什么
普通循环神经网络的思路是把序列数据按时间步逐个喂进网络,每一步都会更新一个隐藏状态 h_t,并把当前时间步的输出传递到下一步。核心公式可以简单写成:
h_t = tanh(W * [h_{t-1}, x_t] + b)
这种方式在短序列上表现还行,但一旦序列变长,梯度在反向传播时要么爆炸要么消失,导致网络很难学到跨越很多时间步的依赖关系。这就是大家常说的“长期依赖问题”。
LSTM 的改动思路不是推倒重做,而是给循环单元加入一个称为“细胞状态”(cell state)的结构,相当于一条贯穿整个序列的传递带。每一步,网络会通过几个门控结构决定:这条传递带上的旧信息要保留多少、新信息要写入多少、最终要向外部输出什么。因为传递带的更新是由小而平滑的加法操作完成的,梯度能够顺利回传,模型就可以记住很久以前的关键模式,而不是只盯着最近几个点。
2.2 用仓库管理来理解 LSTM 的门控机制
LSTM 的结构听起来复杂,其实可以用仓库管理来类比。细胞状态就是仓库里囤的货,代表了历史信息的累积。遗忘门像一个仓库管理员,每天早上盘一遍库存,决定哪些货已经过期可以扔掉;输入门决定今天新到的货要不要放进仓库以及放多少;输出门则根据仓库现状决定今天要往外发多少货。这里的“货”就是负荷数据中提取到的模式,比如每天上午 8 点的开工尖峰、午休时的负荷回落、夜班时段的低负荷平台。
在代码里,这种记忆机制用不到太多复杂的实现,现代深度学习框架都把它封装好了。我只需要确定网络层数、每层的神经元数量以及训练策略,剩下的梯度传递和门控参数都会在训练过程中自动学习出来。
2.3 为什么电力负荷曲线特别适合用 LSTM 来建模
电力负荷数据有两个显著特征:一是周期性极强,工厂和楼宇的负荷曲线往往以一天为周期重复,同一个小时在不同工作日之间相关性也很高,而且存在明显的早晚峰谷;二是连续平滑,负荷不会像股票价格那样跳上跳下,相邻时刻的数值通常有很强的连续性。这两个特征让 LSTM 很容易从中提取到可复用的时序模式。
我当时做过对比:用同样的训练数据,把 LSTM 换成前馈神经网络,输入同样是过去 24 小时的负荷序列,但因为前馈网络不具备时间状态传递能力,模型只能把 96 个输入点当独立特征看待,空间维度过大,训练出来的效果比 LSTM 差了将近 6 个百分点。CNN 虽然能提取局部特征,但时序上的长距离依赖仍然需要额外叠加其他结构。而 transformer 虽然在大规模样本上表现更好,但在工业现场这种样本量只有几万条、训练资源也有限的环境下,明显不如 LSTM 划算。一句话总结我的选型逻辑:用合适的模型解决合适规模的问题,LSTM 在这个量级的数据集上性价比是最高的。
3. 从原始数据库到组建训练集:数据准备才是准确率的真正分水岭
3.1 从 MyEMS 历史库抽取负荷数据时的清洗规则
很多人以为模型效果差是网络结构问题,实际我这几年的经验是,数据准备阶段做得好不好,往往比模型结构本身对最终准确率的影响还大。我这次的数据源是 MyEMS 的计量历史数据表,采集粒度为每个计量点每 15 分钟一条记录,一天 96 个点。
抽取数据的第一步是确定口径。我选择客户进线总表的正向有功功率值作为预测目标,而不是某条单一产线的负荷,这样预测结果对容量管理和购电计划才最有参考价值。
清洗阶段踩过的坑主要有三类。一是“飞点”:通信干扰或表计异常会在某个 15 分钟点产生一个比正常值高好几倍的瞬时值,如果不处理,模型会被这些点带偏;二是“丢点”:因为网络抖动或采集服务重启,数据库里经常出现一个或多个连续时间点缺失;三是“汇总口径冲突”:MyEMS 里同一个物理表可能既有原始读数,也有按小时、按天汇总的数据,如果混用,会导致时间轴不规整。
我最后确定的清洗规则包括:单点负荷超过历史最大值的 3 倍就直接判定为飞点并剔除;连续缺失不超过 2 小时的点采用线性插值补全,超过 2 小时则丢弃该时段样本;所有数据只保留原始 15 分钟记录,汇总数据另行处理,避免和原始序列混在一起。数据清洗完之后,我还会画一遍负荷曲线,用人工视角扫一遍有没有明显的断层或者毛刺,这一步虽然工具简陋,但能发现很多程序自动清洗遗漏的问题。
3.2 特征设计:只传功率值远远不够
模型输入只给负荷功率值也能跑,但效果会很一般。我把特征设计分成三组:历史负荷、时间特征、外部环境特征。
历史负荷毫无疑问是最重要的输入,我选用预测时刻之前 24 小时共 96 个点的负荷值作为核心序列。时间特征包括小时、星期几、是否工作日、是否节假日,其中小时和星期几都做了正弦/余弦编码,避免“23 点和 0 点之间的相邻关系”被模型误解成“23 到 0 距离很远”。外部环境特征主要加入温度和湿度,因为空调用电负荷对温度非常敏感,工厂虽然生产负荷占大头,但夏季高温天的空压机效率变化也会传导到总负荷上。温度数据取自当地气象接口,我在特征层面对其做了滞后处理,让它与负荷变化的相应节奏保持一致。
还有一点容易被忽略:把节假日以及“节假日后第几天”作为特征。因为客户工厂在长假期开工后的第一个工作日,负荷往往会瞬间拉满,模型如果没见过这种规律,几乎必错。把“距上一个节假日的天数”这类计数特征加进去之后,这个问题的收敛速度明显加快。
3.3 滑窗样本的构造与训练集划分
模型不是直接把整年数据灌进去,而是按“滑动窗口”的方式切成一条条样本。我设置输入窗口长度为过去 24 小时(96 个点),输出长度为未来 24 小时(96 个点)。每次滑动 15 分钟,每天 96 个时刻都可以生成一个训练样本。这样一年下来,剔除掉数据质量差的日子,大概能产出 2 万多条有效样本。
训练集、验证集、测试集的划分需要严格按时间顺序进行,不能随机打乱。我用的是约 80% 数据训练、5% 数据做验证、15% 数据做测试。重点在于测试集要保留最后的连续 15 天,这段时间既包含正常工作日,也覆盖了周末,我还特意把其中一个高温日保留在里面,用来验证模型在极端天气下的泛化能力。随机 Shuffle 在这里是禁区——能源数据本身就带有时间趋势,打乱会让模型偷看到未来信息,测试成绩看着漂亮,实则上线必翻车。
4. 模型构建、训练与精度评估:95% 是怎么得到的
4.1 网络结构设计
我使用的网络结构并不复杂,只有两层 LSTM 加一个全连接输出层。第一层 LSTM 设置了 return_sequences=True,这样它会返回每个时间步的隐藏状态,给第二层提供完整的时间信息;第二层 LSTM 不返回序列,只输出最后一个时间步的综合表示,然后接一层神经元数量为 96 的 Dense 层。Dense 层输出 96 个值,正好对应未来 24 小时、每 15 分钟一个点的负荷曲线。
用两层而不是三层或更深的原因很简单:训练样本只有两万多条,网络层数堆上去之后,参数数量急剧增加,过拟合风险远远大于拟合能力的提升。我实际试过三层 LSTM,训练集误差确实降了,但测试集误差反而升了将近一个点,典型的过拟合信号。每层神经元数量我采用 64 个单元,这个参数同样来自多轮对比,32 个单元表现力不足,128 个单元在样本量有限时收益不大,64 是最平衡的点。
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model = Sequential([ LSTM(units=64, return_sequences=True, input_shape=(96, feature_dim)), Dropout(0.2), LSTM(units=64), Dense(96) ]) model.compile(loss='mae', optimizer='adam')这里 feature_dim 是输入特征数量,代码里主要是为了展示结构,实际配置还包含了学习率调度和早停参数,后面会提到。
4.2 训练策略与超参数选择
优化器我直接用 Adam,初始学习率设为 0.001。损失函数没有用均方误差(MSE)而是用了平均绝对误差(MAE),原因是负荷数据的峰值较高,MSE 会对大误差值过度惩罚,导致模型为了照顾极端点而牺牲普通时段的精度,而 MAE 对峰值不那么敏感,整体曲线形态拟合得更均匀。
批量大小设为 64,训练早停条件是验证集损失连续 10 个 epoch 不再下降就停止训练,最大训练轮数控制在 100。我实际跑下来一般在 30 到 50 轮之间收敛。Dropout 加在两层 LSTM 之间,比例 0.2,这个数值太小起不到正则化作用,太大又会阻碍信息流转,0.2 对我来说是经验和实验折中的结果。
训练过程中我还做了一件事:把每个时刻的预测残差单独打印出来,而不只看总的损失值。这样做的好处是能直观看到模型到底在哪个时段最吃力。结果显示,模型在白班峰时段和夜班低谷段的误差普遍较小,误差集中在上午 7 点到 9 点的负荷陡升段,说明模型对快速变化的响应还有提升空间,这也为后面优化特征和调整损失权重提供了依据。
4.3 模型评估结果解析
评估时我把测试集 15 天的数据全部跑了一遍,采用两个指标:MAPE(平均绝对百分比误差)和 R² 决定系数。MAPE 的计算方式是逐点算出“预测值和实际值之差除以实际值”的绝对值再取平均,最后乘 100% 得到百分比。
| 指标 | 测试集结果 |
|---|---|
| MAPE | 4.8% |
| R² | 0.96 |
| 峰值时段 MAPE | 4.2% |
| 低谷时段 MAPE | 5.6% |
业界通常会把“预测准确率”理解成 100% 减去 MAPE,所以我提到的 95%,对应的是测试集一周多时间内的平均预测准确率约在 95.2% 附近。低谷时段因为负荷基数很小,哪怕绝对偏差只有几十千瓦,百分比也会被放大到 5% 以上,这在行业里属于正常现象,不影响整体业务判断。
值得提醒的是,网上很多论文会把 R² 写成 0.99 甚至更高,那通常是在小样本实验环境里的结果。工业生产环境中,数据质量参差不齐,设备检修、生产计划变更、天气突变都会造成误差,能稳定做到 MAPE 5% 左右已经属于可以用于实际决策的水平。
5. 把预测服务接入 MyEMS:解决落地性的问题
5.1 总体架构:把训练和推理分开
模型训练好只是第一步,真正让预测能力在工程中跑起来,需要一套清晰的服务架构。我当时把整个预测模块拆成三个独立的部分,避免相互影响。
第一部分是离线训练脚本,负责从 MyEMS 数据库抽取历史负荷数据,完成清洗、特征构建、模型训练和评估。第二部分是定时推理服务,我用 FastAPI 写了一个轻量接口,内部加载训练好的模型文件,读取最近 24 小时的真实负荷数据,输出未来 24 小时的预测曲线。第三部分是回写服务,负责把预测结果写回 MyEMS 的数据库,或者通过接口直接提供给前端展示。
我把训练和推理严格分离,有两个原因:一是模型训练需要消耗较多 CPU/GPU 资源,如果放在在线推理链路里,会导致接口响应不稳定;二是训练脚本和推理服务可以独立更新,调整模型结构时不用重启在线服务。另外,在推理服务里我还加了模型文件的热加载逻辑,每次模型训练完成之后,服务自动检测生产环境的模型文件版本并加载新模型,尽量做到不停机更新。
myems-load-forecast/ ├── scripts/ │ ├── train_model.py │ └── refresh_forecast.py ├── services/ │ └── forecast_api.py ├── models/ │ └── lstm_model.py ├── data/ │ └── preprocessing.py └── config.ini5.2 预测结果如何回流到 MyEMS 前端
预测结果如果只在 Python 脚本里打印,业务人员根本看不到,也就谈不上指导决策。我选择把预测结果写入 MyEMS 数据库中新建的一张预测数据表,表结构非常简单:计量点 ID、预测时刻、预测值、生成时间。前端通过 MyEMS 原有 API 查询数据表,把真实负荷曲线和预测负荷曲线叠加画在同一张图上,这样运维人员打开页面就能看到“预测线”和“实际线”的对比。
为了让数据链路更加顺畅,我在表设计上给“计量点 ID”和“预测时刻”建了联合索引,预测服务每次写入前先删除同一预测生成时间的旧记录,再插入新记录,保证数据表里始终保留最新一轮预测结果。在实际前端展示时,我还专门加了一个“置信区间带”,因为所有预测都不可能绝对精确,展示一个 10% 上下浮动区间,可以避免业务人员把预测值当精确值来用,这在工业现场特别重要。
5.3 与配电管控运营动作的联动
预测结果接进 MyEMS 页面之后,只做一个“可视化”还不够,真正产生价值的是和各种运营动作联动。我在系统里配置了几条规则,其中最直接的一条是:当未来 24 小时预测峰值超过变压器容量的 85% 时,系统自动触发告警,通知运维值班人员提前启动错峰预案,包括调整空压机启停时间、把部分高耗能产线避开峰段开机。
另外,我也把预测曲线直接对接到了储能控制系统。客户配置了一套电池储能系统,之前控制策略比较简单,每天固定时段充电放电。接入负荷预测后,系统根据未来 24 小时的负荷曲线,在负荷低谷且电价较低的时段多充电,在预测峰值来临前放电,起到削峰填谷作用。这个场景下,预测模型精度直接决定储能的收益测算,所以我们后续迭代的核心方向不是继续堆网络层数,而是把预测误差进一步压到 4% 以下。
6. 实测中踩过的坑与对应的排查链路
6.1 数据源不一致导致的准确率虚高
这个坑是我最想提醒后来人的。模型刚上线时,我在测试集上跑出来的 MAPE 一度低到 3.5%,看着非常兴奋,结果第二天实际页面上的误差却明显大于这个数字。排查到最后发现,问题出在数据源不一致上。
训练时我用的历史负荷数据经过了完整清洗,剔除了所有通信中断和飞点;而实时推理时,MyEMS 实时库里某些总共数据缺失的位置已经被前值填充了,实际数据里依然藏着原始的真值波动。相当于模型在“干净的试卷”上考试,上线后却遇到满是干扰的“真实场”,准确率自然对不上。
解决思路是让训练数据和推理数据做到同源同质。我把推理接口的取数逻辑改成了和训练清洗完全一致的处理流程,并且额外增加了数据质量标记字段:如果实时数据里存在超过 2 小时的连续填充,则不启用该轮预测,直接沿用上一轮结果,并在页面标注“预测数据可靠性不足”。从此准确率指标才真正反映了线上水平。
6.2 长假结束后的预测偏移
另一个比较典型的场景是长假后的第一个工作日。客户工厂在五一假期期间停产了 5 天,假期结束后第一天全线上班,负荷从假期里的几百千瓦瞬间升到八千千瓦以上。模型预测结果明显偏低,和实际值差了将近 15%,当天告警规则差点误报。
排查链路是这样的:先看输入特征,发现模型在训练时接触过的长假期样本远远不够,每年也就一两次,样本量小到模型无法真正学习到“长久停产后的开工模式”。再加了一个“节假日结束后的第几天”特征,不再简单地把星期几交给模型自己理解。另外,我在规则层做了一个辅助判断:当系统识别到第二天是长假后首个工作日时,在预测结果基础上叠加一个最大负荷修正系数,把预测峰值上调到历史开工日的平均峰值水平。
这个坑让我意识到,LSTM 虽然能捕捉周期性,但面对多年只发生一两次的极端工况时,样本量不足的问题模型自身没法解决,必须借助人工规则和特征工程去弥补。后来我又补充了调休上班日和特殊天气预警的处理,整个异常场景的预测效果明显改善。
6.3 模型更新频率的取舍
最后一个要讲的是模型更新的节奏。如果模型每天重新训练,训练成本可控但预测结果会每天小幅变化,业务人员反映曲线“飘”得太快;如果训练一次用半年,设备更换、产线调整后模型又会逐步失准。
我最终采用了两层更新策略:每周日晚上做一次全量重新训练,利用过去三个月的历史数据重新拟合;同时保留每日增量微调任务,只在当天新数据上跑 2 到 3 个 epoch,让模型能够快速适应近期负荷水平的变化。增量微调的收益主要有两点,一是能捕捉生产线设备替换后的负荷特性变化,二是在不影响整体稳定性的前提下保持模型输出的一致感。
另外,每次模型重新训练后,我都会在同一份固定的测试集上重新计算 MAPE。这份测试集从项目开始就冻结起来不再变更,任何模型改动都要在它上面跑分,避免“越改越针对某一段数据而实际上泛化能力下降”的隐性坑。冻结测试集的做法虽然简单,但保证了模型迭代方向的客观性,也是我能持续把准确率稳定在 95% 的重要保障。
在实际做能耗管理平台这几年的经历中,我越来越确信一件事:所谓“AI 驱动能源管理”,真正的门槛不在于会调用模型接口,而在于你能不能把业务问题转化成数据问题,再把模型输出转化成业务动作。LSTM 给了我们一个足够可靠的预测底座,但让负荷预测真正产生价值的,是数据治理的扎实程度、特征工程对业务的响应速度,以及系统建设过程中对异常场景的持续迭代。希望这篇文章对正在做同类项目的朋友有所帮助。