资讯动态

MILP-TD3混合架构:用户侧储能调度约束校正与实现

发布时间:2026/9/19 22:01:25 来源:尧图企业网站定制
简介面向电力系统优化与储能控制领域的科研人员和工程师这份资源提供基于MILP-TD3的用户侧储能系统优化运行完整方案。针对深度强化学习在储能控制中难以严格满足运行约束的问题方案通过MILP确保动作可行、TD3负责优化决策并将电池退化成本纳入运行优化模型适用于分时电价下的24小时实时调度。包内为1个docx文档58KB从环境建模、MILP求解器到TD3算法、增强型MILP求解器再到完整训练流程、性能对比分析与实时调度测试均配有可运行代码和逐步解释。读者可据此快速复现论文实验深入理解约束处理机制与成本优化细节并对比TD3和MILP-TD3在储能控制中的性能差异。已有94人学习浏览适合对储能优化、深度强化学习和混合整数线性规划交叉方向感兴趣的研究者参考。1. 为什么要给 TD3 加一个 MILP 校正器做储能调度的人都有一个共识纯强化学习策略在测试时偶尔会给出一个“看起来合理、实际越界”的动作。SOC 越界、同时充放电、电网功率不平衡这些在仿真里只是扣点奖励在现场就是保护动作甚至设备告警。论文《基于 MILP-TD3 的用户侧储能系统优化运行》给出的思路很直接既然 TD3 擅长在连续动作空间里找优化方向MILP 擅长在约束下找可行点那就让 TD3 先给一个动作MILP 再去把它“掰回”可行域。这个思路不复杂但落地时有两个关键点值得展开一是 MILP 修正器如何构造目标函数才能不破坏强化学习学到的策略方向二是修正器本身的开销能不能落在实时调度的时间窗口内。本文围绕这两个点结合可运行的环境建模、TD3 实现、MILP 修正器设计和训练验证代码完整拆一遍这套流程。2. 储能调度问题的 MDP 建模与电池退化成本2.1 状态、动作与奖励三元组的设计把储能运行转成马尔可夫决策过程MDP核心是定义清楚状态里放什么、动作怎么表示、奖励怎么把“省电费”和“少伤电池”统一起来。状态向量在代码里取五维当前时刻归一化值、SOC 归一化值、当前电价、当前光伏出力归一化值、当前负荷归一化值。这里有两个容易忽略的细节时刻信息必须放进去否则策略无法区分“凌晨低价时段”和“晚高峰高价时段”充放电行为会变得短视光伏和负荷要除以各自最大值做归一化不然神经网络输入量纲差异太大会让训练很不稳定。def _get_state(self): return np.array([ self.current_step / self.time_steps, self.current_soc / self.battery_capacity, self.electricity_price[self.current_step], self.pv_generation[self.current_step] / max(self.pv_generation), self.load_demand[self.current_step] / max(self.load_demand) ])动作设计为标量范围 [-1, 1]正值表示充电负值表示放电。之所以不直接输出功率绝对值是为了让 TD3 的策略网络输出落在有界区间内tanh 激活后天然满足上下限要求。执行环境侧再做一次缩放动作值乘上最大充放电功率得到实际功率指令。奖励函数是运行成本取负号。运行成本拆成两部分与电网交互的电费、充放电导致的电池退化成本。注意代码中grid_power的计算方式负荷减去光伏后的净负荷再加上充电功率、减去放电功率即为从电网吸收的功率。这个值乘上当前时刻电价就是电费如果为负说明光伏过剩向电网馈电电费为负相当于收益。二者相加后再加退化成本取负号即奖励。net_load self.load_demand[self.current_step] - self.pv_generation[self.current_step] grid_power net_load charge_power - discharge_power electricity_cost grid_power * self.electricity_price[self.current_step] degradation_cost (charge_power discharge_power) * self.degradation_cost reward -(electricity_cost degradation_cost)这里最容易被新手忽略的是 SOC 更新公式充电时电能经过效率损耗SOC 增加量是充电功率乘以效率放电时 SOC 减少量要除以效率。公式方向不能写反否则同一个动作在充放电两方向上的 SOC 变化不对称策略会钻空子。2.2 电池退化成本从线性系数到倍率惩罚原始代里退化成本只用一个线性系数self.degradation_cost 0.02乘上充放电功率之和。这个模型作为基线可以但复现论文时往往不够。扩展部分给出的退化模型更接近电化学特性class BatteryDegradation: def __init__(self): self.alpha 1e-4 self.beta 5e-7 self.capacity 100 def calculate_cost(self, charge_power, discharge_power, soc): cycle_aging self.alpha * (charge_power discharge_power) c_rate max(charge_power, discharge_power) / self.capacity rate_aging self.beta * c_rate * (charge_power discharge_power) return cycle_aging rate_aging退化成本拆成循环老化项和倍率老化项。循环老化项本质上是在惩罚总的充放电吞吐量系数 alpha 控制“每 kWh 吞吐让电池折寿多少”倍率老化项惩罚的是大功率充放电因为大倍率会加速锂离子电池负极析锂和正极结构退化。c_rate是充放电功率与容量的比值即当前倍率C 数越大对寿命损伤越高。退化系数是论文复现时最值得调的一组参数。alpha 取太小策略会无视电池寿命疯狂做峰谷套利取太大策略会消极充放电只在高价差时段操作。一般建议先用一天的收益规模估算一个初始量级跑一轮训练后看调度动作里充放电是否过于频繁再反过来调系数。2.3 分时电价与光伏负荷曲线的生成逻辑电价曲线直接写在构造函数里0-5 时 0.3 元/kWh6-7 时 0.5 元/kWh8-11 时 0.8 元/kWh12-17 时又在 0.5 和 0.8 之间波动18-23 时从 0.5 回到 0.3。这不是随机数它模拟了典型的“两峰两谷”型用户侧分时电价。光伏数据用正弦函数加随机扰动模拟for t in range(6, 18): base[t] 15 10 * np.sin((t-6)/12 * np.pi) random.uniform(-2, 2)从 6 时开始爬升14 时前后达到峰值约 25 kW18 时回落到接近 0左右各加 2 kW 的随机扰动模拟云层遮挡效果。负荷数据则在 10 kW 基线基础上在早高峰 7-9 时和晚高峰 18-22 时各叠加一个凸起。这套数据生成逻辑对应的是“白天光伏多、晚高峰负荷高、电价分时激励削峰填谷”的用户侧典型场景。如果想换到工商业场景可以把负荷基线上移到 100 kW 量级光伏出力乘一个更大的系数。3. TD3 网络结构与训练流程实现3.1 Actor-Critic 网络与超参数配置TD3 的实现采用标准的 Actor-Critic 架构Actor 输入状态输出动作Critic 输入状态和动作的拼接输出 Q 值。代码里最值得注意的结构是 Critic 的两个网络完全独立而不是共享前几层再分叉。这是因为 TD3 的 clipped double-Q 技巧要求两个 Q 估计尽量去相关性共享底层特征会让两个头产生相关性削弱“取最小值避免过估计”的效果。class Critic(nn.Module): def __init__(self, state_dim, action_dim): super(Critic, self).__init__() self.layer1 nn.Linear(state_dim action_dim, 256) self.layer2 nn.Linear(256, 256) self.layer3 nn.Linear(256, 1) self.layer4 nn.Linear(state_dim action_dim, 256) self.layer5 nn.Linear(256, 256) self.layer6 nn.Linear(256, 1)Actor 输出后接torch.tanh再乘max_action把动作限制在 [-max_action, max_action]。这里max_action在储能场景中对应动作空间半宽度取 1.0 是因为环境内部会把 [-1, 1] 映射到实际功率。超参数配置上有几个关键值参数取值说明actor 学习率3e-4与 TD3 原论文一致Adam 优化器critic 学习率3e-4两个 critic 共用一个优化器损失相加后反传batch_size256储能场景单步转移数据量小256 比原论文的 100 更适合稳定收敛gamma0.9924 步决策周期折扣因子取 0.99 不会过度短视tau0.005目标网络软更新系数policy_noise0.2目标策略平滑的噪声标准差noise_clip0.5噪声裁剪范围防止过大扰动policy_freq2延迟更新频率每 2 步更新一次 Actor3.2 目标 Q 值计算与延迟策略更新训练的核心逻辑在train方法里。从经验池采样 batch 后先用目标网络计算下一状态的动作再对动作加裁剪噪声以此平滑 Q 值对动作的敏感度noise (torch.randn_like(action) * self.policy_noise).clamp(-self.noise_clip, self.noise_clip) next_action (self.actor_target(next_state) noise).clamp(-self.max_action, self.max_action) target_Q1, target_Q2 self.critic_target(next_state, next_action) target_Q torch.min(target_Q1, target_Q2)torch.min是 TD3 区别于 DDPG 的关键取两个 Critic 输出的较小值作为目标 Q可以有效抑制 Q 值过估计。在储能场景中过估计会表现为策略认为某时段放电收益极高实际执行下来电费计算对不上调度曲线出现异常的深充深放。延迟策略更新体现在self.total_it % self.policy_freq 0的判断上。Critic 每步都更新Actor 每两步更新一次这样做的好处是让 Actor 更新时面对的是相对稳定的 Q 函数梯度避免策略和 Q 值互相追逐导致发散。actor_loss -self.critic.Q1(state, self.actor(state)).mean()Actor 的损失是 Q1 网络对当前策略输出动作的评价值取负。注意这里只用 Q1 而不是 min(Q1, Q2)因为 Actor 更新只需要一个 Q 函数的梯度方向取两个的均值或最小值在实验效果上差别不大但用 Q1 计算量减半。3.3 经验回放缓冲区与训练循环经验池用deque(maxlen1000000)实现容量 100 万条。储能场景单 episode 只有 24 步500 个 episode 会产生 12000 条转移数据远小于容量限制所以容量主要起“防止旧数据污染新策略”的作用。500 个 episode 跑完缓冲区里保留的是最近约 416 个 episode 的数据训练初期那些随机探索的样本已经被挤出有利于末期收敛稳定。agent.replay_buffer.append((state, action, reward, next_state, float(done)))存储的数据元组顺序是 state、action、reward、next_state、done训练采样时按同样的顺序解包代码里t[0]到t[4]一一对应。一个容易踩的坑done必须是 float 类型的 0/1不能是布尔值否则(1 - done)在 PyTorch 里做张量运算会报类型错误。环境返回的done本身是布尔值所以存储时用float(done)做了显式转换。训练循环每 episode 结束后打印当前奖励和最近 10 个 episode 的平均奖励avg_rewards.append(np.mean(rewards[-10:]))这个近期平均奖励比单 episode 奖励更有参考价值可以过滤掉电价随机波动和负荷扰动带来的噪声便于观察收敛趋势。4. MILP 动作修正器目标函数设计与约束处理4.1 为什么纯 TD3 的约束处理不达标深度强化学习在储能控制中的约束处理一般有几种做法罚函数法简单但系数难调调小了约束轻松被突破调大了策略变得畏首畏尾Safe RL 类方法实现复杂收敛性在工程上不够可控。MILP-TD3 的思路是做后置校正策略网络输出一个建议动作MILP 修正器在满足全部运行约束的前提下找一个与建议动作最接近的可行动作。这样 TD3 负责优化方向MILP 负责可行性两个模块各司其职。这种架构的合理性在于TD3 建议的动作已经隐含了电价信号和负荷趋势的信息MILP 修正时目标是最小化与建议动作的偏差不会把策略学到的“低价充电、高价放电”的大方向给破坏掉。如果 MILP 目标改成最小化运行成本本身那 TD3 的存在就没有意义了。4.2 Pyomo 模型的目标函数与线性化处理MILPSolver.solve方法构建了一个两变量的小规模 MILP 模型决策变量是充电功率charge_power和放电功率discharge_power目标是最小化与建议动作的偏差平方和model.obj Objective( expr(model.charge_power - max(0, proposed_action) * self.env.max_charge_rate)**2 (model.discharge_power - max(0, -proposed_action) * self.env.max_discharge_rate)**2, senseminimize)这里有个细节目标函数里的二次项是连续变量上的凸二次函数用 Pyomo 默认的求解器做 MIQP混合整数二次规划或直接线性化都可以求解。如果求解器不支持二次目标可以用绝对偏差的线性近似替代即min (z1 z2)加两个不等式约束把偏差夹住。约束条件有三个核心SOC 范围约束、功率平衡约束、充放电功率上下限通过变量的bounds参数显式声明。SOC 约束的关键在于用当前 SOC 和充放电效率推导下一时刻 SOCnext_soc current_soc (model.charge_power * self.env.efficiency - model.discharge_power / self.env.efficiency) model.soc_constraint Constraint(expr0 next_soc self.env.battery_capacity)注意充放电效率在等式两边的位置与 2.1 节 SOC 更新公式完全一致必须保证 MILP 模型和环境物理逻辑的一致性否则修正器认为的可行动作在环境里执行后依然会越界。功率平衡约束net_load charge_power - discharge_power 0限定了电网侧不向储能反送功率。这个约束在一些用户侧场景下是硬性要求因为关口表的计量方向通常不允许储能向电网倒送电。如果要放开反向馈电将此约束改为等号即可但一般不建议因为大多数地区分布式光伏加储能的并网协议里不包含馈网收益。4.3 GLPK 求解器接入与求解失败兜底solver SolverFactory(glpk) results solver.solve(model) if results.solver.termination_condition TerminationCondition.optimal: optimal_action (model.charge_power() - model.discharge_power()) / max(self.env.max_charge_rate, self.env.max_discharge_rate) return optimal_action else: return 0GLPK 是开源 MILP 求解器Pyomo 通过SolverFactory(glpk)直接调用。在小规模两变量问题中GLPK 求解时间在毫秒级完全满足 1 小时或 15 分钟粒度的实时调度要求。如果追求更快的求解速度可以用appsi_highs后端替代 GLPKHiGHS 在纯线性问题上通常比 GLPK 快数倍。求解失败时返回 0即中性动作不充不放。这个兜底策略在理论上是最安全的宁可少赚电费也不越界。实际运行时建议加一个计数器记录求解失败次数如果失败率高说明线性化或约束定义有问题需要回查模型而不是让系统一直静默运行在中性动作上。4.4 修正器接入训练流程的时机与姿势select_action方法在训练和测试两个阶段都会调用 MILP 修正器。训练阶段动作先加探索噪声再做 MILP 修正测试阶段直接输出 Actor 的动作经 MILP 修正后执行。这个顺序有一个值得注意的地方探索噪声是在 Actor 输出上加的MILP 修正会把这个噪声带来的越界部分消掉所以训练时智能体实际上是在“被约束后的探索”中学习不容易学到越界动作再被惩罚的负样本路径。def select_action(self, state, evaluateFalse): action self.actor(state).cpu().data.numpy().flatten() if not evaluate: noise np.random.normal(0, self.policy_noise, sizeself.action_dim) noise np.clip(noise, -self.noise_clip, self.noise_clip) action (action noise).clip(-self.max_action, self.max_action) corrected_action self.milp_solver.solve(action[0]) return np.array([corrected_action])这里有个潜在的性能瓶颈MILP 求解在每步动作选择时都要执行一次训练时每秒需要完成多次求解。对于两变量模型GLPK 单次求解通常不超过 10 毫秒但如果扩展到多时段联合优化MILP 规模会迅速膨胀就不能每步都调用修正器了。届时可以改为每隔 N 步做一次修正或者只在检测到动作可能违反约束时触发修正。5. 训练评估与性能验证的加速技巧复现论文时最花时间的不是理解算法而是等训练收敛后评估结果。我通常会在训练循环里同步记录三个指标episode 总奖励、近 10 个 episode 的平均奖励、以及单步 MILP 求解耗时。前两个看训练是否平稳第三个用来确认实时性是否达标。验证修正器效果有一个快速方法训练完成后把 MILP 修正器停用直接用 Actor 输出动作跑一轮测试记录每步 SOC再启用修正器跑同一轮测试。对比两条 SOC 曲线你会看到纯 TD3 的曲线在某些时段会跌破 0 或越过容量上限而 MILP-TD3 的曲线始终在 [0, 1] 区间内。这个对比图在答辩和论文中非常有说服力。性能评估时还需要关注日均运行成本的拆分明细。我一般会把每步的grid_power、electricity_price、退化成本逐项存储结束后按峰段汇总。这样能快速判断省下来的钱主要是峰时放电收益还是谷时充电节省如果峰时段的放电功率利用率偏低说明策略在电价高企时段还不够激进可以适当调低退化成本系数。MILP-TD3 这类“强化学习出方向、数学规划保可行”的混合架构在储能调度之外的很多工业控制场景都值得借鉴比如微电网能量管理、电动汽车有序充电、楼宇暖通负荷优化。核心思路是相通的让神经网络负责处理复杂的高维状态到动作的映射把硬约束留给数学规划去收口。实际动手复现时建议先跑通这份基础代码再去替换真实负荷数据和分时电价验证一下你的场景里退化成本系数该取多大。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价