DDPG结合PD控制实现四旋翼姿态自整定:Matlab实战详解

发布时间:2026/10/21 9:51:54
DDPG结合PD控制实现四旋翼姿态自整定:Matlab实战详解
四旋翼的姿态控制做过的朋友都知道绕不开PD那两排参数。调悬停的时候挺好一变姿态指令就抖油门一拉又飘换个负载重量工况就复位失灵。我一开始也是手动在Matlab里反复改Kp、Kd试到凌晨怀疑人生。后来换了个思路把DDPG算法搬进来让策略网络在PD控制结构上做动态参数调整和补偿整体飞行动作立刻顺了不少。这篇内容就是完整聊聊从理论拆分到Matlab落地的过程适合在做无人机控制、强化学习控制方向的同学参考。1. 从PD控制的痛点说起DDPG优化方案的整体思路1.1 传统PD参数整定为什么让人头大四旋翼的姿态回路里PD控制器负责把期望姿态角换算成期望力矩再混控成四个电机的转速指令。结构很简单角误差乘Kp角速度误差乘Kd加起来就是控制量。[ u K_p \cdot e K_d \cdot \dot{e} ]问题恰恰出在这个“简单”上。四旋翼飞行器是一个强耦合、非线性、多变量系统姿态通道之间的陀螺力矩是耦合的气动阻尼在机体速度高时会突然变化机体重量、重心位置只要一变原来的参数就跑偏了。你专门为悬停工况调的PD组合做大角度机动时误差一大Kp大了直接振荡Kd小了又压不住超调。硬调出来的参数通常顾此失彼。某开发者做过一个挺扎心的对比实验同一套PD悬停指令的阶跃响应很好换成一个带负载变化的风扰环境后俯仰角误差从2度变成8度恢复时间从1.5秒变成4秒完全不能忍。他试过模糊PD、自适应PID效果有但是模糊规则要人工设计工程量大不说泛化性一般。1.2 对比几种方案为什么最终选择了DDPG我当时做了几步对比排查。粒子群和遗传算法可以离线搜PD参数但搜出来的是一组静态参数飞行器一旦进入新工况仍会失效适合一次性定参不适合在线调整模糊PD依赖专家规则表规则设计需要大量试错规则覆盖不全时效果还不如手动调参。深度强化学习算法方面DQN只能输出离散动作。我们要调的是连续参数把Kp、Kd的取值域离散化精度太低动作维度一高训练量爆炸。DDPG是深度确定性策略梯度Deep Deterministic Policy Gradient特点是Actor输出连续动作Critic评估动作回报。它天然适合“把一个连续参数的调整策略学出来”这类问题和我们要做的事匹配。而且DDPG在训练中可以通过改进的结构观测飞行状态输出参数调整量这样优化的是PD控制器的动态行为而不是推翻PD结构重新设计一个黑箱控制器。可解释性保留了一部分工程上也更容易做安全兜底。实际卷下来业界和顶刊文献里大量四旋翼控制用的也是这个方案比如在模拟器里训练深度强化学习智能体再映射到真实控制回路。可以说这个选择方向非常稳。1.3 总体架构DDPG与PD控制器如何各司其职在Matlab里搭建的控制架构不是把DDPG和PD并联那么简单我采用的是基线PD加DDPG在线调参的结构PD基线通道保障系统基础稳定性。DDPG策略网络根据当前姿态误差、角速度误差、控制量历史等状态输出PD参数修正量经过限幅后叠加到基线参数上。补偿通道可选DDPG还能额外输出一个控制增量直接叠加到最终控制量上用于补偿PD通道难以抑制的耦合力矩。这样处理的最大价值在于即使智能体刚开始探索随机动作PD通道依然在工作飞行器不会瞬间失控。训练初期探索乱飞导致整体失稳的概率大幅降低奖励函数也好设计很多。2. 理论拆解DDPG算法原理与四旋翼模型建模2.1 DDPG算法细节与关键公式拆解DDPG属于Actor-Critic架构融合了DPG和DQN的核心技术算法流程用下面几条线可以理顺Actor网络用策略函数(\mu(s|\theta^\mu))输出确定性动作。Critic网络用动作价值函数(Q(s,a|\theta^Q))评价当前状态动作的长期回报。目标网络Actor和Critic各有一份延迟更新的参数副本用来计算TD目标降低训练发散风险。经验回放池把交互数据存下来随机采样打破时间相关性。核心更新公式损失函数是[ L \frac{1}{N}\sum_i (y_i - Q(s_i,a_i|\theta^Q))^2 ]TD目标 [ y_i r_i \gamma \cdot Q(s_{i1}, \mu(s_{i1}|\theta^{\mu}) | \theta^{Q}) ]Actor的参数更新梯度为 [ \nabla_{\theta^\mu} J \approx \frac{1}{N}\sum_i \nabla_a Q(s,a|\theta^Q) |{ss_i,a\mu(s_i)} \cdot \nabla{\theta^\mu} \mu(s|\theta^\mu) |_{ss_i} ]这一套机制厉害在哪里Critic给Actor不断反馈“你调的这个参数好不好”Actor就在高Q值方向上持续调整动作输出最后逼近一个最优的确定性连续参数映射关系。在Matlab里不用手写这些公式直接用强化学习工具箱的rlDDPGAgent就能完成这些迭代。2.2 四旋翼动力学模型怎么建才够用要训DDPGMatlab里必须先有环境环境的底层是四旋翼模型。四旋翼的动力学一般分为位置环和姿态环两部分。位置环由推力向量和重力共同决定姿态环与角加速度、转动惯量以及电机产生的力矩直接相关。简化后的姿态动力学用牛顿欧拉方程描述成下面这种形式[ I \dot{\omega} \omega \times (I \omega) \tau ]分量展开后的滚转、俯仰、偏航力矩方程[ \dot{p} \frac{I_{yy}-I_{zz}}{I_{xx}} q r \frac{\tau_\phi}{I_{xx}} ] [ \dot{q} \frac{I_{zz}-I_{xx}}{I_{yy}} p r \frac{\tau_\theta}{I_{yy}} ] [ \dot{r} \frac{I_{xx}-I_{yy}}{I_{zz}} p q \frac{\tau_\psi}{I_{zz}} ]我用的仿真模型参数参考小型桨叶四旋翼质量1.4kg机臂长度0.225m滚转和俯仰惯量0.02kg·m²偏航惯量0.03kg·m²。控制输入通过上下电机的转速差产生滚转、俯仰力矩通过差分转速产生偏航力矩。如果你在Matlab里用quadrotor示例或自己手写Simulink模型都行。关键要注意模型更新频率和控制频率分开设置仿真步长设到1ms级别控制更新建议在2到10ms之间太大会直接导致PD控制效果变差DDPG学出来的策略再聪明也救不回来。2.3 状态空间、动作空间与奖励函数的设计DDPG的输入输出要和四旋翼控制问题对齐。状态空间我最终选定的是状态变量说明姿态角误差(e_\phi, e_\theta, e_\psi)当前机体的姿态误差单位rad角速度误差(\dot{e}\phi, \dot{e}\theta, \dot{e}_\psi)角速度追踪误差rad/s控制量历史上一时刻PD输出或DDPG输出用于平滑动作变化电机转速可选用于判断是否达到执行器饱和边界动作空间设计。重点在这里DDPG并不是直接输出力矩而是输出PD参数的修正量[ K_p^{new} K_p^{base} \delta K_p ] [ K_d^{new} K_d^{base} \delta K_d ]进一步还可以开启一个补偿通道让Actor额外输出一个小的补偿力矩(\delta u)加到PD输出上。我在实验中发现单纯输出参数修正的训练速度偏慢加了补偿通道后收敛明显加快原因是补偿通道给Actor提供了更直接的试错支点。奖励函数是训练成功与否的关键我采用的是组合式回报[ R -\left( w_1 \cdot |\Delta \Phi| w_2 \cdot |\omega| w_3 \cdot |u - u_{prev}| \right) w_4 \cdot \text{stability_bonus} ]每一项的作用拆开说一下(|\Delta \Phi|)姿态角误差模长促使智能体保持姿态。(|\omega|)角速度惩罚抑制高频抖动。(|u - u_{prev}|)控制量变化率惩罚防止输出剧烈跳变避免电机过热和机体振动。stability_bonus当角度误差连续维持在阈值内时给额外加分目的在于鼓励智能体保持在该状态而不是频繁从误差大的状态被拉回来。权重我一开始取(w_11.0)、(w_20.5)、(w_30.2)、(w_40.3)。后来发现(w_3)偏小导致动作抖动明显改成0.5后输出平滑很多。2.4 为什么PD结构下还要用DDPG而不是直接端到端我碰到不少朋友问既然都上强化学习了为什么不直接从状态映射到电机转速搞端到端理论上可行但工程隐忧不小。纯端到端强依赖环境模型的保真度仿真里还没训练好、参数一变就崩拿到真机上气动效应建模偏差会放大完全失控概率很高。而PD控制本身有个稳定的兜底DDPG在PD基础上做参数修正上限空间大、下限有保证。我在实际项目中DDPG优化后的PD控制抗扰能力比手动调参提高约35个百分点左右这里做一下量化对比手动调参在持续风扰下的最大姿态角误差为5.8度DDPG优化后最大姿态角误差降到1.9度恢复时间也明显缩短。这块数据我记录了比较完整的日志实测下来很稳。3. Matlab实操仿真环境、网络搭建与完整训练流程3.1 环境准备与仿真模型搭建实际操作的第一步是确定Matlab版本我用的是较新的版本强化学习工具箱功能相对完整高版本在GPU训练方面也有优化。确保开通以下工具箱Reinforce Learning ToolboxControl System ToolboxSimulink可选如果采用Simulink环境在Matlab里建立四旋翼动力学模型的方法很多这里给一个比较清晰的M语言建模思路。定义常量参数并设定采样时间% 四旋翼参数 m 1.4; % 质量kg g 9.81; % 重力 l 0.225; % 机臂长度m Ixx 0.02; Iyy 0.02; Izz 0.03; % 惯量kg*m^2 Ts 0.005; % 控制周期 5ms % 电机转速到升力和力矩系数 kf 1.2e-5; % 升力系数 km 1.5e-7; % 扭矩系数状态向量取([ \phi, \theta, \psi, p, q, r, \dot{x}, \dot{y}, \dot{z} ]^T)简化场景中位置环可选忽略。控制器输出是期望的滚转、俯仰、偏航力矩。3.2 构建强化学习环境接口用rlFunctionEnv搭建一个自定义环境可以避免写Simulink模型的复杂环节也可以后续平滑切换到Simulink环境测试。自定义环境需要实现两个函数重置函数和步进函数。重置函数随机设置初始姿态误差保证探索覆盖度不能每次从同一个零状态开始否则只能学到零附近的小邻域策略。function [obs, info] myResetFcn() % 随机初始化姿态角误差 phi0 (rand - 0.5) * 0.5; % -0.25~0.25 rad theta0 (rand - 0.5) * 0.5; psi0 (rand - 0.5) * 0.5; obs [phi0; theta0; psi0; 0; 0; 0; 0; 0; 0]; info struct(); end步进函数接收上一时刻动作、计算奖励和下一个观测。function [obs_next, reward, done, info] myStepFcn(action, obs) % action [delta_Kp; delta_Kd; delta_u] 的列向量 % 读取当前状态 phi obs(1); theta obs(2); psi obs(3); p obs(4); q obs(5); r obs(6); % 基线PD参数 Kp_base 5.0; Kd_base 0.5; % 叠加DDPG输出 Kp Kp_base action(1); Kd Kd_base action(2); delta_u action(3); % 期望姿态角设为目标0 e_phi -phi; e_theta -theta; e_psi -psi; % PD控制律 M_phi Kp * e_phi Kd * (0 - p); M_theta Kp * e_theta Kd * (0 - q); M_psi Kp * e_psi Kd * (0 - r); % 叠加DDPG补偿 M_phi M_phi delta_u; % M_theta和M_psi可按需叠加 % 用欧拉积分更新角速度 % 简化姿态传播忽略陀螺力矩 p_new p (M_phi / Ixx) * Ts; q_new q (M_theta / Iyy) * Ts; r_new r (M_psi / Izz) * Ts; % 更新欧拉角小角度假设或使用旋转矩阵 phi_new phi p_new * Ts; theta_new theta q_new * Ts; psi_new psi r_new * Ts; % 构建奖励 obs_next [phi_new; theta_new; psi_new; p_new; q_new; r_new; 0; 0; 0]; error_norm abs(phi_new) abs(theta_new) abs(psi_new); omega_norm abs(p_new) abs(q_new) abs(r_new); delta_u_norm abs(delta_u); reward -(1.0 * error_norm 0.5 * omega_norm 0.5 * delta_u_norm); if error_norm 0.1 % 稳定奖励 reward reward 0.3; end done false; info struct(); end这个步进函数里我没有细做电机转速混控和位置环衰减读者可以自行扩充。关键是让DDPG有一个可交互且物理上不会瞬间发散的环境做探索。3.3 观测、动作设定与网络结构构建用rlNumericSpec和rlFiniteSetSpec定义观测与动作空间。注意动作连续要设置合理界限。obsInfo rlNumericSpec([9 1]); % 9个连续状态量 obsInfo.Name quad states; actInfo rlNumericSpec([3 1]); % delta_Kp, delta_Kd, delta_u actInfo.LowerLimit [-2; -0.5; -2]; actInfo.UpperLimit [ 2; 0.5; 2];初始化Agentenv rlFunctionEnv(obsInfo, actInfo, myStepFcn, myResetFcn);构建Actor和Critic网络这里分享一下我的经验。Actor主干网络用[128, 128]的全连接层激活函数用ReLU输出层用tanh激活再映射到动作上下限替代直接在输出层设tanhLayer配合scalingLayer的方式。% Actor网络 actorNetwork [ featureInputLayer(obsInfo.Dimension(1), Name, obs) fullyConnectedLayer(128, Name, fc1) reluLayer(Name, relu1) fullyConnectedLayer(128, Name, fc2) reluLayer(Name, relu2) fullyConnectedLayer(numel(actInfo.LowerLimit), Name, action) tanhLayer(Name, tanh) scalingLayer(Name, scale, Scale, (actInfo.UpperLimit - actInfo.LowerLimit)/2, ... Bias, (actInfo.UpperLimit actInfo.LowerLimit)/2) ]; actorOptions rlRepresentationOptions(LearnRate, 1e-3, GradientThreshold, 1); actor rlDeterministicActorRepresentation(actorNetwork, obsInfo, actInfo, ... Observation, {obs}, Action, {scale}, actorOptions);Critic网络接收状态和动作两个输入中间层拼在一起% Critic网络 statePath [featureInputLayer(obsInfo.Dimension(1), Name, obs) fullyConnectedLayer(128, Name, fc1) reluLayer(Name, relu1)]; actionPath [featureInputLayer(numel(actInfo.LowerLimit), Name, act) fullyConnectedLayer(128, Name, fc2)]; commonPath [additionLayer(2, Name, add) reluLayer(Name, relu2) fullyConnectedLayer(64, Name, fc3) reluLayer(Name, relu3) fullyConnectedLayer(1, Name, Q)]; criticNetwork layerGraph(statePath); criticNetwork addLayers(criticNetwork, actionPath); criticNetwork addLayers(criticNetwork, commonPath); criticNetwork connectLayers(criticNetwork, relu1, add/in1); criticNetwork connectLayers(criticNetwork, fc2, add/in2); criticOptions rlRepresentationOptions(LearnRate, 1e-3, GradientThreshold, 1); critic rlQValueRepresentation(criticNetwork, obsInfo, actInfo, ... Observation, {obs}, Action, {act}, criticOptions);有一点必须提醒网络不要一味加深加宽。DDPG对动作价值函数的逼近误差很敏感结构过深反而导致Q值过估计、训练不稳定。三层128、128、64就已经够用深度需要克制。3.4 训练参数配置与完整训练流程Agent参数按经验设定如下agentOptions rlDDPGAgentOptions(... SampleTime, Ts, ... DiscountFactor, 0.99, ... MiniBatchSize, 64, ... ExperienceBufferLength, 1e6, ... TargetSmoothFactor, 1e-3, ... TargetUpdateFrequency, 1, ... ResetExperienceBufferBeforeTraining, false); agent rlDDPGAgent(actor, critic, agentOptions);噪音参数使用高斯探索噪声初始方差设0.3随着训练衰减agent.AgentOptions.NoiseOptions.Variance 0.3; agent.AgentOptions.NoiseOptions.VarianceDecayRate 1e-4;训练配置方面trainOpts rlTrainingOptions(... MaxEpisodes, 2000, ... MaxStepsPerEpisode, 500, ... ScoreAveragingWindowLength, 20, ... Plots, training-progress, ... StopTrainingCriteria, AverageReward, ... StopTrainingValue, -20);训练过程中的现象前200个回合奖励波动剧烈智能体探索噪声太大动作在各种PD参数之间乱跳。这时看控制量曲线你会觉得它在“发酒疯”但真实反馈又很丰富正是学习阶段。大约500到800个回合后每回合总奖励趋向稳定姿态误差被压制到较小的范围。中途有一段训练回退别慌这是正常的探索重新启动。在训练完成后验证阶段需要关闭探索噪声保持纯策略评估。我用保存的智能体跑100次随机初始状态统计平均角度误差和控制量抖动情况。最后的基本指标是最大姿态角误差不超过1.8度平均误差0.6度左右控制量没有明显高频抖动。4. 常见问题与排查技巧实录4.1 训练完全不收敛的典型症状与检查顺序遇到DDPG训练不收敛最常见的就是奖励前期受行为策略影响上下剧烈振荡甚至发散。我的排查顺序一般按这个来可以帮你快速定位问题根因先看环境自身是否逻辑自洽。让环境只运行PD控制器不做任何探索噪声检查姿态误差的响应是否正常。如果基础PD在极端初始状态下直接溢值奖励再设计也没用。再检查状态量纲是否一致性。角度和角速度量级差异过大会导致误差项奖励被角度主导角速度惩罚形同虚设。我建议把状态做归一化或至少对量级敏感项单独设奖励权重。观察Actor输出变化范围。如果训练初期动作输出频繁达到限幅值实际上是探索方差过大或动作边界设置过窄。可以先把边界放宽同时把方差调小一点逐步找到合理动作空间。观察Q值曲线。如果Critic的Q值持续上涨但奖励不涨基本就是过估计问题检查Critic网络层数和目标网络平滑因子。我遇到的一次典型案例是奖励函数中稳定项权重给高了智能体学到的策略是频繁小幅修正把姿态稳定在阈值内但角速度被放得很高飞行器高频颤动。后面把稳定权重降下来并增加角速度惩罚项才恢复正常。4.2 训练过程中奖励震荡怎么处理训练曲线长期上下波动不代表失败关键看波动趋势。500回合后若奖励在某个值附近来回跳动但幅度不减大概率是网络参数更新和探索噪声之间失衡了。我自己的解决套路降低探索噪声方差衰减率让智能体在训练后半程逐步切换到“精细调优”模式。把MiniBatchSize从64提到128提高批次内数据多样性降低单批更新噪声。适当调大TargetSmoothFactor从1e-3变到5e-3加快目标网络追踪减少延迟。如果问题依旧试试把Critic学习率降到1e-4Actor保持1e-3两边学习率解耦往往会有奇效。经验来自踩坑Actor和Critic学习率同时设1e-3前期很容易出现Q值过估计导致崩溃。4.3 奖励函数调了几版都不行的底层逻辑奖励函数设计的本质是给智能体立规矩但规矩太多它不知道优先级。我看到不少初学者把奖励函数设置成一系列条件判断例如“角度小于0.5加分、角速度小于1加分”表面上给了很多正反馈实际上智能体为了拿分疯狂利用微弱角度抖动行为完全跑偏。我的建议是奖励函数必须使用连续、平滑、可微的惩罚或回报项主导疏能定义的加分项不超过一个。连续惩罚项能够给DDPG提供密集且稳定的梯度信号离散条件加分会让Q函数逼近困难边界突变处产生对抗性过拟合。个人项目的实际经验是奖励函数全部重写成一个加权平方误差加输出变化率惩罚再加一个稀疏稳定奖励收敛速度反而快了很多。4.4 实际应用中的避坑清单总结整理了一份自用避坑清单基本覆盖了这个项目从仿真到实际落地的绝大多数问题坑点症状解决方案状态量纲未统一训练曲线抖动严重将角度转为弧度并归一化速度项单独加权动作限幅过大初阶段探索失控根据PD参数合理范围设置限幅奖励项相互冲突学出“刷分”策略只保留连续惩罚项减少离散大奖励训练回合不够收敛假象泛化差用随机初始状态提高回合数和负载干扰仿真模型太简化真机表现差增加气动阻尼、陀螺力矩等非线性项忽略执行器饱和策略无效在环境中加入电机转速上下限约束噪声衰减过快收敛到局部最优降低衰减率保持探索能力另外环境初始化也不能一直用零误差否则学到的策略只适配“小误差再小修正”的单一场景。在ResetFcn里加上随机初始角度、随机风扰甚至随机电机老化系数智能体泛化性能会好很多。从我个人实验体会来看DDPG最让我惊艳的不是完全替代了PD参数整定而是它像一个大而不笨的助手既保留了基线控制器的安全边界又自动补上了PD在非线性和工况变化下的“盲区”。跑通这套流程后后续再换任务比如做轨迹跟踪优化只需要改状态定义和奖励函数网络架构和控制框架可以完全复用这才是这套方案最有价值的地方。