
简介面向强化学习与智能机器人方向的开发者与研究者这份资源围绕深度确定性策略梯度DDPG算法提供了一个完整的机器人导航系统实现。项目覆盖路径规划、环境交互、奖励机制、状态空间与动作空间建模并包含神经网络训练、经验回放、目标网络与探索策略等核心模块可直接用于自动驾驶、无人机航拍、工厂自动化等连续控制场景的方案设计与代码学习。压缩包共60个文件核心为Python源码含环境、智能体、网络结构与训练脚本、训练与测试CSV数据以及可运行编译文件、配置文件、说明文档和附赠资源包体大小约6.59MB。目录划分清晰便于按“算法实现—数据记录—文档说明”路径快速上手。已有140人学习下载适合正在研究连续动作空间控制、需要参考完整DDPG导航项目或准备扩展改进算法的中高级学习者。1. 基于DDPG的机器人导航为什么连续动作空间里它比DQN更顺手一个装有激光雷达的差速小车要从 A 点走到 B 点中间有墙、有障碍物你不想手动布置路点也不想写一堆 if-else 避障逻辑而是想让机器人在仿真环境里自己试错、自己学会“看到障碍就减速转向”这种策略——这就是基于深度确定性策略梯度DDPG算法做机器人导航要解决的核心问题。DDPG 属于强化学习里的 actor-critic 家族它最大的特点是能直接输出连续动作比如“线速度 0.3 m/s 角速度 0.5 rad/s”而不是像 DQN 那样只能从预设的离散动作里挑一个。对于移动机器人导航连续的速度指令才是真实控制接口所以 DDPG 在实际落地中比 DQN 更顺手。这套方案的完整链路包括环境交互、状态空间与动作空间设计、奖励机制、神经网络训练、经验回放、目标网络稳定策略最后在仿真环境或实体小车上验证路径规划效果。2. DDPG 的核心机制拆解Actor-Critic、经验回放与目标网络在导航里各自干了什么2.1 为什么机器人导航不能用 DQN离散动作是死穴DQN 在 Atari 游戏上的成功让很多人误以为它可以直接迁移到机器人控制但真做导航时你会发现一个根本矛盾DQN 输出的是离散动作索引。你可以把动作空间定义为“前进、左转、右转、后退”四选一但小车实际底盘接收的是连续速度指令。如果你想用 DQN只能把连续空间离散化比如把角速度从 -1.0 到 1.0 按步长 0.2 切成 10 档线速度再切 5 档组合出来 50 个动作。这个做法有两个问题一是动作粒度太粗机器人走出来的轨迹是折线转弯时机往往刚好错过二是动作数量增多后DQN 的价值网络要同时评估 50 个动作的 Q 值训练样本效率急剧下降。实际项目里我见过有人把 DQN 的离散动作压到 8 个结果小车在窄走廊里来回撞墙根本原因是它永远等不到“刚好对准门缝”的那个动作。DDPG 直接把这个问题绕过去了。它的策略网络actor输入当前状态输出一个多维连续向量例如二维就是 [线速度, 角速度]每个维度经过 tanh 激活函数限制在 [-1, 1]再乘上你设置的速度上限。这样机器人能输出 0.3 m/s 的线速度配合 0.52 rad/s 的角速度这种任意组合控制细腻程度和 PID 手动调参差不多了。所以如果你要做的是“让机器人在连续动作空间里学习控制策略”DDPG 是比 DQN 合理得多的起点而这也是这个项目标题把 DDPG 算法单独拎出来的原因。2.2 Actor-Critic 架构策略网络与价值网络的分工DDPG 里有两个角色Actor演员负责决策Critic评论家负责评价。Actor 是一个参数为 θμ 的神经网络输入状态 s输出动作 aCritic 是一个参数为 θQ 的神经网络输入状态 s 和动作 a输出一个 Q 值表示“在这个状态下执行这个动作长期回报期望是多少”。训练时 Critic 的工作是尽量准确地预测长期回报它的损失函数是 TD errorLQ E[(r γ Q(s, a) - Q(s, a))²]其中 r 是即时奖励γ 是折扣因子Q 是目标 critic 网络的输出a 是目标 actor 网络在 s 状态下输出的动作。这个公式的核心意思是当前 Q 值应该逼近“即时奖励 下一状态的估计 Q 值”也就是把未来的回报逐步往回传。Actor 的更新用的则是策略梯度。为了让 Q 值尽量大Actor 朝着“当前状态对应的 Q 值上升最快”的方向调整参数。具体实现里梯度通过链式法则从 critic 的输出反传到 actor 的输出不需要像 REINFORCE 那样依赖完整回合的回报累计。这也是 DDPG 能在线持续学习的关键——每一步交互完都能立即更新两组网络不必等到一个 episode 结束。在导航任务里Actor 的输入通常是激光雷达距离数据、目标点相对位置、当前速度Critic 的输入除了状态还要拼上 Actor 给出的动作。不要小看这个“拼上动作”的细节Critic 只有同时看到状态和动作才能合理打分否则它无法判断“这个状态下动作 a1 比 a2 好在哪里”。我第一次实现时把 Critic 的输入只写成状态结果 Q 值永远不收敛后来才发现网络结构错了。2.3 经验回放与目标网络DDPG 中两个稳定训练的关键机制强化学习在线训练的一个天然问题是样本相关性太强。机器人这一秒的状态和下一秒的状态只差了一个控制周期如果直接按到达顺序训练网络会反复拟合最近几帧的数据把之前学到的经验忘掉。DDPG 解决这个问题的办法是建一个经验回放缓冲区replay buffer每次交互产生的 (s, a, r, s, done) 五元组都存进去训练时随机采样一批不相关的样本。相当于把机器人走过的路全部录下来训练时打乱重放。缓冲区大小一般设 50 万到 100 万条。导航任务每个 episode 可能产生 200 到 500 条经验100 万条意味着能覆盖大约 2000 到 5000 个完整轨迹这对让机器人记住“走廊尽头的分叉处如何选择”这种稀疏出现的场景很重要。采样批次大小我一般设 64 或 128太大则更新方向过于平均探索性弱太小则梯度噪声大。目标网络是另一个稳定化手段。DDPG 里有两套网络在线网络和目标网络。在线网络实时更新目标网络则通过“软更新”缓慢跟进θ_target ← τ θ_online (1 - τ) θ_targetτ 一般取 0.001 到 0.01。这样做的好处是计算 TD target 时用的 Q 值来自一个变化很慢的网络不会因为当前网络一步跳变过大而导致训练发散。打个比方你在教一个新员工判断工作好坏如果评价标准每天变他会疯掉目标网络就是那个“标准相对稳定”的导师。3. 状态空间、动作空间与奖励机制导航任务建模的细节3.1 状态空间怎么定义激光雷达、目标点与速度信息如何组织导航任务的状态空间设计直接决定算法能不能学出来。我常用的方案是把三部分信息拼接成一个一维向量激光雷达测距值、目标点相对位姿、当前速度。激光雷达部分不是把所有扫描线全部塞进网络。比如 360 度、1 度分辨率的雷达一次返回 360 个数值直接输入会让网络参数暴涨而且相邻角度的距离值高度相关训练效率反而低。常见做法是降采样到 24 到 36 个等间距角度覆盖机器人四周 360 度。另一个做法是使用 180 度前向扫描加尾部两个测距点因为差速小车一般不倒车。我自己的习惯是取 24 维均匀分布在 360 度上的距离值做一个截断超过 3 米的统一置为 3 米这样网络不需要区分“远处 4 米”和“远处 10 米”的差异都能视为无障碍。目标点相对位姿一般用机器人为参考系而不是全局坐标。因为神经网络对绝对坐标不敏感但对“目标在我的左前方 45 度、距离 2 米”这种相对描述学得很快。计算方式是用目标全局坐标减去机器人全局坐标再旋转到机器人坐标系dx_local (dx_global · cos(θ) dy_global · sin(θ)) dy_local (-dx_global · sin(θ) dy_global · cos(θ))然后把 dx_local、dy_local 或者距离和角度拼进状态向量。当前速度线速度、角速度也必须包含否则策略网络不知道小车现在是在往前走还是在打转输出的动作会缺乏连续性轨迹容易出现抖动。最终状态向量形如state [laser_24维, dx_local, dy_local, v, ω]共 29 维。这里维度不宜太高300 维以上的状态向量会让样本效率急剧下降仿真环境里还算能忍真机上训练成本就完全无法接受了。3.2 动作空间连续速度指令的约束与归一化动作空间比状态空间简单但约束条件不比状态少。差速机器人的控制输入常见是 (v, ω)线速度和角速度。DDPG 的 actor 输出层用 tanh 激活输出范围天然是 [-1, 1]你需要把它映射到实际物理限制v_real v_tanh · v_max ω_real ω_tanh · ω_max举个例子你设 v_max 0.5 m/sω_max 1.0 rad/s那 actor 输出的 (0.3, -0.6) 对应实际指令是 (0.15 m/s, -0.6 rad/s)。这里有个容易被忽略的细节差速小车执行 (v, ω) 指令时如果 v 太小而 ω 太大机器人基本上在原地打转里程计积分误差会快速累积。所以有的实现会对动作空间加一个约束比如 |ω| ≤ k · (v ε)限制原地旋转。这个约束可以在 actor 输出之后、环境执行之前做一次处理或者直接在动作向量里加一个惩罚项。另外要注意动作空间不能直接使用原始速度值而不做归一化。原因在于 actor 网络最后一层 tanh 的梯度特征输入范围过大时误差反向传播的梯度数值不稳定。把动作限制在 [-1, 1] 再做缩放既能匹配激活函数的敏感区间又让超参数比如噪声方差与机器人的物理速度上限解耦。改天你想让同一套训练代码跑在不同速度上限的小车上只需要改 v_max 和 ω_max 两个常数网络和噪声参数都不用动。3.3 奖励机制稀疏与稠密怎么结合以及“贪快”的反效果奖励机制是导航训练里最“玄学”也最影响成败的部分没有之一。太稀疏的奖励——比如只有到达终点给 10撞墙给 -10其余每步 0——理论上是可行的但实践中 DDPG 很难从零开始探索到终点尤其当起点和目标距离超过 5 米时随机策略基本找不到目标整个过程就是随机游走训练十几个小时曲线纹丝不动。所以你需要稠密奖励来引导探索。最常见的做法是让每一步的奖励与“到目标点的距离变化”挂钩r_step α · (d_prev - d_now)其中 d_prev 是上一步到目标的距离d_now 是当前步到目标的距离。只要这一步离目标更近就得到正奖励。这个设计思路很直觉但你很快会踩坑机器人可能学会原地转圈或左右横跳因为只要某一帧“碰巧”让距离变小就获得正奖励整体路径根本不高效。因此还要叠加一个时间惩罚r_step α · (d_prev - d_now) - ββ 是一个小正数比如 0.01每一活步都扣一点逼机器人尽快到达。如果任务地图较大β 太大机器人会抄近路撞墙太小又会慢慢悠悠绕远路这个参数需要反复调。最终的奖励函数我的经验写法是分段组合到达目标50并结束当前 episode碰撞障碍物激光雷达最小读数低于安全阈值-20并结束正常步进α·(d_prev - d_now) - βα 取 2.0β 取 0.01这里 α 和 β 的比值要控制好。另一种常见错误是只给“接近目标”的正奖励而不惩罚碰撞距离结果机器人学会紧贴墙边滑行——因为贴着墙走往往能让距离观测值逐渐下降但行为显然是错的。考虑到激光雷达有测量噪声碰撞判定不要只看单一测距点我会取最近的三根激光束的最小值做滤波避免小车视觉上“贴着墙”但雷达偶发读到更远的斜向距离导致漏判。4. 训练实现从网络结构到训练循环4.1 网络结构两个全连接网络BatchNorm 与激活函数的选择DDPG 的 actor 和 critic 网络结构都不复杂复杂的是怎么让它们在导航任务里稳定训练。以 29 维状态输入、2 维动作输出为例我常用的 actor 网络如下import torch import torch.nn as nn import torch.nn.functional as F class Actor(nn.Module): def __init__(self, state_dim29, action_dim2, hidden256): super(Actor, self).__init__() self.fc1 nn.Linear(state_dim, hidden) self.bn1 nn.BatchNorm1d(hidden) self.fc2 nn.Linear(hidden, hidden) self.bn2 nn.BatchNorm1d(hidden) self.fc3 nn.Linear(hidden, action_dim) def forward(self, state): x F.relu(self.bn1(self.fc1(state))) x F.relu(self.bn2(self.fc2(x))) # tanh 把动作压到 [-1, 1]之后在环境侧缩放为实际速度 return torch.tanh(self.fc3(x))BatchNorm1d 在这里很重要。激光雷达距离值虽然做了截断归一化但不同场景下数据分布仍然差异很大比如空旷房间和狭窄走廊的雷达读数方差完全不同。没有 BatchNorm网络很容易在训练中后期因为输入的分布漂移而出现梯度不稳定。需要注意的是推理时 BatchNorm 要切换成 eval 模式否则它还会用训练 batch 的均值方差做归一化造成行为不一致。Critic 网络结构略有不同它的输入是状态拼接动作class Critic(nn.Module): def __init__(self, state_dim29, action_dim2, hidden256): super(Critic, self).__init__() # 先单独编码状态 self.fc1 nn.Linear(state_dim action_dim, hidden) self.bn1 nn.BatchNorm1d(hidden) self.fc2 nn.Linear(hidden, hidden) self.bn2 nn.BatchNorm1d(hidden) self.fc3 nn.Linear(hidden, 1) def forward(self, state, action): x torch.cat([state, action], dim1) x F.relu(self.bn1(self.fc1(x))) x F.relu(self.bn2(self.fc2(x))) return self.fc3(x)隐藏层宽度我选 256。太窄比如 64拟合不了激光雷达观测与最优动作之间的复杂映射太宽比如 1024在样本量不够的导航场景里很容易过拟合表现为训练集上 Q 值一直涨但仿真测试时小车一进新环境就乱撞。中间层的激活函数建议 ReLU输出层 actor 用 tanh、critic 不用激活这是 DDPG 的标准配置一般不需要创新。4.2 训练循环采样、更新、软更新的完整节奏训练循环是整篇代码里最需要照抄的部分节奏不对网络就学不动。我按顺序拆解如下# 超参数 lr_actor 1e-4 # actor 学习率过大会导致策略跳变 lr_critic 1e-3 # critic 学习率通常比 actor 大 5 到 10 倍 tau 0.005 # 目标网络软更新系数 gamma 0.99 # 折扣因子 batch_size 64 # 采样批次大小 buffer_size 500000 # 经验回放缓冲容量 noise_std 0.1 # 探索噪声标准差 noise_decay 0.9995 # 噪声衰减系数 # 初始化网络与目标网络 actor Actor(state_dim, action_dim) critic Critic(state_dim, action_dim) actor_target Actor(state_dim, action_dim) critic_target Critic(state_dim, action_dim) # 把在线网络参数复制到目标网络 actor_target.load_state_dict(actor.state_dict()) critic_target.load_state_dict(critic.state_dict()) # 优化器 optim_actor torch.optim.Adam(actor.parameters(), lrlr_actor) optim_critic torch.optim.Adam(critic.parameters(), lrlr_critic) # 经验回放缓冲区 replay_buffer deque(maxlenbuffer_size) for episode in range(max_episodes): state env.reset() # 重置到随机起点和目标点 total_reward 0 done False for step in range(max_steps_per_episode): # 1. 动作选择加探索噪声OU 噪声或高斯噪声 action actor(state) noise np.random.normal(0, noise_std, sizeaction.shape) action np.clip(action noise, -1.0, 1.0) # 2. 与环境交互 next_state, reward, done, info env.step(action) # 3. 存入经验回放 replay_buffer.append((state, action, reward, next_state, done)) # 4. 经验足够后开始训练 if len(replay_buffer) batch_size: batch random.sample(replay_buffer, batch_size) states, actions, rewards, next_states, dones zip(*batch) states torch.FloatTensor(np.array(states)) actions torch.FloatTensor(np.array(actions)) rewards torch.FloatTensor(np.array(rewards)).unsqueeze(1) next_states torch.FloatTensor(np.array(next_states)) dones torch.FloatTensor(np.array(dones)).unsqueeze(1) # 计算 TD targetr γ * target_Q(s, target_actor(s)) next_actions actor_target(next_states) target_Q critic_target(next_states, next_actions) y rewards gamma * (1 - dones) * target_Q # 更新 critic current_Q critic(states, actions) critic_loss F.mse_loss(current_Q, y.detach()) optim_critic.zero_grad() critic_loss.backward() optim_critic.step() # 更新 actor目标是最大化当前 Q 值 actor_loss -critic(states, actor(states)).mean() optim_actor.zero_grad() actor_loss.backward() optim_actor.step() # 软更新目标网络 for target_param, param in zip(actor_target.parameters(), actor.parameters()): target_param.data.copy_(tau * param.data (1.0 - tau) * target_param.data) for target_param, param in zip(critic_target.parameters(), critic.parameters()): target_param.data.copy_(tau * param.data (1.0 - tau) * target_param.data) state next_state total_reward reward if done: break # 每 20 个 episode 衰减一次探索噪声 if episode % 20 0: noise_std max(0.02, noise_std * noise_decay)这段循环里需要注意几个细节。dones 的处理很关键当 terminal 发生时 target Q 不能再往后传否则目标值会包含超出 episode 的虚假回报代码里用 (1 - dones) 做掩码实现。actor 的损失在 PyTorch 里写成负的期望 Q 值最大化 Q 等价于最小化 -Q。两个网络共用一个状态输入但图层不同需要确认梯度不会穿到对方网络的参数上所以 actor 的 loss 只反传到 actor 的参数critic 的 loss 只反传到 critic 的参数PyTorch 的优化器设计天然保证了这一点。4.3 超参数学习率、噪声、回放缓冲区的配对建议超参数里第一个要强调的是 actor 和 critic 学习率的比例。我见过太多人把两个网络的学习率都设成 1e-3结果 critic 学得飞快actor 跟不上策略每更新一步就变化很大训练曲线反复振荡。经验做法是 critic 用 1e-3actor 用 1e-4 或者更低的 3e-5。理由很简单actor 的更新要依赖 critic 提供的梯度信号如果这个信号本身不稳actor 的低学习率就是一个天然的滤波器。探索噪声的标准差建议初始 0.1 到 0.2随训练进行衰减到 0.02 左右。注意不要衰减到 0因为 DDPG 的确定性策略一旦完全失去噪声就容易陷入局部最优比如永远沿着某一条固定路径走换一个起点就不会了。我一般保留 0.02 到 0.05 的残余噪声作为持续探索。经验回放缓冲区的大小也直接影响训练效果。缓冲区过小低于 10 万条会让数据分布偏向最近的轨迹样本相关性上升缓冲区过大超过 200 万条会让旧经验占据主导而旧经验里大量是早期随机探索产生的无效数据拖慢后期学习。导航场景我常用 50 万条同时每 episode 结束后把当天新数据优先参与采样。5. DDPG 导航训练避坑4 个高频问题与排查方法5.1 训练半天 loss 不降或者 reward 曲线平躺现象训练 500 个 episodetotal reward 始终在 -100 到 -50 之间波动没有上升趋势。原因最常见的是探索噪声太小导致机器人从第一个 episode 开始就只做小幅动作永远撞不上墙也到不了目标学不到任何有效梯度信号或者是奖励函数里的距离变化项 α 太小正负奖励几乎抵消网络分辨不出哪些动作更好。解决先检查噪声。把噪声标准差临时提高到 0.3观察机器人是否开始“乱窜”。如果它都不怎么动说明动作输出被 tanh 限制后乘以最大速度还是太小检查速度映射。再检查奖励曲线里每一档的占比——我常用的办法是打印最近 100 步的平均奖励等指标看奖励是否稀疏如果只有到达和碰撞两种非零奖励就要把距离差项调大。α 可以从 2.0 往上加直到机器人明显表现出“朝向目标走时奖励变高”的行为。5.2 轨迹抖动小车走出的路径在目标点周围来回打转现象训练后机器人能到达目标点附近但始终定不下来在目标点周围画出半径 0.3 米左右的圈或者反复前后蠕行。原因到达目标的判定距离太苛刻比如设置为 0.1 米而机器人实际控制精度达不到于是只能在附近反复徘徊也可能是速度映射比例失调最小速度指令已经超过了精度所需的控制分辨率导致机器人永远过冲。解决把到达判定阈值放宽到 0.15 到 0.3 米取决于机器人尺寸和定位精度。如果这个阶段训练曲线已经很高说明策略本身没问题是任务定义与控制分辨率的匹配问题。另一个做法是在距离小于 0.5 米时把线速度上限从 v_max 降为 0.3·v_max——相当于一个简单的“低速接近段”可以显著消除尾部抖动。这个技巧不需要改网络改环境封装即可。5.3 仿真里跑得好真机就翻车sim-to-real 的观测噪声问题现象在 gazebo 仿真里测试成功率 90%部署到真实小车后前几步就撞墙或者朝目标方向走的过程中突然偏转。原因仿真环境里的激光雷达读数没有噪声状态转换是确定性的真机上雷达有镜面反射、混叠、盲区里程计积分也会漂移。仿真里网络可能过度依赖“雷达值超过 3 米”这种干净特征真机上这个条件会因为玻璃或低反射物体失效。解决在仿真训练时给激光雷达观测加高斯噪声方差约为测量值的 2% 到 5%给里程计位姿加累积漂移。另一个更有效的做法是随机化起始位姿和地图障碍物布局。每次 reset 时把障碍物位置随机挪动 10 到 20 厘米目标点随机变化逼网络学习“通用的避障导航策略”而不是某一张地图的记忆。我踩过的坑是用了固定地图训练仿真测试一次通过率 95%真机一换场地立刻翻车加随机化后这个问题明显缓解。真机测试前还要确认速度上限是否和仿真一致两个环境的 v_max 差 0.1 m/s 都可能让策略的行为边界失效。5.4 训练崩溃Q 值突然爆炸变负数现象训练正常进行到几百个 episodecritic loss 突然从小幅波动变成上万然后所有 Q 值输出变成很大的负数之后再不恢复。原因最常见的是奖励没有做尺度控制。如果奖励函数里最大奖励是 50碰撞惩罚是 -20而 Q 网络的初始输出接近 0目标值在训练初期会在 [-20, 50] 之间跳变梯度很容易让某些层的权重更新幅度过大进入“死亡 ReLU”或梯度爆炸状态。另一个原因是 batch 采样到了连续同一时刻的样本导致 TD target 自相关过高引发连锁发散。解决一是把奖励整体 scale 到 [-1, 1] 区间比如到达 1.0、碰撞 -0.5、步进惩罚 -0.01这些值能学而大数值反而降低稳定性。二是检查采样是否均匀——确保随机采样不要按 episode 顺序取样。三是给 critic 的梯度加 cliptorch.nn.utils.clip_grad_norm_(critic.parameters(), 1.0)防止单个 batch 的异常样本把网络打飞。这个坑的特点是一旦崩溃基本救不回来只能 rerun所以要在训练脚本里周期性保存模型检查点至少每 100 个 episode 存一次。5.5 维度不匹配state 与 action 拼接时的隐性错误现象程序不报错训练也能跑但效果极差。看了网络结构发现 actor 输出维度是 2critic 输入维度是 state_dim action_dim似乎没错。原因可能是状态向量里混入了未归一化的原始距离值比如激光雷达返回 0.08 到 10 米不等的原始值而其他特征都在 0 到 1 之间导致网络输入分布严重倾斜。距离值需要先除以雷达量程把范围压到 [0, 1]。另一种可能是在拼接时用了错误的数据类型action 是 numpy float64state 是 torch float32torch.cat 直接报类型错但如果你用 tensorflow 反而不会报错只会静默做类型转换训练效果就悄悄变差。解决统一在采样后做一次torch.as_tensor(state, dtypetorch.float32)和torch.as_tensor(action, dtypetorch.float32)。把状态向量的每个维度做 min-max 归一化范围全部压到 0 到 1 之间。归一化手段不必复杂但一定要有。这里可以先把雷达距离除以测量上限把 dx_local 和 dy_local 除以最大距离常数速度除以 v_max然后拼接就能发现训练稳定性上一个台阶。6. 评估与验证怎么确认导航系统真的“学会”了6.1 三个核心评估指标到达率、路径长度比、平均速度训练结束时不能只看 total reward那个数值包含了太多奖励权重的主观因素。我习惯在固定的测试场景里跑固定数量的 episode比如 50 个起点和目标随机统计三个客观指标。到达率是最直观的定义为成功到达目标点的 episode 数量除以总 episode 数量。对 5 米以内的近距离导航我认为低于 80% 都说明策略还不稳对 10 米以上的复杂环境60% 以上可以接受。路径长度比是实际行驶轨迹长度除以两点间直线距离这个比值越接近 1 越好。1.0 到 1.3 说明策略路径接近最优1.5 以上说明绕路明显可能原因是奖励函数里距离差项权重不足。要注意碰撞结束的 episode 不参与这个统计否则路径长度会被截断拉低数值产生“撞墙了但路径比很高”的假象。平均速度只统计成功到达的 episode衡量机器人在实际导航中的效率。单位是 m/s一般低于 v_max 的 50% 说明策略过于保守。三个指标放在一起看才能判断系统的真实水平——比如到达率 95% 但路径长度比 1.8机器人可能只是学会了“安全地绕远路”并不会让你满意。6.2 可视化复盘轨迹叠加与 Q 值曲线怎么读代码跑完只能看到 reward 曲线太抽象我强烈建议做两个可视化。第一个是把每个测试 episode 的机器人轨迹画到地图上用不同颜色区分成功和失败轨迹。失败的轨迹会告诉你策略在哪类障碍物附近出问题——是总在直角转弯处撞墙还是到了目标点附近就开始乱转还是被凹形障碍物困住。这些信息比任何 loss 曲线都直接是调参最快的方式。第二个是看 Q 值曲线。把同一个状态下比如起点处critic 对不同动作的打分差异可视化。一个合格的 critic 应该在“朝向目标且避障”的动作上打出明显高于“朝向墙”的动作的分数。如果两者差距不显著说明 critic 还没有学会分辨好动作和坏动作actor 自然也学不好。这个检查可以直接用训练好的模型在测试状态上做一次前向推理打印 Q 值。最后说一个我自己的习惯从记录的训练检查点里挑出过去 20 个 episode 平均 reward 最高的那个模型而不是训练结束时的最终模型。因为强化学习训练过程本身就带着波动最后一帧未必是最优策略。把最好的检查点导出、跑完整测试集、保存轨迹可视化结果这个流程才算完整闭环。整个 DDPG 导航系统的实现核心不是把代码跑通而是建立起“策略—仿真—评估—调整”的正反馈每一次踩坑都让奖励函数和观测归一化更贴近机器人实际。希望帮到你。本文还有配套的精品资源点击获取