做过微电网能量管理的人,应该都体会过这种尴尬:光伏出力一会儿猛涨一会儿暴跌,电价一天好几个峰谷,储能电池的SOC稍微没控好就到限,还要在“多买电”和“少卖电”之间反复权衡。传统规则策略写了几十条if-else,换一个天气场景就失效;模型预测控制虽然效果好,但需要精确的光照、负荷预测模型,实际部署时又常常被预测误差打脸。
这个项目就是冲着这个问题去的——用深度强化学习里的**深度期望Q网络算法(DEQN)**来做微电网的实时能量调度。我把它做成了一套完整的Python实现:以光伏发电、储能电池、电网交互和负荷需求为对象,通过智能体与仿真环境不断试错,学习“什么时候充电、什么时候放电、什么时候从电网买电”的最优决策,目标是在满足负荷需求的前提下,把系统运行成本压到最低。第二版迭代(也就是项目名里的002)主要修正了第一版在奖励函数设计和状态归一化上的两个明显缺陷,训练稳定性和策略泛化能力都有明显提升。
这篇博文不打算只贴代码,我会把整个项目的思路、环境建模方式、DEQN的算法原理、训练过程中踩过的坑和调参心得全部讲透。适合两类人看:一类是想把强化学习真正用到能源调度场景的同学,另一类是已经在做微电网优化、想把手里的数学规划方案换成数据驱动方案的工程师。
1. 项目思路与方案选型
1.1 为什么用深度强化学习而不是模型预测控制
微电网能量管理本质上是一个带约束的序贯决策问题。决策变量是每个时段的储能充放电功率、与电网的购售电功率,约束条件包括电池的SOC上下限、充放电功率限值、功率平衡方程,目标函数是运行周期内的总成本最小化。传统的做法主要有两条路:
第一条是真·规则控制。设定SOC阈值,比如低于30%就充电,高于80%就放电。好处是简单、可解释、稳定,但问题在于规则很难覆盖所有运行场景。光伏出力受天气影响很大,电价曲线在尖峰平谷之间切换,如果规则里的阈值设置不当,会出现“光伏大发时还在买电充电、晚上电价高峰时电池却没钱放电”这种离谱操作。
第二条是模型预测控制(MPC)。在每个控制周期内求解一个有限时域的优化问题,只执行第一步,然后滚动更新。MPC效果确实好,但它依赖一个精确的预测模型和求解器,每一步都要调数学规划求解器,计算开销大,而且一旦光伏或负荷预测偏差过大,控制效果会迅速劣化。
深度强化学习的思路和上面两种都不一样。它不依赖精确的未来信息,而是在一个模拟环境里通过大量试错来学习“状态到动作”的最优映射。训练的时候给它看各种可能出现的场景——晴天、阴天、电价尖峰、SOC偏低——它自己总结出什么状态下应该做什么动作。等到部署时,只需要输入当前的状态观测值,模型直接给出动作,计算负担极小,具备很强的实时性。
1.2 为什么选DEQN而不是标准DQN
标准DQN(Deep Q-Network)在很多离散动作控制任务上已经验证了效果,但直接拿来做微电网调度,会暴露出几个问题。最典型的是Q值的过估计。微电网的动作空间是连续功率的离散化,动作之间的差异并不总是很大,比如“以24kW充电”和“以30kW充电”在多数场景下效果差不多,Q值的微小波动就可能导致动作在相邻档位间来回跳,训练过程非常不稳。
DEQN的核心改进在于引入“期望状态价值”的概念。它把Q值拆解成两部分:状态价值V(s),也就是“当前这个状态下,无论做什么动作,未来累积回报的期望值”;以及优势函数A(s,a),也就是“这个动作相对于平均水平好多少”。这样一来,即使某些动作的Q值估计有误差,只要优势函数的相对排序稳定,策略就不会剧烈抖动。这个拆解思路与Dueling DQN的思想有相通之处,但在实现上DEQN更侧重用状态期望价值来降低Q值估计的方差,在微电网这种奖励信号延迟且带有强周期性的场景里表现更稳。
另外我选DEQN还有一个实际原因:训练效率。标准DQN对每个动作都维护一个独立的Q值,需要大量采样才能区分动作间的细微差异。而DEQN的状态价值共享了大部分网络结构,采样效率更高。实测相同训练步数下,DEQN的收敛速度大约比标准DQN快25%到30%,考虑到微电网仿真一天只有96个决策时点(15分钟一个时段),这个效率差距是很值得的。
1.3 项目的整体架构与运行流程
整个项目的运行逻辑可以分成“训练”和“部署”两条线。
训练线是这样的:先搭建一个模拟微电网环境的仿真器,接收智能体输出的动作(充放电功率),返回下一时刻的状态(光伏、负荷、电价、SOC)和即时奖励(运行成本相关);智能体内部有DEQN网络、经验回放池和目标网络,每执行一步就把状态转移存入经验池,每积累一定样本后从池子里随机采样一批进行梯度更新。
部署线就简单多了:加载训练好的模型权重,实时读取当前的光伏出力、负荷功率、实时电价和SOC,输入网络做一次前向计算,动作集合里优势函数最大的那个就是当前时段的调度指令。
我做的这个项目里,仿真环境的时间粒度是15分钟,一天就是96个调度点,一个训练回合就是96步。训练时用了某地区一年份的光伏和负荷历史数据(脱敏处理过),再叠加随机扰动增强场景多样性。整体流程图不画了,就按这个逻辑往下走。
2. 微电网环境建模
环境建模是整个强化学习项目的地基,地基没打牢,算法再先进也白搭。我曾在第一版里因为环境里的SOC更新公式写错了一个系数,导致整个训练过程都学不到有效策略,那个教训至今记忆犹新。下面拆开讲。
2.1 状态空间定义
状态空间的设计决定了智能体能“看到”什么。我最终采用了8维状态向量:
- 当前时段的光伏实际出力(kW)
- 当前时段的负荷需求(kW)
- 当前时段的实时购电电价(元/kWh)
- 当前时段的实时售电电价(元/kWh)
- 储能电池当前的SOC(0到1之间的浮点数)
- 未来4小时的光伏预测出力均值(kW)
- 未来4小时的负荷预测均值(kW)
- 当前时段编号(0到95的整数,做归一化后输入)
这里需要注意两点。第一,加入“未来4小时的光伏预测均值”和“负荷预测均值”这两维,是为了给智能体提供一定的前瞻性。虽然强化学习的理论框架要求策略只依赖当前观测,但在实际工程中,给模型一点粗糙的预测信息能明显提升策略在电价拐点处的表现。不需要很精确,均值级粗粒度就够。
第二,时段编号也可以用正弦编码代替归一化后直接输入,把白天和夜晚的周期性信息编码进去,效果差别不大。但我在实际比较后发现,直接归一化到0和1之间最简单,而且不会引入额外的相位问题。
2.2 动作空间与调度约束
动作空间设计是强化学习应用中一个关键的“艺术性”选择。把连续量直接作为动作输出,需要用DDPG、PPO这类连续动作算法。但为了保证代码简洁和训练的稳定性,我选择把充放电功率离散化:从-24kW到+24kW,按6kW的步长分成9个离散档位。负值表示充电(从电网或光伏吸收功率给电池),正值表示放电(电池输出功率给负荷或反送电网)。
为什么是6kW而不是更细?动作空间粒度越细,智能体的决策精度越高,但Q值的估计难度也越大。9个离散档位对于30kW级的小型微电网来说已经足够,每个档位对应22%左右的功率变化,在成本曲线上不会产生太大的性能损失。
约束处理方面,最关键的是SOC的物理惯性。电池的SOC不是智能体可以任意设定的,它遵循一个递推关系:
soc_next = soc + (charge_power * charge_efficiency * dt - discharge_power * dt / discharge_efficiency) / battery_capacity2.3 奖励函数设计
奖励函数是整个微电网强化学习的灵魂,也是我看过翻车最多的环节。目标当然很清晰:最小化系统运行成本,但直接把这个目标翻译成奖励,实际训练效果会很糟。
最终采用的奖励函数分三层:
第一层是运行成本项。每个时段从电网买电的费用减去向电网卖电的收益,再加上电池折算损耗成本:
cost = max(grid_purchase_power, 0) * purchase_price - max(grid_sell_power, 0) * sell_price cost += battery_degradation_penalty # 基于充放电循环次数的折算成本 reward = -cost第二层是SOC越限惩罚。如果下一时刻的SOC超出[0.2, 0.9]的区间,奖励会额外减去一个大的惩罚值。我用的惩罚公式是:
if soc_next < 0.2: reward -= (0.2 - soc_next) * 50 elif soc_next > 0.9: reward -= (soc_next - 0.9) * 50惩罚系数50是我反复调过的结果。系数太小,智能体会频繁触碰SOC边界,加速电池寿命衰减;系数太大,智能体会变成“过度保守派”,宁可多买电也不敢充电,导致运行成本升高。
第三层是动作平滑惩罚。电池大幅度频繁切换充放电状态会严重损害实际使用寿命,所以在相邻时段的功率差值超过12kW时,奖励会扣掉一个固定惩罚。这相当于是给动作加了一个软约束,实测下来明显减少了训练曲线上那种锯齿状的振荡现象。
2.4 数据准备与场景划分
数据是微电网仿真中最大的变量。光伏出力数据来自某地一年的历史记录,时间间隔15分钟,共35040个点。我把数据按8比1比1的比例划分成训练集、验证集和测试集——必须按时间先后切,不能随机打乱,否则会引入未来数据的泄漏,测试结果会虚高。
场景构造时还要注意一个问题:直接用一年真实数据训练,遇到连续阴雨天的概率偏低,智能体对极端场景的适应能力会不足。我的做法是,在真实数据的基础上叠加随机缩放因子,模拟多云、阵雨等天气。光伏出力的随机扰动设置为±30%的乘法扰动,负荷数据的扰动是±10%。这么做可以让智能体在训练期看到更丰富的状态覆盖,测试期对未见天气的适应能力也好很多。
3. DEQN算法核心与代码实现
3.1 DEQN的基本原理
先花点篇幅把DEQN的数学直觉讲清楚。标准DQN通过神经网络直接近似Q(s,a),也就是“在状态s下执行动作a,之后按最优策略走所能得到的期望累积回报”。它的Loss函数是:
loss = (r + gamma * max_a' Q_target(s', a') - Q_predict(s, a)) ** 2问题出在max运算符上。由于神经网络输出有逼近误差,max会倾向于选中被高估的动作,导致Q值持续偏高,这在动作空间较大或者动作之间差异不显著时尤其明显。
DEQN改用期望价值做基准线。定义状态价值V(s)为所有动作Q值的期望(或者说是“在这个状态下遵循当前策略能得到的平均累积回报”),再定义优势函数A(s,a)为某个动作和平均水平之间的差距,两者之和还原成Q值:
Q(s, a) = V(s) + A(s, a)在训练时,对V(s)和A(s,a)分别进行估计。V值代表状态的基础好坏,例如在电价高峰时段且SOC偏高时,这个状态的未来收益期望本身就不错;优势函数则告诉智能体,在众多可选动作中哪个更值得做。这样分解的好处是:即使某个动作的Q值估计有随机误差,只要优势的排序不被打乱,最终策略的选择依然稳定。
微电网场景里,大多数时段下“充电”“放电”“保持”的差异并不大(因为光伏和负荷具备天然的平衡关系),真正的好决策体现在少数关键时段。这种“大多数动作相似、少数动作至关重要”的问题结构,恰好是DEQN的优势区。用生活化的比喻来说,标准DQN像是给每个动作单独打分,而DEQN先判断“这个情景整体值不值钱”,再判断“这个动作在这个情景里是不是比别的动作更好”。
3.2 网络结构与关键代码
网络结构我采用了一个相对简洁的双塔架构。输入是8维状态向量,经过共享的全连接层提取特征后,分叉成状态价值塔和优势塔,最后合并输出9个动作的Q值。
import torch import torch.nn as nn import torch.nn.functional as F class DEQNet(nn.Module): def __init__(self, state_dim=8, action_dim=9, hidden_dim=128): super(DEQNet, self).__init__() self.shared_fc = nn.Sequential( nn.Linear(state_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU() ) self.value_head = nn.Sequential( nn.Linear(hidden_dim, 64), nn.ReLU(), nn.Linear(64, 1) ) self.advantage_head = nn.Sequential( nn.Linear(hidden_dim, 64), nn.ReLU(), nn.Linear(64, action_dim) ) def forward(self, state): features = self.shared_fc(state) value = self.value_head(features) advantages = self.advantage_head(features) # 优势函数减去均值,保证可辨识性 q_values = value + (advantages - advantages.mean(dim=-1, keepdim=True)) return q_values这里有个关键的实现细节:advantages.mean(dim=-1, keepdim=True)这一行。如果不做这个中心化处理,V值和A值之间存在退化问题——任意V值加上一个常数C、A值减去同一个常数C,得到的Q值完全相同,网络会陷入无法区分两者的状态。强制让A的均值为0,保证Q值的分解唯一。
隐藏层维度我设为128,一共有两个共享全连接层加两个独立输出头,总参数量不到3万。这个规模在CPU上训练完全没有压力,一轮96步的仿真大概只需要30毫秒的推理时间,实际部署时用树莓派级别的设备也能跑到实时。
3.3 经验回放与目标网络
DEQN的训练必须配合两个稳定器:经验回放池和目标网络。
经验回放池的作用是打破样本之间的时间相关性。微电网的状态序列天然是强时间相关的,连续几个时段都是同一个趋势,如果按顺序拿这些样本训练,网络的梯度方向会来回震荡。我把经验池的容量设为20000条,训练时随机均匀采样64条作为一小批数据来更新网络。随机采样相当于把时间相关性打散,让每次梯度更新都覆盖更多样的场景。
目标网络是另一个关键。在标准的TD更新里,目标值r + gamma * V(s')本身也依赖同一个网络计算出,这就相当于“拿自己的预测当作标准答案来修正自己”,容易出现自激震荡。我的做法是维护两份网络参数:一份是实时更新的在线网络,另一份是每隔固定步数从在线网络复制过来的目标网络。计算Loss时,用目标网络计算V(s'),在线网络只负责输出当前Q值的预测。这样目标值在一定周期内是稳定的,训练过程的收敛性会好很多。
# 目标网络更新频率 if total_steps % target_update_freq == 0: target_net.load_state_dict(online_net.state_dict())3.4 训练循环的关键细节
训练主循环其实很朴素,核心就是“采集一条轨迹、按批抽样、梯度更新”三步。但有几个细节我调试了很久才理顺。
第一是gamma的设置。微电网一天96步,如果gamma太大(接近1),累积回报的计算会跨越太多天,导致智能体难以分辨“今天的行为”和“很久以后的回报”之间的因果链。但gamma太小又会让智能体变得短视,只顾眼前的电价,不考虑电池SOC的长期规划。我最终设在0.99,同时限制单条轨迹的长度就是一天96步,每个回合结束时做一次状态重置,这相当于在“保持长期视野”和“避免信用分配困难”之间取了合理的平衡点。
第二是探索策略。训练初期,智能体对环境一无所知,需要大量的随机动作来探索;训练后期,策略逐渐成形,继续随机探索会破坏已学到的经验。我用的是标准的epsilon-greedy策略,epsilon从1.0逐步衰减到0.05。衰减速率我做了两次调整,最终设为每5000步衰减10%,25000步后达到下限。这个衰减速率配合经验池容量,刚好能让智能体在“充分探索”和“充分利用”之间平滑过渡。
第三是梯度裁剪。强化学习训练中偶尔会遇到异常大的TD误差(比如某个奖励值因为边界惩罚突然变大),导致梯度爆炸、参数飞掉。我在每次反向传播前加了一步梯度裁剪,clip值设为10。这是一个小而保平安的操作,强烈建议加上。
4. 训练实操与性能表现
4.1 超参数的选择与依据
这部分直接放一张我最终采用的核心超参数表,方便你直接抄:
| 参数 | 取值 | 说明 |
|---|---|---|
| 学习率 | 1e-4 | 偏保守,防止Q值更新过猛 |
| 批量大小 | 64 | 与隐藏层128维匹配,样本量适中 |
| 经验池容量 | 20000 | 覆盖约200天运行数据 |
| 目标网络更新频率 | 200步 | 约两个回合更新一次 |
| gamma | 0.99 | 兼顾长期收益与收敛效率 |
| epsilon初始/最小 | 1.0 / 0.05 | 前期充分探索 |
| epsilon衰减速度 | 每5000步×0.9 | 25000步后趋稳 |
| 优化器 | Adam | 默认beta参数即可 |
| 每回合步数 | 96 | 对应一天的调度周期 |
关于学习率,我特别想多说一句。第一版我用的是5e-4,训到一半发现Q值曲线出现了周期性的暴跌。排查很久发现是学习率偏大,导致网络在TD误差较大的时段发生参数震荡。降到1e-4之后,同样训练20万步,曲线的稳定性完全不一样。如果你也遇到类似的波动,先查学习率,别急着调网络结构。
另一个容易忽视的是批量大小与经验池的匹配。如果批量太小,梯度估计的方差大;如果批量太大,每次更新覆盖的样本过于同质。64这个值在实际调试中几乎没有让我失望过,属于一个很好的起始点。
4.2 训练过程的收敛曲线解读
训练期间我记录了每个回合的累积回报以及每500步的平均TD误差。这里分享几个典型的“读懂训练曲线”的判断技巧。
第一个特征是前期快速下降然后进入平台期。前5000步,智能体还处于随机探索阶段,累积运行成本只比“纯从电网买电”的基准策略略好一点。5000步之后,随着epsilon衰减和Q值估计逐渐准确,成本开始快速下降,大约在8万步左右进入平台期。平台期的波动幅度大约在±3%以内,这个波动属于正常范围,因为每天的光照和负荷数据不一样,环境本身的难度也不同。
第二个特征是TD误差的渐次下降。在我这个环境里,TD误差从初期的0.8左右最终稳定在0.15到0.2之间。如果TD误差停滞在0.5以上,基本可以判断奖励函数没有正确引导智能体,需要回头审视是奖励稀疏、还是奖惩尺度失衡。
还有一个需要留意的地方:即使训练结束后,累积回报曲线偶尔还会冒出几个尖峰向下的异常点。我查过这些异常点对应的运行日,基本都是连续阴雨天+高负荷的极端场景。这说明智能体虽然总体学会了应对常规场景,但对极端场景的鲁棒性仍然有限。对这类场景,要么扩充训练数据中的极端样本比例,要么在部署时搭配规则兜底。
4.3 与规则策略和MPC的对比结果
训练完成后,我用测试集数据做了对比测试。对比对象是三类基准策略:固定阈值规则策略(SOC低于30%充电、高于80%放电)、基于完美预测的MPC策略(理论上限)、以及DEQN策略。评价指标是测试期平均每日运行成本。
| 策略 | 平均日运行成本(元) | 相对规则策略节省 |
|---|---|---|
| 规则策略 | 186.3 | 基准 |
| DEQN策略 | 132.7 | 28.8% |
| MPC(理想预测) | 124.5 | 33.2% |
DEQN策略相比规则策略节省约28.8%的成本,相比理论最优的MPC也只差4%左右。最关键的是,DEQN的推理过程只需要一次前向计算,而MPC每一步都需要求解一个有数百个变量的混合整数规划问题,计算时间差了2到3个数量级。在某些对实时性要求高的调度场景,这4%的差距完全可以用计算开销的优势来弥补。
从具体调度行为来看,DEQN学到的策略也很有意思。它会在电价低谷时段适当提高充电功率,在光伏大发时段优先满足本地负荷而非盲目卖电,在电价高峰时段把电池SOC控制在足够放电的水平。这些行为没有通过任何规则显式定义,完全是智能体从奖励信号里自己总结出来的,这让我对强化学习在能源调度里的应用价值有了更具体的信心。
5. 常见问题与排查实录
以下是这个项目里踩过的最典型的几个坑,每个都折腾了我不少时间,整理成问答形式供你排查时参考。
5.1 训练不收敛,累积回报长期不下降
这种情况第一版几乎每次都会碰到。我的排查顺序是:先看奖励函数的量纲是否合理,再看状态归一化是否到位,最后检查探索率是否衰减。
奖励量纲是第一个会出问题的地方。如果运行成本在几百块量级而SOC越限惩罚也是几百块,那么两者的权重关系就非常微妙。我的建议是将SOC越限惩罚的量级设为运行成本量级的5到10倍,确保约束在奖励信号中足够醒目。
状态归一化是第二个高频雷区。光伏出力、负荷、电价、SOC的量纲完全不同,如果不归一化,神经网络前几层的权重会被量纲大的特征主导。我做了一个简易的在线归一化模块,用训练集的均值和标准差对每个特征做标准化,效果立竿见影。这里注意,一定要用训练集的统计量来做归一化,不能让测试数据混入均值计算,否则会有信息泄漏。
5.2 Q值过估计和动作频繁抖动
如果你画出某个状态下的Q值分布,发现某些动作的Q值显著偏高且不符合物理直觉,基本就是过估计了。DEQN的结构本身能缓解这个问题,但有两个配套操作值得一起用上。
第一个是降低目标网络的更新频率,从每200步更新改到每500步或1000步,让目标值更稳定。第二个是在Loss上叠加一个L2正则项,限制网络参数的范数不要过大。
动作频繁抖动的问题,除了奖励函数里的动作平滑惩罚外,还可以在动作选择时加一个“滞回区间”。比如当前动作是放电12kW,下一步的优势函数最大值是放电18kW,但两者差距小于某个阈值(比如Q值差小于0.5),就保持原动作不变。这个操作完全在部署端实现,不需要重新训练模型,工程上非常方便。
5.3 电池SOC长期卡在下限附近
训练中期我发现一个怪现象:智能体总把电池放空,导致光伏大量弃光或者高价买电。原因是奖励函数里对低SOC状态的惩罚是线性的,但实际运行中SOC从0.2趴窝和从0.1趴窝都没太大差别。惩罚不够重时,智能体会倾向于“多放电赚眼前的电费节省,以后的事以后再说”。
修起来也不复杂,把线性惩罚换成平方惩罚就行:
if soc_next < 0.2: penalty = (0.2 - soc_next) ** 2 * 80 reward -= penalty平方项会急剧放大极低SOC的惩罚强度,让智能体在逼近下限时更加谨慎。
5.4 模型在测试集上表现明显差于训练集
这是泛化问题。第一版我的训练数据和测试数据来自同一个月的不同时段,结果测试时表现正常;换到另一个季度完全不同天气模式的数据上,策略性能掉了将近10%。后来我在训练数据里加入了随机天气扰动,并在测试时同样加入了适度扰动再取平均值来评估,这本质上相当于强化版本的数据增强,能更真实地反映模型的泛化能力。
还有一个容易被忽略的点:如果部署环境的光伏容量或负荷水平与训练环境差异太大,模型的输入分布已经偏移,最简单的办法是在部署前用少量线上数据做一次轻量微调。强化学习的微调和监督学习一样,不需要很多样本,固定住网络前几层,只微调最后输出层的参数,几百条数据就足够。
我个人在整个项目里最深的一个体会是:强化学习项目的成败更多取决于环境设计和奖励塑造,而不是算法本身的复杂程度。DEQN只是在标准DQN上做了一个不算复杂的结构改进,但它对这个任务的有效性提升非常显著。如果你正在做类似的调度或控制项目,我建议你先把状态、动作、奖励这三件事想透,再考虑要不要上更复杂的算法——很多时候问题出在前端,而不是模型。
最后再分享一个小技巧:训练过程中每隔一段时间就保存一次模型权重,并在验证集上跑一次性能评估,保存最优的那份。别只盯着最后一步的模型,训练曲线最优值往往出现在一个你意想不到的时间点。设置一个模型断点续训机制,也会让你在调参时更从容,不用每次都从头开始。