如果你挖过数据中心节能优化的坑,大概率撞过这样一面墙:单独看电力、热力、算力任何一个维度,都有成熟的优化办法;可一旦把三者放进同一个优化问题里,它们就开始互相拆台。算力调度把任务挪到更空的服务器上,负载是均衡了,局部热点却冒了出来;冷却系统赶紧加大功率压制温度,电费又涨了回去。三个维度互为因果,牵一发动全身。我最近在Matlab里完整复现了一个基于DQN的数据中心电力-热力-算力三维协同智能调度项目,这文章把整个思考路径、建模细节和代码骨架整理出来,供读者参考。
这个课题适合两类人看。一类是做数据中心能源管理、机房运维优化的工程师,另一类是准备用强化学习做智能调度相关课题、需要从Matlab仿真入门的同学。它解决的核心问题可以概括成一句话:在IT负载、分时电价、室外温度都随时间动态波动的环境下,如何设计一个能在线实时决策的调度策略,把“算力任务怎么分配、服务器怎么调频、冷却设备怎么控制”三个动作联动起来,最终实现总成本更低、温度不越限、服务质量不下降。
下面按我自己做这个项目的顺序来讲。先讲清楚物理问题本身,再谈为什么选DQN,然后是状态动作奖励的设计,紧接着是Matlab实现的关键代码骨架,最后是训练阶段那些教科书里不会写的坑和调参经验。
1. 三维协同在协同什么:电力、热力、算力的物理耦合关系
1.1 三个维度各自的“任务清单”
先说电力侧。数据中心花的是真金白银的电费,总功耗大致由三块构成:IT设备功耗、冷却系统功耗、供电和散热辅助设备功耗。其中IT设备功耗主要由服务器的工作状态决定,冷却系统功耗则是为了维持服务器运行环境温度而付出的代价。
热力侧要关心的是温度约束和制冷成本。服务器计算时产热,热量如果不能及时带走,进风温度超过阈值就会触发降频甚至宕机。为了控制温度,冷却系统需要持续工作,产热越多、冷却负担越重、功耗越大。这里有个常被忽略的点:热问题是空间性的,某个机柜产生热点和机房整体平均温度达标并不矛盾。
算力侧则是调度手段的核心。任务进来之后,要决定分配给哪台服务器、每台服务器跑多少负载以及是否调高或者调低CPU频率档位。算力调度决定了IT设备功耗的大小和空间分布,继而决定了发热的空间分布,最终决定了冷却系统要用多少力气来压制热点。
1.2 耦合链路:为什么几个维度会“互相拆台”
这三者之间是一条直接的因果链:算力分配改变功耗分布,功耗分布改变热分布,热分布反过来决定冷却功耗,冷却功耗又回到电力总成本里。如果只盯着其中一段做优化,很容易在另外的维度上付出代价。
举一个我在仿真里验证过的例子。为了省电,把任务尽量集中到少数几台服务器上,其余服务器进入休眠。集中化的结果是那几台“主力机”CPU利用率长期跑高,出风口温度直接顶到阈值。冷却系统检测到热点之后局部加大送风,空调压缩机功耗上扬,省下的服务器功耗反而被冷却功耗吃掉了一大部分。
反过来也一样。有的策略追求温度绝对平稳,让所有服务器低频运行、冷却设备全速运转,这样温度确实好看,但IT响应延迟变大、冷却电费虚高,属于用算力性能和服务质量换热稳定。
这说明一个本质问题:电力、热力、算力不应该被拆开独立优化,它们是一个强耦合的联合决策问题。最优策略不是让某个维度最低,而是让三个维度协同后的综合代价最小。
1.3 数学意义上的“难”在哪
把这个问题转换成数学语言,就是一个带约束的序贯决策优化问题。状态量包括了负载、电价、温度等多个维度的动态变量,动作量包括了任务分配、频率调节、冷却控制等多个决策变量,约束包括了温度上下限、服务器算力上限、服务质量等硬性条件。
这个问题的难点有三个:一是高维状态空间让传统查表方法直接失效;二是环境是非线性和动态的,负载曲线、电价曲线、室外温度都是变化的,固定策略很难适应;三是决策需要在线实时给出,不可能在每次调度时做耗时的全局寻优。这些难点正是后面选择DQN的根本原因。
2. 为什么是DQN:传统优化方法做不到的实时在线决策
2.1 传统方法卡在哪几个地方
我最早尝试过模型预测控制(MPC)的思路,也考虑过混合整数线性规划。但折腾一圈下来发现,这些方案在数据中心调度场景里各有硬伤。
MPC的问题在于需要足够精确的热-电联合模型,而且冷却系统的动态响应、气流组织的非线性特性很难用一组线性方程描述。模型稍微失配,控制效果就明显退化,实际部署的时候可能要花大量时间做系统辨识。
数学规划方法,比如把问题建构成MILP,求解最优解在理论上是可行的,但计算开销太大。数据中心的调度周期往往要求秒级到分钟级,一台普通服务器要处理几十台设备的联合优化,常规求解器很难在限时内返回结果。哪怕用了简化松弛,工程实现也很笨重。
启发式算法比如遗传算法、粒子群,虽然不需要精确模型,但每次决策都需要跑一轮迭代寻优,而且自适应能力差。电价一变、负载一波动,就要重新调整规则参数,做出来的东西像一座随时可能塌的积木塔。
| 方案 | 是否需要精确模型 | 在线决策速度 | 高维状态处理 | 主要瓶颈 |
|---|---|---|---|---|
| 模型预测控制MPC | 需要 | 一般,依赖求解器 | 中 | 模型失配敏感,调参困难 |
| 数学规划MILP | 需要 | 慢 | 低 | 在线计算开销大 |
| 启发式算法 | 不需要严格模型 | 取决于迭代次数 | 中 | 自适应能力弱,反复调整 |
| DQN | 不需要 | 快,一次前向传播 | 高 | 需要离线训练,奖励设计敏感 |
2.2 DQN能在线决策的三块基石
DQN把Q-learning和深度学习结合起来,核心是用神经网络逼近Q值函数,解决高维状态空间下查表不可行的问题。它有三块基石,每一块都在数据中心调度场景里有明确意义。
第一是神经网络逼近器。状态向量可能包含几十个维度,用传统的Q表没法存,但一个两层全连接网络就能把它映射成各个动作的Q值估计,决策时只要一次前向传播就能选动作,速度完全满足在线调度需求。
第二是经验回放。数据中心运行数据是强时序相关的,直接拿相邻样本训练网络会让参数更新方差很大。经验回放把历史转移样本存进一个缓冲池,每次从中随机采样一个小批量来训练,打破样本之间的相关性,训练过程稳定很多。
第三是目标网络。训练过程中,如果用同一个网络同时计算目标Q值和当前Q值,目标本身会跟着参数一起漂移,容易振荡甚至发散。DQN维护一个参数滞后更新的目标网络,让训练目标在一段时间内保持固定,收敛性大幅改善。
2.3 为什么不是PPO、DDPG
经常有人问我,现在强化学习算法这么多,为什么不用PPO或者DDPG。这个问题要结合实际决策空间来回答。
DDPG、TD3这类算法面向连续动作空间,而数据中心调度里的动作,比如任务分配到哪台服务器、服务器调哪个频率档位、冷却设备开哪个档位,本质上是离散选择。虽然也可以把连续动作输出后映射到离散档位,但多一层映射就多一层误差,不如直接做离散动作来得干净。
PPO适合高维或者连续动作的复杂策略学习,但它的策略梯度类方法在训练时对奖励量纲和网络结构更敏感,调参成本高。对于我们这个问题的规模,DQN配合经验回放和目标网络已经足够,而且实现稳定、易于复现,升级到Double DQN也只需要改动几行代码。所以从工程稳妥性出发,DQN是更合理的起点。
3. 状态、动作、奖励怎么设计,DQN才有东西可学
3.1 状态设计:要让智能体“看得见”所有相关因素
很多新手第一步就栽在状态设计上,把能采集到的数据一股脑全塞进去。实际上状态设计的核心原则是:每个变量都必须与决策目标有清晰关联,且最好归一化到相近的量纲范围。
我做这个项目时使用的状态向量大致由三部分构成。算力侧包括当前IT总负载、各服务器CPU利用率、任务队列长度;电力侧包括当前分时电价、各服务器实时功耗、整站总功耗;热力侧包括各机柜进风温度、出风温度、室外温度。每一项都做了归一化处理,比如电价除以当日最高价、温度除以允许温度上限。
这里要特别强调归一化的重要性。第一次跑的时候我把电价直接以“元/度”的单位塞进状态,服务器利用率却是0到1的小数,两者数值范围差了一个数量级。结果DQN网络前两个回合梯度基本被电价维度带着走,利用率维度完全学不到东西,损失曲线奇形怪状。归一化之后,训练稳定性和收敛速度立刻改善了一大截。
3.2 动作设计:控制粒度与动作空间爆炸的取舍
动作空间直接决定学习难度。这个问题的动作有三块:任务分配、服务器频率档位、冷却设备档位。如果完全自由组合,动作数就是各维度档位数量的乘积。假设有10台服务器、每台3个频率档位、冷却设备3个档位,动作总数已经是10乘以3再乘以3等于90种,再想加入更细的分配粒度,动作空间很容易破百甚至上千。DQN输出层每增加一个神经元,探索难度都会显著上升。
我采用的折中方案是合理降维。首先是任务分配,不用“每台服务器分配多少负载”这种逐台细粒度表示,而是把任务按比例在活跃服务器分组之间分配,每个动作代表一种分配模板。这样动作含义是“把负载按模板A或模板B分配”,而不是直接输出每台服务器的负载数值。其次是频率档位,让同组服务器共享一个档位选择,把10台服务器的独立频率决策压缩成2到3个组的联合决策。最后是冷却档位,离散成低、中、高三档或者更细的五档。
这样设计之后,动作总数被压到了20到30之间,DQN探索压力小了很多,收敛速度明显加快。方向是对的:动作设计追求的是“足够表达的决策空间”,而不是“所有可能细节的自由组合”。
3.3 奖励函数:把省电、不超温、不违约翻译成一个数
奖励函数是强化学习里最讲究手艺的部分。我在这个项目里把奖励设计成了四部分的加权组合,整体形式是:
奖励 = 负的(电力成本权重 × 当前总功耗 + 冷却功耗权重 × 0.5 + SLA违约惩罚权重 × 违约标志 + 温度越限惩罚权重 × 越限幅度)
电力成本是主目标,权重设为1.0左右;冷却功耗虽然本质上也属于电力,但单独拆出来乘以0.3到0.5的权重,可以让智能体更明确地感知到“降低IT负载后冷却负担也会下降”的间接联系。SLA违约意味着任务延迟超标,直接给一个比较大的惩罚项,权重可以设到20到50,约束服务质量底线。温度越限是硬约束,越限幅度越大惩罚越重,权重甚至可以达到100以上,确保智能体优先避开危险温度区域。
这里有个容易踩的坑:惩罚项不是越大越好。我最初把温度越限惩罚设成1000,结果温度稍微偏高一点,总奖励就被压成了巨大的负数,其他所有维度的信号都被淹没了,网络几乎学不到任何有效信息,只会一味降低所有服务器频率来避免温度上升,连正常的负载都完不成。后来把温度惩罚降到200,SLA惩罚保持30左右,各维度信号才能重新平衡。
另外要注意奖励的稀疏性。如果只在一天结束时给一次总奖励,中间过程没有任何反馈,DQN几乎不可能学到有效策略。所以每一步都要根据当前状态计算即时奖励,让智能体每一步都知道自己动作的好坏。
3.4 约束处理:用惩罚而不是用硬限幅
数据中心调度里有几个硬约束,比如温度上限、负载能力上限。处理这些约束的方式有两种:一是动作输出后直接限幅,保证不越界;二是在奖励里给越限行为大惩罚。
实际经验是两者结合。比如负载分配动作就应当在输出层加softmax保证比例合理,这才是物理可执行的动作。但温度这类由环境动态决定的指标,无法通过限幅动作来保证不越限,只能依靠奖励惩罚来引导智能体学会主动规避。这种“动作限幅保证可行性、奖励惩罚引导安全性”的搭配,工程上最稳妥。
4. Matlab仿真环境和DQN代码骨架:可以直接改着用的实现
4.1 文件结构与整体架构
整个Matlab工程我分成了三块:环境类、DQN智能体、训练脚本。环境类负责模拟数据中心的运行逻辑,包括负载进入、功耗计算、温度更新、冷却控制,它的对外接口就是强化学习标准的step和reset;DQN智能体负责网络定义、经验回放、epsilon贪心选动作、目标网络更新;训练脚本把两者串起来,跑回合循环并记录训练指标。
文件清单大致是:
DataCenterEnv.m:数据中心环境类,包含服务器参数、功耗模型、温度模型DqnAgent.m:DQN智能体类,包含网络、经验池、训练方法runTraining.m:训练主脚本,配置超参数并启动训练plotResults.m:结果可视化脚本,画奖励曲线、功耗曲线、温度曲线
4.2 环境类的step函数怎么实现
环境类是仿真器的心脏。属性部分定义服务器数量、CPU利用率、服务器功耗、出风温度、IT负载、电价等关键状态变量。step函数的输入是当前状态和动作,输出是下一状态、奖励和是否终止。
下面是环境类的核心骨架,可以直接在Matlab里创建文件使用:
classdef DataCenterEnv < handle properties nServer = 10; % 服务器数量 cpuUtil = zeros(1,10); % 各服务器CPU利用率 serverPower = zeros(1,10); % 各服务器功耗 outletTemp = zeros(1,10); % 各服务器出风口温度 itLoad = 0.6; % 当前IT负载,范围0~1 price = 0.5; % 当前电价 outdoorTemp = 20; % 室外温度 t = 0; % 当前时刻 end methods function obs = getObservation(this) obs = [this.itLoad, this.price, this.outdoorTemp, ... this.cpuUtil, this.outletTemp]; end function [nextObs, reward, done] = step(this, action) % action为离散索引,解码为任务分配、频率档位、冷却档位 [allocRatio, freqLevel, coolLevel] = decodeAction(action); % 更新CPU利用率:按分配比例增加负载 for i = 1:this.nServer this.cpuUtil(i) = min(1.0, this.cpuUtil(i) + allocRatio(i) * this.itLoad); % 频率档位影响利用率上限 this.cpuUtil(i) = min(this.cpuUtil(i), freqLevel(i) * 0.5); end % 功耗模型:线性模型 P = P_idle + (P_max - P_idle) * util P_idle = 100; % 空闲功耗 P_max = 300; % 满载功耗 itPower = sum(P_idle + (P_max - P_idle) .* this.cpuUtil); % 热模型:一阶热平衡,温度增量正比于产热,反比于冷却 heat = sum(this.cpuUtil) * 10; coolPower = coolLevel * 50; this.outletTemp = this.outletTemp + 0.05 * (heat - coolPower * 0.8); this.outletTemp = min(this.outletTemp, 90); % 温度上限保护 % 奖励:总成本 + SLA惩罚 + 温度越限惩罚 totalPower = itPower + coolPower; slaPenalty = 0; if this.itLoad > 0.8 && mean(this.cpuUtil) < 0.5 slaPenalty = 30; end tempPenalty = 200 * sum(max(0, this.outletTemp - 85)); reward = -(1.0 * totalPower * 0.01 + 0.3 * coolPower * 0.01 + ... slaPenalty + tempPenalty); % 推进时间,一天结束则done this.t = this.t + 1; done = (this.t >= 288); % 按5分钟一个步长模拟一天 nextObs = this.getObservation(); end end end这个代码里要注意的细节是动作解码逻辑decodeAction要单独实现。我建议把它写成一个独立的函数,输入动作索引,输出分配比例向量、频率档位向量、冷却档位标量,这样环境类逻辑干净,后续改动作空间也容易。
功耗模型用了最常见的线性模型,即服务器功耗对CPU利用率近似线性,从空闲功耗平滑过渡到满载功耗。热模型则简化成一阶热平衡,产热正比于总利用率,冷却正比于冷却档位,这种做法在早期仿真验证阶段足够用,而且物理趋势正确。
4.3 DQN网络与经验回放的核心代码
DQN智能体的核心分三块:网络定义、经验回放、目标网络更新。网络我用了一个两层全连接结构,输入层维度等于状态向量长度,中间层64个神经元,输出层维度等于动作数量。虽然网络结构简单,但对这个规模的问题已经够用,也更稳定。
% 状态向量长度和动作数量 stateSize = 2 + 3 + 2 * 10; % itLoad, price, outdoorTemp + cpuUtil + outletTemp actionSize = 24; % 构建DQN网络 lgraph = layerGraph([ featureInputLayer(stateSize, 'Normalization', 'none', 'Name', 'state') fullyConnectedLayer(64, 'Name', 'fc1') reluLayer('Name', 'relu1') fullyConnectedLayer(64, 'Name', 'fc2') reluLayer('Name', 'relu2') fullyConnectedLayer(actionSize, 'Name', 'fc3') regressionLayer('Name', 'output') ]); % 初始化两个网络:在线网络和目标网络 onlineNet = dlnetwork(lgraph); targetNet = dlnetwork(lgraph); targetNet = syncNetwork(targetNet, onlineNet); % 复制当前参数经验回放池我用了一个简单的循环队列来存储转移样本,每个样本包含当前状态、动作、奖励、下一状态、是否终止。训练时每次随机采样一个小批量,用在线网络计算当前Q值,用目标网络计算目标Q值,再通过梯度下降更新在线网络参数。
function updateOnlineNetwork(this, batch) % 从经验池抽取batch states = cat(1, batch.state); actions = cat(1, batch.action); rewards = cat(1, batch.reward); nextStates = cat(1, batch.nextState); dones = cat(1, batch.done); % 计算当前Q值 qValues = predict(this.onlineNet, states); qValues = qValues(sub2ind(size(qValues), 1:length(actions), actions')); % 计算目标Q值:r + gamma * max(Q_target(s')) nextQ = predict(this.targetNet, nextStates); targetQ = rewards + 0.95 * max(nextQ, [], 2) .* (1 - dones); % 梯度下降更新在线网络 loss = mse(qValues, targetQ); [grad, ~] = dlgradient(loss, this.onlineNet.Learnables); this.optimizer = adamupdate(this.optimizer, this.onlineNet.Learnables, grad); end这里有一个Matlab实现中容易出错的地方:dlgradient需要loss是关于网络可学习参数的显式函数,所以更推荐把前向传播写成一个独立函数,确保误差逆推链路清晰。上面的代码是简化示意,实际工程里我会把前向计算封装成modelLoss函数,传入网络参数和数据,返回loss和梯度。
4.4 训练主循环:epsilon贪心与目标网络同步
训练主循环的逻辑和所有DQN实现一致:观察状态、epsilon贪心选动作、执行动作获得奖励和下一状态、存入经验池、定期采样训练、定期同步目标网络。
% 训练参数 numEpisodes = 200; maxSteps = 288; epsilon = 0.9; epsilonDecay = 0.995; epsilonMin = 0.05; batchSize = 64; targetSyncFreq = 200; env = DataCenterEnv(); agent = DqnAgent(); for ep = 1:numEpisodes obs = env.reset(); totalReward = 0; for step = 1:maxSteps % epsilon贪心 if rand() < epsilon action = randi(actionSize); else qValues = predict(agent.onlineNet, obs); [~, action] = max(qValues); end [nextObs, reward, done] = env.step(action); agent.storeTransition(obs, action, reward, nextObs, done); totalReward = totalReward + reward; obs = nextObs; % 每隔一定步数训练一次 if mod(step, 4) == 0 && agent.bufferSize >= batchSize batch = agent.sampleBatch(batchSize); agent.updateOnlineNetwork(batch); end % 定期同步目标网络 if mod(step, targetSyncFreq) == 0 agent.targetNet = syncNetwork(agent.targetNet, agent.onlineNet); end if done break; end end % 每回合结束衰减epsilon epsilon = max(epsilonMin, epsilon * epsilonDecay); fprintf('Episode %d, totalReward %.2f, epsilon %.2f\n', ... ep, totalReward, epsilon); end这个循环本身不复杂,但有个关键细节:epsilon衰减的节奏直接影响训练质量。我一开始设置了0.99的衰减系数,结果前50回合epsilon还居高不下,智能体一直在做随机动作,训练进展缓慢。后来把衰减系数调成0.995左右,并且设置了0.05的下限,探索和利用的平衡才比较合理。
如果不想手写这些底层逻辑,Matlab的Reinforcement Learning Toolbox里提供了rlDQNAgent和rlTrainingOptions,可以直接配置好环境和网络后一键训练。但我个人建议至少手写一遍主干循环,否则对经验回放和目标网络的理解只能停在“会用API”的层面,出了问题很难排查。
5. 训练稳定性和收敛速度:我踩过的坑与调参顺序
5.1 损失不下降先查奖励设计,而不是换网络结构
训练过程中最让人抓狂的现象是:损失函数震荡或者干脆不下降,奖励曲线一路走低。我最初遇到这个问题时,第一反应是加深网络、调学习率,折腾了快两天,一点用没有。
后来把每个维度的奖励拆开单独看,才发现了问题所在。当时SLA惩罚的权重设得太高,只要负载稍高就开始罚,导致智能体发现“降低服务器利用率”才是躲避惩罚最稳妥的办法,于是学会了一个极其保守的策略:总是把负载压得很低,所有服务器都在摸鱼,温度当然不超,SLA却永远在违规边缘徘徊,总奖励比随机策略还差。
调试教训很明确:奖励曲线不上升,第一步要检查的不是网络容量,而是奖励设计是不是“说出了我们真正想要的目标”。把奖励拆成逐维度记录,每个维度的惩罚贡献一目了然,比在损失函数上猜测更高效。
5.2 经验池大小和批量采样数量怎么配
经验回放池的大小直接影响训练稳定性。池子太小,样本多样性不足,网络容易来回震荡;池子太大,早期经验占的比例过高,后期有用的新经验反而被稀释。
我试过的组合里,经验池大小在5000到10000之间比较合适,批量大小64到128。训练回合多了以后,还要注意定期清空或者缩小早期样本的影响。实际做法是设置一个上限,超过上限就把最老的样本挤出去,可以用循环队列实现。
批量大小我建议先从64开始。批量太小梯度噪声大,批量太大则每次更新计算量大、训练慢。不要盲目追求大batch,这个规模的问题64个样本足够提供稳定梯度了,我试过用256的batch反而没有明显改善,还拖慢了单步更新速度。
5.3 目标网络更新频率和学习率的配合
目标网络更新频率和学习率是互相牵连的两个参数。更新太频繁,目标网络跟在线网络几乎同步,DQN退化成“自己追自己”,训练方差大;更新太慢,目标Q值长期不变,策略改进会变滞后。参考范围是每200到1000步同步一次。
学习率我建议设在1e-3到1e-4之间。1e-3跑得快,但后期容易出现Q值振荡;1e-4稳定,但前期收敛慢。折中方案是先用1e-3跑前2000步,等奖励曲线开始抬头,再降到3e-4左右细调,这样整体收敛速度会比较理想。
这里还有个经典问题:过估计。DQN在计算目标Q值时用的是max操作,天然偏向高估乐观的Q值,在数据中心这类奖励函数带多个惩罚项的场景里,过估计会让策略变得过分冒险或者过分保守。解决思路很简单:把网络结构改成Double DQN,用在线网络选动作、目标网络给Q值,训练稳定性会有明显改善。改动量很小,推荐在基础版跑通后立刻升级。
5.4 对照实验和结果评价的基本思路
训练完成之后,要验证策略到底有没有比传统方法好,不能用“看起来奖励曲线上升了”这种话来说服自己。我习惯建立三组对照:固定阈值策略、贪心策略、DQN策略。
固定阈值策略就是“利用率超过80%则把任务挪到利用率最低的服务器”,这是运维里常见的经验规则;贪心策略是每一步都选择“当前状态下让总功耗最小”的动作;DQN策略是训练收敛后的智能体。在相同的负载曲线、电价曲线和温度条件下跑一段模拟,比较累计电力成本、温度越限次数、SLA违约次数三个指标。
从我复现这个项目的经验看,DQN相对于固定阈值策略,在总成本上通常能降10%到20%,这主要来自综合优化而不是单点压榨:它会主动在电价低谷时段把任务集中处理,在电价高峰时段降低冷却档位以牺牲微小温度裕量换取电费下降,策略的行为模式明显比固定阈值灵活。这个改善幅度依赖具体参数设置,但趋势是一致的。读者自己复现的时候,判断标准应该是三个指标至少有两个比对照组明显更优,而不是单纯追求某个指标降到最低。
训练过程的指标记录也很重要。我每50步记录一次当前累计奖励值,画出滑动平均曲线,同时记录服务器平均利用率、温度越限次数、冷却功耗占比。这样训练结束后不仅能看奖励收敛情况,还能从物理量层面确认智能体到底学会了什么行为模式,避免“奖励高但物理行为不合理”的假收敛。
最后说一个我做这个项目最大的体会。整个课题周期里,我在环境建模和奖励设计上花的时间远多于跑DQN本身的时间。很多入门者一上来就盯着网络结构和超参数来回调,却忽略了智能体看到的那个“数字世界”是否合理。如果奖励函数没有把我们的工程诉求准确翻译成数值信号,换再复杂的网络结构也是白搭。建议复现这个项目的读者,先花两三天把电力模型、热模型、任务模型在Matlab里跑扎实,再用DQN进场,后面会发现训练阶段反而顺很多。