简介面向计算机、自动化、电子信息等专业的毕业设计或课程大作业一套基于TensorFlow与Gazebo的DDPG端到端移动机器人导航项目完整可用。代码均经过测试并通过运行验证拿来即可使用既能支撑答辩与项目展示也可作为深度学习新手从理论到实践的进阶资料。压缩包共28个文件以Python源码、演示动画GIF、项目配置XML和说明文档MD为核心整体约50MB结构紧凑、模块划分清晰方便按需查阅。目前已有55人学习/下载资源中特意保留的action_dim2失败与action_dim1成功对比实验直观展示了连续控制中动作维度对训练效果的影响。结合源码、说明与演示动画读者可以深入理解DDPG网络构建、Gazebo仿真环境交互以及端到端导航的训练思路并在此基础上继续改造适配课程设计、毕业设计等实际场景。1. 为什么说DDPG导航项目的重头不在训练而在环境做连续控制的移动机器人导航最棘手的地方反而不是DDPG神经网络本身而是仿真环境一次次在细节处把训练流程击穿。这份基于TensorFlow与Gazebo的DDPG深度强化学习导航资源把源码、说明文档、论文和数据集整理成了一套可以从零复现的毕业设计闭环。它解决的是移动机器人从激光雷达观测到速度指令的端到端映射问题——连续动作空间正好是DDPG的强项而Gazebo提供可重复的碰撞代价和随机化场景。适合做毕业设计、课程大作业或者想快速把手里的ROS仿真环境跑起来验证强化学习效果的人。接下来我会把网络设计、奖励函数、超参设置以及我实际复现时踩过的坑逐条拆给你看。2. DDPG选型与Gazebo环境搭建从连续动作到仿真通信2.1 DDPG不是黑匣子确定性策略梯度在导航里的定位先明白DDPG在导航任务里的定位。移动机器人导航归根结底是一个马尔可夫决策过程每个时刻读取机器人状态输出一个动作动作造成状态转移环境给出奖励。但这里的动作不是离散的按键而是连续的线速度和角速度v, w。如果用DQN动作空间必须先离散化比如把角速度分成-1.0、-0.5、0、0.5、1.0五档档位越细精度越高但Q值输出维度也跟着成倍增加训练效果很难看。DDPG的关键在于用确定性策略梯度求解。Actor网络直接输出一个连续的动作值Critic网络去评估这个动作在当前状态下到底好不好。Actor的学习信号从Critic的梯度中来而不是像传统Policy Gradient那样靠采样一大片动作来估算期望回报。这样做的优点是方差低适合控制频率高的机器人场景缺点是对超参数敏感奖励函数稍有偏差就容易不收敛这一点我会在第5章重点展开。DDPG项目在TensorFlow 2.x下面实现时用的都是Keras的Model子类把Actor和Critic定义成两个独立的网络再挂一个目标网络做软更新。还有一个必须理解的概念是“经验回放”。Gazebo里采样的每个状态s动作a奖励r下一状态s会存进一个很大的循环队列训练时随机抽一批出来更新网络。这样做的原因是把时间上强相关的样本打散避免网络因为连续几十步都撞墙而把模型带偏。如果你的训练脚本只采样不存buffer每一步的经验都会直接丢弃学习过程就会变成“机械记忆最近的动作”梯度更新方向极其不稳定。选型理由用几句话概括就是动作连续、控制频率快、需要离线复用历史经验这三点都指向DDPG。而Gazebo在这个链路里的角色是数据源和碰撞裁判它输出激光雷达和里程计数据同时用物理引擎计算碰撞、摩擦和电机响应。把真实世界换成仿真环境的好处是你可以在一张地图上随机初始化机器人位置跑几百次这种随机化在真实实验里很难做到。TensorFlow负责网络定义和梯度计算Gazebo负责数据生成和交互反馈两者通过ROS话题通信这是这套资源的基本架构。2.2 Gazebo环境搭建模型、话题和传感器数据流Gazebo环境搭建在毕业设计里最容易卡死的点是版本匹配。常见做法是装ROS Noetic配Gazebo 11或者ROS Melodic配Gazebo 9。这套资源对应的环境按论文里的说明来装如果你机器上已经装过别的ROS版本一定要先查版本兼容表否则launch文件启动后会提示找不到gazebo_ros插件这是第一道坎。这里给出我一般会走的环境验证流程# 1. 启动仿真世界这里以TurtleBot3为例换成资源包里的launch文件即可 roslaunch turtlebot3_gazebo turtlebot3_world.launch # 2. 另开终端确认话题已经发布 rostopic list | grep -E scan|odom|cmd_vel # 3. 直接看一下激光雷达数据有没有刷新5秒内应有连续数据 rostopic echo /scan --noarr -n 5代码逻辑说明第一步启动仿真世界本质上是把机器人URDF模型、激光雷达传感器和物理引擎串起来第二步用rostopic list检查话题/scan是激光雷达、/odom是里程计、/cmd_vel是速度指令话题这三条构成了DDPG训练的数据闭环第三步是看数据刷新频率如果/scan长时间没有新数据多半是传感器插件没有加载成功需要回头查launch文件。常见做法里还有一个必查项是模型路径。Gazebo默认要从GAZEBO_MODEL_PATH读取模型库我遇到过好几次启动后场景一片空白的情况原因就是模型库没有下载完整或者环境变量没有写进~/.bashrc。检查方法很简单echo $GAZEBO_MODEL_PATH ls /usr/share/gazebo-*/models | head -20如果资源包里自带world文件还要确认world里引用的模型名字和实际model目录里的名字完全一致大小写不一致也会导致加载失败。这一步看似基础但几乎所有第一次跑Gazebo的人都会在它上面花掉半天时间。传感器数据流这部分我建议把训练代码直接订阅ROS话题而不是从Gazebo内部拿数据。这样做的边界很清楚数据格式、坐标系、时间戳都由ROS层处理后续要换真机时算法代码不用改只要把话题来源从Gazebo换成真机的传感器节点。下面这张表整理的是这套导航系统最常用的几个话题和数据结构写论文时也可以直接引用话题消息类型内容在导航里的用途/scansensor_msgs/LaserScan激光雷达的距离数组观测状态输入/odomnav_msgs/Odometry机器人位置与速度状态与目标计算/cmd_velgeometry_msgs/Twist线速度和角速度动作输出/base_linktf坐标系变换把目标点转到机器人坐标系其中/scan的angle_min、angle_max和angle_increment决定了激光雷达的视野角度和分辨率Gazebo默认是180度但不同厂家的真机传感器可能是270度。状态空间设计时要和这个参数保持一致不然换真机时间维度对不上。另外要检查激光雷达装在哪个高度、能不能扫到障碍物底部雷达高度太高的话低矮障碍物会直接漏检训练出的策略在真实场景里会有盲区。这个细节放在选型章来讲是为了让你在一开始就建立“把Gazebo当成真机替身”的意识。下面进入状态空间、动作空间和奖励的设计这些才是DDPG能否收敛的核心。3. 端到端导航实现状态、动作、奖励与Actor-Critic网络3.1 状态空间设计激光雷达降采样和目标坐标组合端到端导航的重头戏在第一层观测状态到底怎么组织。有人误以为“端到端”就是把激光雷达原始数据全部塞进网络其实工程里很少这么做。Gazebo的激光雷达一个周期能出几百个点如果全塞进网络Actor和Critic的输入维度会变得巨大而且相邻角度上的点在物理上高度冗余。常见做法是先降采样之后再拼上目标信息。我一般会把180度的激光数据下采样到36个点每5度一个再加3个目标相关量目标距离、目标相对于机器人朝向的角度差、上一时刻的动作。为什么一定要加目标信息因为纯激光数据只有障碍物分布没有“我要去哪”的意图导航就变成了无头苍蝇乱撞。import numpy as np def build_obs(laser_ranges, laser_angle_min, laser_angle_max, goal_pose, robot_pose): 从激光雷达和机器人/目标位姿构建DDPG观测向量 laser_ranges: 原始激光距离数组 goal_pose / robot_pose: 世界坐标系下的位姿 # 1. 激光数据过滤无效值归一化到0~1 ranges np.clip(np.nan_to_num(laser_ranges, nan3.5), 0.0, 3.5) ranges ranges / 3.5 # 2. 均匀采样36个点从原始点数组里按索引抽 idx np.linspace(0, len(ranges) - 1, 36, dtypeint) obs_lidar ranges[idx] # 3. 目标坐标转到机器人坐标系 dx goal_pose[0] - robot_pose[0] dy goal_pose[1] - robot_pose[1] dist np.sqrt(dx**2 dy**2) theta np.arctan2(dy, dx) - robot_pose[2] # 4. 拼接成最终观测 obs np.concatenate([obs_lidar, [dist, theta / np.pi]]) return obs.astype(np.float32)这段代码的逻辑有四个要点首先把激光原始数据里的nan和超出量程的值统一裁剪到3.5米再除以3.5实现归一化避免距离值量纲差异影响网络输入然后用等距取索引的方式把激光点数从几百压到36这一步直接把网络输入维度降了一个量级第三是把目标位置从世界坐标转到机器人坐标系theta除以pi是为了让角度变化范围落在[-1,1]区间最后把激光和两个目标标量拼起来得到一个38维的观测向量。参数说明lidar分辨率、max_range、目标距离的截断值都是可调的。如果机器人速度比较快我会把36改成48或64让网络看得更远如果环境里障碍物很密3.5米的最大探测距离可以缩到2.5米让网络更关注近处的避障。但记得所有改动都要同步改网络输入层维度这点很多初学者会漏掉一改观测维度就忘了改Actor和Critic的Input层直接报维度不匹配。3.2 动作空间与速度约束避免原地打转的限幅细节动作空间是线速度v角速度w两维看起来简单但限幅很有讲究。底盘的物理约束一般是线速度上限0.22m/s、角速度上限2.75rad/s这是TurtleBot型底盘常见参数。如果直接把Actor网络输出不加限制训练初期网络会疯狂输出大角速度机器人变成原地陀螺永远不会前进碰撞次数和训练时长都会爆表。def action_to_twist(action, v_max0.22, w_max2.75): 把Actor输出的连续量映射到底盘速度指令 action: 网络输出, shape(2,), 范围理论上在(-1,1)之间 v np.clip(action[0], -v_max, v_max) w np.clip(action[1], -w_max, w_max) # 常见做法倒车速度限一半防止训练初期频繁倒退碰撞 v np.clip(v, -v_max * 0.5, v_max) # 原地旋转最小角速度阈值低于这个值直接置0避免机器人缓慢抖动 if abs(w) 0.05: w 0.0 twist Twist() twist.linear.x v twist.angular.z w return twist这段代码在速度映射上做了两层保险。第一层倒车限幅到正转速度的一半因为导航任务中绝大多数情况需要前进倒退只用于脱困限幅能显著降低碰撞频率第二层设置0.05rad/s的角速度死区避免机器人因为微小角速度输出而一直缓慢转动这在Gazebo里会表现为“画圈前进”看起来像漂移。实际调参过程中如果机器人还是打转我会把v_max降到0.18让前进和转弯的比例更平衡。动作空间设计还有一个被忽略的细节Actor网络的输出是tanh激活值域天然在(-1,1)但如果你把动作直接当作速度用0.22和2.75这两个限幅就完全没意义。必须经过上述映射代码还原成物理量纲。而且限幅参数要和你使用的机器人模型对齐不同底盘参数差别很大Gazebo世界文件里的两轮差速模型和真车的最大速度可能完全不同迁移真机前一定要重新查底盘手册。3.3 奖励函数设计稀疏奖励在Gazebo里根本学不动这是整个项目中最玄学的部分。资源包里的reward设置如果直接用很可能训练两三个小时reward曲线都不动一下。原因很直白稀疏奖励下机器人要随机撞到目标点才得到一次正反馈在连续动作空间里这个概率低到可以忽略。所以必须做奖励塑形。我的默认奖励构成是四项叠加到达目标加10分、碰撞罚-5分、距离下降给正向小奖励每步加0.1、角速度过猛罚-0.1。所有距离量都尽量放到机器人坐标系下计算不要直接用世界坐标距离否则机器人转弯时距离计算也会有波动。def compute_reward(dist_now, dist_last, collision, reached, omega): 塑形后的奖励函数 dist_now/dist_last: 机器人到目标的当前距离与上一时刻距离 collision: 是否碰撞 reached: 是否到达目标 omega: 当前角速度, 用于限制原地打转 if reached: return 10.0 if collision: return -5.0 # 距离接近奖励: 比上一时刻更近就给正奖励 delta dist_last - dist_now reward 0.0 if delta 0: reward 0.1 # 角速度惩罚: 抑制原地旋转行为 reward - 0.1 * min(abs(omega), 1.0) return reward奖励函数里最关键的是距离接近奖励的方向判断。注意delta dist_last - dist_now只有“现在距离比上一时刻近”才加0.1这意味着机器人每往前走一小步只要方向正确就能持续获得正向反馈形成稳定的学习信号。碰撞惩罚-5和到达奖励10的比例也很重要我调过很多版本如果碰撞惩罚小于-3机器人会倾向“冲过去撞了再倒车”训练过程反复横跳如果大于-8机器人会变得极度保守在障碍物前很远就停下来永远到不了目标。角速度惩罚这一项经常被忽略但它对训练质量影响很大。不加这一项时机器人学到的最优策略可能是原地快速旋转扫描目标方向然后直接冲过去看似有效但动作非常暴躁转真机时电机根本承受不了。加了惩罚后策略会自动收敛到平缓的转弯。参数0.1可以调整如果发现转弯还是太猛可以提到0.2但要小心别让机器人不敢转弯变成全程直线行驶然后频繁撞墙。提示奖励函数的输出要在同一个量级内到达奖励、碰撞惩罚和逐帧奖励不能差太多否则大数值奖励会主导梯度让网络忽略小奖励携带的方向信息。3.4 Actor与Critic网络TensorFlow Keras的两种结构网络结构在毕业设计里不必追求花哨MLP加ReLU足够完成这个任务。Actor要输出连续动作所以最后一层用tanh把值映射到(-1,1)Critic要把“动作好不好”评出来所以输入是状态和动作拼在一起的向量。这里有一个细节Critic最好把动作在第二层才拼进来比第一层直接拼接效果更稳这也是DDPG原论文实现里的常见做法。import tensorflow as tf def make_actor(obs_dim, action_dim2): obs_input tf.keras.Input(shape(obs_dim,)) x tf.keras.layers.Dense(256, activationrelu)(obs_input) x tf.keras.layers.Dense(256, activationrelu)(x) action tf.keras.layers.Dense(action_dim, activationtanh)(x) return tf.keras.Model(obs_input, action) def make_critic(obs_dim, action_dim2): obs_input tf.keras.Input(shape(obs_dim,)) action_input tf.keras.Input(shape(action_dim,)) x tf.keras.layers.Dense(256, activationrelu)(obs_input) # 动作在第二层拼接Critic能先提取状态特征再融合动作信息 x tf.keras.layers.Concatenate()([x, action_input]) x tf.keras.layers.Dense(256, activationrelu)(x) q_value tf.keras.layers.Dense(1)(x) return tf.keras.Model([obs_input, action_input], q_value)Actor和Critic的隐藏层都取256加256对38维输入、2维输出的任务规模来说已经足够。如果训练时发现欠拟合可以先加到512再观察不要一上来就堆大网络如果加了容量还不见涨那大概率是奖励函数的问题而不是网络容量不够把这个判断顺序记住能省很多时间。Critic把动作放在第二层连接背后的逻辑是让Critic先独立理解状态再去看动作在这个状态下的价值。参考DDPG原论文的对比实验这种做法比第一层直接拼接整体表现要好。另外注意Actor的激活函数是tanh输出天然在(-1,1)配合3.2节的动作映射代码把数值还原到真实速度范围整个动作链路才算完整。4. 训练主循环与关键超参从回放采样到软更新4.1 训练回环经验回放、Critic更新和软更新tau训练主循环的骨架很简单但每一步都要知道在做什么。先从replay buffer里随机采样一小批经验这批经验包括当前状态s、动作a、奖励r、下一个状态s、是否结束done。然后用目标网络计算下一个状态的最大Q值这是一个“未来价值”的估计值因为当前Critic更新需要一个目标标签但未来价值无法精确知道所以用目标网络来近似。核心训练代码里必须包含三个更新Critic更新、Actor更新、目标网络软更新。这里给一个浓缩版的核心循环batch_size 128 gamma 0.99 tau 0.005 # 从replay buffer随机采样一个batch states, actions, rewards, next_states, dones replay_buffer.sample(batch_size) with tf.GradientTape() as tape: # Critic的预测Q值 q_values critic([states, actions], trainingTrue) # 目标网络计算下一状态的动作与Q值 next_actions target_actor(next_states) next_q_values target_critic([next_states, next_actions]) # 贝尔曼方程的目标值 targets rewards gamma * (1 - dones) * next_q_values critic_loss tf.reduce_mean(tf.square(targets - q_values)) critic_grads tape.gradient(critic_loss, critic.trainable_variables) critic_optimizer.apply_gradients(zip(critic_grads, critic.trainable_variables)) # 更新Actor目标是最大化Critic给当前状态动作的Q值 with tf.GradientTape() as tape: cur_actions actor(states, trainingTrue) cur_q_values critic([states, cur_actions]) actor_loss -tf.reduce_mean(cur_q_values) actor_grads tape.gradient(actor_loss, actor.trainable_variables) actor_optimizer.apply_gradients(zip(actor_grads, actor.trainable_variables)) # 软更新让目标网络慢慢贴近当前网络 for target_param, param in zip(target_actor.weights, actor.weights): target_param.assign(tau * param (1 - tau) * target_param) for target_param, param in zip(target_critic.weights, critic.weights): target_param.assign(tau * param (1 - tau) * target_param)逻辑说明Critic的loss是目标Q值和预测Q值的均方差目标Q值由奖励加上折扣后的下一状态Q值得来。dones是一个关键掩码如果这一step已经结束比如碰撞就不能再加入下一状态的价值否则网络会学到“碰撞后还有未来价值”的错误逻辑。Actor的loss取负的Critic Q值均值因为梯度下降方向是让loss变小而我们要让Q值变大所以取负号。软更新公式是目标参数 tau * 当前参数 (1-tau) * 目标参数tau取0.005意味着每次只向当前网络移动0.5%这是为了让目标值稳定变化不会因为当前网络突然大幅更新而导致训练震荡。训练循环里还有一个容易被忽略的边界条件就是episode结束的判断。碰撞和超时都要算结束。我一般把单回合步数上限设为150步超过就结束并给一个额外的小惩罚不然机器人会在迷宫里绕圈绕到天荒地老缓冲区里全是绕圈数据网络被污染得很厉害。超时惩罚要单独记录日志训练后期如果超时频率降下来说明策略确实在逼近目的地而不是在撞墙和绕圈之间来回切换。4.2 超参抄作业表学习率、噪声和缓冲区参数这份资源包里默认的超参可以直接跑通但在不同Gazebo场景下有几个参数需要手动调。我把一套在TurtleBot3地图上能稳定收敛的初始参数列出来后面调参以这个为基准每改一个参数只动一个变量不要同时改多个参数初始值调参方向与经验actor学习率1e-4太大容易震荡太小收敛慢换复杂地图先降到3e-5critic学习率1e-3比actor高一个量级可以让Q值先稳定下来gamma折扣因子0.99接近1表示看重长期收益路径规划任务常用0.95~0.99tau软更新系数0.0050.001~0.01之间越小目标网络越稳定buffer容量200000Gazebo跑数据慢建议不少于10万条经验batch_size12864~256之间显存不够先降到64探索噪声std0.1训练前5万步用0.2之后降到0.05单episode步数上限150地图变大时按对角线长度增加参数说明拆几个重点actor学习率不能照搬critic学习率经验是critic学习率比actor高一个量级critic先把“状态-动作值”估计准确actor的梯度才可信。探索噪声std指加在动作上的高斯噪声标准差强化学习需要随机性来探索未知区域但噪声过大会导致动作在限幅边缘反复横跳所以训练中段要线性递减。buffer容量这个参数最容易被忽视Gazebo仿真一小时内采样不到两万条经验如果buffer只有五万很快就存满旧经验被挤掉前面学到的东西会被冲掉。注意DDPG的目标网络必须在训练启动前复制一份当前网络的初始权重而不是随机初始化。如果漏了这一步critic_loss初期会爆炸reward曲线第一轮冲到很高然后迅速归零。多提一个容易被忽略的细节保存模型时要同时保存当前网络和目标网络。很多同学训练完只保存actor下次加载时直接用忘了目标网络也需要重建。如果直接从空白的target_actor开始做软更新前几百步的目标Q值是错的训练效果会明显退化。4.3 训练监控reward曲线之外的三个实操信号很多新手盯着reward曲线reward一波动就慌。真实经验是reward曲线噪声极大因为单次episode里碰撞惩罚和到达奖励差异悬殊一两步之间reward就可能跳好几分。我一般用三个更稳定的信号判断训练健康状况平均episode长度、碰撞率、到达率。这三个信号比纯reward更平滑且物理含义清晰。操作上可以写一个简单的记录器在每个episode结束后把length、collision_num、arrive_num写进CSV然后用matplotlib画图。不需要一开始就上TensorBoard等训练跑到一半再逐步把loss、q值、平均reward加进去。中期看critic_loss是否持续下降后期看到达率是否突破0.5这两个才是真正的收敛信号。# 每隔100个episode算一次平均回合长度和碰撞次数 tail -n 100 train_log.csv | awk -F, {sum$1; n} END {print avg episode length:, sum/n} awk -F, {if($20) coll} END {print collision episodes:, coll} train_log.csv这个命令在训练跑起来以后每100个episode计算一次平均长度和碰撞频率。平均长度下降说明避障能力提升因为机器人更快到达或更快失败但如果长度持续下降而到达率不涨说明策略陷入局部最优比如学会了提前撞墙结束episode来避免长时间绕圈。这时候不是继续等而是回去调奖励函数或者重置网络这就是第5章要展开的坑。5. 复现中最容易翻车的五个坑5.1 Gazebo启动后空白场景模型库路径没生效现象roslaunch一切正常但Gazebo主界面一片灰白机器人模型也没出现终端没有任何报错。原因GAZEBO_MODEL_PATH环境变量没设置或者模型库没有下载完整。资源包里的world文件引用模型时如果模型找不到Gazebo只会静默跳过而不是报错最终展示一个空白场景。解决把资源自带的models目录路径写进~/.bashrc再重新sourceecho export GAZEBO_MODEL_PATH$HOME/.gazebo/models ~/.bashrc echo export GAZEBO_MODEL_PATH${GAZEBO_MODEL_PATH}:$PWD/models ~/.bashrc source ~/.bashrc解决后重新启动launch。如果还是空白用gz model --list查看模型有没有被加载。另外注意world文件里的mesh引用路径不能是相对路径否则换目录启动照样加载不出来。这个坑80%的Gazebo新手都会遇到不要怀疑代码问题先查模型库。5.2 reward长时间不涨奖励值域没做归一化现象训练两个小时reward一直在很小的负值附近波动没有上升趋势critic_loss也不下降。原因奖励函数量纲和观测量纲没有统一。比如碰撞惩罚给-5、到达奖励给10而激光雷达观测归一化在0~1之间目标距离却没有归一化输入量级差异巨大。网络输入层被大数值维度主导梯度更新方向被带偏。解决把所有观测和奖励都做归一化。观测侧dist要除以max_range奖励侧我习惯把碰撞惩罚改成-1.0、到达奖励改成2.0、接近奖励0.02让总奖励大致落在[-1,2]区间。改完归一化之后reward曲线立刻开始有波动说明梯度更新方向变正常了。这是我从一个五万步学费里换来的经验强化学习的输入输出量纲不统一比网络结构错误还致命。5.3 机器人原地打转角速度限幅和噪声参数过激现象训练到中期机器人到了目标旁边却不停下来一直原地转圈或者一个大转角把自己带偏平均episode长度只有30步。原因角速度限幅太宽或者探索噪声太大。DDPG在探索阶段会往动作上加高斯噪声如果噪声标准差0.2且角速度上限2.75噪声累加之后角速度很容易触到2.75边界机器人每次不是左转就是右转根本没有直线行驶的机会。解决把探索噪声std在训练初期降到0.1角速度上限降到1.5同时加角速度死区。把3.2节代码里的死区从0.05提高到0.1让微小角度误差不触发转弯机器人就能更稳定地逼近目标。我遇到这个问题时的做法是把打转时的episode长度打印出来发现几乎都是50步以内结束说明还没走几步就转晕了。降低噪声后平均回合长度很快涨到80以上才开始真正学避障。5.4 从仿真转真机话题对不上激光雷达角度范围不一致现象仿真里训练好的模型拿到真机上完全失灵机器人一进走廊就往墙上撞甚至一启动就在原地螺旋。原因Gazebo默认雷达180度、分辨率每度一个点真机雷达可能是270度、分辨率0.36度。网络输入层是定长的如果真机扫描点数不是36个降采样后的形状网络直接报维度错误即使reshape成功角度范围和起始角不同也会导致观测语义错位网络拿到的“第5个点”在仿真里和真机上指向完全不同的方向。解决状态构建层要单独写一个适配函数把真机数据先裁剪成和仿真一致的角度范围。最有效的做法是提前在代码注释里写明雷达角度范围和分辨率把lidar_range_deg和downsample_points这两个常量集中定义换平台只改一处。这也是资源包里值得优先看的部分它把仿真和真机的适配接口单独抽出来了改起来不用翻整个训练脚本。5.5 物理时间与训练速度严重不匹配现象训练跑了一整晚实际只训了一万步仿真里机器人连一圈都没走完日志显示每一步都卡在等待传感器数据上。原因Gazebo的物理仿真步长和训练脚本同步太紧。训练代码每次都要等待/scan更新如果/scan发布频率是20Hz那么训练频率最高也只有20Hz再加上仿真负载波动实际频率可能只有5Hz。解决把replay buffer的填充和网络训练拆成两个线程Gazebo只负责采样训练线程在buffer积累到一定量后再开始。另一个常见做法是提升Gazebo的仿真速度把real time factor调大但物理精度会下降碰撞判断相对不可靠我一般不动它。更推荐的做法是单独设一个高频的cmd_vel发布节点和DDPG的训练步长解耦避免每步都等待Gazebo物理仿真一圈的时间。拆成双线程之后训练速度能提升3到5倍这是投入产出比最高的一项改动。6. 把训练好的模型拿去做评测与改进6.1 评测流程成功率、固定case和动作回放训练完模型后别急着说“能导航了”。我每次都会强制走一遍固定评测流程单独启动一个评测脚本在地图里固定摆放20个起点和10个目标点生成200组起点-目标对每个episode设定最大步数上限为200统计到达率。到达率超过70%才敢往下做真机迁移。评测时把机器人的速度、角速度和激光扫描数据都录成日志跑完之后按时间轴回放专门观察“哪里有犹豫、哪里有漂移”这些主观判断比单一数字更容易看出策略短板。评测环节最能发现的问题是训练场景和评测场景的差异。如果训练时机器人只从固定起点出发评测时换个起点就找不到目标说明网络记住了路径而不是学会了导航。所以评测地图一定要和训练地图错开布局最好把起点、目标点、障碍物位置全部重新随机化。一个低成本高价值的改进是在奖励函数上动手脚。上面第3章的奖励函数都是围绕“到达目标”做正向引导如果想让机器人学避障不绕远路可以把奖励改成到达奖励10加碰撞惩罚-10加每步惩罚-0.01让机器人倾向于尽快到达而不拖沓。想要更平滑的轨迹可以再加入一个步间角速度变化惩罚让相邻两步的角速度差越小奖励越高。这些塑形项加完之后训练出来的轨迹从“锯齿状”变成“平滑弧线”后面转真机时电机跟随效果会好很多。我自己的教训是第一次跑通时完全没做评测直接拿模型在Gazebo里跑结果在某些角落反复卡住。后来我花了一整天把评测脚本和日志回放跑顺才真正看出来问题是奖励函数对近距离障碍物没有惩罚梯度机器人会在“还敢往前吗”和“撞上去算了”之间反复横跳。从那以后我每次修改奖励函数或网络结构都强制自己先跑一遍200组固定评测case用数据说话不再凭感觉判断收敛。希望帮到你。本文还有配套的精品资源点击获取
