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

资讯详情

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

深度强化学习无人机航路规划:MDP建模与PPO训练实战

深度强化学习无人机航路规划:MDP建模与PPO训练实战 简介一篇面向无人机自主导航与智能决策的毕业设计论文以单个PDF文件存放于压缩包中包体约26.6MB。论文题为“基于深度强化学习的无人机航路规划方法研究”聚焦强化学习与路径规划融合适合相关方向高年级本科生和研究生阅读。全篇约80页核心内容覆盖两个创新算法在静态障碍环境下提出动态自适应势场算法DAPF将障碍物视为智能体借助多智能体强化学习动态调整势场解决人工势场法局部极小点问题在动态障碍环境下提出自适应扰动流场动态系统算法AIFDS借鉴流水避石思想可与SAC、DDPG、PPO、TD3等连续动作空间强化学习算法结合并设计多进程加速框架缩短训练时间。论文还包含对比实验与性能分析可作为毕业设计选题、研究设计、实验安排的参考。目前已有3363人学习浏览配套源码可从作者GitHub仓库获取便于复现和二次开发。1. 深度强化学习航路规划先别急着写代码想清楚这三点再说无人机三维航路规划的传统做法是 A*、RRT、遗传算法这类离线的路径搜索环境一变就要重新规划。而深度强化学习的思路是让无人机在仿真环境里不断试错把“避障 到达目标”磨成一种端到端的策略输入传感器和自身状态直接输出飞行动作。这个方向之所以热门是因为它在动态环境和未知障碍下比传统算法更省事也更有论文可做。但我见到的毕业设计翻车大多不是算法不会写而是状态、动作和奖励模型没定清楚后面全白搭。我会把建模、选型、训练和落地验证完整过一遍目标是让你能跑出一个真能规划航路的智能体。2. 把航路规划写成MDP状态空间、动作空间与奖励函数的落地设计绝大多数深度强化学习航路规划工作第一步都是把航路规划问题写成马尔可夫决策过程也就是 MDP。核心是让无人机在某个状态下依据策略选择一个动作然后环境给一个新的状态和奖励。没有把这个建模过程想清楚后面换什么算法都是空谈。下面分别展开三个要素的设计并给出我在实际项目中常用的一套配置。2.1 状态空间用相对坐标还是绝对坐标直接决定泛化能力状态空间设计最容易踩的第一个坑是把无人机和目标位置都用绝对世界坐标塞给网络。例如[x, y, z, goal_x, goal_y, goal_z]看起来没什么问题训练也能收敛但等你把起降平台的位置挪到另一个地方网络基本报废。原因很简单神经网络拟合的是绝对位置的映射表换个起点等于换了个场景。我一般会采用相对坐标 环境感知的组合。具体来说状态向量包含三个部分无人机相对目标位置[dx, dy, dz]直接反映“该往哪个方向飞”当前速度向量[vx, vy, vz]让策略知道当前惯性避免动作抖动最近障碍物的距离或者按扇区取的一组距离值这是避障的关键输入。如果是三维路径规划的数学建模还可以把无人机当前航向角、俯仰角也放进去配合飞控的底层控制器。不过对深度强化学习来说能直接拿到速度向量就足够角度信息往往可以省掉减少网络输入的维度。状态空间越小训练越容易稳定。这里还有一个细节目标相对位置要不要归一化我建议把相对位置除以距离最大值压缩到 [-1, 1] 区间否则状态向量里数值量级差太多会导致神经网络早期梯度被大数值维度主导。可以在环境reset时记录当前场景的边界大小在_get_obs里做归一化这一步对训练速度的影响非常直观。2.2 动作空间离散三档、多档还是连续控制动作空间的选取跟最终部署目标强相关。如果是纯仿真论文三类做法各有人用离散动作把飞行方向分成上、下、左、右、前、后六个方向或加上“悬停”共七档好处是用 DQN 这类离散算法直接处理训练简单坏处是动作粗糙飞行轨迹有明显折线感面对稠密障碍时容易卡死。离散多档则是在六个方向上再加不同速度档位但在三维空间下组合会爆炸。连续动作则是动作向量直接是速度和角速度指令或者归一化的加速度指令用 PPO、TD3、SAC 这类连续动作算法是目前最贴近真实飞控的做法。我自己的原则是只要不是赶时间交差都选连续动作。深度强化学习在无人机方向的核心卖点是端到端的连续控制如果缩回离散动作那和栅格化的 A* 在行为上区别不大论文说服力也弱。动作向量的具体含义建议定义为“相对机体坐标系的加速度增量”或“速度增量”而不是绝对坐标系下的位移。道理和状态空间一样绝对坐标系下学到的策略换起点就失效相对机体坐标的策略天然具备平移不变性。环境和网络之间的这层坐标变换是我在航路规划训练中特别在意的一件事很多开源的 demo 不管这个导致换场景后的成功率直线下降。2.3 奖励函数稀疏主奖励加密集 shaping还要小心奖励冲突奖励函数是航路规划里最“玄学”的部分也是最值得花时间调的部分。比较直观的设置如下到达目标给较大的正向奖励如 10碰撞障碍物或飞出边界给较大的负向奖励如 -10每一步给一个小的时间惩罚如 -0.1距离缩小给一个正向 shaping 奖励如0.1 * (上一步距离 - 当前距离)。这个结构在绝大多数航路规划里都能快速收敛。第一版我建议让 shaping 项占主导主奖励只作为终止信号。等能靠 shaping 稳定跑到终点附近再把 shaping 系数调小让主奖励主导。因为纯稀疏奖励在三维环境中起步阶段梯度太弱很容易让模型一直在原地打转甚至把“停在起点”学成最优策略。奖励冲突是另一个高频翻车点。最常见的冲突是避障惩罚和“向目标前进”的 shaping 同时存在时无人机贴着障碍物边缘既享受了“距离目标变近”的 shaping 奖励又没撞上障碍物形成一条贴墙飞的危险习惯。要解决这个问题一个可复现的做法是对障碍物设置“危险区”距离惩罚离障碍物小于安全距离时每步扣0.5 * (安全距离 - 实际距离)让靠近障碍物本身变成一种代价而不是撞上才罚。到这里MDP 的三个要素已经覆盖。下面进入算法选型。3. 算法选型DQN、PPO、TD3还是SAC按场景对号入座深度强化学习算法现在到处都是但对无人机航路规划来说真正值得考虑的其实只有少数几个。我见过不少学生一上来就用 SAC理由是“SAC 是 SOTA”结果训练时间成倍增加还说不清为什么好。算法选型要看定义的动作空间、环境复杂度以及你打算花多少时间调参。3.1 算法的核心差异和选型清单常见算法可以分成两个维度动作空间和价值函数的估计方式。DQN 及其变体Double DQN、Dueling DQN只支持离散动作适合动作档位少、状态连续的问题DDPG 支持连续动作但训练稳定性偏差超参数敏感TD3 是 DDPG 的改进版对过估计问题做了双重 Q 网络和延迟更新相对更稳PPO 基于策略梯度稳定性和超参鲁棒性好是目前工程实践默认首选SAC 是最大熵框架样本效率高但对奖励量级比较敏感调参成本不低。我在实际项目中大多数选 PPO因为它在无人机航路规划这种中等规模任务上收敛快、对学习率和 reward scale 的容忍度比其他算法高。下面这个表可以帮你快速对号入座。场景推荐算法理由离散动作六方向/七档位DQN或 Double DQN实现简单训练稳定连续动作追求稳定收敛PPO超参数鲁棒适合各类无人机路径规划连续动作仿真算力充足且想冲最优SAC样本效率高但要花时间调奖励连续动作且环境有高维图像输入TD3 图像编码器缓解 Q 值过估计适合端到端视觉感知3.2 为什么 PPO 是毕业设计和工程落地的默认起点PPO 的核心思路是约束策略更新的幅度用 clip 替代 KL 散度约束不让新旧策略差太远。这带来的直接好处是训练稳定性好。航路规划问题里环境动态多、奖励通道多如果用一个不稳定的算法很难判断是奖励函数写错了还是算法振荡了。PPO 能过滤掉大量“算法问题”的干扰把注意力集中在环境本身。用 PPO 时的关键参数我一般这样设学习率3e-4到1e-3Adam 优化器太大容易把策略冲飞clip epsilon 0.2这是论文里的默认值大多数任务可以直接用熵系数 0.001 到 0.01如果发现策略过早收敛到同一种动作把熵系数调大增加探索每个 batch 更新 5 到 10 轮。这几组参数基本能覆盖从二维栅格到三维连续空间的无人机路径规划需求。你不需要每个参数都精调重点是先让整体流程跑通再观察训练曲线判断问题出在哪部分。3.3 什么时候该退回去用 DQN如果你的状态量只有十维以内动作也只有六个方向用 DQN 反而更快出结果。DQN 在这种低维离散场景下训练几百个 episode 就能看到效果而且调参简单对做毕业设计来说能在有限时间里拿到完整曲线。但要注意DQN 的标准做法是经验回放、目标网络和随机采样一旦把状态量扩大或者动作档位增加训练效率下降很明显。我的建议是除非你明确知道“我就要用离散动作”否则还是优先 PPO不要因为 DQN 代码简单就选 DQN。3.4 为什么不建议直接上 SACSAC 在高样本效率测试中看起来很美但在无人机航路规划里有个实际困难它的最大熵目标会让策略在训练早期保持很大的动作方差而过多探索三维空间的危险区会让采样效率反而下降。此外SAC 对奖励量级异常敏感环境建模时奖励项多量级稍不一致就容易让温度参数自动调节偏离预期。如果你坚持用 SAC建议先用 PPO 跑通一个能收敛的奖励比例再原样替换成 SAC这样至少你确认奖励函数没问题。4. 搭建训练环境与PPO实现一份可以直接跑通的代码骨架现在进入实操环节。我会给出一份小型无人机航路规划环境的 Python 实现以及配套的 PPO 训练循环代码。这个代码骨架不是写论文用的理想化 demo而是可以接障碍物配置、接起降平台、接真机数据的可扩展版本。4.1 定义环境从三维位置状态到奖励计算环境用 Gymnasium 接口写这是目前训练代码最通用的格式。下面这段代码实现了前面说的状态空间和奖励函数。import numpy as np import gymnasium as gym from gymnasium import spaces class DronePathEnv(gym.Env): def __init__(self, configNone): super().__init__() config config or {} # 起点、终点、障碍物都在 config 里配置 self.start np.array([0.0, 0.0, 10.0], dtypenp.float32) self.goal np.array(config.get(goal, [80.0, 80.0, 25.0]), dtypenp.float32) self.obstacles [np.array(obs, dtypenp.float32) for obs in config.get(obstacles, [])] self.obstacle_radius config.get(obstacle_radius, 3.0) self.safe_dist config.get(safe_dist, 5.0) # 危险区距离 self.max_steps config.get(max_steps, 400) self.dt 0.1 self.v_max config.get(v_max, 8.0) # 最大速度 m/s self.world_bound config.get(world_bound, 100.0) # 连续动作归一化的加速度指令 [ax, ay, az] self.action_space spaces.Box(low-1.0, high1.0, shape(3,), dtypenp.float32) # 状态相对目标位置(3) 速度(3) 最近障碍距离(1) 到目标距离(1) obs_dim 8 self.observation_space spaces.Box(low-np.inf, highnp.inf, shape(obs_dim,), dtypenp.float32) def reset(self, seedNone, optionsNone): super().reset(seedseed) self.pos self.start.copy() self.vel np.zeros(3, dtypenp.float32) self.step_count 0 self.last_dist_to_goal float(np.linalg.norm(self.goal - self.pos)) return self._get_obs(), {} def _get_obs(self): rel_goal (self.goal - self.pos) / self.world_bound # 归一化到 [-1, 1] obs np.concatenate([ rel_goal.astype(np.float32), self.vel / self.v_max, [self._closest_obstacle_dist() / self.world_bound], [np.linalg.norm(self.goal - self.pos) / self.world_bound] ]).astype(np.float32) return obs def step(self, action): # 动作是归一化加速度先缩放到实际最大加速度 acc np.clip(action, -1.0, 1.0) * self.v_max * 2.0 self.vel acc * self.dt speed np.linalg.norm(self.vel) if speed self.v_max: self.vel self.vel / speed * self.v_max # 限速 self.pos self.vel * self.dt 0.5 * acc * (self.dt ** 2) self.step_count 1 dist_to_goal np.linalg.norm(self.goal - self.pos) reward 0.0 terminated False truncated self.step_count self.max_steps # 1. 时间惩罚越晚到越小 reward - 0.05 * self.dt # 2. 距离 shaping引导向目标 reward 0.5 * (self.last_dist_to_goal - dist_to_goal) # 3. 障碍物危险区惩罚靠太近就扣分 for obs in self.obstacles: d np.linalg.norm(self.pos - obs) if d self.obstacle_radius self.safe_dist: reward - 0.3 * (self.obstacle_radius self.safe_dist - d) # 4. 碰撞终止 if self._check_collision(): reward - 15.0 terminated True # 5. 到达终点 if dist_to_goal 3.0: reward 15.0 terminated True # 6. 超出边界 if np.any(self.pos self.world_bound) or np.any(self.pos -self.world_bound): reward - 15.0 terminated True self.last_dist_to_goal dist_to_goal return self._get_obs(), reward, terminated, truncated, {} def _closest_obstacle_dist(self): if not self.obstacles: return float(self.world_bound) return float(min(np.linalg.norm(self.pos - obs) for obs in self.obstacles)) def _check_collision(self): for obs in self.obstacles: if np.linalg.norm(self.pos - obs) self.obstacle_radius: return True return False这里的关键参数解释一下action是归一化加速度指令范围 [-1, 1]正好契合 PPO/SAC 这类算法默认的高斯策略输出范围不需要额外的缩放逻辑。状态量里相对目标位置和速度都做了归一化数值范围可控。障碍物危险区惩罚用的是obstacle_radius safe_dist作为判断半径实际项目里可以根据无人机电机选型带来的机体尺寸和传感器噪声来调整这个安全余量。奖励里的 shaping 系数0.5和时间惩罚0.05是相对比较稳的一组默认值如果环境里目标距离很远可以把 shaping 系数调大。4.2 搭建PPO训练器Actor-Critic网络与clip更新接下来是 PPO 的训练代码。Actor-Critic 结构里Actor 输出连续动作的均值Critic 输出当前状态的估计价值。注意用可学习的log_std对数标准差让探索范围在训练过程中自我调节。import torch import torch.nn as nn import torch.optim as optim class ActorCritic(nn.Module): def __init__(self, obs_dim, act_dim, hidden128): super().__init__() self.actor nn.Sequential( nn.Linear(obs_dim, hidden), nn.Tanh(), nn.Linear(hidden, hidden), nn.Tanh(), nn.Linear(hidden, act_dim) ) self.critic nn.Sequential( nn.Linear(obs_dim, hidden), nn.Tanh(), nn.Linear(hidden, hidden), nn.Tanh(), nn.Linear(hidden, 1) ) self.log_std nn.Parameter(torch.zeros(act_dim)) # 可学习的探索尺度 def get_action(self, obs): mu self.actor(obs) std self.log_std.exp().clamp_min(0.1) dist torch.distributions.Normal(mu, std) action dist.sample() log_prob dist.log_prob(action).sum(dim-1) return action, log_prob def evaluate(self, obs, action): mu self.actor(obs) std self.log_std.exp().clamp_min(0.1) dist torch.distributions.Normal(mu, std) log_prob dist.log_prob(action).sum(dim-1) value self.critic(obs).squeeze(-1) return value, log_prob, dist.entropy().sum(dim-1)PPO 的更新逻辑放在下面使用重要性采样比率加上 clip 截断。def ppo_update(model, optimizer, obs, actions, old_log_probs, advantages, returns, clip_eps0.2, epochs10): # 先对优势做归一化减少各 batch 间量级波动 advantages (advantages - advantages.mean()) / (advantages.std() 1e-8) for _ in range(epochs): values, new_log_probs, entropy model.evaluate(obs, actions) ratio (new_log_probs - old_log_probs).exp() surr1 ratio * advantages surr2 torch.clamp(ratio, 1.0 - clip_eps, 1.0 clip_eps) * advantages policy_loss -torch.min(surr1, surr2).mean() value_loss nn.MSELoss()(values, returns) entropy_loss -entropy.mean() # 负号因为我们最大化熵 total_loss policy_loss 0.5 * value_loss 0.01 * entropy_loss optimizer.zero_grad() total_loss.backward() nn.utils.clip_grad_norm_(model.parameters(), max_norm0.5) # 梯度裁剪 optimizer.step()为什么这样做ratio是旧策略和新策略下动作的概率比值当ratio超出[1-clip_eps, 1clip_eps]时对应优势项被截断策略不会因为个别极端样本而震荡。returns是折扣累计奖励作为 Critic 的回归目标。梯度裁剪max_norm0.5是航路规划这类环境的经验值能减少更新时的大梯度冲击。4.3 主训练循环与经验收集主循环的职责是采样若干个 episode把状态、动作、奖励存下来然后计算 GAE再调用上面的函数更新策略。GAE 的折扣系数我一般设gamma0.99lambda 设0.95在路径规划里比较通用。def compute_gae(rewards, dones, obs_t, model, gamma0.99, lam0.95): with torch.no_grad(): values model.critic(obs_t).squeeze(-1).numpy() adv np.zeros_like(rewards) gae 0.0 next_value 0.0 for t in reversed(range(len(rewards))): delta rewards[t] gamma * next_value * (1 - dones[t]) - values[t] gae delta gamma * lam * (1 - dones[t]) * gae adv[t] gae next_value values[t] returns adv values return returns, adv model ActorCritic(obs_dim8, act_dim3) optimizer optim.Adam(model.parameters(), lr3e-4) batch_size 4096 for update in range(1000): obs_buf, act_buf, logp_buf, rew_buf, done_buf [], [], [], [], [] obs, _ env.reset() while len(obs_buf) batch_size: obs_t torch.FloatTensor(obs).unsqueeze(0) action, log_prob model.get_action(obs_t) action action.squeeze(0).numpy() n_obs, reward, terminated, truncated, _ env.step(action) obs_buf.append(obs) act_buf.append(action) logp_buf.append(log_prob.item()) rew_buf.append(reward) done_buf.append(terminated or truncated) obs n_obs if terminated or truncated: obs, _ env.reset() obs_t torch.FloatTensor(np.array(obs_buf)) act_t torch.FloatTensor(np.array(act_buf)) logp_t torch.FloatTensor(np.array(logp_buf)) returns, advantages compute_gae(rew_buf, done_buf, obs_t, model) ppo_update(model, optimizer, obs_t, act_t, logp_t, advantages, returns) if update % 50 0: avg_reward np.mean(rew_buf) print(fupdate {update}, avg_reward: {avg_reward:.2f})这段代码里done_buf记录每个 step 是否终止或截断计算 GAE 时用(1 - dones[t])把终止点之后的累计回报隔断避免跨 episode 的错误累积。batch 大小建议 2048 到 4096 步更新 5 到 10 轮。如果训练曲线一直上不去第一件事不是调网络层数而是把环境里生成的随机障碍物打印出来人工看一遍很多时候是障碍物间距太小或者生成在起点附近导致初始探索阶段永远在几分钟内撞墙这属于环境问题不是算法问题。5. 训练踩坑记录收敛失败、过拟合和仿真真机不一致的排查清单我在无人机路径规划项目里反复遇到过几个问题下面按“现象 → 原因 → 解决”的顺序整理成清单。可以直接拿这份清单当排查手册用。5.1 现象训练几百个episode奖励曲线纹丝不动原因奖励量级和输入状态量级匹配不上。最常见的是相对位置值非常大比如几百米网络初始输出的动作几乎随机碰撞惩罚又大导致所有样本都是“撞墙”的负奖励策略梯度完全被负奖励主导。不是没学是信号被淹没了。解决先检查归一化。把状态里的相对目标位置除以一个最大距离奖励也控制到每次累计不超过 ±10。训练初期如果发现平均回报是 -20 左右且稳步向 -10 靠近这其实是在学“先别撞墙”是正常过程不要误以为没收敛。5.2 现象奖励稳步上升但无人机在障碍物前反复贴脸抖原因奖励 shaping 和避障惩罚互相打架。神经网络学到局部最优靠近障碍物时每步步进中目标距离减少带来的 shaping 奖励刚好大于危险区惩罚于是它就贴着障碍物表面缓慢滑行不撞也不走。解决把危险区惩罚系数提高或者给障碍物碰撞加一个更高的终止惩罚。还有一种更“狠”的做法是把危险区内 shaping 奖励直接乘上一个接近 0 的掩码让“贴着障碍物接近目标”的收益彻底消失。这样策略只能选择绕行或者加速通过。5.3 现象训练时换一个起点成功率骤降原因模型在训练场景上过拟合。深度强化学习网络虽小但照样会把起点的偏好比如总是向左转编码在第一个隐藏层里。特别是动作空间定义在绝对坐标系时换起点等于换输入分布网络自然失效。解决动作和状态尽量用相对量。更进一步的常规做法是多随机化起点和终点每次环境reset时随机生成起点和目标点让网络必须学会泛化。这点在无人机起降平台这类真实任务里尤其重要起降平台位置变了航路规划策略不能跟着失效。5.4 现象仿真里飞得很好换到真机就乱飞原因仿真和真机的输入分布不一致。仿真里的状态是理想值速度、位置都很干净真机上这些量带噪声和延迟而且无人机电机选型决定的最大速度、加速度响应时间也不同。网络在仿真里学到的策略很可能正好依赖了“状态完全精确”这个假设。解决一方面在仿真里给状态加高斯噪声做 domain randomization另一方面在真机验证前先做硬件在环仿真把仿真状态替换成飞控估算出的状态看看策略的抗噪能力。如果无人机飞控提供的速度估计相差太大就不要直接上真机。5.5 现象添加新障碍物后策略需要完全重训原因训练数据里没有这类障碍物布局网络只能靠插值泛化而三维空间里的插值在环境变化大时基本不适用。解决在训练阶段就把障碍物数量、位置、半径都随机化。训练完成后如果要部署到新地图可以保留网络权重做小规模微调而不是从零开始。技巧是降低 PPO 学习率到原来的十分之一只在新地图上采样少量轨迹更新几分钟就能适应这比重新训练便宜得多。6. 从仿真到部署验证指标、观测策略以及最后一次调参先说我常用的验证方法再讲一个部署前的检查技巧。验证分三层第一层是统计成功率和平均航路长度分别在多个随机场景下评估第二层是绘制一条飞行轨迹看是否平滑第三层是可视化策略在各状态下的动作分布发现奇怪行为。成功率统计时我习惯跑固定 100 个随机初始化的 episode计算到达终点的比例成功率低于 90% 不会考虑部署。然后看平均飞行时间如果比最短路径长很多说明策略在绕远路。另一个有用的技巧是“干预式评估”在仿真中随时暂停手动把无人机推向障碍物方向观察策略是否快速避让。这个做法能提前暴露奖励函数漏洞。因为统计成功率只反映结果过程里如果策略在某些状态下有“自杀式”倾向从成功率里看不到。真机部署的最后一公里我会先跑一次硬件在环仿真HIL把飞控的底层速度估计作为状态输入观察策略是否还稳定。然后再把模型导出成 ONNX在板载计算机里推理一次确认耗时在控制周期内。这里特别提醒不要忽视飞控和策略层的时间延迟深度强化学习模型输出频率如果只有 5Hz飞控是 50Hz中间必须加一层插值缓冲。调参的最后心得很多同学执着于找一个“万能调参组合”实际不存在。我在不同机型、不同起降平台配置上同一份 PPO 代码需要调整的通常只有三个参数危险区惩罚系数、shaping 系数、熵系数。网络结构反而不动。这三个参数直接对应当前任务的物理属性比如机体尺寸、传感器噪声、最大速度。如果我能回到第一次做这个题目的时候我会让自己尽早开始做随机化和干扰注入而不是盯着训练曲线焦虑。把所有可能的未知提前一天暴露给网络比后期刷超参数更有效。希望这一套从建模、选型到训练和验证的流程能帮到你少走一点我当时走过的弯路。本文还有配套的精品资源点击获取
返回列表