MATLAB实现自适应动态规划:HDP/DHP/ADHDP/ADDHP算法解析与应用

发布时间:2026/9/14 5:30:47
MATLAB实现自适应动态规划:HDP/DHP/ADHDP/ADDHP算法解析与应用 简介自适应动态规划ADP系列MATLAB实现覆盖HDP、DHP、ADHDP、ADDHP四种变体适合研究控制优化、需要在不确定性与时变环境中进行决策的工程师和算法学习者。压缩包共5个文件包含4个.m源码文件与1个PDF理论文档整体约162KB体量精简。源码分别对应混合动态规划、离散动态规划及其自适应扩展版本便于对照差异PDF为Landelius的学术论文可辅助理解算法背后的数学基础与典型应用场景。已有482人学习下载。通过运行和调试代码能够直观掌握ADP的迭代更新与策略改进机制并快速迁移到控制系统设计或仿真项目中若在C#工程中使用也可借助MATLAB工具部署为计算模块拓宽应用方式。资源结构清晰适合从理论到编码快速上手。1. 从贝尔曼方程到ADPHDP/DHP/ADHDP/ADDHP到底解决了什么拿到一个同时包含 HDP、DHP、ADHDP、ADDHP 四个算法的 MATLAB 压缩包如果只想跑通 demo半小时就够了但要真拿去做控制系统优化得先把一件事想明白ADP 不是在算一张价值表而是用神经网络去逼近动态规划里的价值函数和策略。这套代码的价值在于它把贝尔曼方程拆成了评价网络、动作网络、模型网络的迭代更新让我能从离散状态空间的表格遍历平滑过渡到连续状态空间的实时决策。对电力调度、机械臂轨迹跟踪、通信资源分配这类场景HDP 和 DHP 正好覆盖了价值迭代和基于梯度的策略优化两条典型路线ADHDP 与 ADDHP 又为动作依赖型问题提供了 Q 函数式解法。适合对动态规划有基础、想快速复现一种 ADP 变体并对比其收敛速度的工程师即使你是做 C# 上位机的也可以把编译后的 MATLAB 组件集成进去做在线决策内核。2. 三种网络结构与MATLAB实现模型网络、评价网络、动作网络怎么搭打开任意一个.m文件你会发现 MATLAB 实现 ADP 的方式非常有规律绝大多数代码被拆成主循环、评价网络、动作网络三个相对独立的部分模型网络有时独立成函数有时直接用系统的状态转移矩阵代替。理解这三者各自负责什么比看懂任何一行具体代码都重要因为四个变体之间的差异本质上只是评价网络输出和目标构造方式不同控制流结构几乎是同一套。2.1 为什么ADP需要三个网络而不是一张价值表经典动态规划里价值表的下标就是离散状态更新公式也是查表式地遍历。一旦状态变成连续量比如四旋翼的偏航角、机械臂关节的力矩表的规模会立刻爆炸。ADP 的做法是用一个带 sigmoid 或 tanh 激活的单隐层网络去近似价值函数这样状态输入是连续的输出是标量HDP或与状态同维度的向量DHP。动作网络负责根据当前状态给出控制量评价网络负责给这条控制路径打分模型网络则描述状态如何演变可以是已知转移矩阵也可以是从数据里辨识出来的参数化模型。三个网络各司其职更新链条才能闭合。Landelius.pdf里对这套结构的数学收敛性做了完整推导读第二章能避免很多权重初始化阶段的玄学问题。2.2 模型网络用已知系统矩阵还是在线辨识这个压缩包里多数 demo 用的是已知模型也就是在仿真的每一步用同一个状态转移函数推动系统前进这不代表模型网络没用。工程上模型不精确或者根本没有模型的情况更常见。一种容易落地的做法是把系统动态封装成函数句柄这样无论推导出的是线性矩阵A、B还是非线性函数主循环代码都不需要改。% 把系统模型封装成函数句柄方便在已知/辨识模型之间切换 sys (x, u) nonlinear_dynamics(x, u); % 非线性动态返回x(t1) x_next sys(x, u); % 主循环中推进一步 % 如果只有线性模型可以写成 % sys (x, u) A * x B * u;上面代码里nonlinear_dynamics需要你自己定义返回值必须和x同维度。用函数句柄的好处是主循环不需要关心模型是解析推导还是神经网络辨识出来的只要输出接口对齐模型部分可以被任意替换。我在做倒立摆控制时先替换成从 Simulink 导出的线性化矩阵验证算法再换成非线性模型调试时问题边界非常清晰。参数说明x代表状态向量u是动作向量A、B是线性化系数矩阵切换模型类型时保持x_next维度不变否则评价网络输出维度也要同步修改。2.3 评价网络的反向传播结构与权值更新2.3.1 权值的两种传递方式全局变量和函数句柄这个压缩包里大概率能看到global w_critic这类写法或者通过结构体critic.W传入传出。全局变量在 MATLAB 老代码里非常常见但我建议复用时改成结构体传参因为全局变量在多实验对照时很容易串尤其在addpath多个目录时会出现同名变量互相覆盖的诡异问题。% 推荐用结构体保存评价网络组件 critic.W1 rand(hidden_size, n_state) * 0.1 - 0.05; critic.W2 rand(1, hidden_size) * 0.1 - 0.05; critic.b1 zeros(hidden_size, 1); critic.b2 0; % 前向计算 h1 tanh(critic.W1 * x critic.b1); J critic.W2 * h1 critic.b2;critic.W1是从状态到隐藏层的权值critic.W2是隐藏层到评价输出的权值b1、b2是偏置项。初始化范围取正负 0.05 这类小量是为了避免 tanh 把梯度压死在饱和区如果直接使用rand不做缩放前几步更新会非常不稳定表现为评价输出一直停留在零附近。hidden_size的选取跟状态维度相关一般取状态维度的两到三倍即可过大会让单步更新噪声变大。2.3.2 在线更新与批量更新的取舍ADP 大多数应用是边采样边更新也就是每得到一个(x, u, r, x_next)四元组就立即计算一次误差并更新权值。这种方式响应快适合在线控制任务但误差曲线波动明显。如果是在数据集上离线训练则应该攒够一个 batch 再统一更新此时误差曲线平滑代价是要多维护一个环形缓存。压缩包里的 demo 基本都是前者直接修改成批量模式时需要增加一个buffer结构否则每次都覆盖旧样本经验回放就失去意义。% 在线更新每步立即反向传播 error target - J; critic.W2 critic.W2 lr_c * error * h1; critic.W1 critic.W1 lr_c * error * (critic.W2 .* (1 - h1.^2)) * x;上面是典型的单步梯度下降写法。lr_c是评价网络学习率一般取 0.01 到 0.05 之间1 - h1.^2是 tanh 函数的导数它对隐藏层激活值逐元素计算critic.W2把评价标量误差往隐藏层反向扩散。这里的维度关系是状态维度一旦变化critic.W1的列数必须跟着变否则矩阵乘法直接报维度不匹配。批量更新时只需把多步的error向量化把x换成矩阵即可但要注意隐藏层激活矩阵的形状和单样本写法相反。2.4 三个网络输入输出速查表网络角色输入输出更新依据模型网络当前状态 x、动作 u下一时刻状态 x_next系统辨识误差 x_next - f(x,u)评价网络当前状态 x或含动作 u价值标量 J 或协状态 lambdaHDPrgamma*J(x_next)-J(x)DHPlambda 误差动作网络当前状态 x控制量 u使评价输出最小化的梯度方向对 ADHDP 和 ADDHP评价网络的输入会额外拼接动作量 u对应表格中的“含动作 u”一列。这样设计的原因是只有把动作纳入评价输入Q 函数才能回答“当前状态用哪个动作最优”而状态价值 J 只能评估状态好坏无法直接对比动作。3. HDP.m与DHP.m的迭代差异状态价值路线与协状态路线先纠正一个命名误区DHP 在全称上常被写成 Discrete Heuristic Programming但在 Landelius 的博士论文和多数 ADP 文献里DHP 的标准全称是 Dual Heuristic Programming对应的中文叫对偶启发式规划和“离散”无关。HDP 则是 Heuristic Dynamic Programming。两者共享同一个主循环框架区别在于评价网络输出的对象从价值标量变成了价值梯度这一步改动带来了收敛速度的质变。3.1 HDP.m核心状态价值估计与TD误差构造HDP 是最容易上手的变体评价网络输出状态价值J(x)更新目标是经典的 TD 形式。主循环先由动作网络给出控制量推进到下一状态再通过评价网络得到J_next然后构造误差。代码结构与第 2 章给出的示例一致核心差异在目标值的构造方式。% HDP主循环价值目标构造与评价网更新 u policy(x); % 动作网络给出控制 x_next sys(x, u); % 模型网络推进一行 J_now critic_eval(x); % 当前状态的价值 J_next critic_eval(x_next); % 下一状态的价值 reward cost_fun(x, u); % 一步代价通常取负奖励 target reward gamma * J_next; % HDP的TD目标 e_c target - J_now; % 评价误差 critic_update(e_c); % 单步更新评价网gamma是折扣因子HDP 代码里通常取 0.95 到 0.99它控制未来代价在目标中的权重。reward一般写成负的代价函数目的是让 HDP 在最小化累计代价时恰好等价于最大化累计奖励。critic_update完成的是第 2 章写过的反向传播。注意target的计算依赖x_next所以模型网络的前向传播必须先于评价网络的目标构造执行如果顺序写反误差会一直震荡不收敛。3.2 DHP.m的协状态输出为什么梯度值比价值更好使DHP 和 HDP 表面差别只在评价网络的输出维度但更新公式差了一个层级。DHP 的输出是协状态lambda dJ/dx也就是价值对状态的梯度。目标更新不再是标量差分而是两个协状态之间的递推关系。这要求系统模型可微且雅可比矩阵必须正确这也是 DHP 实现比 HDP 更容易出 Bug 的根因。% DHP核心构造协状态目标并回归 lambda_now critic_eval(x); % 当前协状态 lambda_next critic_eval(x_next); % 下一协状态 % 目标由系统雅可比与价值梯度链式导出 target_lambda d_cost_dx(x, u) gamma * d_sys_dx(x, u) * lambda_next; e_c target_lambda - lambda_now; critic_update(e_c); % 按向量误差更新这里d_cost_dx是代价函数对状态的梯度d_sys_dx是系统动态对状态的雅可比矩阵。如果模型是线性化后的A矩阵d_sys_dx直接取A即可。相比 HDPDHP 多了一阶导数信息收敛速度更快但代价是模型梯度必须是解析的或数值上稳定否则误差会在导数放大下变得很吵。gamma的取值在 DHP 里也需要相应调大否则目标中当前代价占比过重协状态的长期信息会被逐步稀释。3.3 同一套主循环下两种算法的对接位置差异点HDP.mDHP.m评价网络输出标量 J向量 lambda目标构造reward gamma*J_nextd_cost_dx gamma*A*lambda_next动作网络反传先算 dJ/du 再回传经协状态和雅可比直接回传收敛速度慢但稳快但易震荡这个对照规律意味着把HDP.m改成 DHP 时不需要重写整个框架只需复制一份主循环替换评价网络前向函数与target构造函数剩下的模型推进、回合终止、reward 计算逻辑几乎可以原样保留。实际项目里如果系统动态能够求导我一般先跑通 HDP 确认逻辑再切到 DHP 提速避免一开始就在协状态维度上排查基础错误。3.4 怎么判断算法真的收敛了跑通 demo 后把每一步的评价误差e_c画出来正常情况下误差会在前几百步剧烈震荡后呈衰减趋势最终维持在小范围内波动。如果误差完全不降先检查 reward 符号是否与目标一致再检查 gamma 是否过大最后检查模型网络返回的x_next是否发散。DHP 还要额外看雅可比矩阵的规模如果范数长期大于 10梯度目标会把协状态推得非常大几乎必然导致 NaN。注意误差曲线不能只看最后的绝对值要同时观察是否出现周期性的尖峰那通常意味着动作网络和评价网络的更新频率不匹配。4. ADHDP.m与ADDHP.m的自适应机制Q函数求解与梯度级联ADHDP 和 ADDHP 里的 A 常被误解为“在线学习”但它们强调的是策略在运行过程中持续调整而不是训练一次就冻结。HDP/DHP 可以固定模型离线训练ADHDP/ADDHP 则更多用于模型不完全已知时边获得新数据边更新评价和动作网络。因此这两个变体对超参数和权值初始化更敏感跑出来的波动也更大。4.1 为什么Q函数会改变评价网络的输入结构ADHDP 对应 Q 函数版本。状态价值 J 只能评估状态好坏无法直接对比动作Q 价值把动作作为评价网络输入的一部分于是评价网络可以直接回答“当前状态采取这个动作能获得多少长期回报”。代价是评价网络的输入维度变成n_state n_action隐藏层规模也要相应增大否则 Q 曲面的拟合精度不够动作网络很容易收敛到局部次优点。4.2 ADHDP.m动作价值目标构造的实现% ADHDPQ函数作为评价网络的输出 u policy(x); x_next sys(x, u); Q_now critic_eval([x; u]); % Q(s,a) Q_next critic_eval([x_next; policy(x_next)]); % 下一状态下取最优动作的Q target_q reward gamma * Q_next; % 贝尔曼最优性方程 e_c target_q - Q_now;评价网络的输入从单独的 x 变成[x; u]的拼接向量输出仍是标量 Q。动作网络的更新目标是让动作在评价网络里对应的 Q 值最大这会对动作网络施加一个负梯度方向的更新。训练时动作网络和评价网络必须交替更新先固定动作网络采样再固定评价网络更新 Q 函数接下来再固定 Q 更新动作。如果两边学习率一样大整个系统很容易振荡这也是 ADHDP 调参中最常见的失控模式。4.3 ADDHP.m协状态与动作梯度的一体化链式更新ADDHP 是四个变体里最绕的一个。它保留 DHP 的协状态输出又引入 Q 函数的动作维度。评价网络输出的是dQ/dx和dQ/du的组合动作网络的更新直接依赖这些梯度。链条大致是动作网络给出 u评价网络给出对状态的梯度模型网络给出状态转移的雅可比三者相乘得到动作部分的改进方向。% ADDHP梯度回传路径 [lam_x, ~] critic_eval(x, u); % 评价网输出对状态的偏导 f_x model_jacobian_x(x, u); % 模型对状态求导 f_u model_jacobian_u(x, u); % 模型对动作求导 % 动作网络梯度近似由 f_u^T * lam_x 得到 grad_u f_u * lam_x;model_jacobian_x和model_jacobian_u分别是对状态和对动作求雅可比不同系统差别很大必须手动确认结果。ADDHP 某些实现里还会对动作网络多套一层链式法则导致公式变长但本质没有变。调试时如果动作不收敛优先打印grad_u的范数看是否出现量级漂移量级在训练过程中持续上涨通常是评价网络没有收敛带动了动作梯度发散。4.4 调参表与稳定性边界参数含义HDPDHPADHDPADDHPlr_c评价网络学习率0.01-0.030.005-0.020.01-0.030.005-0.02lr_a动作网络学习率0.005-0.010.005-0.010.001-0.0050.001-0.005gamma折扣因子0.950.95-0.990.9-0.950.95hidden隐藏层单元数6-108-1210-2012-20epochs训练步数2000200050005000上表是我用倒立摆和线性二次型基准测试时常用的起始值。ADHDP/ADDHP 因为动作参与评价输入需要更大的 hidden 规模保证 Q 曲面的拟合能力这是它们训练更慢的原因之一。如果某个变体发散优先调小 lr_c 和 lr_a而不是调整网络宽度因为隐层宽度对稳定性的影响远小于学习率。4.5 最常见的三个坑初始化、归一化和NaN四份代码最常踩的坑有三个。第一权重初始化范围过大tanh 激活函数直接饱和梯度消失导致评价网络输出恒为零误差曲线看起来是一条水平线实际是网络根本没有学习。第二状态和动作没有归一化当状态量级从 0.1 到 1000 变化时雅可比矩阵元素量级差异会把梯度更新带偏表现为前几步误差正常随后突然爆炸。第三NaN 经常发生在 DHP/ADDHP 的雅可比计算里尤其是状态穿越零点时出现除零或奇异矩阵这时要在模型网络出口加isnan检查打印出是哪一步产生的才能区分是模型发散还是评价网络发散。提示排查 NaN 时不要只看误差曲线要在x_next计算之后立刻执行any(~isfinite(x_next))把异常定位到模型网络还是评价网络。5. MATLAB实例封装为C#可调用模块的验证捷径如果要把这份 ADP 代码嵌入 C# 上位机比较常见的做法是用 MATLAB Compiler SDK 生成 .NET 程序集把 MATLAB 的数值计算能力封装成 C# 可调用的类。这个方案适合控制算法已经验证完毕、只差部署的场景不适合把整个训练过程都搬到 C# 里因为跨语言重复造轮子的成本远超收益。5.1 编译成.NET组件的命令与文件要求在 MATLAB 命令窗口执行deploytool选择 .NET Assembly 目标把主函数和依赖的.m文件全部添加进去再设置输出类名。注意主函数里不要用global传递权值编译后全局变量不会保留跨调用状态要么把参数全部通过函数入参传递要么把权值序列化成文件在 C# 侧管理。% 在MATLAB命令窗口执行 deploytool -build adp_solver.prj % 或者直接用命令行编译 compiler.build.dotNETAssembly(adpHDP.m, OutputDir, build);adp_solver.prj是部署项目文件首次创建后建议纳入版本管理。入口函数要声明为function u PolicyEvaluate(x)而不是脚本因为编译后的组件需要有明确的函数签名可供 C# 反射调用。编译成功后生成目录里会包含.dll文件和对应的setup.exe后者用于目标机器安装 MATLAB Runtime。5.2 C#侧的MWArray参数构造与调用using MathWorks.MATLAB.NET.Arrays; using MathWorks.MATLAB.NET.Utility; var solver new ADPSolver(); // 类名由编译时定义 double[] state { 0.1, 0.2, -0.3 }; MWArray x new MWNumericArray(state); MWArray u solver.PolicyEvaluate(x); // 入口函数名与.m函数一致 double[,] result (double[,])u.ToArray();MWNumericArray是 MATLAB 与 C# 之间数值数组的桥接类型。状态变量必须是一维或二维数组不能传 nullMATLAB 侧函数签名要写成function u PolicyEvaluate(x)。若返回值是矩阵ToArray()返回double[,]再自行索引即可。C# 里传入的数组默认是列优先和 MATLAB 一致不需要额外转置但如果你在 C# 侧使用了MathNet.Numerics这类库构造矩阵那就必须检查其内部存储顺序否则数据通路是反的。5.3 验证策略快照比对跨语言调用最容易出问题的是矩阵维度顺序和数值精度差异。验证做法是先在 MATLAB 里跑一次完整策略序列把每一步的 u 保存成二进制.bin文件再在 C# 里调用编译后的组件跑同一组初始状态逐点比较。误差在 1e-6 量级说明数据通路没有问题如果差异超过 1e-3优先检查 C# 侧输入的矩阵是否需要转置而不是怀疑算法本身。我一般会把对比脚本和基准数据放在同一个工程目录每次改完参数后重新生成快照再跑 C# 单元测试这样每次改动都能自动回归避免在集成阶段才发现策略漂移。本文还有配套的精品资源点击获取