☰
无人机三维路径规划:深度强化学习与A*混合算法详解
2026/10/3 1:20:03 网站建设 项目流程

简介:一份基于Python实现深度强化学习(DRL)无人机三维路径规划系统的完整项目实例,面向具备Python与PyTorch基础的研究人员、工程师及高年级学生。核心采用近端策略优化(PPO)算法与演员-评论家结构,结合三维环境建模、连续动作输出、局部障碍感知与安全距离检测,并与A*搜索算法融合形成混合式路线生成机制,兼顾路径安全性与最优性。文档完整覆盖环境建模、状态编码、奖励函数设计、PPO训练流程、碰撞检测、路线评价及GUI可视化界面,可直接用于城市物流、应急救援、森林巡检和电力巡线等复杂三维场景的科研、教学与工程原型开发。资源包内含1个docx技术文档,共126KB,内容包含完整程序代码、GUI设计与实现细节。已有90人学习下载,是快速上手深度强化学习连续控制任务及无人机路径规划算法的实用参考资料。

1. 深挖无人机三维路径规划项目:DRL、PPO 和 A* 是怎么拼成一套系统的

无人机三维路径规划如果只靠传统 A* 或者人工势场,很容易在障碍密集的低空场景里跑不动;如果只靠深度强化学习,训练出来的策略又没法在每个随机地图上都保证绝对安全。这份项目资源给我的第一印象是它把两边都留住了:用近端策略优化(PPO)训练连续动作策略,让无人机学会绕开三维障碍物逼近目标;同时保留三维 A* 搜索做兜底,强化学习路线不合格就自动回退到可验证的搜索路线,最后由一个路线评价器统一打分选路。系统面向城市物流、应急救援、森林巡检和电力巡线这类典型低空场景,代码用 Python 和 PyTorch 实现,带完整的 GUI 可视化界面和逐模块代码讲解,适合有 Python 基础、想拿 PPO 做连续控制项目的工程师和研究生直接复现。

2. 环境建模与状态编码:把三维空间压成策略网络吃得到的向量

2.1 为什么用球形障碍物说话:碰撞判定便宜且可扩展

三维路径规划最容易翻车的地方不是算法,而是环境表示。很多新手一上来就把地图切成高分辨率三维体素,状态向量一下变成几百万维,训练样本效率直接崩盘。这个项目的做法是先用连续坐标描述飞行空间,障碍物用球体建模,理由很实际:球体之间、球体与无人机之间的碰撞判定只需要一次欧氏距离运算,训练时每步都要做几十次这样的检查,运算量必须压得住。

球体还有一个好处是安全半径可以叠加。无人机本身有一个安全半径,障碍物有一个半径,碰撞阈值直接取两者之和,几何意义清晰,后面对接激光雷达点云或体素地图时也只需要替换障碍数据结构,碰撞检测接口不用动。正文里提到的建筑物、山体、杆塔这类障碍,在训练场景里都先抽象成球体,遇到长方体或更复杂的形状,留到后续版本替换碰撞函数就行。我习惯把球体半径再外扩一点作为膨胀层,因为策略网络输出的动作是连续值,采样时很容易擦到边界,膨胀一小段距离能明显降低训练早期的碰撞率。

2.2 环境类实现:状态返回、碰撞检测与归一化

环境类承担的任务是:接收动作、推进位置、检测碰撞、判断到达、返回奖励和状态。状态向量不能只丢坐标进去,否则网络学不到“目标在哪个方向”“附近有没有障碍”这两件关键信息。这个项目把状态组织成六个分块:当前位置归一化坐标、目标相对向量归一化、当前速度、最近几个障碍物的相对位置和半径、剩余步数比例、安全半径相关变量。所有向量都除以空间尺寸或对角线长度做缩放,保证不同地图尺寸下网络输入分布稳定。

下面是环境类核心逻辑的简化版本,重点看状态拼接和碰撞判定怎么写:

import numpy as np class Env3D: def __init__(self, box, obstacles, start, goal, safe_radius=0.5, max_step=1.0): # box: [(x_min, y_min, z_min), (x_max, y_max, z_max)] self.box = np.array(box, dtype=np.float64) self.obstacles = np.array(obstacles, dtype=np.float64) # [N, 4]: cx, cy, cz, r self.start = np.array(start, dtype=np.float64) self.goal = np.array(goal, dtype=np.float64) self.safe_radius = safe_radius self.max_step = max_step self.max_steps = 300 self.reset() def reset(self): self.pos = self.start.copy() self.step_count = 0 return self._get_state() def check_collision(self, pos): # 距离小于无人机安全半径与障碍半径之和,判为碰撞 delta = self.obstacles[:, :3] - pos dist = np.linalg.norm(delta, axis=1) if np.any(dist < (self.obstacles[:, 3] + self.safe_radius)): return True return False def step(self, action): # 动作前三维是方向,最后一维是步长比例,全部在 [-1, 1] direction = np.tanh(action[:3]) norm = np.linalg.norm(direction) + 1e-6 direction = direction / norm step_len = (action[3] + 1.0) * 0.5 * self.max_step new_pos = self.pos + direction * step_len # 越界裁剪,并标记碰撞惩罚来源 new_pos = np.clip(new_pos, self.box[0], self.box[1]) dist_before = np.linalg.norm(self.goal - self.pos) dist_after = np.linalg.norm(self.goal - new_pos) reward = 0.0 done = False if self.check_collision(new_pos): reward -= 1.0 done = True else: # 距离差分奖励:靠近目标给正反馈,远离给负反馈 reward += (dist_before - dist_after) / np.linalg.norm(self.box[1] - self.box[0]) if dist_after < 0.8: reward += 2.0 done = True self.pos = new_pos self.step_count += 1 if self.step_count >= self.max_steps: done = True return self._get_state(), reward, done, {} def _get_state(self): diag = np.linalg.norm(self.box[1] - self.box[0]) rel_goal = (self.goal - self.pos) / diag # 只取最近 6 个障碍物,不够补零 obs_feat = np.zeros((6, 4)) if len(self.obstacles) > 0: dists = np.linalg.norm(self.obstacles[:, :3] - self.pos, axis=1) idx = np.argsort(dists)[:6] for i, j in enumerate(idx): obs_feat[i, :3] = (self.obstacles[j, :3] - self.pos) / diag obs_feat[i, 3] = self.obstacles[j, 3] / diag state = np.concatenate([ (self.pos - self.box[0]) / (self.box[1] - self.box[0]), rel_goal, obs_feat.flatten(), [self.step_count / self.max_steps] ]) return state.astype(np.float32)

逻辑说明:动作先拆成方向和步长两部分,方向向量做归一化保证模长为 1,不会因为网络输出的尺度问题导致位移忽大忽小;步长映射到[0, max_step],这样动作空间既有连续性,又限制了单步最大位移,避免策略乱跳。奖励的核心是距离差分,正负号能引导智能体往目标移动;碰撞惩罚设成相对固定的负值,不要一开始就拉得太大,否则梯度会被少数极端回报主导。

参数说明:safe_radius是无人机安全半径,调大后路线会离障碍更远,但可能绕路;max_step控制单步移动距离,步长太大路线稀疏容易跳过窄通道,太小训练步数变长;max_steps是回合上限,相当于给策略一个时间压力。状态里只取最近 6 个障碍物,是因为全局障碍数量变化会导致状态维度不稳,局部感知也基本覆盖了当前决策需要的信息,缺的用零填充。

2.3 奖励函数分层设计:密集距离反馈与安全惩罚

奖励函数是这个项目里最值得抄作业的部分。它没有只用“到达给高分、碰撞给低分”的稀疏奖励,而是做成分层结构:靠近目标每一步都产生距离差分奖励;进入障碍物安全缓冲区后按距离递增惩罚;碰撞给固定大惩罚并结束回合;到达目标给成功奖励;转向过大再扣一点,抑制路线抖动。分层的好处是训练早期不会因为零奖励而冷启动,靠近目标的动作立刻有反馈,网络梯度信号始终存在。

我在调参时一般把碰撞惩罚固定在-1.0,成功奖励+2.0,然后通过距离差分系数控制探索倾向。如果碰撞率降不下来,就先调高安全距离惩罚,而不是直接加大碰撞惩罚,因为极端负奖励容易让策略变得过于保守,甚至停在原地不动。至于转向惩罚,它属于事后平滑项,权重不要超过距离奖励的三分之一,否则策略会为了少转向而走远路。

3. 演员-评论家与 PPO 训练循环:动作怎么出、梯度怎么算、模型怎么存

3.1 连续动作空间设计和网络结构

为什么用到深度强化学习时不选 DQN 而是演员-评论家加 PPO?因为 DQN 处理离散动作很自然,但无人机前后左右上下组合出来的连续位移,离散化之后动作数会爆炸,而且相邻动作之间的关系被切断了。这个项目把动作设计成 4 维连续量:前三维是空间方向,第四维是步长比例,策略网络输出高斯分布的均值和方差,采样后 tanh 压缩到[-1, 1]。推理阶段直接用均值,不用采样,路线会稳定很多。

网络结构分两条分支,特征提取可以共享也可以分离,工程上我习惯先共享一层再做分支,减少参数量:

import torch import torch.nn as nn import torch.nn.functional as F class ActorCritic(nn.Module): def __init__(self, state_dim, act_dim=4, hidden=256): super().__init__() self.shared = nn.Sequential( nn.Linear(state_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), ) # 动作均值头 self.actor_mean = nn.Linear(hidden, act_dim) self.log_std = nn.Parameter(torch.zeros(act_dim)) # 状态价值头 self.critic = nn.Linear(hidden, 1) def forward(self, state): feat = self.shared(state) mean = self.actor_mean(feat) return mean, self.log_std def act(self, state, greedy=False): mean, log_std = self.forward(state) std = log_std.exp() dist = torch.distributions.Normal(mean, std) if greedy: action = mean else: action = dist.sample() return torch.tanh(action), dist def evaluate(self, state, action): mean, log_std = self.forward(state) std = log_std.exp() dist = torch.distributions.Normal(mean, log_std.exp()) # 动作采样后经过 tanh 压缩,计算对数概率时要补上 tanh 的雅可比修正 log_prob = dist.log_prob(action).sum(dim=-1) - torch.log(1 - action.pow(2) + 1e-6).sum(dim=-1) value = self.critic(self.shared(state)).squeeze(-1) return log_prob, value, dist.entropy().sum(dim=-1)

逻辑说明:actor_mean输出的是 tanh 之前的原始均值,log_std作为可学习参数而不是网络输出层,这样策略方差能自适应调整,训练更稳。evaluate里补了 tanh 的雅可比修正项,严格来说 tanh 压缩后动作的分布不再是标准高斯,直接拿高斯对数概率会有偏差,补上这行训练会更稳。工程简化版本也可以不补,直接用原始高斯加动作裁剪,但代价是早期梯度可能不够精确。

参数说明:hidden=256在这个规模的地图里足够,障碍数量少、状态维度在 30 上下时 128 也能跑;act_dim=4对应三维方向加一维步长,如果以后要加入速度约束或偏航角,在这里扩展维度就行。log_std初始值是 0,对应标准差 1,采样覆盖范围合理,不需要额外调。

3.2 广义优势估计和策略更新循环

PPO 的训练循环里,广义优势估计(GAE)是承上启下的关键。它用gamma和lam两个参数折中偏差和方差:gamma管远期回报的折扣,lam管优势估计的平滑程度。这个项目的轨迹采样是整段进行的,每完成一个回合就把状态、动作、奖励、价值、终止标志存进缓存,然后统一计算优势值并更新策略。

def compute_gae(rewards, values, dones, gamma=0.99, lam=0.95): advantages = [] gae = 0.0 values = values + [0.0] # 回合结束后的引导价值为 0 for t in reversed(range(len(rewards))): delta = rewards[t] + gamma * values[t + 1] * (1 - dones[t]) - values[t] gae = delta + gamma * lam * (1 - dones[t]) * gae advantages.insert(0, gae) return advantages def ppo_update(agent, states, actions, old_log_probs, returns, advantages, optimizer, clip_eps=0.2, epochs=10): advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8) for _ in range(epochs): log_probs, values, entropy = agent.evaluate(states, actions) ratio = (log_probs - old_log_probs).exp() loss1 = -ratio * advantages loss2 = -torch.clamp(ratio, 1 - clip_eps, 1 + clip_eps) * advantages actor_loss = torch.max(loss1, loss2).mean() critic_loss = F.mse_loss(values, returns) total_loss = actor_loss + 0.5 * critic_loss - 0.01 * entropy.mean() optimizer.zero_grad() total_loss.backward() nn.utils.clip_grad_norm_(agent.parameters(), max_norm=0.5) optimizer.step()

逻辑说明:GAE 从回合末尾往前递推,dones[t]是为了在碰撞或到达时截断累积,避免把终止状态之后的虚拟回报传回去。PPO 更新里ratio是新旧策略概率比,clip_eps=0.2表示概率比超出[0.8, 1.2]的部分不再提供激励,这是防止策略一步更新过猛的核心机制。优势值标准化我放在循环外面做,这样各个 batch 的尺度一致,碰撞惩罚和距离奖励混在一起时不会出现量纲压制。

参数说明:gamma=0.99适合中等长度回合,回合步数 300 的话折扣衰减不明显;lam=0.95是 GAE 常用默认值,偏大时优势估计更平滑,偏小时更依赖即时奖励;epochs=10代表每条轨迹数据被复用 10 次,太大容易过拟合当前 batch,太小样本利用率低。0.5 * critic_loss和0.01 * entropy这两个权重是常见配置,前者平衡价值网络收敛速度,后者防止策略过早确定。

3.3 课程训练、早停与最优模型保存

训练阶段这个项目采用了难度递进的课程策略:先跑 4 个障碍物的大宽通道,模型稳定后加到 8 个,再到 12 个。这样做是因为三维搜索空间大,直接从高密度障碍开始训练,早期样本全是碰撞,策略很难学到有效信号。我复现时会在环境生成函数里加一个难度参数,每 N 轮评估一次成功率,连续几轮提升不到0.5%就切换下一难度。

模型保存不能只存网络权重,还要把状态归一化参数、障碍物数量范围、动作缩放系数一起存成字典。否则训练时状态是归一化的,推理时直接喂原始坐标,策略表现会完全走样。早停的标准我一般定成两个:一是当前难度成功率超过 0.95 且平均路径长度不再下降,二是连续训练轮次收益无提升就回退到上一轮保存的 best 模型,避免后期过拟合到特定障碍布局。

4. 混合选路、碰撞检测与 A* 兜底:常见问题排查与复现

4.1 候选路线综合评价:路径长度、安全风险与转向变化

强化学习策略本质是一个近似决策函数,它跑出来的路线不能直接信,必须经过评价器验证。这个项目的评价器做三件事:合法性检查、代价计算、多路线对比。合法性检查逐段看是否碰撞、是否越界、终点是否落在目标容差范围内;代价计算把路径长度、最小障碍距离、转向角变化、动作步数统一折算成一个标量。这样设计是为了让“自动决策”变成“自动生成、自动验证、自动选择”,工程上可解释性更强。

def evaluate_route(route, env, goal_tol=0.8): if len(route) < 2: return False, float("inf") if np.linalg.norm(np.array(route[-1]) - env.goal) > goal_tol: return False, float("inf") total_len = 0.0 turn_penalty = 0.0 min_obstacle_dist = float("inf") prev_dir = None for i in range(len(route) - 1): seg = np.array(route[i + 1]) - np.array(route[i]) total_len += np.linalg.norm(seg) if env.check_collision(np.array(route[i + 1])): return False, float("inf") # 记录最小障碍距离 delta = env.obstacles[:, :3] - np.array(route[i + 1]) dist = np.linalg.norm(delta, axis=1) - env.obstacles[:, 3] min_obstacle_dist = min(min_obstacle_dist, dist.min()) # 转向角惩罚 if prev_dir is not None and np.linalg.norm(prev_dir) > 1e-6: cos_angle = np.dot(seg, prev_dir) / (np.linalg.norm(seg) * np.linalg.norm(prev_dir) + 1e-6) turn_penalty += (1.0 - cos_angle) prev_dir = seg cost = total_len + 0.5 * turn_penalty + 0.3 * (1.0 / (min_obstacle_dist + 1e-3)) return True, cost

逻辑说明:goal_tol=0.8是到达判定容差,小于这个距离就视为到达,太小会导致强化学习明明离目标很近但被判失败,触发不必要的 A* 兜底。转向惩罚用相邻两段位移向量的夹角余弦来算,方向完全一致时为 0,折返时为 2,能有效抑制路线锯齿。最小障碍距离折算成代价项时用了倒数形式,离障碍越近代价越高,但不会因为极端接近直接否决整条路线。

参数说明:代价公式里0.5是转向权重,0.3是安全权重,如果更看重飞行安全就把0.3调大;路径长度本身就是原始数值,三种代价的量纲不同,权重需要跟着地图尺度调整。地图大了以后路径长度数值会变大,转向和安全权重可能需要同步缩小,否则路径长度会失去主导地位。

4.2 A* 兜底触发条件与边界处理

A* 在这里不是主规划器,而是强化学习路线的安全网。触发条件有三个:强化学习路线发生碰撞或越界、终点未到达、路线长度显著超过同场景 A* 路线长度。三维 A* 在实现时需要把连续空间离散成体素栅格,分辨率我一般取max_step的 0.5 倍,太粗会漏掉窄通道,太细搜索节点爆炸。启发函数用三维欧氏距离,每个栅格的碰撞判定要在原始障碍物半径上再做一次膨胀,否则 A* 给出的路线在连续空间里可能擦到障碍物。

边界处理是最容易忽略的环节。A* 搜索出来的路线是栅格点序列,直接丢给无人机飞会走折线,我习惯先做拐点压缩:只保留方向发生明显变化的节点,再用线性插值把拐点之间的线段细化到步长级别,最后过一遍第一节的evaluate_route确认代价。如果 A* 路线和强化学习路线都合法,统一用评价器算代价,选小的输出。

4.3 五条踩坑记录:现象、原因与解决

坑一:碰撞检测阈值设错,路线贴着障碍物飞被判安全。现象是训练时收益曲线看着正常,但渲染路线能看见无人机从障碍物内部穿过。原因是碰撞判定把无人机当成一个点,只比较位置到障碍球心的距离和障碍半径,忽略了无人机自身安全半径。解决方法是阈值改成障碍半径加无人机安全半径之和,我在这份代码里就是直接按这个方法处理的,复现时一定要检查check_collision里加的不是o[3]而是o[3] + safe_radius。

坑二:碰撞惩罚和距离奖励量纲差距太大,训练不收敛。现象是前几千步收益一路下滑,后来突然停滞。原因是距离差分除以对角线长度之后数值很小,碰撞惩罚-1.0相对过大,绝大多数轨迹被负奖励主导,策略学会了原地不动。解决方法是把距离差分奖励放大到与碰撞惩罚同一个数量级,或者干脆先移除碰撞惩罚、只用安全缓冲区逐级递增的负奖励,等策略能稳定避开远距离障碍后再把碰撞惩罚加回来。

坑三:A兜底没触发,强化学习路线卡在死角里反复绕圈。* 现象是步数耗尽但路线评价器显示终点未到达,A* 却没有被调用。原因是我一开始只检测碰撞和越界,没有检查“未到达目标”这个失败条件。解决方法是把兜底触发条件放宽:只要终点距离大于容差、步数耗尽、或者路线代价高于同场景 A* 的代价值,就强制进入 A* 流程。这个判断放在evaluate_route返回之后,逻辑很简单但很容易漏。

坑四:三维 A搜索时间波动巨大,小地图也要卡好几秒。* 现象是同样的地图,偶尔 0.1 秒出结果,偶尔 3 秒还在搜。原因是开放列表里堆元素太多时没用哈希表做去重,同一个栅格被重复压入堆,导致堆膨胀。解决方法是维护一个visited三维布尔数组,节点入堆前先判重;再配合分辨率粗一档的预搜索,先出可行路线再细化,规划耗时能压到可接受范围。GUI 里跑这种搜索一定要放到后台线程,否则界面直接冻结。

坑五:训练时保存的模型换到新地图完全不会飞。现象是训练图上表现好,换一张障碍布局成功率骤降。原因有两个,一是状态归一化参数只存了权重大小的模型,没存坐标缩放系数;二是训练时障碍物布局太单一,模型过拟合到固定位置。解决方法是保存检查点时把box、safe_radius、max_step、归一化均值方差一起存进字典,训练场景每次随机生成障碍中心,并且跑完一张图就用独立的测试地图评估一遍再决定是否保存。

5. GUI 可视化与工程部署:把训练好的模型变回可操作的工具

5.1 GUI 功能拆解与线程模型

GUI 在这个项目里不是装饰,而是承担训练监控、路线展示、参数调试和结果导出的综合面板。我拆了一下它的功能结构,大概包含五个核心区:三维路线显示区,用 Matplotlib 3D 画障碍物球面和规划路线;训练曲线区,实时刷新回合收益、成功率、平均路径长度;自动选路区,读当前地图数据后调用评价器选最优路线;参数配置区,训练前设置学习率、障碍数量、最大步数等;路线导出区,把选中的路线输出成坐标文件。

线程模型是这个 GUI 最容易踩坑的地方。训练循环是长时间操作,如果直接跑在主线程里,界面会卡死、曲线不刷新、点按钮没反应。正确做法是训练跑在后台线程,周期性地把收益、步数、路线数据通过队列或信号传给主线程,主线程只负责画图。后台线程还要支持停止指令,用标志位控制循环退出,而不是强杀线程。

5.2 后台训练线程和训练曲线刷新

一个实用的后台线程骨架长这样:

import threading, time from collections import deque class TrainThread(threading.Thread): def __init__(self, env_config, callback): super().__init__() self.callback = callback self.stop_flag = False self.history = deque(maxlen=100) def stop(self): self.stop_flag = True def run(self): agent = ActorCritic(state_dim, act_dim=4) optimizer = torch.optim.Adam(agent.parameters(), lr=3e-4) for epoch in range(1000): if self.stop_flag: break # 采样一批轨迹、计算 GAE、更新策略 # ... if epoch % 10 == 0: self.history.append({ "epoch": epoch, "success_rate": sr, "avg_length": avg_len, }) self.callback(self.history)

逻辑说明:callback是主线程注入的更新函数,训练线程每 10 轮推一次数据,主线程拿到后刷新折线图。stop_flag是协作式停止,训练循环每轮开头检查一次,比强制 terminate 安全得多。history只保留最近 100 个采样点,防止界面刷新时数据量过大。GUI 里所有网络更新操作都必须在主线程做,所以这里只用callback抛数据,不带任何 matplotlib 调用。

5.3 模型加载、推理脚本与已有数据预测

训练完的模型可以直接加载做纯推理,推理阶段有个关键区别:动作用greedy=True,不采样。推理函数逐帧推进环境、记录轨迹、每 N 步做一次碰撞复查,最终输出完整路线和代价。已有数据预测则是指给定一段历史飞行坐标序列,用模型逐点评估这段轨迹的合理性,输出每个时间步是否安全、哪里转向过激、哪里有碰撞风险。

def infer_route(model, env, max_steps=300): state = env.reset() route = [env.pos.copy()] for _ in range(max_steps): with torch.no_grad(): state_t = torch.FloatTensor(state).unsqueeze(0) action, _ = model.act(state_t, greedy=True) action = action.numpy().flatten() state, reward, done, _ = env.step(action) route.append(env.pos.copy()) if done: break valid, cost = evaluate_route(route, env) return route, valid, cost

逻辑说明:torch.no_grad()推理时不能省,否则显存会被反向图占光;greedy=True直接用动作均值,路线不会出现同一起点两次推理结果不同的情况,这对工程落地很重要。evaluate_route在推理结束后再跑一遍,目的是把“策略输出”和“安全验证”两层逻辑分开,前者出结果,后者把关。

参数说明:max_steps=300要和训练时保持一致,推理时如果步数比训练少,目标距离较远的场景会提前耗尽步数;比训练多则可能出现训练分布外的长轨迹行为。已有数据预测时,我一般先把原始坐标按等时间间隔重采样到固定步长,再喂给模型逐点推理,否则不同日志长度会让结果不可比。

5.4 部署到应用场景时要注意的接口问题

从仿真到真实飞行,这个项目留给我的接口扩展点很明确。第一是坐标系,仿真里用的是相对坐标,真机必须先把经纬度转成东北天坐标,再映射到仿真空间,而且要和飞控用同一套原点约定,否则路线挂载到飞控后会出现系统性偏移。第二是动作限幅,策略网络输出的步长比例要乘真实最大飞行速度,并在飞控端再做一次硬限幅,避免模型异常输出时指令越界。

第三是传感器接入,项目目前是纯仿真状态,真实部署需要把激光雷达点云或深度相机数据先转成障碍物列表,这一步的延迟直接决定策略还能不能实时响应。第四是模型导出,PyTorch 模型转 ONNX 时要注意输入输出张量的 batch 维度固定为 1,动态轴要显式声明,否则在边缘设备的推理引擎里会报维度错误。第五是安全优先级,紧急返航、通信丢失保护、电量过低报警这些逻辑必须独立于强化学习策略,放在飞控底层,策略层没响应也不能影响安全兜底。

6. 验证指标与超参调试:先看哪几个数,再动哪一个旋钮

评估模型有没有练到位,我一般先看五组数:成功率、平均路径长度、平均飞行步数、最小障碍距离、推理耗时。成功率是所有测试地图中到达目标的占比,低于 0.8 说明策略泛化不行;平均路径长度和平均飞行步数反映路线效率,两者一起看能排除“绕远路”的情况;最小障碍距离看安全裕度,数值贴近安全半径说明路线太极限;推理耗时决定能不能上真机,单步超过 50 毫秒就要考虑换小网络或做算子融合。

调超参我坚持一次只动一个旋钮,顺序是学习率、裁剪系数、折扣因子、GAE 参数、网络宽度。学习率先试3e-4,崩了就降到1e-4,震荡就退回3e-4并减小 batch;裁剪系数默认 0.2 一般不用动,但训练不稳时可以先调低;折扣因子0.99适合中等步数回合,如果你的地图动辄上千步,要用0.995。记录实验时固定随机种子,不然同一组参数两次结果差异会误导判断。

参数对照表里基本就这几项:学习率1e-4 ~ 3e-4,裁剪系数0.1 ~ 0.3,折扣因子0.98 ~ 0.995,GAE lambda0.9 ~ 0.98,隐藏层宽度128 ~ 512,轨迹缓存大小2048 ~ 8192。训练时如果成功率上不去,先检查奖励量纲而不是盲目加网络容量;平均路径变长但成功率上升,说明碰撞惩罚和距离奖励的比例偏紧,适当降低转向惩罚权重让路线更直。

我自己踩过最深的一次坑,是同时改了学习率、奖励权重和障碍密度,结果训练发散后根本分不清是哪个参数导致的,只能全部回退重新来。从那以后我每次训练都强制走一遍固定流程:先跑基准配置、只改一个参数、记录成功率、路径长度和最小安全距离三组数,确认有效再动下一个。希望帮你把复现时间花在真正值得调的地方。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询