四旋翼控制这事吧,圈里人基本都绕不开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适合解决这个问题的原因
DDPG(Deep Deterministic Policy Gradient)是连续动作空间的强化学习算法。四旋翼PD增益恰好是连续变量(比如 (K_p \in [0,10])),DDPG天然适配。相比于DQN,它不需要把动作离散化,也就避免了一个增益分辨率带来的性能损失。相比于PPO,DDPG在样本效率上通常更高,尤其适合仿真器中训练——虽然它超参数敏感,但只要控制好探索噪声和经验回放,稳定性也能接受。
再一个原因是,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_{i+1}, \mu'(s_{i+1})) ]
这里 (\mu') 和 (Q') 是目标网络。软更新系数 (\tau) 通常取0.001或0.005,更新太慢学习慢,更新太快容易发散。我做过对比,(\tau=0.001) 时姿态曲线的收敛速度比0.01慢了大约30%,但全程更稳,没有出现后期振荡。飞行控制场景我偏向小 (\tau)。
2.3 探索与利用的平衡实践
DDPG是确定性策略,如果不加噪声,它一开始就输出固定动作,根本不会探索。标准做法是给动作叠加Ornstein-Uhlenbeck噪声,因为OU噪声在时间维度上相关,能让增益值平滑变化,更符合物理系统的连续特性。相比之下,高斯白噪声会让 (K_p) 在相邻步长之间突变,四旋翼控制信号会抖得厉害。
在MATLAB里实现OU噪声不复杂,核心迭代式是:
[ x_{t+1} = x_t + \theta(\mu - x_t) + \sigma \mathcal{N}(0,1) ]
我用的参数是 (\theta=0.15)、(\mu=0)、(\sigma=0.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) 是一个小的正向基础奖励,保证不惩罚得太狠。具体系数我用了 (\alpha=2.0)、(\beta=0.1)、(\gamma=0.005)、(b=0.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的更新损失采用MSE:
function 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.8s;DDPG优化后超调1.2°,调节时间0.6s。
- 抗风扰动:在1s时加入幅值0.2N·m、持续0.5s的扰动力矩。固定PD最大姿态偏差4.1°,恢复时间1.2s;DDPG优化后最大偏差2.6°,恢复时间0.7s。
- 载荷变化:把转动惯量增加30%。固定PD出现明显振荡,DDPG优化后的增益自适应变化,仍能保持稳定。
这里的提升不只是Ixx参数的改变,关键是DDPG学到了“当误差大时增大 (K_p),当角速度过大时增大 (K_d)”这样的经验性规则,相当于自动执行了增益调度。
5.3 关键超参数经验表
我整理了一份在四旋翼姿态控制场景下比较可靠的DDPG超参数范围,供参考。
| 参数 | 建议范围 | 我的取值 | 影响 |
|---|---|---|---|
| Actor学习率 | 1e-4 ~ 3e-4 | 2e-4 | 太高导致策略振荡,太低收敛慢 |
| Critic学习率 | 1e-4 ~ 3e-4 | 2e-4 | 建议与Actor相同或稍大 |
| 折扣因子 (\gamma) | 0.95 ~ 0.995 | 0.99 | 越大越考虑长远回报 |
| 软更新系数 (\tau) | 0.001 ~ 0.01 | 0.005 | 越大目标网络更新越快,易发散 |
| 经验缓存大小 | 1e5 ~ 1e6 | 2e5 | 太小样本多样性差 |
| Batch大小 | 64 ~ 256 | 128 | 越大更新稳定,但计算慢 |
| OU噪声 (\sigma) | 0.1 ~ 0.3 | 0.2 | 探索强度 |
| OU噪声衰减 | 0.1 ~ 0.3 | 0.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变到50,DDPG随便一步探索就可能让姿态失稳。解决办法是先用很小的动作范围预训练几万步,确认系统稳定后再逐步放宽。我当时是固定 (K_p) 为3.0、(K_d) 为1.0,只让DDPG输出补偿力矩,训练稳定后再加入增益增量。这样分阶段学习,成功率大幅提升。
6.2 奖励持续上升但控制性能反而变差
这说明奖励函数被“钻空子”了。有次我加入控制量惩罚时 (\gamma) 设得过大,DDPG发现让误差一直保持在中等水平、控制量很小,奖励反而比误差趋近于零时更高。检查办法是随机固定一个状态,手动计算几个动作的奖励,看趋势是否符合直觉。也可以把奖励分解成误差项和控制项,分别记录,用来定位是哪个部分造成误导。
6.3 Q值出现NaN
Q值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 还能不能继续改进
如果你想让控制效果更顺滑,可以考虑以下几个方向:
- 在奖励函数中加入控制量的变化率惩罚,防止增益突变导致电机转速跳变。
- 使用双延迟DDPG(TD3),它能有效解决DDPG的Q值高估问题,代价是算法更复杂。
- 引入注意力机制或LSTM,让Actor网络能利用历史状态,增强对扰动模式的识别。
- 在状态空间中增加机体振动特征,让算法感知到结构模态,从而避免激励机体谐振。
我个人目前正在尝试用期望姿态的微分信号作为DDPG的输入,让它在跟踪快速变化的轨迹时提前调整增益。从初步结果看,动态响应滞后有所改善,但训练稳定性还需要继续打磨。
回到这次实践本身,我最深的体会是:DDPG不是万能的,它更像一个极其耐心的参数搜索器,能在一个高维空间中为你找到“控制律参数与飞行状态的函数关系”。但这个搜索是否有效,完全取决于你喂给它的状态、动作和奖励是否像样。MATLAB的灵活性和可视化能力让整个迭代过程非常顺畅,但核心难点仍然是控制问题本身——理解动态特性、合理设计奖励、规划安全边界,这些工作做扎实了,DDPG才能发挥价值。如果你正准备尝试类似方案,建议从单通道姿态PD增益优化起步,先把基础流程跑通,再逐步扩展。最后再分享一个小技巧:训练好的Actor网络,一定要做输入状态的归一化记录,否则换了工况后,不同量纲的状态直接输入网络,动作会非常离谱。这一段我调试了很久才意识到,记录下来希望对你有用。