☰
MP-DQN深度强化学习无人机避障与目标追踪:Python从零复现
2026/10/12 1:24:09 网站建设 项目流程

简介:基于深度强化学习的无人机自主避障与目标追踪论文复现资源,采用TensorFlow 2.2构建多感知Deep-Q-Networks(MP-DQN),面向具备Python编程能力与强化学习基础的科研人员和工程开发者。资源通过可运行代码与逐段解释展示了环境参数设定、两层全连接QNetwork双模态结构、经验缓冲区设计、奖励函数推导及ε-greedy细化规则,并给出批处理梯度下降训练流程与独立测试环节的平均追捕成功率统计。包体为1个docx文档(共18页),大小仅28KB,以Python代码片段、参数配置与算法讲解为主,轻量易用。全套方案重点覆盖自动驾驶、机器人导航与无人机追踪等典型场景,已有382人学习使用。配套说明还提出增强CNN适配大尺度地图、引入更多固定障碍物、基于事件重要性实施非均匀重播采样等改进方向,便于读者在模拟环境中进一步优化避障性能与追踪效率。

1. 用深度强化学习做无人机自主避障与目标追踪,为什么 MP-DQN 值得复现

第一次把 DQN 代码跑通时发现,训练 loss 明明往下走,可仿真画面里的无人机还是直直冲向障碍物——后来才明白,稀疏奖励环境里普通 DQN 的前期探索效率太低。深度强化学习解决无人机自主避障与目标追踪的关键,不在网络有多深,而在怎么让模型在“避开障碍”和“追到目标”两个互相拉扯的目标里找到平衡。MP-DQN 算法做的正是这件事:用多步回报缓解稀疏奖励问题,用优先级经验回放让有价值的样本更快参与训练,用目标朝向编码避免避障时丢失追踪目标。本文用 Python 从零复现这套算法,覆盖环境搭建、网络实现、训练调参与避坑,最终交付一个可运行的最小闭环。适合正在做深度强化学习论文复现、或想在自己数据集上快速验证算法的开发者。

2. 仿真环境与状态空间设计:让无人机在 Python 里先“看见”障碍和目标

2.1 为什么先在 2D 平面仿真里复现:传感器模型简化与坐标定义

论文复现的第一步不是写网络,而是把环境定义清楚。如果一上来就接 AirSim 或 Gazebo,视觉噪声、IMU 漂移、风场扰动一股脑混进来,你根本分不清模型不收敛是因为策略写得差,还是因为传感器输入太脏。我一般先把环境压到 2D 平面:无人机是一个带朝向的质点,障碍物用圆表示,目标点用二维坐标表示,激光雷达退化成几条测距射线。等策略稳定了,再把传感器换成深度图或点云输入,迁移到更接近实飞的环境。这是论文复现里最稳妥的分层调试路径,也是 18 页复现笔记里最容易被省略、却最影响结果的一步。

坐标系放在机体坐标系下定义:无人机机头朝向为 x 轴正方向,y 轴向左,偏航角 yaw 逆时针为正。全局地图坐标通过 yaw 旋转矩阵变换到机体坐标,网络输入全部是相对量。这么做的好处很直接:模型学到的是“离左前障碍 0.3 米时该右转”这类相对判断,而不是“坐标 (42, 78) 处有障碍”这种绝对记忆。换个地图后者立刻失效,前者几乎无损迁移。这是复现中第一个值得留意的设计决策。

2.2 状态空间组成:测距环、目标角度编码和目标距离

状态空间设计直接决定训练难度。这里我取 state_dim = 7,由四部分拼接而成:三条测距射线(正前、左前、右前)的归一化距离,目标方位的 sin/cos 编码,目标归一化距离,以及当前线速度。目标角不用原始弧度而用 sin/cos 成对编码,是为了避开角度环绕问题——当目标从 +179° 变到 -179°,原始角度值跳变很大,但 sin/cos 两个值仍然平滑连续,网络更好拟合。

import numpy as np class UAVEnv: def __init__(self, map_size=100.0, max_range=10.0, max_lin_vel=2.0): self.map_size = map_size self.max_range = max_range self.max_lin_vel = max_lin_vel self.state_dim = 7 self.action_dim = 4 self.reset() def reset(self, seed=None): if seed is not None: np.random.seed(seed) self.uav = np.array([5.0, 5.0], dtype=np.float32) self.target = np.array([88.0, 82.0], dtype=np.float32) self.yaw = 0.0 self.linear_vel = 0.0 self.obstacles = self._random_obstacles() self.step_count = 0 return self._get_observation() def _get_observation(self): # 三条测距射线:相对机头 0°、-45°、+45°,结果除以最大量程归一化 ray_angles = [0.0, -np.pi / 4, np.pi / 4] dists = [min(self._ray_cast(a) / self.max_range, 1.0) for a in ray_angles] # 目标在全局坐标系下的相对位移 dx = self.target[0] - self.uav[0] dy = self.target[1] - self.uav[1] dist_target = np.hypot(dx, dy) / self.map_size # 全局向量旋转到机体坐标,目标方位角用 sin/cos 编码,避免角度环绕跳变 cos_yaw, sin_yaw = np.cos(self.yaw), np.sin(self.yaw) local_x = dx * cos_yaw + dy * sin_yaw local_y = -dx * sin_yaw + dy * cos_yaw target_angle = np.arctan2(local_y, local_x) obs = np.array([ dists[0], dists[1], dists[2], np.sin(target_angle), np.cos(target_angle), dist_target, self.linear_vel / self.max_lin_vel, ], dtype=np.float32) return obs

代码里的三个细节值得单独说。第一,ray_cast 用射线与障碍圆求交,按命中距离返回最近值,没射中就返回 max_range,这是 2D 平面里替代激光雷达最廉价的做法。第二,目标距离归一化除以地图边长而不是 max_range,因为目标可能远在 10 米之外,除以固定地图尺寸让值落在 [0,1],避免网络输入数值量纲不一致。第三,速度除以 max_lin_vel 后同样落在 [0,1],如果不归一化,线速度数值偏大,会在反向传播时主导梯度方向,挤压其它特征的贡献。这些看起来很小的归一化操作,实际对收敛速度影响很大。

2.3 动作空间与动力学模型:离散四动作与运动学约束

动作空间选择了离散四动作:悬停、左转前进、右转前进、全速前进。不用连续动作输出,原因是在这类论文复现里,离散动作配合 DQN 分支稳定且易调试;连续控制留给 DDPG 或 SAC 协议,那是另一套调参逻辑。无人机运动学用 unicycle 模型近似,线速度和角速度由动作映射得到,积分步长 dt = 0.1 秒。

def step(self, action): dt = 0.1 max_speed = 2.0 # 最大线速度 m/s turn_rate = 0.35 # 转向角速度 rad/s,约 20°/s if action == 0: yaw_rate, speed = 0.0, 0.0 elif action == 1: yaw_rate, speed = -turn_rate, max_speed * 0.6 elif action == 2: yaw_rate, speed = turn_rate, max_speed * 0.6 else: yaw_rate, speed = 0.0, max_speed self.yaw += yaw_rate * dt self.uav[0] += speed * np.cos(self.yaw) * dt self.uav[1] += speed * np.sin(self.yaw) * dt self.linear_vel = speed self.step_count += 1 collision = self._check_collision() reached = np.hypot(self.target[0] - self.uav[0], self.target[1] - self.uav[1]) < 1.0 done = collision or reached or self.step_count >= 500 reward = self._get_reward(collision, reached) return self._get_observation(), reward, done, {"collision": collision, "reached": reached}

动作到运动的映射里,左转和右转不是原地转,而是带 60% 速度的前进转向。这样更贴近真实无人机飞行特性,避免模型用“原地画圈”来刷存活时间。转向角速度 0.35 rad/s 大约是每秒 20 度,这个值经过测试属于“能灵活绕障、又不至于让状态变化太快”的区间。dt = 0.1 配合 500 步上限,单回合最长飞行 50 秒,足够覆盖从起点到目标点的路径。速度上限 2 m/s 是按小型无人机室内飞行安全速度取的,如果你在仿真里发现任务太简单或太难,优先调这个参数而不是奖励。

3. MP-DQN 的网络结构与训练逻辑:把论文里的改进点写成 PyTorch 代码

3.1 MP-DQN 的三处关键改进:多步回报、优先级回放与 Double Q

先把 MP-DQN 改了什么讲清楚。普通 DQN 用单步回报估计价值:Q(s,a) = r + γ·max Q(s',a')。这在奖励稀疏的避障任务里问题很大——无人机撞墙前可能 20 步都没有有效反馈,单步 TD 误差传播太慢。MP-DQN 把单步换成 n 步回报:累加连续 n 步奖励,再和 n 步后的状态价值相加。这样有效奖励能更快流回起始状态附近,训练信号不再“隔靴搔痒”。

第二处是优先级经验回放。普通回放池均匀采样,但避障场景里大量样本是“旁边没障碍、目标也没接近”的平庸转移,真正有价值的碰撞样本和目标接近样本占比很低。MP-DQN 按 TD-error 绝对值给样本打优先级,越高越容易被采到,同时在采样时用重要性采样权重修正偏差,防止高优先级样本反复出现导致过拟合。

第三处是 Double Q。计算 TD 目标时不再直接用目标网络 max Q,而是先用在线网络选出最优动作,再用目标网络给出该动作的估值。这两个值分开,能明显缓解 DQN 的 Q 值过估计问题。论文里对 MP 的命名有两种解释,一种叫 Multi-path,一种叫 Multi-step Prioritized,但核心复现点就是上述三个机制,名字不是重点。

3.2 用 PyTorch 定义值网络:7 维输入到 4 动作输出

网络结构不需要花哨。一个两层全连接、每层 128 个隐藏单元,输入 7 维状态,输出 4 个动作的 Q 值,足够应付这个规模的任务。更深或者加 LSTM 反而更容易过拟合。这里有一个容易被忽略的初始化细节:用 xavier_uniform 初始化线性层权重,并给偏置清零,否则前几次前向传播 Q 值会偏大,配合优先级回放容易在开局就炸 loss。

import torch import torch.nn as nn class MPQNetwork(nn.Module): def __init__(self, state_dim=7, action_dim=4, hidden=128): super().__init__() self.net = nn.Sequential( nn.Linear(state_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, action_dim) ) # 均匀初始化,控制网络输出的初始 Q 值量级 for m in self.modules(): if isinstance(m, nn.Linear): nn.init.xavier_uniform_(m.weight) nn.init.zeros_(m.bias) def forward(self, x): return self.net(x) online_net = MPQNetwork() target_net = MPQNetwork() target_net.load_state_dict(online_net.state_dict()) optimizer = torch.optim.Adam(online_net.parameters(), lr=1e-3) def soft_update(tau=0.005): # 软更新:目标网络参数缓慢逼近在线网络 for p, tp in zip(online_net.parameters(), target_net.parameters()): tp.data.copy_(tau * p.data + (1.0 - tau) * tp.data)

隐藏层 128 是一个经过权衡的值:7 维输入本身信息量不大,128 足够拟合 Q 函数,又不至于像 256 那样需要更多样本才能训稳。激活函数用 ReLU,中间不加 dropout——强化学习里加了 dropout 会让 Q 估计出现额外随机性,训练过程更难稳定。软更新系数 tau = 0.005 的意思是每次训练步只把目标网络往在线方向挪 0.5%,这比固定每隔 N 步硬拷贝更平滑,也是 MP-DQN 复现中能明显提升稳定性的改动之一。

3.3 优先级经验回放:用 TD-error 给样本排队

优先级回放的核心是一个带权重的采样器。我用数组直接实现,不用复杂的 SumTree,训练 500 回合这个规模足够。关键是两个超参数:alpha 控制优先级差异的放大程度,alpha=0 就是普通均匀采样;beta 控制重要性采样权重的补偿强度,通常从 0.4 开始随训练线性增到 1.0,因为训练后期优先级分布趋于平稳,不再需要强补偿。

class PrioritizedReplayBuffer: def __init__(self, capacity=50000, alpha=0.6, beta=0.4): self.capacity = capacity self.alpha = alpha # 优先级放大系数 self.beta = beta # 重要性采样权重系数 self.buffer = [] self.priorities = np.zeros(capacity, dtype=np.float32) self.pos = 0 def push(self, transition, td_error=1.0): # 新样本初始优先级给 1.0,避免刚进池的样本被饿死 priority = (abs(td_error) + 1e-6) ** self.alpha if len(self.buffer) < self.capacity: self.buffer.append(transition) self.priorities[len(self.buffer) - 1] = priority else: self.buffer[self.pos] = transition self.priorities[self.pos] = priority self.pos = (self.pos + 1) % self.capacity def sample(self, batch_size): n = len(self.buffer) probs = self.priorities[:n] probs = probs / probs.sum() indices = np.random.choice(n, batch_size, p=probs) # 重要性采样权重:补偿采样偏差,最后归一化到最大值为 1 weights = (n * probs[indices]) ** (-self.beta) weights = weights / weights.max() batch = [self.buffer[i] for i in indices] return batch, indices, weights.astype(np.float32)

push 里给新的样本 td_error 初值设 1.0,是为了防止首次被采到的概率为零。因为新样本还没算过 TD-error,直接给 0 的话 priority 就是 0,永远轮不到它。这个细节点论文里不会写,但复现时特别致命。sample 函数里 weights 归一化到最大值 1,等价于把权重缩放对梯度大小的影响控制住,不用再手动调整学习率。

计算 TD-error 时我在复现里用了 Double Q 的写法:

def compute_td_error(online_net, target_net, state, action, reward, next_state, done, gamma=0.99, n_step=3): with torch.no_grad(): q_now = online_net(state).gather(1, action).squeeze(1) if done: q_next = torch.zeros_like(q_now) else: # Double Q: online 网络选动作,target 网络估值 next_action = online_net(next_state).argmax(dim=1, keepdim=True) q_next = target_net(next_state).gather(1, next_action).squeeze(1) td_target = reward + (gamma ** n_step) * q_next td_error = td_target - q_now return td_error.cpu().numpy()

这里的 n_step 和 gamma 是组合使用的。gamma 取了 0.99,n_step 为 3,折算下来的有效折扣是 0.99³ ≈ 0.97,既保留了一定的远期回报,又不会让方差过大。如果 n_step 取到 5 以上,单条样本的奖励范围太长,在奖励塑形幅度不一致时方差会明显变大,这个坑后面专门讲。

4. 训练流程与参数配置:奖励塑形、epsilon 退火和回合设计

4.1 奖励塑形:把稀疏到达奖励变成稠密引导信号

奖励是复现里最“玄学”的部分,但有一些基本原则可以遵循:事件奖励负责定义任务边界,过程奖励负责引导探索方向。碰撞和到达目标使用较大的绝对值,避免被过程奖励淹没;过程奖励则控制在 -0.1~0.5 的小范围,防止无人机为了贪图过程奖励而偏离任务。

事件奖励值说明
碰撞障碍物-10回合立即终止
到达目标区域(距离 < 1.0m)+20回合立即终止
单步存活-0.05鼓励尽快完成,避免巡航浪费
目标距离每减小 1m+0.5稠密引导,按步更新
连续转向角度过大-0.02防止原地左右摇摆绕圈

碰撞 -10 和到达 +20 的差值设成 30,是为了让“撞墙前急转弯”这种行为的长期回报仍然高于“直接撞墙结束”。如果差值太小,模型会发现撞墙结束回合和绕路到达的总回报差不多,训练就失去方向感。

def _get_reward(self, collision, reached): reward = -0.05 # 存活成本 if collision: reward -= 10.0 elif reached: reward += 20.0 else: # 稠密引导:距离缩小幅度正比给奖励 prev_dist = self._prev_dist_to_target cur_dist = np.hypot(self.target[0] - self.uav[0], self.target[1] - self.uav[1]) reward += 0.5 * (prev_dist - cur_dist) self._prev_dist_to_target = cur_dist # 过度转向惩罚 if abs(self._last_yaw_rate) > 0.3: reward -= 0.02 return reward

稠密引导用“距离变化量”而不是“当前距离”。因为只用当前距离的话,网络会把“离目标近”和“奖励高”简单捆绑,模型学会停在目标附近不动;而用变化量则要求每一步都比上一步更接近目标,这种相对奖励在训练中稳定得多。存活成本 -0.05 是一个很小的值,单回合 500 步最多累计 -25,不会压过到达奖励,但足以让“原地悬停到回合结束”的策略无利可图。

4.2 关键训练参数:epsilon 退火、学习率与经验池容量如何搭配

参数配置直接影响训练曲线形状,这是复现最花时间的地方。下面这组参数是我在同类任务上反复调过的基础配置,可以直接作为起点。注意把所有参数写进单独配置文件,不要散落在训练循环里。

参数推荐值说明
回合数 episodes600小规模地图 400~600 回合足够
每回合最大步数500超出即截断终止
epsilon 初始/最低1.0 / 0.05初始全探索,最低留 5% 随机
epsilon 衰减系数0.995 / 回合约 300 回合降到 0.1
gamma0.99目标任务周期短,0.95~0.99 均可
n_step3多步回报步数
学习率1e-3用 Adam 优化器
batch_size64过小梯度噪声大,过大样本利用率低
buffer 容量50000约 100 回合的样本量
训练频率每 4 步优化一次平衡采样效率与计算开销
目标网络更新 tau0.005软更新系数

epsilon 退火是复现里要亲手盯的第一个参数。0.995 的衰减意味着大约 300 回合后 epsilon 降到 0.1 附近,此时模型已经积累了一定经验,开始从探索转向利用。如果衰减太快,前期有效样本不足;太慢,则模型始终在随机尝试,舍不得收敛。学习率 1e-3 是最通用的起点,但配合优先级回放时我会把学习率降到 8e-4,因为高优先级样本通常会放大梯度,稍小的学习率能对冲这个效应。

4.3 完整训练循环:回合交互、TD loss 计算和模型保存

训练循环本身并不复杂,但有几个节奏点需要把握好:经验池没攒够 batch_size 之前不开始训练;网络每 4 步优化一次;每个回合结束后做软更新。模型保存分为两种:按固定间隔保存可恢复的中间模型,以及单独保存当前最优评估模型。

def select_action(state, epsilon): # epsilon 贪心:随机动作与在线网络最优动作之间切换 if np.random.rand() < epsilon: return np.random.randint(4) with torch.no_grad(): q = online_net(torch.as_tensor(state, dtype=torch.float32).unsqueeze(0)) return int(q.argmax(dim=1).item()) def train_loop(episodes=600): buffer = PrioritizedReplayBuffer() step_count = 0 for ep in range(episodes): state = env.reset() done = False ep_reward = 0.0 while not done: action = select_action(state, epsilon) next_state, reward, done, info = env.step(action) # 暂存到临时队列,等攒够 n_step 再生成带多步回报的样本 temp_queue.append((state, action, reward, next_state, done)) if len(temp_queue) >= n_step: state0, action0, r_sum = temp_queue[0][0], temp_queue[0][1], 0.0 for i in range(n_step): r_sum += (gamma ** i) * temp_queue[i][2] done_n = temp_queue[n_step - 1][4] next_n = temp_queue[n_step - 1][3] td_error = compute_td_error(online_net, target_net, torch.as_tensor(state0).unsqueeze(0), torch.LongTensor([action0]), torch.tensor([r_sum]), torch.as_tensor(next_n).unsqueeze(0), done_n) buffer.push((state0, action0, r_sum, next_n, done_n), td_error[0]) temp_queue.pop(0) state = next_state ep_reward += reward step_count += 1 if step_count % 4 == 0 and len(buffer.buffer) >= 64: batch, indices, weights = buffer.sample(64) # 转成 tensor 并计算 loss loss = compute_batch_loss(batch, weights) optimizer.zero_grad() loss.backward() optimizer.step() # 新一批 TD-error 更新到 buffer 优先级 with torch.no_grad(): new_td = compute_batch_td(batch) for idx, td in zip(indices, new_td): buffer.priorities[idx] = (abs(td) + 1e-6) ** 0.6 soft_update(0.005) epsilon = max(0.05, epsilon * 0.995) if ep % 50 == 0: torch.save(online_net.state_dict(), f"models/mpdqn_ep{ep}.pth")

temp_queue 在这里起到桥梁作用:因为 n_step 样本需要等到未来 n 步的奖励都拿到才能生成,队列保证了数据在时间维度上的完整。每 4 步优化一次是我在训练中反复对比后确定的折中频率——每次优化都做会增加计算负担,但收益递减;每 4 步能让经验池中的新鲜样本和旧样本比例更均衡。模型保存按 50 回合一个间隔,不要只存最后一版,因为训练后期可能出现阶段性过拟合,中间那个模型反而泛化更好。

5. 复现 MP-DQN 的避坑指南:5 个让训练翻车的常见问题

5.1 loss 爆炸到几百,训练直接失去意义

现象:训练开始 20 个回合左右,loss 从个位数暴涨到几百甚至上千,后面就再也没降回来。

原因:优先级回放里重要采样权重没有归一化。当某些样本 TD-error 特别大时,(n * probs)^(-beta)的权重会拉高到几十倍,梯度被个别样本主导。另一个常见原因是网络初始化后 Q 值过预估,导致开局 TD-error 就偏大。

解决:sample 里务必加weights / weights.max()归一化;网络采用 xavier_uniform 初始化;如果还是不稳定,把学习率从 1e-3 降到 5e-4。这三个动作组合能压住大部分爆炸情况。

5.2 无人机原地打转,避障和追踪完全失效

现象:训练几百回合后,无人机会在一个小范围内反复左右旋转,既不去目标点也不主动避障,回合奖励稳定在 -5 左右不再上升。

原因:动作 0(悬停)的速度被设置成 0,而左转右转动作带前进速度。模型发现左转右转会改变位置、增加碰撞风险,悬停又能拿稳定的存活奖励,于是策略坍缩成“原地悬停保平安”。再加上转向惩罚设得太重,进一步放大了对转动的畏惧。

解决:把动作 0 改成“保持当前方向缓慢前进”而不是纯悬停,速度给 0.3 m/s;转向惩罚从 -0.02 降到 -0.01。让无人机即使不动也有微小前进趋势,防止模型躲进零运动的安全区。关键是奖励设计里不能让“什么都不做”成为局部最优点。

5.3 避障成功率高,但目标追踪基本在瞎撞

现象:模型能绕过障碍物,但经常在绕过障碍后方向偏掉,找不到目标点,最终在目标点附近绕圈。

原因:状态里只用了目标距离,没把目标方位编码进去。网络知道“目标远不远”,却不知道“目标在哪个方向”,追踪自然无从谈起。这是状态空间设计不完整导致的,不是网络结构问题。

解决:把目标相对方位角的 sin/cos 放进观测向量,就是我 2.2 节写的第 4、5 个维度。加了这两个维度后追踪成功率通常能从 30% 以下提升到 70% 以上。注意用 sin/cos 而不是原始角度值,理由前面已经说过,角度环绕会让网络白白浪费大量容量去拟合跳变。

5.4 换一张地图模型就全废,泛化能力几乎为零

现象:训练时用固定障碍物分布,模型在训练图上表现完美,一旦随机生成新地图,成功率骤降到接近 0。

原因:环境在每次 reset 时用同一组障碍物坐标,模型记住了障碍坐标到动作的映射,而不是学到“距离近就该避让”的泛化规则。本质上是把强化学习偏成了对训练集的过拟合。

解决:reset 里用_random_obstacles()每回合重新生成障碍位置,但固定起点和目标点,保持任务难度一致。并且验证时用另一套障碍布局评估模型真实效果。这个改动让训练难度提升,但换图后的成功率能从几乎为 0 提升到稳定 70% 以上,这个代价很值得。

5.5 n_step 设为 5 或更大,策略反而明显变差

现象:把 n_step 从 3 改成 5 甚至 8 后,训练 loss 依然下降,但最终回合奖励和成功率都下降,无人机行为变得激进、频繁撞障碍。

原因:多步回报累加了 n 步的稠密奖励和存活成本,n 越大累计值方差越大。尤其在奖励塑形里存在目标距离变化量这种正负波动的项,5 步累加后方差被放大,导致 TD target 噪声过大。另外 n 太大时,部分样本的 done 标记与真实终止状态错位,逻辑上也会引入偏差。

解决:回调 n_step = 3,并确认奖励塑形里的引导幅度控制在 0.5 以下,避免与事件奖励数值差距过大。如果确实需要更长的回报视野,建议改研究方向用 GAE 或 DDPG 这类连续控制算法,不要在 DQN 里硬撑多步累计。

6. 把训练好的模型接到闭环验证与实飞准备:先做这 3 件事

模型训练不代表复现结束,真正的验证环节常常能推翻你以为已经调好的成果。我习惯用固定回合数的随机地图评估代替肉眼看曲线,评估标准是避障成功率与目标追踪成功率的组合阈值。

def validate(policy, episodes=50): success = 0 collision_count = 0 for _ in range(episodes): state = env.reset() # 新的随机障碍布局 done = False while not done: action = policy.select_action(state, epsilon=0.0) # 纯利用,不探索 next_state, _, done, info = env.step(action) state = next_state success += int(info["reached"]) collision_count += int(info["collision"]) return success / episodes, collision_count / episodes

验证时注意三个细节。其一,epsilon 必须设 0.0,验证阶段不允许随机探索,否则评估结果混入了探索的偶然性,不同回合之间可比性差。其二,每回合 reset 要重新生成障碍布局,评估的是一次完整的策略泛化能力,不是模型对某一张地图的死记硬背。其三,把碰撞率和成功率分开统计,不要只看成功率——一个成功率 80%、但每失败一次都撞机的模型,和成功率 70%、失败只是超时的模型,部署价值完全不同。

部署检查我一般还会叠加两件事。动作平滑性检查:把连续 100 步的动作序列打印出来,如果出现频繁的 1 和 2 来回切(左转右转反复横跳),说明策略对状态噪声过度敏感,需要在动作输出端加一个最小切换间隔或一阶低通滤波。时间延迟测试:在 step 函数里人为加 30~50ms 延迟,模拟真实飞控指令发送的耗时,如果成功率掉得厉害,说明策略缺乏对延迟的鲁棒性,实飞时要预留更大的安全距离。

现在我每次跑完训练都会顺手把最后 50 回合的轨迹叠加在地图上存成一张 PNG,作为后续任何算法改动的对照基线。这个习惯帮我省了不少重复训练的时间,也让我能快速判断一次改动的收益是真实提升还是随机波动。希望帮到你。

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

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

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

立即咨询