去年做某片区充电站负荷预测项目时,调度那边给我提了个让我印象很深的需求:别总给我一条曲线,给我一个区间。当时我们的点预测模型在节假日和极端天气下偏差能到百分之三四十,电网做日前调度时只能按最保守情况留裕度,经济性很难看。后来我们尝试了条件扩散模型做电动汽车充电负荷概率预测,直接把预测输出从一条曲线变成了一组场景,也就是把"未来可能长什么样"完整地交给了下游决策者。
这篇文章想完整记录下来我对充电负荷特性、扩散模型原理、训练坑点和工程落地的心得。整个过程有理论推导的部分,也有大量试错成分,希望能给正在做同类预测的朋友提供一个可以少走弯路的参考。
1. 为什么充电负荷预测不能只给一条曲线
1.1 充电负荷的随机性到底有多大
刚开始做这个项目时,我一度以为充电负荷预测跟普通电力负荷预测差不多——无非是找出周期性规律,套一个时序模型。结果翻开数据之后发现完全不是这么回事。普通居民负荷的规律性主要来自生活习惯,早晨、晚间两个峰非常稳定,误差再大也是在某个范围内波动。但充电负荷受个人出行决策支配,车主什么时候插枪、充到多少电量走、今天去不去充电站,都是典型的随机事件。
更麻烦的是,这种随机性还带着明显的多峰特性。同一个工作日的午后两点,某片区的充电站可能有六百千瓦负荷,也可能只有一百千瓦——前者是有一批网约车司机集中补电,后者是大家都还没出门。这种"两种完全不同的状态在同一时段内都可能出现"的情况,单靠一条均值曲线根本无法表达。如果电网调度只看均值,真实负荷落到高值区域时早就越限了。
我还发现不同充电场景的随机性来源甚至不一样。居民区慢充站的负荷跟着下班到家时间走,比较温和;商圈快充站受商场活动、天气和节假日的叠加影响,波动剧烈;高速服务区站点则完全受出行潮汐支配,节假日能比平日高四五倍。所以第一版模型如果按"一个站点一条预测曲线"的方式去做,几乎必然失败,因为你要预测的不是一个数,而是一个分布。
1.2 点预测给业务带来了什么麻烦
点预测在业务侧的困境,用一个例子就能说清楚。某次调试点预测结果,某供电公司用我们的98%置信上限做配变容量校核,结果因为峰值段预测偏低,校核结论偏乐观,逼得他们不得不临时手动加装移动储能。反过来如果每个时段都按照历史最大值去做P95上限,所有场景又会过于保守,造成容量浪费和调度成本上升。
这就是概率预测的意义:给决策者提供一整套未来场景,而不是一个"最可能的数字"。有了场景集合,调度可以回答三类问题:明天的负荷有多少概率突破800千瓦?什么时候突破的概率最大?如果需要削减负荷,按什么量级安排需求响应才既能保证安全又不至于过度?这些问题点预测一个都答不了。
1.3 工程上对概率预测的具体期待
我的体会是,做概率预测不能只交出一张"阴影图"就完事。电网侧实际需要的是三种明确产品:
- 场景集合:通过采样得到若干条未来负荷曲线,每条曲线代表一种可能的演化路径,用于模拟计算和风险评估。
- 分位点序列:把场景集合在每个时刻汇总成P10、P50、P90等分位线,方便快速判断趋势和偏置。
- 事件概率:比如"未来24小时内负荷超过容量的概率"“首次越限的期望时刻”,这是调度最关心的量化指标。
所以后续模型设计时,我们的目标不是让预测结果"漂亮",而是让输出能顺畅地支撑这三个产品形态。条件扩散模型的天然产出就是场景集合,这正好踩在需求上。
2. 条件扩散模型凭什么能处理这件事
2.1 扩散模型到底在干什么
很多人一听到扩散模型就觉得是图像生成那套高大上的东西,其实它的底层思想非常朴素。你可以把数据生成过程理解为两步:
- 前向过程:把一张清晰的图片一步步加高斯噪声,直到完全变成雪花噪点。这个过程的每一步是有明确数学公式的,不需要学习。
- 反向过程:训练一个网络,学习如何从噪点中一步步把噪声去掉,还原出原始图片。训练完成后,从纯噪声开始,网络就能一步步"解码"出新的图片。
对应到我们的任务,就是把"未来的负荷曲线"当成"一张图",训练一个去噪网络。但这里有个关键问题:单纯从噪声中生成曲线,生成的是这个站点所有历史时刻的随机样本,而不是"明天下午两点"的曲线。所以我们必须要给生成过程加条件——这就是条件扩散模型的由来。
2.2 条件是如何"灌"进生成过程的
条件的作用是告诉生成网络"现在是什么样的背景,未来大概会往哪个方向演化"。我们的做法是把三类条件信息编码成向量,然后通过特定的方式注入去噪网络:
- 时间条件:小时、星期几、是否节假日,经过正弦位置编码再拼接。
- 气象条件:温度、降雨量、风速,归一化后直接作为连续特征。
- 历史负荷窗口:过去24小时的数据向量,通入一个小型编码器提取状态特征。
注入方式上,我推荐FiLM(特征线性调制),也就是让条件向量为每一层卷积的输出计算一个缩放参数和一个偏移参数。相比简单地在输入层拼接,FiLM对深层特征的控制力强得多,尤其在数据本身噪声比较大的时候,条件的引导会更为直接。
2.3 为什么不用分位数回归或VAE
在做方案选型时,我们也对比过其他概率预测方法,最后选择扩散模型不是因为它时髦,而是基于一个核心判断:充电负荷的分布形态太不规整了,传统方法啃不动。
| 方法 | 对分布形态的假设 | 典型痛点 |
|---|---|---|
| 分位数回归 | 每个分位点独立建模 | 不同分位线可能交叉,无法表达多峰分布;变量多时参数训练繁琐 |
| MC Dropout | 近似贝叶斯推断 | 不确定性捕捉粗糙,容易低估极端场景 |
| 变分自编码器(VAE) | 潜变量服从高斯先验 | 高斯先验压力太大,难以表达"要么很低要么很高"的双峰分布 |
| 条件扩散模型 | 无显式分布假设 | 采样计算量大,但分布表达能力最强,适合多峰场景 |
我见过不少团队用分位数回归做充电负荷预测,说实话在业务上够用,但一到节假日这种分布形态剧烈改变的时段就会露馅。扩散模型虽然训练要重一些,但它的输出分布完全由数据驱动决定,不需要我拍脑袋假设它服从某个参数族。电池充电行为这种受离散决策支配的负荷,恰恰需要这种自由建模能力。
3. 数据准备与特征工程:踩平的坑比模型本身多
3.1 数据连续性比想象中严重
项目一开始就栽在数据上。站点过去两年的表计数据看起来数量充足,但真正清洗完才发现,里面混杂着升级改造、停运检修、采集终端离线等各种记录。如果直接把粗糙数据喂给模型,扩散模型会把这些中断和跳变当作正常模式去学,生成结果里就会出现一堆莫名其妙的"阶梯式突变"。
处理建议是两条腿走路:
- 物理规则清洗:剔除负荷跃变超过物理上限的记录,比如15分钟内从0跳到800千瓦又回落,基本可以判定为表计异常。
- 缺失值补偿:短时段缺失用相邻均值插值;连续超过一天的缺失直接整段标记,不让模型去学"全零站点"的状态。
这里特别提醒一下:扩散模型对训练数据的分布非常敏感,它学到的分布就是训练数据的分布。如果你拿一段充满异常尖刺的数据去训练,生成场景就会高频出现尖刺,业务侧根本没法用。
3.2 零值问题比预想难缠
夜间充电站负荷大量为0,这个问题我一开始没太当回事,直到生成的场景出现了一个诡异现象:夜间时段要么全部接近0,要么突然出现一串非0值,两种状态泾渭分明。刚开始我觉得这是模型没学好,后来分析才发现这是数据分布的真实形态——夜间有少数网约车会来充电,所以本来就是个双峰分布。
对于这种情况,处理上我们有两条路线:
- 路线一:直接保留零值样本,让扩散模型自己学双峰分布,生成时也能保持这种二值性。
- 路线二:把"是否为零"单独建模,只要非0部分才预测负荷数值。
实际测试下来,路线一的整体效果更好,因为扩散模型对多峰分布的表达是天然支持的,强行拆成两段反而引入额外误差。但代价是采样时需要更多样本才能稳定估计"什么时段有充电需求"。
3.3 特征工程的最终清单
经过多轮消融实验,跑下来有效的特征组合如下:
| 特征类型 | 具体内容 | 对模型的作用 |
|---|---|---|
| 时间编码 | 小时、星期几、节假日标志、节假日前后一天 | 捕捉周内/日内周期与节假日特殊模式 |
| 气象特征 | 温度、降雨量、风速、体感温度 | 影响出行及充电意愿,尤其对商圈站 |
| 历史负荷 | 过去24小时负荷、过去7天同时刻负荷 | 让模型知道当前站点状态 |
| 站点属性 | 站点类型(居民区/商圈/高速)、快充/慢充比例 | 让同一模型共享训练,同时区分不同站点特性 |
有一点需要提醒:气象特征不一定要真实预报值,用历史同期平均值效果也尚可,因为充电负荷对天气的敏感性没有想象中高,反倒是节假日标志作用显著。
4. 模型与训练实操:能落地的细节都在这里
4.1 网络结构选择与消解过程
网络结构我们一开始迷信UNet,因为它确实是扩散模型做图像时的标配。但迁移到一维时间序列后,我发现问题在于:时间序列的长度通常只有几百个点,UNet的降采样-上采样结构容易把关键细节磨平,生成的曲线偏光滑,反而丢失了充电负荷快速波动的重要特征。
最终采用的结构是三层的一维残差卷积网络,每层由两个卷积块加一个时间步嵌入映射组成,条件信息用FiLM注入到每个残差块的中间层。参数总量只有UNet的三分之一,但效果一点不差。关键原因在于,负荷曲线不像图像那样有层次化的空间结构,用太深的网络反而过拟合训练数据的噪声模式。
4.2 训练超参数:一遍遍试出来的
训练配置也经历过不少弯路。最开始我们按图像领域的习惯,把扩散步数T设成1000,损失函数用标准的噪声预测MSE,训练很稳定,但采样实在太慢。后来用DDIM采样器把推理步数压到50,效果几乎无损。
几个关键超参数的实测经验如下:
| 参数 | 推荐设置 | 备注 |
|---|---|---|
| 扩散步数T | 1000(训练)/ 50(采样) | 训练步数影响生成质量,采样用DDIM加速 |
| 噪声调度 | cosine | 比线性调度在高负荷段生成更自然 |
| 条件丢弃率 | 0.1 | 训练时有10%概率随机丢弃条件,避免测试时无条件模式崩溃 |
| 学习率 | 1e-4,余弦退火 | 太高容易在生成分布上出现"模式崩溃"式塌缩 |
| 批大小 | 64(每条序列长度96) | 显存压力不大,重点是序列采样策略 |
调试中还发现一个有意思的现象:如果模型容量太小,生成的场景会倾向于平均化,损失虽低但场景之间的差异不足;容量太大又会在低负荷时段生成一堆毛刺。所以模型容量选择其实是个平衡问题,不能只看训练损失。
4.3 训练数据切分:防泄漏是第一要务
概率预测模型最怕时间泄漏。如果训练集的最后一个时间窗口和验证集有半天重叠,模型在指标上会显得非常强,到真正上线就会露出原形。我们的切分策略是以">某时间点"为边界,训练集、验证集和测试集完全按时间先后切分,且不允许任何窗口跨边界。
另外,不同站点的负荷水平差异悬殊——高速站点可能是居民区站点的十倍。为了统一建模,我们没有对每个站点单独训练模型,而是把所有站点数据混合训练,并在特征里加了站点类型和快充比例这两个属性。这样做的好处是,数据量大的站点可以帮助数据量小的站点提升泛化,尤其是新站点在完全没有历史数据的情况下,也能利用同类站点的分布特征生成合理的先验场景。
5. 踩坑记录与调试过程:不能跳过的部分
5.1 坑:生成结果出现负值和异常尖峰
上线第一版模型后,业务方反馈说生成的某些场景下负荷出现了负值,还有个别场景尖刺高到离谱。我第一反应是数据预处理有bug,排查了很久才发现是输出层的问题——我们用线性输出层直接回归负荷值,但充电负荷的分布右偏且存在零值,去噪网络在低值区域很容易越界生成负数。
解决办法不是简单加一个clamp,因为clamp会破坏梯度和分布形状。我们改成了一个更自然的做法:输出层接上sigmoid函数,把输出值限制在0到1之间,再乘以一个固定的基准容量值。这样网络学的是"相对负荷比例",生成值天然非负且不会无界。
5.2 坑:条件信息失效,模型退化成无条件生成
有一段时间我注意到,给定不同的天气条件和节假日标志,生成的场景分布几乎没有差异。这意味着条件信息根本没有被网络利用。排查下来发现两个原因:
- 第一,条件向量维度太低,其中节假日标志就一个二进制数,对深层网络的影响太微弱。
- 第二,FiLM层放在残差块中间位置,但前几层已经把所有输入特征抹得差不多了,条件到后面已经无法恢复。
修复方法是把条件向量维度提高到128,并且对时间条件使用正弦编码后与气象和历史特征拼接,再在FiLM注入前过一个两层的MLP特征提取器。修复后,节假日和工作日生成场景的差异变得非常明显,而且能正确表现"节假日夜晚仍有较高负荷"这种业务特征。
5.3 坑:训练损失正常,但生成的场景过度集中
最让人头疼的坑是:训练损失曲线非常漂亮,MSE一直下降,但采样出来的几组场景几乎长得一样,整个场景集合的方差比训练集小得多。这是个典型的"过平滑"问题。
排查后发现,原因在于我们使用了过多的历史负荷信息作为条件,而且条件覆盖了未来一段时间的真实信息——这是一种标签泄漏。虽然训练时每个样本的条件都包含"未来一段时间的平均值",模型学习到"只要照着这个条件复现就行"的捷径,生成时条件不存在了,它就退化成输出平均场景。删掉这个泄漏特征后,样本多样性立刻恢复了。
5.4 坑:多站点联合建模的隐性偏置
联合训练多站点模型时,我们发现生成场景整体偏向数据量大的站点类型,少量站点被"淹没"。比如高速服务区站点样本只有居民区的十分之一,生成结果中高速站的尖峰特性就被平滑掉了。
解法是用站点类型嵌入向量替代简单的快充比例特征,同时在采样训练批次时,按站点类型做分层抽样,保证每个batch里各类站点都有一定比例。这样模型对每个站点类型的条件响应都能学到足够多,不会出现偏置。
6. 评估方法与落地验证:指标、场景与实际效果
6.1 评估指标:别只盯着MAE
概率预测的评价和点预测完全不同。模型上线前,我们用三个指标做综合评估:
| 指标 | 作用 | 我们的结果 |
|---|---|---|
| CRPS | 衡量预测分布与真实观测的差距,越小越好 | 比分位数回归方法低约18% |
| P90覆盖率 | 看真实值落在P90区间的比例是否接近90% | 由86%提升到89%左右,接近理论值 |
| 极端场景捕捉率 | 统计负荷超过某阈值时模型是否给出足够高的场景样本 | 在节假日下午时段提升明显 |
这里特别想强调CRPS这个指标。它本质上是把整个预测分布和真实值做逐点比较,如果预测分布太宽、太保守,CRPS照样会惩罚你;太窄、太自信,也会在真实值落在外侧时受罚。相比单纯看分位数覆盖率,CRPS更能反映整个模型预测分布质量的综合水平。
6.2 场景可视化:业务侧的沟通起点
落地过程中,把采样结果画成可视化图形是推进项目最有效的手段。我们的做法是,把预测的未来24小时负荷场景集合画成一个"影子带",中间一条粗线是P50,外部边界是P10到P90,再把前一天实际负荷曲线叠上去。
这一张图比任何指标数字都管用。业务方看到影子带在午高峰时段明显变宽时,立刻理解了概率预测的优势:模型不是不知道,而是坦诚地告诉你"这个时段不确定性大"。接下来他们会基于影子带讨论是否安排需求响应、是否预留储能容量。
6.3 采样数量与推理时延的平衡
扩散模型的采样成本是实打实的工程问题。加载一个1000万参数的模型,生成单条负荷曲线大约需要几秒钟,而业务侧希望日内滚动预测能覆盖多个站点。我们的实测结果:
- 单站点生成200条场景,DDIM采样50步,耗时约3秒,完全满足滚动预测需求。
- 全片区50个站点,用批量推理同时生成,总耗时约15秒,尚可接受。
但实话说,如果站点数量再涨一个量级,就需要考虑模型轻量化或分布式推理方案了。目前200条场景在统计上足够支撑P50和P90估计,但如果要做小概率极端事件分析,比如P99级别,建议把采样数量提到1000条以上。
6.4 模型更新频率:初期频繁,后期稳定
上线初期我们每周重训一次模型,因为充电行为还在快速变化,老模型很快过时。一个月后增量变缓,改成每月重训加每日增量微调。实践过程中发现,扩散模型对数据分布漂移的反应比传统模型慢一拍,所以重训时的数据窗口不能拉太长,保留最近三个月数据效果最好。
我的个人体会是,评估和上线阶段遇到的坑,比训练阶段更多。数据泄漏、采样多样性、指标误读、业务侧对概率输出的理解门槛,每一样都能让项目延期。但反过来想,这些坑也都是可以提前规避的——只要你在决策选型时想清楚,自己做概率预测是为了表达不确定性,而不是为了把训练损失刷得更低。
如果你正在犹豫要不要把扩散模型引入负荷预测,我的建议是:先用一个站点的小数据做一周快速验证,重点看生成场景的多样性和条件敏感性。如果这两点都比现有点预测方法有优势,再大规模推广不迟。概率预测这条路,方向对了,细节的坑都是可以一步步填平的。