尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

融合Q-learning与人工势场:无人机航迹规划MATLAB实战

融合Q-learning与人工势场:无人机航迹规划MATLAB实战 最近在做一个无人机航迹规划的仿真项目目标是在二维静态障碍环境下让无人机从起点安全飞到终点同时路径要尽可能短、平滑度要够。试了几种传统方法之后最终采用了Q-learning强化学习与人工势场融合的混合算法在MATLAB里完成整套仿真。这篇文章就是这次实践的完整复盘从算法选型的思路、原理拆解、代码实现到调试踩坑全部摊开讲清楚希望对做路径规划、强化学习入门或者MATLAB仿真的朋友有实际帮助。先说结论融合算法比单一算法效果更好但难点不在“能跑通”而在于怎么设计状态空间、奖励函数和切换机制。项目本身适合有一定MATLAB基础、对路径规划或强化学习感兴趣的读者哪怕你是刚接触Q-learning按文章里的思路一步步来也能复现出一套可用的仿真结果。1. 方案选型为什么要把Q-learning和人工势场搅在一起1.1 先看单一算法各自有什么毛病人工势场法APF是路径规划里非常经典的方法核心思想很直观目标点对无人机产生引力障碍物产生斥力无人机沿着合力方向前进。这个方法的优点是计算量小、实时性好MATLAB里几十行代码就能出效果非常适合做动态避障的底层控制器。但它的缺点同样明显最有名的两个问题一是局部极小值陷阱。当无人机、目标点和障碍物在一条直线上时斥力和引力可能恰好大小相等、方向相反无人机就卡在原地不停震荡永远到不了终点。这在复杂障碍分布下特别容易触发比如U型陷阱和走廊夹道场景。二是目标不可达问题GNRON。如果障碍物就在目标点附近斥力会随距离减小而增大无人机还没靠近目标就被推走了。我刚开始测试的时候明明终点附近只有一个很小的障碍物无人机就是绕不进去这让我一度怀疑代码写错了后来才发现是势场模型本身的坑。Q-learning是强化学习里的基础算法模型无关不需要知道环境转移概率靠试错和奖励信号学到策略。它的优势在于离散状态空间下能收敛到最优策略天然适合解决APF的局部极小值问题。但Q-learning也有自己的短板动作空间一旦离散化路径就会变成折线段不够平滑训练收敛速度慢状态空间太大时内存爆炸而且纯Q-learning规划出的路径往往贴着障碍物走安全裕度很难保证。1.2 融合的设计思路谁管宏观谁管微观融合算法的核心不是简单把两种方法堆在一起而是明确分工。在我这个项目里设计思路是Q-learning负责宏观决策在栅格化的环境中学习出一条无碰撞的大致走向也就是知道当前在哪个栅格、下一步该往哪个方向走。这个层面解决经典APF最难搞定的“被困住”问题因为Q-learning的探索机制允许无人机先远离目标再绕回不追求每一步都朝终点。APF负责微观平滑在每个栅格单元内部用人工势场计算精确的合力方向把Q-learning给出的离散动作转化成连续的移动指令。这样路径不再是锯齿状而是一条有物理意义的平滑曲线。换句话说Q-learning是在“战略层”做规划APF是在“战术层”做跟踪。融合之后Q-learning帮APF跳出局部极小值APF帮Q-learning消除离散化带来的路径抖动。必须提醒一点融合的难点在于切换逻辑。我在测试中发现如果每一个栅格步长都让APF完全接管Q-learning训练出的策略根本没用如果完全让Q-learning动作主导路径质量又不达标。最终采用的方案是让APF作为基础控制器Q-learning作为“干预层”只在检测到局部极小值特征比如连续几步位移极小、速度方向反复翻转时才介入重新规划下一步的大方向。这个设计避免了频繁切换导致的震荡问题也让训练负担小很多。1.3 为什么选MATLAB而不是Python很多朋友问过这个问题。从复现角度来看MATLAB的优势在于矩阵运算和可视化集成度高调试路径规划这类二维问题非常顺手plot函数随手画图比Python要调matplotlib方便不少。我早期用Python配NumPy和Matplotlib跑过一版环境搭建、绘图、数据处理来回切精力消耗比算法本身还大。另外MATLAB的Simulink后续可以方便地接无人机动力学模型这篇仿真虽然只做了运动学层面的路径规划但如果后续要做控制闭环验证MATLAB的迁移成本会低很多。当然Python在深度学习扩展方面更强比如想上DQN、PPO这些深度强化学习算法MATLAB的Deep Learning Toolbox虽然也能做但生态和社区资源确实不如PyTorch丰富。我的建议是先跑通逻辑重点在算法验证MATLAB足够要部署到真机或扩展深度强化学习就转Python。2. 核心原理拆解从Q表格更新到势场力合成2.1 Q-learning部分状态空间、动作空间和奖励函数怎么定Q-learning的原理一句话就能说清维护一张Q表Q(s,a)表示在状态s下执行动作a的期望累积奖励每次执行动作后根据环境反馈的奖励R更新Q值。更新公式如下Q(s,a) Q(s,a) alpha * (R gamma * max(Q(s, a)) - Q(s,a))其中alpha是学习率gamma是折扣因子。这个公式的核心是用未来的最大Q值来修正当前的Q值估计相当于在时间上反向传播奖励信息。在这篇文章的仿真里具体设计如下状态空间把飞行区域划分成20×20的栅格每个栅格是一个状态状态编号可以映射为(row, col)。为了让Q-learning学到“当前位置目标方向”的信息我额外把目标方位分成8个扇区编码进状态这样状态空间从400扩展到了3200个但仍在Q表格能接受的范围内。需要说明的是如果环境更大网格更细状态空间会指数增长这时就要考虑函数逼近了。动作空间无人机在每个栅格可选择8个方向动作对应8邻域移动加上原地等待一共9个动作。原地点在正常训练中很少被选中但我故意保留它因为在某些狭窄通道里原地等待配合探索能帮无人机“想清楚”下一步怎么走反而提高了训练成功率。奖励函数这是整个融合算法里对结果影响最大的部分。我的设计是到达终点100撞到障碍物-50每一步移动-1惩罚步数鼓励短路径距离目标比上一步更近5正向引导距离目标比上一步更远-2反向惩罚这个奖励结构初看没问题但实际训练中发现一个问题无人机容易在终点附近“原地转圈”刷距离奖励。后来我在奖励里加了“若与上一步状态相同则额外惩罚-3”并减少了距离奖励的权重收敛速度才有明显提升。奖励函数设计一定要和动作特性匹配否则智能体很容易找到刷分的漏洞。为了平衡探索和利用我使用epsilon-greedy策略初始探索率0.9随着训练轮数线性衰减到0.05。衰减速度不能太快我试过两版线性衰减到0.1之后训练还没收敛加快衰减确实短暂提升了早期奖励但最终策略反而偏差。经验是探索率衰减到最终值的时间点最好设置在总训练轮数的50%-60%附近。2.2 人工势场部分引力、斥力与改进人工势场的数学原理很清晰。目标点产生引力势场U_att 0.5 * k_att * d^2其中k_att为引力增益系数d为无人机到目标点的距离。引力是势场的负梯度方向大小随距离增大而线性增大。这里要注意的是如果直接用二次势场距离很远时引力会非常大可能直接把无人机拉向障碍物所以实际中一般设置一个距离阈值超过阈值改用线性势场。障碍物产生斥力势场U_rep 0.5 * k_rep * (1/rho - 1/rho0)^2 (当rho rho0) U_rep 0 (当rho rho0)其中rho为无人机到障碍物的距离rho0是斥力影响半径k_rep是斥力增益系数。斥力只在影响半径内生效这样也避免远端障碍物干扰路径选择。经典的斥力场模型有个问题无人机靠近目标时如果目标附近有障碍物引力会变小但斥力不一定会变小导致无人机永远到不了终点。解决这个问题的常用手段是在斥力函数里乘上距离因子(d^n)让斥力在靠近目标时自动衰减。我在仿真里用的就是这个改进方案n取2效果明显目标附近即使有障碍物也能顺利抵达。合力计算则是简单的矢量叠加F_total F_att sum(F_rep)无人机沿着合力方向移动。这里有一个容易忽略的细节多个障碍物产生的斥力必须逐个计算再叠加不能只取最近的障碍物。我一开始图省事只取了最近障碍物结果无人机在两堵墙中间穿行时路径诡异——它根本感知不到第二堵墙的存在。2.3 融合逻辑状态判断与优先权切换融合的核心在于设计一个切换判据。我的实现思路是维护一个“被困计数器”每当无人机的单步位移小于设定阈值比如0.1m或者连续3步的位移方向明显振荡航向角变化超过60度就判定进入了局部极小值状态。此时暂停APF模式切换到Q-learning模式由Q表给出当前栅格下的最优动作方向无人机先朝这个方向移动一格再重新回到APF模式下继续精确跟踪。这个设计的好处是Q-learning在训练时已经学会了全局避障知道“往哪个方向走才能最终到达终点”。当APF困住时Q-learning给出的动作往往会把无人机带离陷阱区域等离开之后APF又能正常工作。我在代码里为这个切换加了一个冷却时间避免无人机在两种模式之间来回抖动。3. MATLAB仿真实现全流程3.1 环境搭建与参数初始化整个仿真我分三个脚本组织init_params.m负责参数设置和地图生成train_qlearning.m负责Q-learning训练main_simulation.m负责融合规划并输出结果。这样分开写调试起来不用每次都跑全部代码。先看参数初始化部分% init_params.m clc; clear; close all; %% 地图参数 map_size 20; % 地图大小 20x20 start_pos [1, 1]; % 起点坐标 goal_pos [18, 18]; % 终点坐标 %% 障碍物设置矩形障碍物 obstacles [ 4, 4, 6, 6; % [左下角x, 左下角y, 右上角x, 右上角y] 10, 3, 12, 7; 7, 10, 9, 12; 14, 12, 16, 16; 3, 14, 5, 18; ]; %% APF参数 k_att 1.0; % 引力增益系数 k_rep 2.0; % 斥力增益系数 rho0 2.5; % 斥力影响半径 step_size 0.2; % APF单步移动距离 max_iter 500; % 最大迭代步数 threshold_dist 0.3; % 到达终点判定距离 %% Q-learning参数 num_states map_size * map_size * 8; % 状态 栅格位置 x 目标方位扇区 num_actions 9; % 8个方向 原地等待 alpha 0.1; % 学习率 gamma 0.9; % 折扣因子 epsilon 0.9; % 初始探索率 epsilon_min 0.05; % 最小探索率 epsilon_decay 0.995; % 探索率衰减系数 num_episodes 500; % 训练轮数为什么状态空间要乘8因为我把目标方位分成了8个扇区编码进状态。这样做的动机是普通Q-learning只知道“我在哪个栅格”但不知道目标在哪边刚训练时完全是随机乱撞。加入方位信息后Q表能更快地学到“目标在东北方向时我应该尽量往东往北走”。这个改动把收敛轮数从大约400轮降低到250轮左右效果很显著。障碍物我直接用矩形表示这样在地图上判定是否碰撞非常简单检查点是否落在某个矩形范围内即可。如果大家用的是圆形障碍物碰撞判定改成距离判断就行原理一样。3.2 Q-learning训练核心代码训练部分的核心代码如下% train_qlearning.m load(params.mat); % 省略参数传递细节 %% 初始化Q表 Q zeros(num_states, num_actions); %% 定义动作对应的栅格位移 % 8个方向上、右上、右、右下、下、左下、左、左上 actions [0, 1; 1, 1; 1, 0; 1, -1; 0, -1; -1, -1; -1, 0; -1, 1; 0, 0]; %% 障碍物栅格标记 occupy_map false(map_size, map_size); for i 1:size(obstacles, 1) x1 obstacles(i,1); y1 obstacles(i,2); x2 obstacles(i,3); y2 obstacles(i,4); occupy_map(x1:x2, y1:y2) true; end %% 训练主循环 episode_rewards zeros(1, num_episodes); for ep 1:num_episodes % 状态初始化 drone start_pos; total_reward 0; % 计算初始状态编号(栅格 目标方位) state compute_state(drone, goal_pos, map_size); for step 1:200 % 每轮最多200步 % epsilon-greedy选择动作 if rand() epsilon action randi(num_actions); else [~, action] max(Q(state, :)); end % 执行动作 new_drone drone actions(action, :); % 边界检查 if new_drone(1) 1 || new_drone(1) map_size || ... new_drone(2) 1 || new_drone(2) map_size new_drone drone; % 撞边界原地不动 end % 碰撞检测 if occupy_map(new_drone(1), new_drone(2)) reward -50; new_state state; % 撞墙保持原状态 else % 计算奖励 dist_now norm(drone - goal_pos); dist_next norm(new_drone - goal_pos); if norm(new_drone - goal_pos) 1.0 % 到达终点栅格附近 reward 100; new_state compute_state(new_drone, goal_pos, map_size); else reward -1; % 步数惩罚 if dist_next dist_now reward reward 5; % 靠近目标奖励 else reward reward - 2; % 远离目标惩罚 end new_state compute_state(new_drone, goal_pos, map_size); end end % Q值更新 Q(state, action) Q(state, action) alpha * ... (reward gamma * max(Q(new_state, :)) - Q(state, action)); state new_state; drone new_drone; total_reward total_reward reward; % 到达终点 if norm(drone - goal_pos) 1.0 break; end end episode_rewards(ep) total_reward; epsilon max(epsilon_min, epsilon * epsilon_decay); end %% 保存Q表 save(q_table.mat, Q);这里有几个细节需要展开说。compute_state函数是整个状态编码的核心它把栅格位置和目标方位合并成一个整数索引function idx compute_state(pos, goal, map_size) row pos(1); col pos(2); % 计算目标方位扇区0-7 dx goal(1) - row; dy goal(2) - col; angle atan2(dy, dx); if angle 0 angle angle 2*pi; end sector floor(angle / (pi/4)) 1; if sector 8 sector 8; end % 状态索引 栅格位置 * 8 扇区 idx (row - 1) * map_size col; idx (idx - 1) * 8 sector; end注意状态编号从1开始因为MATLAB索引从1开始。如果栅格是20×20那么位置部分的范围是1到400乘以8后状态总数是3200。Q表大小是3200×9内存占用很小完全不需要担心性能问题。训练过程中我建议加一个进度显示每50轮输出一次当前平均奖励和路径长度这样不用等全部训练完就能判断是否收敛。如果奖励曲线一直震荡不上升多半是奖励函数设计或学习率有问题不要硬着头皮跑完所有轮次。3.3 融合规划主流程代码训练完Q表后下面是融合规划的核心代码% main_simulation.m load(params.mat); load(q_table.mat); %% 初始化占位 drone start_pos; path drone; trapped_count 0; is_q_mode false; mode_log []; %% 主循环 for iter 1:max_iter dist_goal norm(drone - goal_pos); if dist_goal threshold_dist disp(到达目标点!); break; end % 计算APF合力方向 F_total calc_apf_force(drone, goal_pos, obstacles, ... k_att, k_rep, rho0); % 计算APF作用下下一步位置 F_norm F_total / norm(F_total); next_pos_apf drone step_size * F_norm; % 碰撞检测 if is_collision(next_pos_apf, obstacles) is_q_mode true; end % 检测局部极小值(位移过小或振荡) if iter 2 prev_displacement norm(path(end, :) - path(end-1, :)); if prev_displacement 0.05 trapped_count trapped_count 1; else trapped_count 0; end if trapped_count 3 is_q_mode true; end end % 融合决策 if is_q_mode % Q-learning干预 state compute_state(round(drone), goal_pos, map_size); [~, action_idx] max(Q(state, :)); % 动作到方向向量 action_dir actions(action_idx, :); if norm(action_dir) 0 action_dir [1, 0]; % 原地等待则默认向东 end action_dir action_dir / norm(action_dir); next_pos drone step_size * action_dir; % 如果Q方向也碰撞用随机扰动 if is_collision(next_pos, obstacles) theta rand() * 2 * pi; next_pos drone step_size * [cos(theta), sin(theta)]; end is_q_mode false; % 只干预一步 else next_pos next_pos_apf; end % 更新位置 drone next_pos; path [path; drone]; mode_log [mode_log; is_q_mode]; end %% 可视化 figure; plot(path(:,1), path(:,2), b-, LineWidth, 1.5); hold on; % 绘制障碍物 for i 1:size(obstacles, 1) rect_x [obstacles(i,1), obstacles(i,3), obstacles(i,3), obstacles(i,1), obstacles(i,1)]; rect_y [obstacles(i,2), obstacles(i,2), obstacles(i,4), obstacles(i,4), obstacles(i,2)]; fill(rect_x, rect_y, k); end plot(start_pos(1), start_pos(2), go, MarkerSize, 10, LineWidth, 2); plot(goal_pos(1), goal_pos(2), r*, MarkerSize, 12, LineWidth, 2); axis equal; grid on; xlabel(X); ylabel(Y); title(基于Q-learning与APF融合的无人机航迹规划); legend(规划路径, 障碍物, 起点, 终点);这段代码里我最想强调的是is_q_mode变量的处理。一开始我把它设计成持续介入模式即一旦切换就连续用Q-learning走好几步结果路径在陷阱区域附近出现明显拐折不平滑且总路径长度变长。后来改成只干预一步APF在下一步自动接管路径质量立刻提升了一个档次。这说明融合算法的切换粒度非常重要步子太大容易把APF的微观优势破坏掉。3.4 实验结果与对比分析跑完仿真后我对三种方案做了对比单独APF、单独Q-learning、融合算法。结果如下表所示方案是否到达终点路径长度迭代次数路径平滑度单独APF有时失败不稳定50-100很平滑单独Q-learning能较长折线200-300较差融合算法稳定到达最短80-120较好单独APF在简单地图上表现很好但一旦起点、终点和障碍物的位置关系形成局部极小值配置就会卡住。我在测试地图里设计了一个U型陷阱区域单独APF几乎每次都困在陷阱底部。单独Q-learning虽然能绕出来但路径明显是锯齿状折线在栅格对角线上来回横跳看起来很不专业。融合算法很好地平衡了两者避开陷阱靠Q-learning路径平滑靠APF。奖励收敛曲线也值得看。前80轮累计奖励值在负值区间大幅波动这是正常的因为无人机一直在撞障碍物。大约150轮之后曲线开始稳定上升250轮后基本收敛。如果你的奖励曲线长时间不上升先检查是不是epsilon衰减太慢导致探索过度再检查奖励函数里是否存在“绕远路反而得分高”的漏洞。我刚开始设计的奖励对“靠近目标”给分过高无人机就学会了绕着目标画圈每次都能拿距离奖励但永远到不了终点。这个问题花了我一个下午才定位到。4. 调试实录与问题排查技巧4.1 经典坑位一Q-learning训练发散奖励曲线一路走低这是我调参过程中踩得最深的一个坑。排查思路是从最基础的参数开始挨个检查。先看学习率alpha设太大更新波动就大我一开始设0.5奖励曲线剧烈抖动根本没法看。调成0.1后稳定性好很多。再看折扣因子gamma这个参数控制未来奖励的重要性设太大会让算法太“短视”设太小又只看眼前0.9算是一个比较均衡的值。最后检查epsilon衰减如果衰减太慢后期还在大量随机探索已经学到的策略会被破坏表现就是收敛后奖励突然跳水。我的建议是训练时把单轮奖励、平均奖励曲线都画出来如果平均奖励在中后期还在大幅波动优先怀疑epsilon衰减其次是alpha。这一步排查顺序能省很多时间。4.2 经典坑位二APF在狭窄通道里路径震荡APF在狭窄通道里出现震荡是常态原因是两侧障碍物的斥力大小不对称合力方向来回摆动。我一开始以为是斥力增益没调好把k_rep从2调到5结果震荡更严重——斥力过大反而增加了不稳定性。后来查资料发现问题的根源在于力的方向变化太剧烈而移动步长没有配合调整。解决思路有两个方向一是缩小步长让路径在震荡中缓慢前进但会增加计算次数二是对力方向做低通滤波即这一步的实际方向是上一步方向和当前合力方向的加权平均。我采用了第二种方案滤波系数0.3效果立竿见影路径明显平滑了。这个滤波的思路很像无人机实际飞行中的速度控制——不可能瞬间改变航向总要有个过渡过程。所以在仿真阶段就加入这个约束后期接动力学模型时会更容易。4.3 经典坑位三融合切换瞬间路径突变融合算法跑一段时间后会发现在切换点附近路径出现突兀的折角。这个问题的原因是APF和Q-learning给出方向之间的夹角太大突然切换导致路径不连续。解决思路是在切换点做一个方向加权过渡公式如下actual_dir w * dir_apf (1-w) * dir_q其中w在切换后的几个步长内从0逐渐变到1这样方向就能平滑过渡。我在代码里加了3步的过渡期效果不错。这个方法在路径规划领域叫“混合控制下的方向插值”原理和贝塞尔曲线平滑有共通之处不过更轻量也不需要额外计算曲线参数。4.4 常见问题速查表问题现象可能原因解决方案Q-learning不收敛学习率过高或探索率衰减过慢调低alpha加快epsilon衰减APF路径震荡步长过大或力方向突变缩小步长或对力方向做低通滤波目标不可达目标附近有障碍物且斥力不衰减斥力函数乘距离因子改进融合切换处折角两种模式方向差异过大切换点做方向加权过渡路径贴障碍物过近斥力影响半径过小增大rho0或提高k_rep训练耗时过长状态空间过大加入目标方位编码减少无效探索5. 一点自己的体会整套仿真做下来最大的感受是融合算法的瓶颈不在算法本身而在接口设计。Q-learning和APF各自都是成熟的方法难的是怎么定义它们之间的交互方式——什么时候切换、切换粒度多大、切换后怎么过渡。这些细节如果不花心思调融合效果可能还不如单一算法好。从工程角度看这类融合思路也不只适用于无人机航迹规划。任何“全局规划局部控制”的两层架构都可以借鉴比如机器人导航里的A*或Dijkstra做全局路径、DWA做局部避障本质上也是这个套路。想通这一层你做的不只是一个仿真而是一套可以迁移到其他场景的规划框架。
返回列表