四旋翼控制这事吧圈里人基本都绕不开PD控制器。结构简单、参数直观、调好了效果也稳所以不管是在PX4固件里还是MATLAB仿真中PD仍然是主流底层策略。但做久了你会发现固定PD参数很难应对全包线飞行——悬停调好了高速前飞又抖载重变了原来的增益就偏软。折腾过手动调参的人都会懂那种挫败感。所以我把目光投向了深度强化学习确切地说是DDPG算法用它来在线优化PD控制器的增益。这篇文章就把我从理论推导到MATLAB实现的完整过程讲清楚包括状态怎么定义、奖励函数怎么设计、网络怎么搭、训练有哪些坑以及最终效果对比。适合学过控制理论、想把强化学习落地到飞行器仿真中的朋友参考也适合那些对“AI代替人工调参”抱有兴趣的工程师。下面开始。1. 为什么把DDPG和PD结合在一起1.1 四旋翼动力学和控制的基本框架先铺垫一下被控对象。四旋翼是一个典型的多输入多输出系统常见处理方式是把它拆成内环姿态环和外环位置环。内环控制roll、pitch、yaw三个角速度以及对应的角度输出的是四个电机转速指令。外环负责位置和水平速度输出期望姿态角给内环。整个控制律通常写成位置环PD或PID输入位置和速度误差输出期望加速度。姿态环PD或PID输入角度和角速度误差输出力矩或电机推力增量。以姿态环为例标准PD形式是[ \tau_p K_{p,\theta}(\theta_d - \theta) K_{d,\theta}(0 - \dot{\theta}) ]这里的 (K_{p,\theta}) 和 (K_{d,\theta}) 就是需要调整的增益。固定增益的问题在于飞行状态变了、转动惯量变了、执行器饱和了最优增益其实在跟着变化。而手动调参又只能取一个折中值结果就是处处可用、处处不优。1.2 固定PD增益的局限性我举个例子。某款轴距450mm的四旋翼悬停起飞重量1.2kg我最初把roll通道 (K_p) 设为6.2(K_d) 设为2.1悬停表现很好姿态稳定度在±0.8°以内。但改成带载飞行后总重增加到1.6kg同样一组参数roll通道的响应明显变慢抗风能力也变差。我又手动把 (K_p) 拉到7.8带载好了空载又开始振荡。这种“调好A状态丢了B状态”的现象本质上是系统参数质量、惯量、重心位置发生了变化。PD是线性控制器它的增益设计基于某个标称工作点。一旦远离标称点性能必然退化。解决办法有几个增益调度、自适应控制、滑模控制但都需要建立较精确的模型或者靠人工经验划分工况。而DDPG这类无模型强化学习算法恰恰可以直接通过与环境交互学到一个从误差状态到增益的非线性映射本质上做的是“数据驱动的自适应调参”。1.3 DDPG适合解决这个问题的原因DDPGDeep Deterministic Policy Gradient是连续动作空间的强化学习算法。四旋翼PD增益恰好是连续变量比如 (K_p \in [0,10])DDPG天然适配。相比于DQN它不需要把动作离散化也就避免了一个增益分辨率带来的性能损失。相比于PPODDPG在样本效率上通常更高尤其适合仿真器中训练——虽然它超参数敏感但只要控制好探索噪声和经验回放稳定性也能接受。再一个原因是DDPG的演员网络可以直接输出连续增益值以很小的延迟完成前向推理在MATLAB仿真中一个步进周期内完全跑得完。这为将来部署到机载电脑比如某个嵌入式平台留下了基础。2. DDPG算法核心原理再梳理2.1 Actor-Critic为什么适用于连续控制DDPG本质上是Actor-Critic结构加上DQN里的两个关键技巧经验回放和目标网络。Actor是策略网络输入当前状态输出确定的动作Critic是Q网络输入状态和动作输出Q值。训练的目标是让Actor输出的动作能让Critic评分更高同时让Critic的预测尽可能接近真实回报。这个结构和四旋翼控制结合时我的理解是Actor就像一个“增益生成器”它根据当前误差和角速度输出合适的 (K_p)、(K_d)Critic则像一个“控制器评估师”它不断学习怎样的 (K_p)、(K_d) 在什么状态下能带来更好的姿态跟踪效果。两者一起迭代最终收敛到一个稳定的增益策略。2.2 经验回放和目标网络的关键作用强化学习最怕样本相关性和目标不稳定。四旋翼仿真中相邻两个控制周期内的状态高度相关如果直接按时间顺序更新网络很容易陷入振荡。经验回放把过去的转移样本 ((s,a,r,s)) 存入缓存每次随机采样一个batch更新相当于打断了样本间的时间相关性。目标网络则解决“自举导致发散”的问题。DDPG给Actor和Critic各维护一套目标网络参数缓慢跟踪在线网络计算TD目标时使用目标网络[ y_i r_i \gamma Q(s_{i1}, \mu(s_{i1})) ]这里 (\mu) 和 (Q) 是目标网络。软更新系数 (\tau) 通常取0.001或0.005更新太慢学习慢更新太快容易发散。我做过对比(\tau0.001) 时姿态曲线的收敛速度比0.01慢了大约30%但全程更稳没有出现后期振荡。飞行控制场景我偏向小 (\tau)。2.3 探索与利用的平衡实践DDPG是确定性策略如果不加噪声它一开始就输出固定动作根本不会探索。标准做法是给动作叠加Ornstein-Uhlenbeck噪声因为OU噪声在时间维度上相关能让增益值平滑变化更符合物理系统的连续特性。相比之下高斯白噪声会让 (K_p) 在相邻步长之间突变四旋翼控制信号会抖得厉害。在MATLAB里实现OU噪声不复杂核心迭代式是[ x_{t1} x_t \theta(\mu - x_t) \sigma \mathcal{N}(0,1) ]我用的参数是 (\theta0.15)、(\mu0)、(\sigma0.2)。注意 (\sigma) 不是固定常数我做了衰减处理训练到20万步时降为初始值的0.1。这很关键前期大幅度探索找方向后期小幅度探索微调增益否则训练永远收敛不到精准值。3. 用DDPG优化PD的参数和补偿项3.1 状态空间设计强化学习要解决的是“给出当前飞行状态决定PD增益如何调整”因此状态必须包含足够的信息来体现控制性能。我最初只用了角度误差和角速度误差训练结果虽然能稳定但收敛慢因为在强扰动下仅靠这两个量无法区分“是增益太小”还是“外部扰动太大”。经过几轮迭代最终状态向量定为6维姿态角误差 (e_\theta \theta_d - \theta)角速度误差 (e_{\dot{\theta}} \dot{\theta}_d - \dot{\theta})当前角速度 (\dot{\theta})累计误差的滑动平均对PD来说不需要但作为性能反馈有帮助期望角速度变化率反映指令的激进程度上一时刻的PD输出控制量用来感知执行器是否饱和实际训练还试过加入高度和水平速度但姿态环训练时这些量基本是常量加入后反而扰乱了特征。所以我建议状态维度宁少勿多先用最直接反映姿态控制品质的量。3.2 动作空间设计动作空间对应需要优化的参数。我分别试验过两种方案方案A直接输出 (K_p) 和 (K_d)范围按手动调参经验限定在 ([0.1, 10]) 和 ([0.01, 3])。方案B输出增益增量 (\Delta K_p)、(\Delta K_d)每步执行 (K_p \leftarrow K_p \Delta K_p)步长限制在±0.5。方案B在实际训练中更容易稳定。原因是直接输出绝对增益时Actor网络输出层的激活函数必须做饱和处理一旦探索噪声导致输出越界控制器就会不稳定训练中断。增量式则天然保持了增益的连续性。我还增加了一个补偿项让DDPG不只调增益还直接输出一个前馈补偿力矩 (\tau_{comp})。最终动作是3维(\Delta K_p)、(\Delta K_d)、(\tau_{comp})。前馈补偿的意义在于PD只能对误差做比例微分反馈对于已知的扰动力矩比如常值风没有直接抑制作用而DDPG学到的是根据状态直接给出补偿量相当于给PD加了积分和前馈效果。3.3 奖励函数设计实操奖励函数是强化学习里最影响收敛质量的部分。我第一版写的是分段稀疏奖励姿态角误差小于1°就给1否则给0。结果训练了20万步几乎不动因为稀疏奖励导致梯度过零。后来改成密集奖励[ r -(\alpha e_\theta^2 \beta \dot{e}_\theta^2 \gamma \tau^2) b ]其中 (b) 是一个小的正向基础奖励保证不惩罚得太狠。具体系数我用了 (\alpha2.0)、(\beta0.1)、(\gamma0.005)、(b0.5)。注意不要加不必要的“姿态角速度超过阈值直接终止”这种条件除非你想让它学成保守策略。我曾经设置了角速度超过100°/s就终止回合结果DDPG学到的是把增益调得很小避免角速度过大但跟踪速度变得很慢。奖励计算中还需要对控制量 (\tau) 做惩罚这是为了避免DDPG为了追求小误差而输出超大控制力矩毕竟现实电机有饱和限制。(\gamma) 取值要小否则它会牺牲跟踪精度来省能量。3.4 整体控制框架集成整个闭环结构是给定期望姿态角 (\theta_d)PD控制器根据当前误差和增益计算基础控制量 (u_{pd})DDPG的Actor网络根据状态生成增益增量 (\Delta K_p, \Delta K_d) 和补偿量 (u_{comp})最终控制量[ u K_p e_\theta K_d \dot{e}\theta u{comp} ]DDPG在每一步更新增益但更新频率不必与控制器频率相同。我是在500Hz的控制循环里每隔5步即100Hz更新一次增益因为姿态角响应的时间尺度是几十毫秒量级增益变化太频繁会让系统产生高频激励反而让Critic难以学习。在仿真中DDPG与四旋翼动力学是解耦的动力学模块用逐步积分计算状态DDPG只负责观测状态、输出动作然后通过奖励函数获得标量信号。这种结构最大的好处是方便后期替换成真实飞控因为DDPG模块可以作为一个独立的“智能调参器”挂在原有PD外层。4. MATLAB仿真环境搭建与关键代码实现4.1 整体代码架构MATLAB里实现整套系统我建议拆成三个脚本/函数文件quad_dynamics.m四旋翼刚体动力学函数输入控制量输出状态导数。ddpg_agent.m定义DDPG智能体包含Actor和Critic网络的初始化、预测、更新函数。train_quad_ddpg.m主训练脚本负责初始化环境、循环交互、存储经验、调用更新。如果想把运行速度提上去可以把动力学和网络更新都用MATLAB的GPU加速但要注意MATLAB的强化学习工具箱虽然方便却不太好灵活定制网络结构。我最终还是自己手写了DDPG核心代码这样每行都知道在干什么。4.2 四旋翼动力学函数参考四旋翼动力学模型可以采用简化刚体模型忽略气动阻尼和旋翼陀螺效应对验证算法足够了。核心方程如下[ \dot{p} v ] [ m\dot{v} -mg\begin{bmatrix}0\0\1\end{bmatrix} R(\Theta)\begin{bmatrix}0\0\\sum T_i\end{bmatrix} ] [ \dot{\Theta} W(\Theta) \omega ] [ I\dot{\omega} \tau_{xyz} - \omega \times (I\omega) ]这里 (T_i) 是每个旋翼升力(\tau_{xyz}) 是三个轴的控制力矩(R(\Theta)) 是旋转矩阵(W(\Theta)) 是欧拉角速率到角速度的变换矩阵。在我的仿真里我用了更简化的一维姿态通道来做初始验证只控制一个旋转自由度比如roll模型为[ \ddot{\theta} \frac{\tau}{I_{xx}} ]这听着太简单但对强化学习调参足够了。一维通道收敛后再扩展到三轴。下面是roll通道的动力学函数参考function dtheta quad_dynamics_roll(theta, dtheta, tau, I_xx) ddtheta tau / I_xx; dtheta [dtheta; ddtheta]; end实际训练三维姿态时要注意欧拉角奇异问题建议用四元数表达姿态。不过MATLAB自带的角度转四元数函数足够不用自己写。4.3 DDPG智能体的核心类结构我用MATLAB的classdef定义智能体字段包括Actor网络的权重、Critic网络权重、目标网络权重、经验缓存、OU噪声参数等。Actor网络结构是两层全连接隐藏层各128个节点激活函数用ReLU输出层用tanh将动作缩放到[-1,1]然后线性变换到实际动作范围。Critic网络则是状态和动作拼接后过两层全连接输出一个Q值这里隐藏层也是128个节点。下面是Actor网络前向传播和更新的一段关键代码function action actor_forward(obj, state) h1 relu(obj.W1 * state obj.b1); h2 relu(obj.W2 * h1 obj.b2); raw obj.W3 * h2 obj.b3; % 动作范围 [-0.5, 0.5] 对应增益增量 action obj.action_scale * tanh(raw); endCritic的更新损失采用MSEfunction loss critic_loss(obj, s_batch, a_batch, r_batch, s_next_batch, done_batch) q_target r_batch obj.gamma * (1 - done_batch) .* obj.q_target(s_next_batch, obj.actor_target(s_next_batch)); q_pred obj.q_online(s_batch, a_batch); loss mean((q_pred - q_target).^2); endActor的更新用的是梯度上升核心代码policy_gradient obj.critic_gradient_wrt_action(s_batch, a_current); grad_actor -diag(policy_gradient) * actor_jacobian;更简单的方式是用dlgradient自动微分但手写梯度能帮助你理解公式。建议先用MATLAB深度学习工具箱的dlarray自动微分搭通流程再优化为手写版。4.4 主训练循环的搭建训练主循环的伪代码逻辑如下初始化动力学模型参数比如质量、惯量、重力加速度。初始化DDPG智能体设置学习率、折扣因子、软更新系数、经验缓存大小。每个回合开始随机生成期望姿态角可以是阶跃、正弦或随机平滑曲线。每个控制周期读取当前状态。通过Actor网络加噪声输出动作。更新PD增益并计算控制输入。将控制输入传给动力学模型积分一步。计算奖励。存入经验缓存。每隔一定步数从缓存采样batch更新Critic和Actor。每若干回合保存一次网络权重绘制奖励曲线。在训练中我加快速度的一个技巧是不用固定步长仿真直接用ode45积分连续动力学而是用固定步长的离散模型步长1ms。这样DDPG的每一步对应一个固定时间间隔奖励和状态转移都更规范训练也更快。5. 训练调试与效果对比5.1 训练曲线怎么读训练时我们记录每个回合的总奖励和平均姿态角误差。一个比较理想的过程是前几万步奖励快速上升中间有一段平台期之后继续缓慢上升直到收敛。如果奖励曲线在某个数值附近剧烈震荡通常有两种原因一是学习率太大二是探索噪声衰减太快导致策略还没收敛就进入利用阶段。我刚用 (3 \times 10^{-4}) 的学习率训练时Critic损失在5000步之后开始NaN。排查发现是奖励值太大Q值估计爆炸。后来把奖励系数整体缩放并给Critic输出加了L2正则问题立刻消失。所以遇到NaN先检查奖励量级建议让奖励范围维持在[-10,10]之间。5.2 与固定增益PD的效果对比固定PD参数我用试凑法选了一组合适的悬停增益作为基线。DDPG优化后的策略在三种测试场景下做了对比阶跃响应期望roll角瞬间从0°到30°。固定PD超调约3.5°调节时间0.8sDDPG优化后超调1.2°调节时间0.6s。抗风扰动在1s时加入幅值0.2N·m、持续0.5s的扰动力矩。固定PD最大姿态偏差4.1°恢复时间1.2sDDPG优化后最大偏差2.6°恢复时间0.7s。载荷变化把转动惯量增加30%。固定PD出现明显振荡DDPG优化后的增益自适应变化仍能保持稳定。这里的提升不只是Ixx参数的改变关键是DDPG学到了“当误差大时增大 (K_p)当角速度过大时增大 (K_d)”这样的经验性规则相当于自动执行了增益调度。5.3 关键超参数经验表我整理了一份在四旋翼姿态控制场景下比较可靠的DDPG超参数范围供参考。参数建议范围我的取值影响Actor学习率1e-4 ~ 3e-42e-4太高导致策略振荡太低收敛慢Critic学习率1e-4 ~ 3e-42e-4建议与Actor相同或稍大折扣因子 (\gamma)0.95 ~ 0.9950.99越大越考虑长远回报软更新系数 (\tau)0.001 ~ 0.010.005越大目标网络更新越快易发散经验缓存大小1e5 ~ 1e62e5太小样本多样性差Batch大小64 ~ 256128越大更新稳定但计算慢OU噪声 (\sigma)0.1 ~ 0.30.2探索强度OU噪声衰减0.1 ~ 0.30.15衰减过快会过早收敛到局部最优这些参数是耦合的不要单独调整某一个。比如增大学习率的同时最好也增大batch size否则样本利用率跟不上。5.4 训练过程中的实时监控技巧MATLAB训练时我在循环里每100步打印一次当前奖励、Q值和增益。Q值的变化可以判断Critic是否在正常学习如果Q值一直不涨说明奖励设计或状态表达有问题如果Q值涨但实际姿态误差没降说明Critic学到了但Actor没跟着优化需要检查Actor梯度计算。另一个实用技巧是把DDPG输出的 (K_p) 曲线画出来看看它是否在合理范围内震荡。如果 (K_p) 一直顶在上限说明基线PD的增益上限设低了应放宽动作空间如果 (K_p) 快速切换说明噪声过大或更新频率太高可以降低更新频率或者增大动作平滑惩罚。6. 常见问题和故障排查实录6.1 训练初期就发散姿态直接翻车这个我遇到得最多。最常见的原因是动作空间设得太大比如允许 (K_p) 从0变到50DDPG随便一步探索就可能让姿态失稳。解决办法是先用很小的动作范围预训练几万步确认系统稳定后再逐步放宽。我当时是固定 (K_p) 为3.0、(K_d) 为1.0只让DDPG输出补偿力矩训练稳定后再加入增益增量。这样分阶段学习成功率大幅提升。6.2 奖励持续上升但控制性能反而变差这说明奖励函数被“钻空子”了。有次我加入控制量惩罚时 (\gamma) 设得过大DDPG发现让误差一直保持在中等水平、控制量很小奖励反而比误差趋近于零时更高。检查办法是随机固定一个状态手动计算几个动作的奖励看趋势是否符合直觉。也可以把奖励分解成误差项和控制项分别记录用来定位是哪个部分造成误导。6.3 Q值出现NaNQ值NaN大多源自损失太大导致梯度溢出。排查路径先检查奖励里有没有inf比如姿态误差为0而控制量极小时如果惩罚项里有除法就会出现inf。然后检查归一化状态和奖励都最好做归一化处理。我在这里踩过坑直接把角度单位从度变成弧度后误差值缩小了约57倍奖励变成了原来的0.03倍导致梯度消失。后来统一用弧度并对奖励做了标准化问题就消失了。6.4 收敛到局部最优只有一条通道效果好如果只有roll通道效果提升明显pitch通道不理想可能是回合初始化状态分布不均匀。我最初每个回合只给roll方向加阶跃期望导致DDPG只在roll附近探索。改成随机在roll和pitch方向生成期望角并随机加入风扰动后两个通道的性能都趋近一致。这提醒我们状态和期望信号的覆盖范围一定要广否则数据分布偏向某个方向策略学成跛脚。6.5 MATLAB仿真速度太慢怎么办MATLAB的循环效率相比C差距很大尤其是我在训练中每一步都调用神经网络更新。一个有效办法是使用MATLAB的parfor并行跑多个回合的仿真但由于DDPG更新依赖共享经验缓存标准parfor并不可行。另一个常用办法是减少每回合步数比如原本仿真20秒如果训练目标是阶跃跟踪可以缩短到5秒。如果必须长时间仿真我建议把网络更新频率降低例如每10步更新一次网络同时增大batch size能保持稳定。再一个经验是不要每步都把状态转成dlarray这非常耗时。可以先把状态批量缓存统一在更新阶段转换。我的训练速度因此提升了约40%。7. 工程落地与后续扩展7.1 从姿态环推广到位置环这个框架不光能优化PD增益也能优化整个级联控制器。比如外环位置控制器用DDPG输出内环期望姿态角的最大值限制或者输出速度增益。我在三轴姿态训练稳定后尝试让DDPG同时输出内环 (K_p, K_d) 和外环速度增益收敛后实现了四旋翼从悬停到点飞行的小范围位置跟踪。难点在于状态维度增加后训练所需步数显著增加。建议先用姿态环验证算法再逐步扩展。7.2 真实飞行器部署的几点提醒仿真和实机之间有巨大的sim-to-real gap。我总结三个必须处理的问题控制频率差异仿真中我用的500Hz实机飞控通常也是500Hz或更高但如果DDPG推理耗时超过2ms就会影响控制环路。建议部署前先用coder工具将网络生成为C代码或量化到轻量级推理框架。延迟问题仿真中状态是理想可得的实机有传感器滤波延迟。如果状态包含角速度DDPG必须适应这个延迟。可以在仿真中加入一阶低通滤波模拟传感器特性再训练。安全保护DDPG网络在实机上必须设置增益上下限和输出限幅同时准备一个紧急情况切换回固定PD的机制。我建议任何基于强化学习的增益调节算法都要保留底层PD硬限制DDPG只负责在一个安全范围内微调。7.3 还能不能继续改进如果你想让控制效果更顺滑可以考虑以下几个方向在奖励函数中加入控制量的变化率惩罚防止增益突变导致电机转速跳变。使用双延迟DDPGTD3它能有效解决DDPG的Q值高估问题代价是算法更复杂。引入注意力机制或LSTM让Actor网络能利用历史状态增强对扰动模式的识别。在状态空间中增加机体振动特征让算法感知到结构模态从而避免激励机体谐振。我个人目前正在尝试用期望姿态的微分信号作为DDPG的输入让它在跟踪快速变化的轨迹时提前调整增益。从初步结果看动态响应滞后有所改善但训练稳定性还需要继续打磨。回到这次实践本身我最深的体会是DDPG不是万能的它更像一个极其耐心的参数搜索器能在一个高维空间中为你找到“控制律参数与飞行状态的函数关系”。但这个搜索是否有效完全取决于你喂给它的状态、动作和奖励是否像样。MATLAB的灵活性和可视化能力让整个迭代过程非常顺畅但核心难点仍然是控制问题本身——理解动态特性、合理设计奖励、规划安全边界这些工作做扎实了DDPG才能发挥价值。如果你正准备尝试类似方案建议从单通道姿态PD增益优化起步先把基础流程跑通再逐步扩展。最后再分享一个小技巧训练好的Actor网络一定要做输入状态的归一化记录否则换了工况后不同量纲的状态直接输入网络动作会非常离谱。这一段我调试了很久才意识到记录下来希望对你有用。