最近帮A同学调了一套无人机避障的仿真代码他拿着一些经典的Q-Learning栅格迷宫案例直接往三维动态环境里套结果路径规划出来的航线不是在障碍物附近反复横跳就是绕远路绕到电量耗尽。这个问题我太熟悉了。二维静态栅格下的Q-Learning教程遍地都是可一旦任务变成基于动态三维环境下的无人机自主避障路径规划难度会上升好几个量级状态空间爆炸、奖励稀疏、环境非平稳导致Q值震荡每一个坑都能让人调到头秃。这篇博文就围绕这个课题展开用Matlab完整实现一遍Q-Learning算法在动态三维环境中的无人机避障路径规划。文章会从算法选型逻辑讲到环境建模再到核心代码拆解、奖励函数设计和动态障碍物下的避坑经验。无论你是正在做课程设计、毕设还是想入门强化学习在机器人领域的应用这篇内容都能直接给你一套可复现的操作路径。1. 为什么选Q-Learning做避障先算清楚这笔账1.1 Q-Learning的核心逻辑其实是一本账本先把Q-Learning的原理用大白话捋一遍。所谓Q值就是在某个状态下执行某个动作长期来看能拿到的累计回报——可以把它想象成一本不断更新的账本账本上每一格记录的是某个状态s下采取动作a的期望收益。算法要做的事情很简单不断尝试、不断记账、不断修正对收益的估计直到账本收敛到能指导决策的稳定数值。更新公式是经典的时序差分形式Q(s, a) Q(s, a) alpha * (r gamma * max(Q(s, a)) - Q(s, a));这个公式的直觉理解方式是拿当前账本里的旧估计加上实际拿到的奖励 未来最优收益的折算 - 旧估计这个差值的一部分逐步逼近真实价值。alpha是学习率决定每一步修正的幅度gamma是折扣因子代表对远期收益的重视程度。关键是Q-Learning属于model-free算法也就是它不需要知道环境的动力学模型——无人机下一步会飞到哪、风怎么吹、障碍物怎么动这些统统不用显式建模只需要通过大量试探让Q值自己记住哪些选择能带来高回报。这个特性在无人机避障场景下太有吸引力了因为真实飞行环境往往很难精确建模你能拿到的只有传感器读数。1.2 和常见路径规划算法比它赢在哪、输在哪很多人会问A*、RRT、人工势场法不是也能做避障吗为什么非要用Q-Learning我做了一组对比这个问题就清楚了。算法环境适应性动态障碍物支持需要环境模型实现复杂度适用场景A*静态栅格弱需重规划需要栅格地图低全局路径搜索RRT静态/低速变化弱重规划代价高需要碰撞检测模块中高维空间搜索人工势场静态为主易陷入局部极小值需要势场函数低局部避障Q-Learning动态环境可训练通过训练隐式适应不需要中离散状态空间的在线/离线决策Q-Learning最突出的优势在于它不依赖实时重规划只要训练阶段见过足够多样的动态场景训练好的策略在运行时就是一张查表决策响应速度快。而A*和RRT在障碍物移动后往往要重新搜索计算开销随环境复杂度陡增。但它的短板也很明显状态空间一爆炸Q表就变得巨大训练时间根本扛不住。所以这个算法的适用前提是——把三维空间的状态表示控制在一个可接受的离散规模内。这正是本文后面环境设计部分要重点解决的问题。2. 三维动态环境建模把连续空间装进Q表2.1 三维栅格与状态编码先回答矩阵能不能装下Q-Learning的前提是状态可枚举所以第一步就是把连续的三维空间离散化。我的做法是把飞行区域划分为均匀的三维栅格。以100m × 100m × 50m的仿真区域为例栅格尺寸取5m那么三个维度分别有20、20、10个格子总状态数就是4000个。这里有一个经验之谈栅格尺寸不能拍脑袋定。太小状态数按三次方增长训练时间爆炸太大路径精度差无人机贴着障碍物边缘飞的时候容易因为离散化误差撞上去。一般取无人机自身尺寸的1.5到2倍比较合理比如翼展2.5米左右的机型栅格取5米就够用了。状态编码也需要设计。最简单的方案是直接用无人机所在栅格的坐标作为状态索引把(x, y, z)映射成一维编号state_index sub2ind([Nx, Ny, Nz], x_idx, y_idx, z_idx);这个方案只用了坐标信息适合环境相对简单的场景。如果还想让无人机感知到目标方向、障碍物距离等信息可以把这些信息也拼接到状态里但每加一个维度Q表规模就指数级膨胀。我的建议是初期先用纯坐标状态跑通全流程再加入更多感知信息。2.2 动作空间设计6方向还是12方向动作空间决定了无人机每一步能往哪走。最基本的方案是6方向动作前后、左右、上下再加上一个悬停动作共7个。这个设计的好处是状态转移简单、Q表列数少、训练快坏处是无人机只能轴向运动绕障轨迹会显得很机械。如果想更贴近真实飞行可以加入偏航动作变成12方向% 动作编号 1-12 分别对应 % 前、后、左、右、上、下、 % 前左、前右、后左、后右、左上、右上这里可以按需扩展12方向的动作能让路径更平滑、更短但动作空间变大意味着训练所需样本量明显增加。实测下来在动态环境里7动作训练5000回合能达到的效果12动作可能要8000回合以上。对于入门复现我强烈建议先用7动作跑通再往细了做。2.3 动态障碍物的建模方式动态环境的关键在于障碍物的运动状态。我在Matlab里用两种典型模型来模拟动态障碍物周期性运动障碍物沿固定路径往返运动比如 x x0 A * sin(w * t)模拟巡逻机器人或摆动机器臂随机游走障碍物每一步以一定概率向随机方向移动模拟行人或未知移动体。碰撞检测直接用几何距离判断。把无人机和障碍物都抽象成球体只要两个球心距离小于半径之和就判定碰撞。collision (norm(obs_pos - uav_pos) obstacle_radius uav_radius);有人问动态障碍物为什么要用球体抽象因为球体碰撞检测只算一次欧氏距离计算量最小在训练阶段每个episode要做成百上千次检测这一点性能差异非常关键。障碍物形状复杂也别怕可以用多个球体叠加近似。3. Matlab实现核心代码从Q表初始化到训练循环逐段拆解3.1 环境初始化和Q表分配Matlab实现的核心骨架可以分成三块环境参数初始化、Q表分配、训练主循环。先看环境初始化%% 环境参数配置 map_size [100, 100, 50]; % 飞行区域尺寸 (m) grid_size 5; % 栅格尺寸 (m) n_states prod(floor(map_size / grid_size)); % 状态总数 start_pos [5, 5, 5]; % 起点坐标 goal_pos [95, 95, 45]; % 目标点坐标 % 障碍物设置静态动态 static_obs [30, 30, 20, 8; % [x, y, z, 半径] 70, 60, 30, 10; 50, 80, 15, 6]; dynamic_obs struct(center, [60, 40, 25], ... radius, 7, ... amplitude, [15, 0, 0], ... frequency, 0.05, ... phase, 0);Q表的分配要看状态数。7个动作、4000个状态Q表只是一个4000×7的矩阵在Matlab里几乎不占内存。%% Q表初始化 n_actions 7; Q_table zeros(n_states, n_actions);但如果你在状态里加入了障碍物相对位置信息状态数会剧烈膨胀。比如每个栅格额外记录四周6个方向是否有障碍物乘数因子瞬间到几十倍。这种情况下Q表可能达到十万甚至百万级zeros矩阵就装不下了得用稀疏矩阵或容器保存Q_table containers.Map(KeyType, int32, ValueType, any);我个人的建议是先跑通小规模场景再用稀疏手段优化不要在初版代码里引入不必要的复杂度。3.2 训练主循环与ε-greedy策略训练的核心是ε-greedy探索策略——以ε的概率随机探索以1-ε的概率选择当前Q值最大的动作。ε设置太大会一直乱飞太小则容易过早陷入局部最优。%% 训练参数 alpha 0.1; % 学习率 gamma 0.9; % 折扣因子 epsilon 0.9; % 初始探索率 epsilon_min 0.05; % 最小探索率 epsilon_decay 0.999; % 探索率衰减系数 max_episodes 3000; max_steps 200; %% 训练主循环 for episode 1:max_episodes state discretize_state(start_pos, grid_size, map_size); total_reward 0; for step 1:max_steps % ε-greedy选择动作 if rand() epsilon action randi(n_actions); % 探索 else [~, action] max(Q_table(state, :)); % 利用 end % 执行动作得到下一状态和奖励 [next_pos, reward, done] step_environment(uav_pos, action, ...); next_state discretize_state(next_pos, grid_size, map_size); % Q值更新 [max_next_q, ~] max(Q_table(next_state, :)); Q_table(state, action) Q_table(state, action) ... alpha * (reward gamma * max_next_q - Q_table(state, action)); state next_state; total_reward total_reward reward; if done break; end end % 探索率衰减 epsilon max(epsilon * epsilon_decay, epsilon_min); % 每隔100回合打印训练进度 if mod(episode, 100) 0 fprintf(Episode %d, Total Reward: %.2f, Epsilon: %.3f\n, ... episode, total_reward, epsilon); end end这里有一个很多人容易犯的错在步进循环内部没有更新无人机位置变量导致状态一直不变Q表学了个寂寞。每次执行动作后必须同步更新uav_pos。3.3 环境步进函数和状态索引映射step_environment是整个仿真联动的核心。它要做的事包括根据动作计算新坐标、检查是否越界、检查是否碰撞、判断是否到达目标点、计算并返回奖励。function [next_pos, reward, done] step_environment(uav_pos, action, grid_size, ...) % 动作到坐标增量的映射表 action_delta [ 1, 0, 0; % 1: 前 (x) -1, 0, 0; % 2: 后 (-x) 0, 1, 0; % 3: 左 (y) 0, -1, 0; % 4: 右 (-y) 0, 0, 1; % 5: 上 (z) 0, 0, -1; % 6: 下 (-z) 0, 0, 0 % 7: 悬停 ]; delta action_delta(action, :); next_pos uav_pos delta * grid_size; % 默认奖励 reward -1; % 每一步的步进惩罚 done false; % 碰撞判定包含静态和动态障碍物 if check_collision(next_pos, static_obs, dynamic_obs, t) reward -100; done true; return; end % 到达目标判定 if norm(next_pos - goal_pos) grid_size reward 100; done true; return; end % 越界判定 if any(next_pos 0) || any(next_pos map_size) reward -50; done true; return; end end状态索引映射的reverse操作也要注意。训练完成后要从Q表提取路径需要把一维索引还原为三维坐标[x_idx, y_idx, z_idx] ind2sub([Nx, Ny, Nz], state_index); pos [x_idx, y_idx, z_idx] * grid_size;4. 奖励函数设计稀疏奖励为什么坑怎么塑形才不绕圈4.1 最简单的奖励设置为什么训不动初学者最容易犯的错是把奖励设成到终点给100其余时刻给0。这在二维小迷宫里勉强能跑但在三维动态环境里基本不可能收敛。原因很简单无人机在4000个栅格中随机探索能恰好摸到终点的概率微乎其微。没有中间反馈Q值梯度几乎处处为零训练就是瞎碰运气。我在实验里做过对比用稀疏奖励训练2000回合成功到达目标点的次数为0改为距离奖励后500回合左右就能看到明显的成功率上升。所以奖励塑形不是优化项是必需品。4.2 奖励塑形的三个坑第一个坑步进惩罚设太大。比如每一步惩罚-10代理会倾向于原地悬停因为任何移动的即时代价都大于远期收益的折现。我在初期把步进惩罚设成-5结果无人机学会了一动不动训练曲线死水一潭。一般来说步进惩罚在-0.1到-1之间比较合理。第二个坑距离奖励占比过高。如果每步奖励与到目标距离的减少量强绑定无人机很可能被困在局部最优——它会先冲向目标方向遇到障碍物后不会绕行而是在障碍物前反复怼墙因为怼墙也能获得距离奖励。这个现象在动态障碍物场景尤为明显。第三个坑奖励尺度不一致。碰撞惩罚-100到达奖励100但常规步进只有-1数量级差太多导致Q值在事件边界处剧烈跳变训练曲线震荡。解决办法是让各奖励项尺度保持在同一个数量级内。4.3 一套经过验证的奖励模板综合几次调参经验我整理出一套比较稳的奖励参数可以直接抄作业奖励项目数值设置理由到达目标50正奖励不需要太大够引导就行碰撞-50与到达奖励同量级避免Q值跳变过大越界-30比碰撞轻一点但仍要明显负向步进惩罚-0.5鼓励尽快到达但不会压制移动接近目标奖励0.5 × ΔdΔd为本步与目标的距离减少量正向引导远离目标惩罚-0.5 × ΔdΔd为距离增加量防止乱逛进入障碍物危险区-5距离小于2倍半径时给出警示这个模板的核心思路是用大额奖励处理边界条件碰撞、到达、越界用小额连续奖励塑造过程行为接近目标、远离危险。两种尺度相互配合既给了方向感又不会造成Q值波动过大。注意一个细节进入危险区的惩罚要是步进惩罚的数倍但不能超过碰撞惩罚的1/2否则无人机可能为了躲避危险区绕极远的路反而降低效率。5. 动态障碍物下的实测表现收敛波折与三类典型翻车现场5.1 训练曲线的奇怪走向我在仿真平台上进行了多组训练动态障碍物场景有一个非常典型的特征累计奖励曲线呈现先快速上升、后持续波动的形态。原因在于动态障碍物的运动会改变同一状态下的最优决策——某个栅格在t时刻是安全的t1时刻可能就进入了碰撞区。这意味着Q-Learning收敛的理论前提环境静态其实被破坏了。标准Q-Learning的收敛性证明基于马尔可夫决策过程而动态障碍物让状态转移概率随时间变化问题变成了非平稳MDP。体现在训练曲线上就是Q值永远不会完全稳定而是在一个区间内浮动。5.2 三个典型问题与排查过程我在调试过程中遇到了三个非常典型的翻车案例每个排查链路都值得记一笔。第一个是训练前期频繁崩溃。现象是前几百回合累计奖励暴跌到-5000以下看起来像代码写崩了。排查后发现并不是Bug而是epsilon初始值太高无人机大量随机飞行在障碍物密集区反复碰撞。解决方案是把初始epsilon从0.9降到0.7同时加快衰减速度。训练前期的碰撞是必要的探索但不能让探索持续太久。第二个是路径在动态障碍物附近反复横跳。跑出来的路径不是平滑绕障而是在一个区域来回抖动。排查后发现是危险区惩罚设置过重无人机每步都在远离危险区和接近目标之间激烈博弈最终表现为抖动。把危险区惩罚从-10降到-5后抖动明显缓解。第三个是最麻烦的训练1500回合后到达目标的成功率反而下降了。排查过程从奖励参数开始检查未发现问题随后检查epsilon衰减发现已衰减到0.05进入纯利用阶段。但纯利用并不该导致成功率下降。最后定位到问题根源——动态障碍物的随机游走模型导致某些状态在训练后期出现了此前未见过的情况而epsilon太低导致代理丧失了应对新情况的探索能力。解决方案是设置一个epsilon下限反弹机制当连续一定回合成功率下降时自动调高epsilon重新探索。这三个问题总结成一张表问题现象根因分析解决方案前期累计奖励暴跌探索率过高碰撞过于频繁降低初始epsilon并加速衰减路径频繁抖动危险区惩罚与目标奖励博弈失衡调低危险区惩罚系数后期成功率不升反降探索率过低无法适应动态变化增加epsilon反弹机制5.3 动态环境的本质矛盾与应对思路动态环境给Q-Learning带来的根本挑战是环境非平稳性导致Q值更新始终在追赶一个移动的目标。这不是调参能彻底解决的需要从算法层面认识它的边界。一个务实的应对思路是先静态后动态先在静态障碍物环境中训练到Q表收敛再用收敛后的Q表作为动态环境的初值继续训练。这个迁移策略在实验里非常有效训练总回合数可以减少约40%。原因是静态环境下学到的绕障经验对动态场景同样适用动态训练只需要在此基础上增量学习障碍物的运动规律。另一个思路是降低对Q表收敛的执念改为关注策略稳定性——不再追求Q值不变而是追求决策动作序列在连续多次评估中保持一致。我在评估阶段采用的方法是固定Q表运行100次仿真统计成功率、平均路径长度、平均碰撞次数三项指标只有三项指标同时达标才算策略合格。6. 参数调优与后续扩展踩了这么多坑之后留下的建议6.1 核心参数的经验区间参数调优是Q-Learning实践中最耗时的部分我把自己常用的参数区间整理成一张速查表参数经验区间备注学习率 alpha0.05-0.15动态环境建议偏小抑制Q值震荡折扣因子 gamma0.85-0.95越大越看重长远收益但收敛越慢初始 epsilon0.7-0.9环境越复杂初始探索率应越高最小 epsilon0.02-0.1保留一定探索能力以应对动态变化epsilon 衰减0.995-0.999衰减过快会过早进入纯利用阶段步进惩罚-0.1到-1绝对值太大压制移动太小不鼓励高效路径碰撞惩罚-50到-100与到达奖励保持同量级关于动态环境的一个个人体会alpha取0.05比0.1更好。虽然收敛速度下降了一些但Q值的震荡幅度明显减小策略稳定性好很多。道理不难理解——非平稳环境中每个Q值更新里都包含过时信息的噪声学习率越小对噪声的敏感度越低。6.2 训练结束后如何优雅地提取路径很多人在这一步翻车训练完成之后直接贪心选择最大Q值走一条路线发现路径是折线而且最后一段离目标总差一两格。原因是离散化误差。解决办法有两个一是路径平滑后处理用三次样条插值把折线路径变成平滑曲线二是缩小栅格尺寸重新训练代价是训练时间增加。平滑处理我推荐用Matlab自带的cscvn或interp1代码量很小% 提取Q表决策路径 path extract_path(Q_table, start_pos, goal_pos); % 三次样条平滑 smooth_path fnplt(cscvn(path));路径平滑在实际使用中不是锦上添花而是必要工序。因为离散化造成的锯齿路径直接作为飞行轨迹输入飞控会带来不必要的频繁机动既耗能又加速传感器疲劳。6.3 可扩展的改进方向如果你做完基础版想往深了走有三个方向值得关注。第一个是SARSA算法。它和Q-Learning的区别在于更新用的不是最大未来Q值而是实际执行动作的Q值策略更保守在动态环境里碰撞率通常更低。代价是收敛速度慢一些。如果你的场景对安全性要求极高SARSA值得替换试一下。第二个是Double Q-Learning。Q-Learning的max操作天然引入Q值过估计在动态环境的噪声加持下过估计更严重。Double Q-Learning用两张Q表交替更新能显著降低过估计偏差实现起来也不复杂。第三个是深度强化学习路线。当状态空间大到稀疏矩阵都存不下时就该考虑用神经网络近似Q函数了也就是DQN、DDPG这条路线动作空间也可以扩展到连续控制。这部分代码量和调试复杂度都会上一个台阶建议先把Q-Learning版本彻底跑明白再动手。最后再分享一个小技巧训练好的Q表一定要保存到MAT文件里我之前吃过亏——一个调好的模型因为忘了保存重新跑了一遍又是大半天。用一句话总结这次实践带给我的感受Q-Learning在动态三维环境里的难点从来不在算法本身而在于你怎么把真实世界的问题翻译成一张Q表能装下的符号系统。翻译得好后面的路就顺了。
阅读完成 · 觉得有帮助?