一位做园区微网的朋友前段时间找到我拿着历史电价曲线和光伏出力数据问我想不想用强化学习给储能系统做一套自动充放电策略让它在峰谷价差里自己找钱赚。他反复强调了一个词效益最大化。这个词放在静态场景里很好办但放到真实能源市场里问题就变成一串连锁的动态决策——电价在跳、光伏出力在变、储能电量在衰减今天的最优动作明天可能完全不适用。我当时给他的建议是用 Q-learning 起步先跑通一个端到端的 Matlab 仿真把“效益最大化”这个商业目标翻译成算法能优化的目标函数。这篇文章就是完整记录这一套东西从问题建模、状态和动作怎么设计到 Matlab 代码实现、训练结果、参数调优最后还有我踩过的几个坑。适合正在做能源交易策略、微电网调度相关研究的学生或者想快速上手强化学习落地一个交易 Demo 的工程同学。1. 先说结论Q-learning 为什么会跟能源市场“对上频率”传统处理能源调度和交易问题时大家第一反应往往是数学规划或者模型预测控制比如把未来 24 小时的电价预测出来再丢给线性规划求解器买进卖出。这种做法的问题在于你对模型的依赖太强了。电价受供需、天气、检修计划、甚至人为竞价行为影响很难精确预测光伏出力更是有天然的随机性。一旦你的预测模型和真实世界偏差变大整个计划瞬间失效。Q-learning 是另一条思路我不显式建模电价和光伏的统计规律而是让一个智能体直接跟环境交互不断试错最终记住“在什么状态下做什么动作长期收益最大”。这在数学上是无模型强化学习适合那种状态转移不知道、但可以实时采样得到的场景而能源市场恰好是这种场景的典型代表。用一句话概括这个过程智能体每天都在“观察市场状态 → 选择交易动作 → 获得收益 → 更新认知”经过足够多轮的训练它会慢慢掌握一套比人经验更细腻的策略。比如普通工程师总结出来的规则多半是“低谷充电、高峰放电”但 Q-learning 在仿真里还能学会“平段电价较高但隐含未来的高价预期时也值得提前囤一点电”这样更细致的规律。本文使用的场景是常见分布式能源结构屋顶光伏 储能电池 与电网的双向交易接口。负荷在这里先简化处理因为我的目的是把 Q-learning 在能源交易中的价值讲透。仿真收益只代表教学场景不代表真实投资回报但这个框架完全可以拓展到更复杂的用电负荷、需求响应和竞价场景里。2. 把“效益最大化”翻译成强化学习的目标函数2.1 状态设计把市场观察变成离散马尔可夫状态Q-learning 的表格式实现有一个硬约束状态必须是有限离散的。所以第一件事就是把市场观测压缩成几个关键维度。我在仿真中选择了四个状态变量当前时段 t1 到 24代表一天中的哪个小时电价档位1 表示低价2 表示平时3 表示高价储能荷电状态 SOC 档位1 到 5每档 20%对应 20% 到 100% 的剩余电量光伏出力档位1 表示弱2 表示中3 表示强。为什么必须把时间 t 放进状态因为能源市场是强周期性的。如果算法看不到时间它很可能在半夜把电放光结果第二天早上峰价来临时无电可卖。加入时间后算法才有条件学习“什么时段该储备、什么时段该释放”的时序策略。这样组合出来的状态空间大小是 24 × 3 × 5 × 3 1080 个状态动作空间只有 3 个。对于表格式 Q-learning 来说这个规模训练起来非常快普通笔记本几十秒钟就能收敛到一个不错的水平。2.2 动作设计从交易指令到充放电策略每个时段智能体只需要做一个动作充电、放电、静止。充放电功率固定为储能额定功率 30 kW每个决策周期是 1 小时所以每个时段最多充入或放出 30 kWh 的电量。这里要实话实说固定功率是一种简化。真实储能系统的功率是可以连续调节的甚至可以在一个小时内分阶段调节但离散动作对解释算法原理和跑通流程来说完全够用。等你想把策略真正部署到硬件上可以再把动作空间换成连续的用 DQN、DDPG 这类算法来替换。同时动作还受储能物理边界限制SOC 低于 20% 时不能放电高于 95% 时不能充电。这些限制条件必须写进环境函数而不是写在智能体的选择逻辑里。原因很简单环境限制是客观物理规律智能体必须通过试错去学习“这个状态不能选那个动作”而不是靠我们人为封死。2.3 奖励函数效益要算账但不能只有钱奖励函数是整条训练链路里最重要、也最容易设计跑偏的地方。我定义每个时段的即时奖励为充电从电网购电付出购电成本奖励为负放电向电网售电获得收益奖励为正静止奖励为零在每个动作后再扣减一个小的切换惩罚防止智能体频繁无意义地充放。用公式表示就是r_t price × (P_discharge - P_charge) - switch_penalty其中 price 是当前时段的电价P_discharge 是放电功率P_charge 是充电功率。注意充电和放电不能同时发生动作里已经排他。为什么加切换惩罚我最初版本的奖励函数里没有这一项训练出来的策略非常难看智能体会在每十分钟的仿真步长里反复切换充放状态虽然算法账面收益没有明显变差但实际项目里这会让电池健康度迅速下降。加入切换惩罚后策略明显变得平滑。这里的经验是设计奖励函数时永远要把物理设备寿命和运维成本放进去只盯着电费差价优化到最后一定会在别处吃亏。2.4 Q-learning 更新公式为什么长这样核心更新公式如下Q(s, a) ← Q(s, a) α [ r γ max_{a} Q(s, a) - Q(s, a) ]用大白话解释就是我在状态 s 做了动作 a得到即时奖励 r进入状态 s。接下来我不看“实际下一步会做什么”而在 s 的所有可能动作里取最大的 Q 值作为对未来收益的估计。然后我用这个估计值和当前的 Q 值之间的差乘以学习率 α去修正表格里的旧估计。γ 是折扣因子。γ 接近 0智能体只关心眼前的几块钱电费γ 接近 1智能体会为了一周后的大丰收愿意放弃今天的蝇头小利。在能源交易里通常取 0.9 到 0.99 之间。我自己在一天为一个回合的场景里用 0.95效果比较平衡。3. Matlab 代码实现从状态编码到训练主循环3.1 环境函数 marketStepMatlab 里实现强化学习环境最直观方式是写一个“状态转移 奖励”的函数。输入是当前状态、动作、电价序列和光伏序列输出是下一个状态和即时奖励。function [nextState, reward, done] marketStep(state, action, priceSeries, pvSeries) % 状态格式: [时段, 电价档位, SOC档位, 光伏档位] t state(1); priceLevel state(2); socLevel state(3); % 参数设置 priceMap [0.3, 0.8, 1.5]; % 低价/平时/高价对应的电价单位元/kWh capacity 100; % 储能容量 kWh P_rate 30; % 额定功率 kW dt 1; % 决策周期 1 小时 socMin 0.2; socMax 0.95; currentPrice priceMap(priceLevel); soc socLevel * 0.2; % 5档SOC每档20% socNext soc; reward 0; if action 1 % 充电 chargeEnergy min(P_rate * dt, (socMax - soc) * capacity); reward - currentPrice * chargeEnergy; socNext soc chargeEnergy / capacity; elseif action 2 % 放电 dischargeEnergy min(P_rate * dt, (soc - socMin) * capacity); reward currentPrice * dischargeEnergy; socNext soc - dischargeEnergy / capacity; end % 静止时不改变SOCreward为0 % 切换惩罚如果需要可以调整系数 % reward reward - 0.1; if t 24 nextState []; done true; return; end nextT t 1; nextPriceLevel binPrice(priceSeries(nextT)); nextSocLevel max(1, min(5, round(socNext / 0.2))); nextPvLevel binPV(pvSeries(nextT)); nextState [nextT, nextPriceLevel, nextSocLevel, nextPvLevel]; done false; end function level binPrice(p) if p 0.4 level 1; elseif p 1.0 level 2; else level 3; end end function level binPV(pv) if pv 5 level 1; elseif pv 15 level 2; else level 3; end end注意这里环境内部知道真实电价序列是什么智能体只知道当前的离散档位。这种信息不对称在问题建模中很关键算法不需要知道明天具体几块钱一度电它只需要知道大概处于哪个档位区间就能学习出带有鲁棒性的策略。3.2 状态编码函数Q 表本质上是一个二维矩阵行是状态编号列是动作编号。所以必须把 4 维状态向量映射成一个整数索引。在 Matlab 中用 sub2ind 就能实现function idx stateIndex(state) % 状态维度: [24, 3, 5, 3] idx sub2ind([24, 3, 5, 3], state(1), state(2), state(3), state(4)); end这段代码虽然简单却非常容易写出隐性 bug。我建议把状态维度的顺序固定下来写注释并且在测试阶段打印几个状态的索引确认编码没有重复或错位。3.3 训练主循环训练主循环是标准的 Q-learning 流程每个回合初始化状态按 ε-greedy 策略选动作与环境交互更新 Q 表重复直到回合结束。numActions 3; Q zeros(24*3*5*3, numActions); alpha 0.25; % 学习率 gamma 0.95; % 折扣因子 epsilon 0.8; % 初始探索率 epsilonMin 0.05; numEpisodes 1500; episodeRewards zeros(numEpisodes, 1); for ep 1:numEpisodes priceSeries generatePriceSeries(); pvSeries generatePVSeries(); initPriceLevel binPrice(priceSeries(1)); initPvLevel binPV(pvSeries(1)); state [1, initPriceLevel, 3, initPvLevel]; % 初始SOC设为60%档第3档 sIdx stateIndex(state); totalReward 0; for t 1:24 if rand() epsilon action randi(numActions); else [~, action] max(Q(sIdx, :)); end [nextState, reward, done] marketStep(state, action, priceSeries, pvSeries); totalReward totalReward reward; if done target reward; else nIdx stateIndex(nextState); target reward gamma * max(Q(nIdx, :)); end Q(sIdx, action) Q(sIdx, action) alpha * (target - Q(sIdx, action)); if done break; end state nextState; sIdx nIdx; end episodeRewards(ep) totalReward; epsilon max(epsilonMin, epsilon * 0.996); end这里有几个细节值得强调。初始 SOC 设为 60% 档是为了避免一开始就因为电量太低而无法放电导致训练前期大量空转。探索率 epsilon 从 0.8 开始每个回合乘以 0.996衰减到最后稳定在 0.05。这样前期能充分探索状态空间后期策略基本收敛智能体主要依靠已有经验做决策。电价和光伏序列的生成函数我在这里没有完整贴出来因为根据你的真实数据来替换反而更好。简单说generatePriceSeries 可以返回一个长度为 24 的数组例如凌晨到清晨是 0.3 元白天是 0.8 元傍晚是 1.5 元generatePVSeries 返回一条白天中午最高的曲线。你完全可以换成真实市场的历史数据只要把数据清洗成对应的档位即可。3.4 探索率与训练过程可视化训练结束后建议绘制两个图一是每个回合的总收益曲线二是 epsilon 的变化曲线。总收益曲线能让你直观判断收敛速度epsilon 曲线帮你确认衰减策略是否太快或太慢。如果发现第 500 个回合开始收益还在剧烈波动大概率是探索率衰减太慢。反过来如果前 100 个回合就基本不动了说明探索过早结束智能体可能卡在局部最优。我后面会专门讲参数调整规律。4. 仿真结果策略从 Q 表里“长”出来4.1 学到的东西 vs 传统峰谷策略我设计的基线策略是典型的“低谷充电、高峰放电、平时不动”专家规则。基线策略在仿真电价和光伏设定下的平均日收益大约是 68 元。Q-learning 训练 1500 个回合后把 epsilon 置为 0 再跑同一条电价曲线平均日收益大约是 84 到 88 元提升幅度约 20% 到 25%。策略平均日收益行为特征固定峰谷策略约 68 元只在低谷充电、高峰放电Q-learning 表格策略约 86 元学会平价时段提前囤电、峰段灵活释放差异表面上看是收益数字实际上是对电价动态规律的利用深度不同。固定规则把一天简单切成三块而 Q-learning 学到的是状态组合策略在某一个平价时段如果 SOC 较低且未来预计有峰价它可能选择充电但如果 SOC 已经在高位它会选择静止甚至提前卖出。这种“看状态再决定”的灵活度是手写规则很难覆盖的。4.2 α、γ、ε 三个参数的实验规律我在同一环境下分别改了学习率、折扣因子和探索率衰减速度做了几组对照。规律大概如下α 0.05 时收敛很慢1200 个回合后还在缓慢爬坡α 0.5 时前期收益曲线震荡幅度明显变大但收敛后最终结果差不多γ 0.9 时策略更“近视”经常为了眼前的峰谷价差过早放电导致后续机会流失γ 0.98 时整体收益略有提升但训练需要更多回合epsilon 衰减太快比如每回合乘以 0.98会锁定次优策略表现为最终收益比正常衰减低约 8%。建议实际操作中先用 α 0.25、γ 0.95、epsilon 从 0.8 按 0.996 衰减作为起点再根据训练曲线微调。强化学习调参数很讲手感没有绝对万能的一组参数但上述经验值在能源交易场景下普适性很好。4.3 从 Q 表提取策略表看一眼它到底怎么决策很多同学训练完 Q 表就停了这是很大的浪费。Q 表本身就是一张可读的策略表。你可以把每个状态的最优动作提取出来排成一个三列矩阵某时段、某电价档位、某 SOC 档位下应该充、放还是静止。按照这个策略表打印出来你会很清晰地看到低谷电价且电量不高时动作几乎全是充电高峰期且电量充足时动作几乎全是放电平时段的动作则更像一个经验函数跟 SOC 和光伏出力明显相关。这一步对写论文、做汇报特别有用因为它把“黑箱算法”变成了可解释的决策规则。5. 实操踩坑与真实部署前的功课5.1 最容易踩到的五个坑说几个我在真实项目里踩过、也看别人反复踩的坑按严重程度排序第一状态里漏掉时间维度。刚开始做实验的同学常觉得状态只需要电价和 SOC 就够了结果是算法学出一个永远在放电的策略根本没有跨时段规划能力。时间 t 是 Markov 状态里极其关键的一部分它提供了“距离一天结束还有多久”的语义信息。第二奖励函数只有电费差价、没有动作惩罚。这会导致策略过度抖动仿真里数字很好看实际设备受不了。加切换惩罚之后收益可能降低几个百分点但设备寿命和可执行性大幅提升。第三拿训练收益曲线当评估指标。训练过程中 ε 还在探索很多动作是随机选的所以收益偏低且波动大。评估时必须把 ε 设为 0用多条新的电价曲线重跑多轮取平均值。我见过有人拿着训练曲线就说算法收敛了结果落地时效果完全对不上。第四电价和光伏的随机性没控制好。如果每条训练曲线的随机种子不一致Q 表的更新会被噪声剧烈干扰。建议先固定随机种子跑通逻辑再逐步放开随机性测试泛化能力。第五忽略 SOC 的连续值与离散档位之间的边界效应。一个 SOC 45% 的状态被归到 40% 档下一轮动作后可能变成 48%两个档位之间来回横跳导致 Q 值学习失真。解决方式是细化分档或者用线性插值折损下一状态价值。5.2 从仿真到落地还差哪些模块仿真做完只代表你已经理解了 Q-learning 的运行逻辑距离真实部署还差几块重要拼图。首先是数据接入。训练时用的生成曲线在真实系统里要换成平台的电价 API、光伏气象预测和负荷预测而且数据必须同步到每个决策时刻。其次是安全约束。真实储能系统有温度、电压、最大充放电次数等限制需要加在环境函数之外作为硬约束。第三是策略更新方式。市场是时变的去年好用的电价规律今年不一定适用我建议部署时采用滑动窗口式在线训练每天用最近一个月的数据重新训练或微调 Q 表而不是让一套策略用到底。5.3 下一步值得试的扩展方向如果你已经跑通了本文这套基础框架建议往这三个方向扩展。第一动作空间连续化。把固定功率改成可调功率Q 表格就没法用了需要换成 DQN 或 DDPG 系列算法。第二加入负荷和需求响应。目前储能只是峰谷套利当你把可调负荷也放进决策空间问题会从单智能体变成家庭能量管理系统。第三多智能体竞价。当多个储能主体同时参与市场报价价格又反过来受他们行为影响时单智能体模型就不再适用可以用多智能体强化学习做博弈仿真这已经是目前能源市场研究里的前沿方向。个人经验是不要想一口吃成胖子。把本文这套单智能体 Q-learning 跑透把状态设计、奖励函数和参数调试的直觉建立起来后面再切换到更复杂的 DQN 或多智能体系统时你会比别人少走很多弯路。