ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MATLAB实现自适应动态规划:从理论到倒立摆控制实战

MATLAB实现自适应动态规划:从理论到倒立摆控制实战 简介本资源是一套面向控制理论研究者与自动化专业高年级本科生的自适应动态规划ADPMATLAB实现方案聚焦解决传统动态规划在高维系统中面临的“维数灾难”问题通过函数逼近如神经网络近似价值函数或策略函数实现复杂非线性系统的最优控制设计。压缩包共6个文件含4个核心MATLAB源码含主程序main.m、被控对象模型plant.m、神经网络权重更新atraingd.m及网络结构wnet.m、1份说明文档readme.docx和1个简要文本说明说明.txt总大小仅19KB轻量紧凑便于快速部署与原理验证。已有170人学习下载资源结构清晰、模块分工明确提供从系统建模、奖励函数定义、神经网络逼近器构建到在线迭代优化的完整ADP流程代码特别适合理解贝尔曼最优性原理在机器学习框架下的工程落地是掌握近似动态规划思想与MATLAB实践结合的实用入门材料。1. 从“最优”到“可行”为什么我们需要自适应动态规划在控制、优化和决策领域我们常常面临一个终极难题如何为一个复杂的系统比如一个机器人、一个电网或者一个金融投资组合找到一套最优的控制策略理论上贝尔曼方程和哈密顿-雅可比-贝尔曼方程为我们描绘了最优控制的完美蓝图。但现实是骨感的这些方程往往陷入“维数灾难”——状态变量稍微多一点计算复杂度就呈指数级爆炸精确求解变得遥不可及。这就好比给你一张无比精细的世界地图要求你立刻找出从北京到罗马的最短路径精确到每一条小巷这几乎是不可能完成的任务。于是近似动态规划应运而生它放弃了追求理论上“绝对最优”的执念转而寻求一个在计算上可行、在性能上“足够好”的近似最优解。它的核心思想很直观我们不再试图一次性求解整个状态空间的最优值函数而是通过迭代学习用一个参数化的函数比如神经网络、多项式去逼近真实的最优值函数或最优策略。自适应动态规划则是ADP家族中更具“智能”和“在线”能力的一员。它强调系统能够与环境持续交互利用实时产生的数据状态、控制量、代价来动态地调整和更新其近似函数从而适应系统参数的变化、模型的不确定性或者未知的扰动。你可以把它想象成一个有经验的司机他不仅依靠GPS初始模型更会根据实时路况在线数据不断微调路线最终找到一条综合时间、油耗和舒适度的“满意”路径。MATLAB作为科学计算和算法原型的利器无疑是实现ADP思想的绝佳平台。其强大的矩阵运算能力、丰富的优化工具箱以及直观的编程环境让我们能够将ADP那些抽象的迭代更新公式快速转化为可运行、可调试的代码。本文就将聚焦于如何利用MATLAB从零开始搭建一个自适应动态规划的核心框架并解决几个典型的控制问题。我们将避开深奥的数学推导直击算法实现的核心环节与工程细节分享我在将理论公式落地为稳健代码过程中踩过的坑和积累的经验。2. ADP的核心架构与MATLAB实现蓝图在动手写代码之前我们必须清晰地勾勒出ADP算法的基本骨架。一个典型的ADP系统包含三个核心组件评价网络、模型网络和执行网络。这三者构成了一个紧密协作的“三角”关系。评价网络是系统的“评估师”。它的任务是逼近最优值函数即从当前状态出发未来累积代价的最小值。在MATLAB中我们通常用一个前馈神经网络来实现它。网络的输入是系统状态输出是当前状态对应的估计值函数值。它的权重更新目标是让网络输出的值函数估计尽可能满足贝尔曼方程所定义的时间差分误差最小。执行网络是系统的“决策者”。它的任务是根据当前状态直接给出近似最优的控制动作。同样它通常也是一个神经网络输入是状态输出是控制量。它的训练目标是使得评价网络输出的值函数估计在给定该控制动作下最小即寻找使未来代价最小的动作。模型网络是系统的“预言家”。在模型未知或部分未知的情况下我们需要一个网络来学习系统的动力学方程即给定当前状态和控制动作预测下一个状态是什么。这对于计算时间差分误差至关重要。如果系统模型完全已知则可以不使用模型网络。在MATLAB中实现这个架构我们的工作流可以分解为以下几个关键步骤我将用一个简单的离散时间非线性系统作为贯穿始终的例子x(k1) f(x(k), u(k)) 其中x是状态u是控制输入代价函数为J Σ( x’Qx u’Ru )。环境与问题定义在脚本开头明确定义系统动力学f、代价函数、状态和控制量的维度、仿真步长、总步数等。使用MATLAB的匿名函数或单独的函数文件来封装f。神经网络创建与初始化利用Deep Learning Toolbox的feedforwardnet函数或更底层的network对象创建评价网络和执行网络。务必仔细设置网络的层数、每层神经元个数、激活函数输出层常用线性激活函数。初始化权重至关重要小的随机初始值有助于训练收敛。数据存储与回放缓冲区为了实现“经验回放”——这一大幅提升ADP学习稳定性的技巧我们需要创建一个缓冲区如一个结构体数组或cell数组用于存储每一步交互产生的数据元组(x(k), u(k), r(k), x(k1))。缓冲区应有固定大小新的数据会覆盖旧的数据。主循环交互、学习与更新这是算法的核心。在每个时间步k交互将当前状态x(k)输入执行网络得到控制量u(k)。施加控制通过系统模型或真实环境得到下一个状态x(k1)和即时代价r(k)。将经验存入缓冲区。学习从缓冲区中随机采样一小批历史经验数据。对于每条经验计算时间差分误差。这个误差是更新评价网络权重的目标。具体地目标值y r(k) γ * V(x(k1))其中V是评价网络的当前估计γ是折扣因子。然后通过反向传播算法最小化(y - V(x(k)))^2来更新评价网络。更新在更新评价网络后我们固定其权重然后通过反向传播更新执行网络的权重目标是减小评价网络对当前状态输出的值即调整执行网络的输出u使得在新的u下评价网络输出的V更小。策略评估与可视化定期例如每100次迭代暂停学习用当前最新的执行网络策略运行一段完整的仿真计算累积代价并绘制状态轨迹和控制曲线。这能直观地观察学习过程是否收敛策略是否改善。注意在实际编码中评价网络和执行网络的更新顺序、学习率、折扣因子、探索噪声的添加为了鼓励探索通常在执行网络的输出上添加一个小的随机噪声等都是需要精心调节的超参数。一个常见的技巧是使用单独的目标评价网络其权重定期从在线评价网络复制这可以极大地提高学习的稳定性这就是深度Q网络中的“固定目标网络”思想。3. 评价网络与执行网络的MATLAB实现细节理论框架清晰后我们来深入代码层面。我将以两个最关键的神经网络——评价网络和执行网络——为例展示在MATLAB中如何具体构建和训练它们。假设我们的状态x是2维控制u是1维。首先创建网络。虽然可以使用高级APIfeedforwardnet但为了更精细地控制网络结构和训练过程我更喜欢使用底层方式定义网络对象。% 假设状态维度为2控制维度为1 state_dim 2; action_dim 1; % 1. 创建评价网络 (Critic Network) % 目标输入状态x(2维)输出标量值函数V critic_layers [ featureInputLayer(state_dim, Name, critic_input) fullyConnectedLayer(64, Name, critic_fc1) % 隐藏层64个神经元 reluLayer(Name, critic_relu1) fullyConnectedLayer(32, Name, critic_fc2) % 隐藏层32个神经元 reluLayer(Name, critic_relu2) fullyConnectedLayer(1, Name, critic_output) % 输出层1个神经元线性激活 ]; critic_net dlnetwork(critic_layers); % 初始化学习率等优化器参数 critic_learn_rate 1e-3; % 2. 创建执行网络 (Actor Network) % 目标输入状态x(2维)输出控制动作u(1维) actor_layers [ featureInputLayer(state_dim, Name, actor_input) fullyConnectedLayer(64, Name, actor_fc1) reluLayer(Name, actor_relu1) fullyConnectedLayer(32, Name, actor_fc2) reluLayer(Name, actor_relu2) fullyConnectedLayer(action_dim, Name, actor_output) tanhLayer(Name, actor_tanh) % 使用tanh将输出限制在[-1,1]假设控制有界 scalingLayer(Name, actor_scale, Scale, max_action) % 缩放到实际控制范围 ]; actor_net dlnetwork(actor_layers); actor_learn_rate 1e-4; % Actor的学习率通常比Critic小接下来是核心的训练步骤。我们需要自定义训练循环因为标准的trainNetwork不适合这种自定义的强化学习更新规则。以下是评价网络单次更新的关键代码片段function [critic_net, critic_loss] update_critic(critic_net, states, rewards, next_states, gamma, optimizer) % states: dlarray, 形状 [state_dim, batch_size] % rewards: dlarray, 形状 [1, batch_size] % next_states: dlarray, 形状 [state_dim, batch_size] % gamma: 折扣因子 % 前向传播计算当前状态的值V(s) [V_pred, critic_state] forward(critic_net, states); % 计算目标值 y r γ * V(s) % 注意这里计算V(s‘)时需要停止梯度传播防止目标值波动 V_next forward(critic_net, next_states); V_next stripdims(V_next); % 确保是纯数值不包含计算图 y_target rewards gamma * V_next; % 计算损失均方误差 (y_target - V_pred)^2 critic_loss mse(y_target, V_pred); % 反向传播更新评价网络权重 critic_grad dlgradient(critic_loss, critic_net.Learnables); [critic_net, optimizer] adamupdate(critic_net, critic_grad, optimizer, critic_learn_rate); end对于执行网络的更新其目标是最大化或最小化评价网络输出的值。这需要通过评价网络对执行网络输出动作的梯度来更新function [actor_net, actor_loss] update_actor(actor_net, critic_net, states, optimizer) % 通过执行网络得到动作 actions forward(actor_net, states); % 这里需要一个技巧我们需要将状态和动作“拼接”起来输入给评价网络。 % 但标准的评价网络只输入状态。因此一种常见做法是构建一个“Q网络”输入是(state, action)。 % 为了简化我们假设评价网络已经设计为输入状态并输出该状态下的“最优值”。 % 执行网络的更新目标是让这个值更小。所以我们直接取评价网络输出的负值作为损失。 % 更严谨的做法是使用确定性策略梯度定理。 % 计算当前状态-动作对下的Q值这里用V值近似假设策略是确定的 % 注意我们需要计算Q对动作a的梯度因此需要保持计算图 V_value forward(critic_net, states); % 损失定义为 -V(s)因为我们要最小化V即最大化未来回报 actor_loss -mean(V_value); % 反向传播更新执行网络权重 % 关键梯度是从actor_loss流向actor_net但计算图中包含了critic_net actor_grad dlgradient(actor_loss, actor_net.Learnables); [actor_net, optimizer] adamupdate(actor_net, actor_grad, optimizer, actor_learn_rate); end提示上述执行网络更新是一个简化版本。在标准的确定性策略梯度算法中评价网络应该是一个Q函数网络输入为(s, a)输出为Q值。执行网络的更新梯度是∇_a Q(s, a) * ∇_θ π(s)其中π是执行网络。在MATLAB中实现这个需要更精细的自动微分控制。一个实用的方法是使用dlgradient计算Q对动作a的梯度再与执行网络对自身参数θ的梯度链式相乘。4. 经验回放与目标网络提升学习稳定性的工程技巧如果你直接按照第三节的简单更新规则编写代码并运行很可能会发现学习过程极不稳定值函数估计发散策略毫无改善。这是因为在序列数据上进行在线、逐样本的更新会引入高度的相关性导致神经网络陷入局部最优或完全失效。为了解决这个问题我们必须引入两个至关重要的工程技巧经验回放和目标网络。经验回放的思想是打破数据间的时序相关性。我们不再用刚产生的(s, a, r, s’)元组立即更新网络而是将其存储在一个固定大小的循环缓冲区中。每次需要更新时从缓冲区中随机均匀采样一个小批次的数据。这样做有两个好处第一随机采样破坏了数据间的连续相关性第二每份经验都可能被多次用于学习提高了数据利用率。在MATLAB中我们可以用一个结构体数组来实现这个缓冲区buffer_size 10000; % 经验回放缓冲区大小 experience_buffer struct(state, {}, action, {}, reward, {}, next_state, {}, done, {}); buffer_ptr 1; % 当前写入位置 is_buffer_full false; function store_experience(state, action, reward, next_state, done) % 将经验存储到缓冲区 experience struct(state, state, action, action, reward, reward, next_state, next_state, done, done); experience_buffer(buffer_ptr) experience; buffer_ptr buffer_ptr 1; if buffer_ptr buffer_size buffer_ptr 1; is_buffer_full true; end end function batch sample_experience(batch_size) % 从缓冲区中随机采样一个批次的经验 if is_buffer_full buffer_current_size buffer_size; else buffer_current_size buffer_ptr - 1; end batch_size min(batch_size, buffer_current_size); indices randperm(buffer_current_size, batch_size); batch experience_buffer(indices); end目标网络是解决“移动目标”问题的利器。在第三节的更新公式中我们使用同一个评价网络V来同时计算当前值V(s)和目标值r γV(s’)。这就像在射箭时靶子目标值和弓箭手当前网络绑在一起移动导致目标不断变化难以收敛。解决方法是为目标值计算创建一个独立的、更新缓慢的目标评价网络V_target。V_target的网络结构与V完全相同但其权重不是通过梯度下降更新而是定期例如每100步从当前评价网络V中软更新或硬复制过来。硬更新每隔C步直接令V_target V。软更新每一步都进行微量更新V_target_weights τ * V_weights (1-τ) * V_target_weights其中τ是一个很小的数如0.001。软更新通常能带来更稳定的学习过程。在MATLAB中实现软更新tau 0.001; % 软更新系数 function update_target_network(source_net, target_net, tau) source_params source_net.Learnables; target_params target_net.Learnables; for i 1:length(source_params) % 软更新target tau * source (1-tau) * target target_params{i, 3}{:} tau * source_params{i, 3}{:} (1-tau) * target_params{i, 3}{:}; end % 注意dlnetwork的Learnables是表格需要逐元素更新Value字段。 % 上述代码是概念示意实际操作需按MATLAB的dlarray格式处理。 end结合了经验回放和目标网络后评价网络的更新目标变为y_target r γ * V_target(s’)其中V_target是目标网络其权重更新缓慢。执行网络的更新梯度也相应地通过V_target或另一个目标Q网络来计算。这套组合拳经验回放目标网络是深度强化学习如DQN、DDPG得以成功的关键在ADP的MATLAB实现中同样不可或缺。5. 案例实战倒立摆系统的ADP控制为了将上述所有理论和技术串联起来我们选择一个经典的控制问题——倒立摆Cart-Pole作为实战案例。我们的目标是设计一个控制器通过左右移动小车使得摆杆保持竖直不倒。状态x通常包括小车位置、小车速度、摆杆角度、摆杆角速度共4维。控制u是小车受到的向左或向右的力是1维标量。第一步环境建模。我们需要在MATLAB中实现倒立摆的动力学方程。这通常是一组非线性微分方程。为了简化我们可以使用现成的模型或者自己编写一个基于欧拉法的离散仿真器。function [next_state, reward, done] cartpole_step(state, action, dt) % state: [cart_position, cart_velocity, pole_angle, pole_angular_velocity] % action: 施加的力 (标量有范围限制) % dt: 仿真时间步长 % 物理参数 g 9.8; % 重力加速度 mc 1.0; % 小车质量 mp 0.1; % 摆杆质量 l 0.5; % 摆杆长度的一半 force_mag 10.0; % 力的最大值 % 限制动作范围 action max(min(action, force_mag), -force_mag); % 从状态中提取变量 x state(1); x_dot state(2); theta state(3); theta_dot state(4); % 计算动力学 (简化模型) total_mass mc mp; sin_theta sin(theta); cos_theta cos(theta); temp (action mp * l * theta_dot^2 * sin_theta) / total_mass; theta_acc (g * sin_theta - cos_theta * temp) / (l * (4.0/3.0 - mp * cos_theta^2 / total_mass)); x_acc temp - mp * l * theta_acc * cos_theta / total_mass; % 欧拉法积分 x_dot_new x_dot dt * x_acc; x_new x dt * x_dot_new; theta_dot_new theta_dot dt * theta_acc; theta_new theta dt * theta_dot_new; next_state [x_new; x_dot_new; theta_new; theta_dot_new]; % 设计奖励函数我们希望摆杆直立小车在中心附近 % 常用设计角度偏离越小奖励越高角度过大或小车超出界限则终止并给负奖励。 angle_threshold 12 * pi / 180; % 约12度 x_threshold 2.4; % 小车位置界限 done abs(theta_new) angle_threshold || abs(x_new) x_threshold; if done reward -10.0; % 失败惩罚 else % 存活奖励 鼓励角度和位置靠近中心 reward 1.0 (cos(theta_new) - 0.05 * abs(x_new)); end end第二步构建ADP智能体。按照第2、3、4节的框架创建4维输入的评价网络和执行网络初始化经验回放缓冲区设置目标网络。第三步训练循环。这是最核心的部分代码逻辑如下max_episodes 1000; % 最大训练回合数 max_steps_per_episode 500; % 每回合最大步数 batch_size 64; gamma 0.99; % 折扣因子 for episode 1:max_episodes state env.reset(); % 初始化环境状态例如[0;0;一个小随机角度;0] episode_reward 0; for step 1:max_steps_per_episode % 1. 根据当前策略选择动作添加探索噪声 action predict(actor_net, dlarray(state, CB)); % 基础动作 action action exploration_noise * randn(size(action)); % 添加探索噪声 action max(min(action, max_action), -max_action); % 限制动作范围 % 2. 执行动作与环境交互 [next_state, reward, done] cartpole_step(state, action, dt); % 3. 存储经验 store_experience(state, action, reward, next_state, done); % 4. 如果经验缓冲区足够则进行学习 if buffer_current_size batch_size batch sample_experience(batch_size); % 将batch数据转换为dlarray并组织成矩阵 states_batch dlarray(cat(2, batch.state), CB); actions_batch dlarray(cat(2, batch.action), CB); rewards_batch dlarray(cat(1, batch.reward), CB); next_states_batch dlarray(cat(2, batch.next_state), CB); dones_batch [batch.done]; % 更新评价网络和目标网络 [critic_net, critic_loss] update_critic(critic_net, target_critic_net, states_batch, actions_batch, rewards_batch, next_states_batch, dones_batch, gamma, critic_optimizer); % 更新执行网络 [actor_net, actor_loss] update_actor(actor_net, critic_net, states_batch, actor_optimizer); % 软更新目标网络 update_target_network(critic_net, target_critic_net, tau); end state next_state; episode_reward episode_reward reward; if done break; end end % 记录并输出本回合结果 fprintf(Episode %d, Total Reward: %.2f, Steps: %d, Critic Loss: %.4f, Actor Loss: %.4f\n, ... episode, episode_reward, step, critic_loss, actor_loss); % 可选随着训练进行衰减探索噪声 exploration_noise max(min_noise, exploration_noise * noise_decay); end第四步测试与可视化。训练完成后移除探索噪声用训练好的执行网络控制倒立摆并绘制状态轨迹。你会观察到从一个随机的初始角度开始小车能够通过左右移动迅速将摆杆稳定在竖直位置。在这个案例中我强烈建议你将奖励函数设计、探索噪声的衰减策略、网络结构层数、神经元数以及学习率作为超参数进行系统性的调优。使用MATLAB的tiledlayout或subplot功能实时绘制每个回合的总奖励、平均Critic损失和Actor损失曲线是监控训练进程、诊断问题如梯度爆炸、不收敛的必备手段。6. 调试与性能优化让ADP在MATLAB中稳健运行将ADP算法从论文公式转化为能实际运行的MATLAB代码调试是不可避免的一环。以下是我在多次实践中总结出的常见问题与解决策略问题一值函数估计发散NaN或Inf。根因这通常是梯度爆炸导致的。贝尔曼方程中的时间差分目标y r γ * V(s’)在迭代中可能被不断放大尤其是当γ接近1且V(s’)估计不准时。排查与解决梯度裁剪在反向传播更新权重时对计算出的梯度向量的范数进行限制。% 在更新网络权重的步骤中加入梯度裁剪 [critic_grad, global_norm] dlgradient(critic_loss, critic_net.Learnables); if global_norm grad_threshold critic_grad dlupdate((g) g * (grad_threshold / global_norm), critic_grad); end合理初始化网络权重初始化过大会导致输出过大。使用glorot或he初始化。检查奖励尺度确保即时奖励r在一个合理的范围内例如[-1, 1]或[0, 1]。过大的奖励值会直接导致目标y爆炸。降低学习率这是最直接的尝试。将critic_learn_rate和actor_learn_rate降低一个数量级试试。问题二策略毫无改进累积奖励不增长。根因探索不足或执行网络学习停滞。排查与解决增强探索确保在训练初期添加了足够大的探索噪声如高斯噪声。可以设计噪声衰减计划开始大后期小。检查Actor更新确认执行网络的更新梯度是否正确传播。一个简单的验证方法是在更新前后计算同一状态下执行网络输出的变化。如果变化极小可能是学习率太低或梯度计算有误。平衡Critic和Actor的学习如果Critic学得太快而Actor学得太慢Critic的估计会变得不准确进而误导Actor。尝试降低Critic的学习率或提高Actor的学习率。可视化策略定期运行当前策略不带噪声观察状态轨迹。如果轨迹看起来完全随机说明策略没学到东西。问题三训练速度慢。根因MATLAB的循环和自定义梯度计算在未优化的情况下可能较慢。排查与解决向量化操作确保经验回放缓冲区采样和数据预处理如归一化的代码是向量化的避免在循环中对单个元素操作。使用dlarray和dlgradientMATLAB的深度学习工具箱针对dlarray类型的自动微分进行了优化比手动实现反向传播或使用符号数学工具箱快得多。批次大小适当增大batch_size如从32增至128、256可以利用GPU的并行计算能力如果可用但过大可能会影响收敛性。预分配数组在存储经验或记录训练指标时预先分配好固定大小的数组避免动态增长。性能分析使用MATLAB的profile工具找出代码中的性能瓶颈。很多时候慢的不是神经网络前向/反向传播而是数据I/O或逻辑判断。问题四收敛到次优策略。根因奖励函数设计有缺陷或者陷入了局部最优。排查与解决重塑奖励函数奖励函数是指引智能体学习的“指挥棒”。如果它只奖励最终成功稀疏奖励学习会非常困难。尝试设计更密集的奖励例如在倒立摆例子中不仅奖励“不倒”还奖励“角度更接近竖直”、“小车更靠近中心”。增加探索在训练中期甚至后期仍然保留很小的随机探索有助于跳出局部最优。集成方法可以尝试同时训练多个智能体不同的随机种子最后选择表现最好的策略或者使用策略集成。最后一个非常实用的建议是从简单问题开始。不要一开始就挑战高维状态、复杂动力学的系统。可以先在一个简单的线性二次型调节器问题上验证你的ADP代码框架是否正确。在LQR问题上最优解是已知的通过求解Riccati方程得到你可以清晰地对比ADP学习到的策略与理论最优策略的差距从而快速验证代码的正确性。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进