简介:面向强化学习领域学习者、科研人员及机器人控制开发者,这是一套基于深度确定性策略梯度(DDPG)算法的机器人导航系统完整实现,解决未知环境下自主路径规划与目标导航问题,适合已掌握Python基础并希望实践连续控制任务的读者。压缩包共60个文件,大小6.59MB,内含Python源码(.py)、编译字节码(.pyc)、训练/测试数据(.csv)及开发配置文件与说明文档(.xml/.iml/.md/.docx/.txt),涵盖算法、训练、数据与文档全流程。已有141人学习下载,可用于强化学习课程设计、毕业设计或科研复现场景。资源提供完整训练与测试脚本,覆盖环境交互、奖励机制、状态与动作空间构建、演员-评论家(Actor-Critic)网络设计、经验回放、目标网络与OU噪声探索策略等关键模块,并附有训练数据、测试数据、日志记录和使用说明。读者可直接运行训练脚本复现导航效果,也可按代码、数据与文档的类别定位所需内容,作为科研实验或导航应用开发的参考基础。
1. DDPG加持的机器人导航:连续动作空间里的一场"边撞边学"
把一个两轮差速小车从起点送到目标点,听起来是路径规划的老问题,但当你把地图换成未知环境、把障碍换成动态出现的柱子,传统全局规划就失效了。基于深度确定性策略梯度算法(DDPG)的强化学习机器人导航系统,解决的正是这件事:让机器人在不建图的前提下,通过与环境反复交互,自己学会"怎么走"。
导航这个任务天然是连续控制——底盘输出的是线速度和角速度,不是"左转、右转"的离散按键。而DDPG是少有的、在连续动作空间里能稳定学到策略的强化学习算法,它用Actor-Critic结构、经验回放和目标网络,把"传感器读数到控制指令"的映射直接训练出来。这篇文章面向两类人:一类是有ROS或仿真基础、想入局强化学习导航的从业者;另一类是已经跑通过DQN版本导航,想知道为什么换成DDPG之后收敛曲线更平滑、路径更短的人。我会把状态空间定义、奖励机制、网络结构、训练参数和真实踩坑一次讲透。
2. 为什么导航选DDPG而不是DQN或PPO:连续控制、离策略样本效率与端到端决策
导航任务看似简单,选算法却决定了后面三个月的成败。很多人第一步就栽在"用DQN跑导航"上——不是不能跑,而是动作空间的离散化会让机器人行为和路径质量都很别扭。这一章先把选型逻辑讲清楚,再落到状态空间、动作空间、奖励机制和环境交互这四个设计决策上。
2.1 连续动作场景下DQN的离散化困境与PPO的采样代价
DQN能处理高维状态,但动作必须离散。导航里底盘命令是线速度v和角速度ω,理论上都是连续值。如果你把v离散成[0, 0.2, 0.4, 0.6]三档,ω离散成[-1, 0, 1]三档,组合起来是9个动作。机器人走出来的轨迹全是折线,贴着障碍物走的时候,离散角速度会让它要么撞墙要么绕远路。想提高控制精度,把v分成10档、ω分成20档,动作空间变成200个,DQN的Q值输出层直接变成200维,训练难度指数上升,而且很多动作在实际控制里是冗余的。
PPO是策略梯度算法,理论上能处理连续动作,但它是on-policy的——每次更新只能用它当前策略采出来的那批数据。导航环境里一个episode动辄几十步甚至上百步,采样速度慢,而DDPG是off-policy的,用经验回放缓冲区把历史transition存下来反复训练,同样时间去环境里采一次样,DDPG能更新更多步,样本效率高出不少。我做模拟项目X时对比过:同样的仿真环境、同样的目标距离,PPO跑到500万步时成功率才刚过七成,DDPG到200万步已经稳定在八成五以上。这不是说PPO不行,而是单机仿真环境下DDPG的性价比更高。
还有一个细节点:导航策略输出的是控制命令,必须保证动作的连续性。DDPG的Actor网络最后接一个tanh激活函数,输出天然被压缩在[-1,1]区间,再映射到底盘线速度、角速度的范围,命令平滑且不会突变。这对真机尤为重要——底盘电机受不了高频阶跃信号。
2.2 状态空间设计:从激光雷达原始数据到归一化特征向量
状态空间定义是DDPG导航里第一个决定成败的设计点。常见做法是把三类信息拼成一个向量:里程计给出的机器人位姿(x, y, yaw)、目标点在机器人坐标系下的相对位置(dx, dy)、激光雷达或深度传感器的测距数据。这里有个关键细节:目标坐标一定要转换到机器人坐标系下,而不是用全局坐标。因为策略网络学到的是"当前看到什么、该往哪打方向"的映射,如果输入全局坐标,机器人到了不同位置就得重新泛化,学习难度大增。
我一般会用10个方向的激光测距值,均匀分布在-90度到+90度之间,加上目标距离和夹角。这样做的好处是输入维度低(12维),网络训练快,而且对传感器更新频率的要求不高。有人把全量720线激光数据直接丢进去,状态维度高了10倍,网络参数多了几十万,收敛速度明显变慢,导航成功率反而没提升。
状态归一化是另一个容易被忽略的点。激光测距范围可能是0.1米到10米,目标距离甚至可能到20米,尺度差异大会让神经网络的初始梯度更新方向被数值大的维度主导。我通常会把激光数据除以最大测量距离,把相对距离除以一个经验阈值(比如10米,超过10米就截断),夹角除以π,让所有分量落在[-1,1]附近。这一步不做,你会发现训练前期loss下降很快,后面怎么调都不动——因为网络参数已经跑到某个维度的"深谷"里了。
2.3 动作空间与奖励机制:稀疏大奖励、稠密小奖励怎么搭配
动作空间在导航里一般定义为两维:线速度v和角速度ω。真实底盘的速度有限制,比如v∈[0, 0.5]m/s,ω∈[-1.0, 1.0]rad/s。DDPG输出的是[-1,1]的值,映射时线速度需要再做个偏移,把[-1,1]映射到[0,0.5]而不是让机器人能倒车,因为很多室内导航任务里倒车动作会导致路径抖动、训练不稳定。角速度则直接线性映射。
奖励机制是强化学习导航最像"玄学"的部分。我采用的通用框架是三步走:第一步,每步给一个小的时间惩罚,比如-0.01,让机器人明白"走得越快越好";第二步,靠近目标给稠密正奖励,用上一时刻相对距离减去当前时刻相对距离,乘以一个系数,相当于鼓励每一步都朝目标方向前进;第三步,到达目标区给一个稀疏大奖励,比如+10,同时碰撞或超时给-5并终止这一轮。
这里要特别注意稀疏奖励和稠密奖励的比例。如果距离差奖励系数太大,机器人会为了每一步多减少一点距离而贴近障碍物走,导致碰撞率上升;如果太小,奖励信号被时间惩罚淹没,机器人学了10万步还在原地打转。我调参的一个经验值是:时间惩罚-0.01,距离差奖励系数0.5(按米计算差值),到达奖励+10。这几个值需要根据你环境里的最大距离来缩放,不要让每个episode的总奖励超过几十,否则Q值目标容易爆炸。
2.4 环境交互与训练循环:step里那套"感知-决策-执行-反馈"闭环
DDPG训练本质上是把这个循环跑几百万次:环境输出状态s,Actor网络根据s算出一个动作a,底盘执行a,环境推进一个仿真步长,返回下一时刻状态s'和奖励r。这些数据(s, a, r, s', done)被存进经验回放缓冲区,训练时随机采样一小批出来,更新Critic网络和Actor网络。
这个循环里有一个容易翻车的点:动作噪声。完全用确定性策略去探索,机器人只会沿着当前策略走,一旦初始策略差,它永远发现不了"绕开障碍物"这条路径。常见做法是在动作上加Ou噪声(Ornstein-Uhlenbeck噪声),它不像高斯白噪声那样每一步独立,而是带有时间相关性,让机器人朝某个方向持续偏转一段时间,更适合探索空间中的连续路径。训练前期噪声幅度大,后期逐步衰减,让策略从"疯狂试探"过渡到"稳定执行"。
环境交互的仿真步长也要控制好。步长太大会导致机器人在两个状态之间穿墙而过,碰撞检测失效;步长太小则单个episode步数太多,训练速度慢。我常用的设置是仿真步长0.1秒,单episode最大步数200步,对应20秒的导航时间,够跑完一个10米左右的规划任务,又不至于让超时惩罚永远触发不了。
3. 搭建仿真导航环境并落地DDPG:从仿真环境封装到经验回放的最小可跑代码
原理聊完,这一章落地。目标是让你在本机把一个能跑通的DDPG导航训练循环拉起来。我会按"环境定义→奖励封装→经验回放→训练主循环"的顺序,给你一套最小可跑的代码结构。仿真环境用什么不重要,重要的是环境接口要统一成reset和step两个方法。
3.1 仿真环境的选择与坐标系约定:统一接口才能换环境不换代码
仿真环境我建议直接用一个支持自定义机器人模型和传感器、能返回激光数据和里程计的通用物理仿真平台,这类平台社区里有很多。关键不是挑贵的,而是把它的输出统一封装成"状态向量+碰撞标志+到达标志+步数计数"。坐标系上约定:机器人初始位置在原点,朝向x轴正方向;目标点用全局坐标表示,在reset时随机生成。
封装这一步决定了你后面换环境、换地图的代价。我做某跨平台系统时就是先写了一个环境适配层,内部把不同仿真平台的传感器数据格式统一成标准结构体。这样训练代码完全不感知底层是哪个仿真器。环境类的核心接口只有两个:reset()返回初始状态,step(action)返回(s', r, done, info)。
还要明确"到达条件"和"碰撞条件"的判定。到达条件我用的是目标点欧氏距离小于0.3米——这个阈值接近机器人底盘半径,太大会让机器人还没到位就提前结束,路径质量差;太小则后期收敛极慢。碰撞条件则由仿真平台的物理引擎直接给出,我只需读取对应的碰撞标志位。
3.2 定义状态空间、动作空间与奖励函数:一张代码把前面三节串起来
class NavEnvWrapper: def __init__(self, sim, max_steps=200): self.sim = sim self.max_steps = 200 self.steps = 0 self.lidar_angles = self._generate_angles(10) # -90到+90度,10束 def reset(self): self.steps = 0 self.sim.reset_pose(x=0.0, y=0.0, yaw=0.0) self.sim.set_goal_random(min_dist=2.0, max_dist=8.0) return self._get_state() def _get_state(self): # 激光数据除以最大量程,归一化到[0,1] lidar = self.sim.get_lidar(angles=self.lidar_angles) lidar_norm = np.clip(lidar, 0, 10.0) / 10.0 # 目标在机器人坐标系下的相对距离和夹角 dx, dy = self.sim.get_goal_in_robot_frame() dist = np.sqrt(dx**2 + dy**2) / 10.0 # 距离归一化 angle = np.arctan2(dy, dx) / np.pi # 夹角归一化到[-1,1] return np.concatenate([lidar_norm, [dist, angle]]) def step(self, action): self.steps += 1 # action来自DDPG输出,范围在[-1,1],映射到底盘速度 v = (action[0] + 1.0) * 0.25 # 映射到[0, 0.5] m/s w = action[1] * 1.0 # 映射到[-1.0, 1.0] rad/s self.sim.set_velocity(v, w) self.sim.step(dt=0.1) # 计算奖励分量 state_prev = self.sim.get_goal_dist() collision = self.sim.is_collision() done = self.steps >= self.max_steps self.sim.step_physics() state_new = self.sim.get_goal_dist() reward = -0.01 - 0.5 * (state_new - state_prev) if state_new < 0.3: reward += 10.0 done = True if collision: reward -= 5.0 done = True return self._get_state(), reward, done, {"collision": collision}这段代码里的核心逻辑有三个地方要特别注意:一是_get_state里激光、距离、夹角都做了归一化,这是前面说的训练稳定性的关键;二是动作映射时把线速度下限钳制在0,禁止倒车,这大幅减少了策略初期摸索的无效动作;三是距离差奖励用的是"上一时刻减当前时刻",距离减小则得到正奖励,这一项能让策略很直观地学会朝目标走。
参数上,碰撞惩罚-5和到达奖励+10的绝对大小要匹配。如果你的环境里目标距离普遍是5米,机器人在不碰障碍的情况下需要接近50步到达,时间惩罚累计约-0.5,距离奖励累计约+2.5,最后拿+10,单episode总奖励约12,量级合理。如果目标距离改到15米,最好把到达奖励提升到+15,保持奖励量级不失控。
3.3 实现经验回放缓冲区:DDPG样本效率的根基
经验回放是DDPG能反复利用历史数据的关键机制。它的作用有两个:一是打破时间相关性——连续采样得到的数据前后高度相关,直接训练会让网络参数剧烈波动;二是提高样本效率——一条transition能被反复用来更新网络多次。
import random from collections import deque import numpy as np class ReplayBuffer: def __init__(self, capacity=200000): self.buffer = deque(maxlen=capacity) def add(self, s, a, r, s_next, done): self.buffer.append((s, a, r, s_next, done)) def sample(self, batch_size): batch = random.sample(self.buffer, batch_size) # 转成numpy数组,方便直接做矩阵运算 s = np.array([x[0] for x in batch], dtype=np.float32) a = np.array([x[1] for x in batch], dtype=np.float32) r = np.array([x[2] for x in batch], dtype=np.float32) s_next = np.array([x[3] for x in batch], dtype=np.float32) done = np.array([x[4] for x in batch], dtype=np.float32) return s, a, r, s_next, done def __len__(self): return len(self.buffer)回放缓冲区的大小是DDPG训练里的一个关键超参数。我见过有人把容量设成1万,结果训练到后期缓冲区里全是旧策略的数据,新策略在探索阶段产生的高质量轨迹占比太小,策略更新方向被老数据带着跑,收敛速度越来越慢。容量设太大也有问题——200万条transition占内存几十GB,而且采出来的样本可能包含非常早期的、策略还没成形时的劣质数据。我常用的设置是20万,如果你的仿真环境步长是0.1秒、一个episode平均100步,20万大约能存2000个episode,基本覆盖最近几小时训练的数据分布。
采样方式这里有个小坑:done标志为True的transition,计算TD目标时下一状态的Q值应该置为0,否则会把终止状态的未来奖励重复计算进去。这个在训练循环里要单独处理,我在下面给出正确写法。
3.4 训练主循环:采样、更新、软更新三个环节的节奏控制
def train_loop(env, actor, critic, target_actor, target_critic, replay, args): noise = OUNoise(theta=0.15, sigma=0.2) for episode in range(args.max_episodes): s = env.reset() ep_reward = 0 # 每回合把噪声sigma略微衰减,从探索逐步过渡到利用 noise.sigma = max(0.02, 0.2 * (1.0 - episode / args.max_episodes)) while True: a = actor.get_action(s) a_noisy = np.clip(a + noise.sample(), -1.0, 1.0) s2, r, done, _ = env.step(a_noisy) replay.add(s, a_noisy, r, s2, done) if len(replay) > args.batch_size * 100: train_step(actor, critic, target_actor, target_critic, replay, args) s = s2 ep_reward += r if done: break if episode % 20 == 0: # 周期性衰减噪声并在当前策略下跑一次无噪声评估 eval_success = evaluate(env, actor, eval_episodes=10) print(f"ep {episode}, reward {ep_reward:.2f}, success {eval_success:.0%}")训练节奏上有两个经验点。一是延迟开始训练:先往缓冲区里攒数据,攒够几千条再开始更新网络。因为刚初始化的策略是随机动作,如果立刻开始训练,网络会被一堆毫无意义的失败transition带着走,后面再想掰回来很难。二是一个episode内做一次更新还是多次更新,DDPG没有硬要求,我一般每个step更新一次,batch_size取64到128之间。batch_size太小,梯度方差大;太大则更新次数少,样本利用率低。
噪声衰减的速度很关键。OU噪声的sigma从0.2线性衰减到0.02,衰减周期最好和整个训练时长匹配。衰减太快,策略还没探索出多样化路径就过早收敛到局部最优;衰减太慢,训练后期机器人每步动作都带抖动,评估时路径弯曲。我见过的成熟做法是把噪声标准差和训练进度绑定,前20%的回合保持全量噪声,中间50%线性衰减,最后30%维持一个很小的底噪。
4. 神经网络训练:Actor-Critic结构、目标网络与超参数调优
DDPG的训练稳定性很大程度取决于网络结构设计和目标网络更新方式。很多人直接在仓库里复制一份网络代码就跑,等训练发散了才回来调网络——这个顺序应该反过来。这一章把Actor-Critic的结构、目标网络的软更新逻辑和训练监控指标一次讲清。
4.1 Actor与Critic网络结构设计:输入输出维度与隐藏层选择的考量
Actor网络输入是状态向量s,输出是动作a,输出层用tanh激活把动作压缩到[-1,1]。Critic网络输入是状态和动作的拼接,输出是一个标量Q值。这里有个容易被忽略的细节:Critic的输入应该把状态和动作在第一个隐藏层之前拼接,还是分别经过一个子网络再融合?两种做法都能跑,但实践下来,把状态和动作直接在输入层拼接、然后共同经过两层全连接,实现简单且收敛速度不差。分别编码的优势是能分别提取状态特征和动作特征,但导航任务里状态维度和动作维度都不高(12维状态+2维动作),没必要引入额外的连接结构。
隐藏层宽度我常用的配置是两层全连接,每层400个神经元。400这个数字不是拍脑袋——导航状态包含激光数据,10束激光之间存在空间相关性,神经元太少无法建模"哪边近、哪边远"的关系;超过512之后训练速度明显下降,但成功率没有提升。也可以用[400, 300]这种递减结构,后半段做特征压缩,效果与[400, 400]接近。训练时对Actor输出层和Critic输出层的初始化需要特别处理——最后的权重初始化为均匀分布且范围很小,比如0.003,这样初始策略输出的动作接近零均值,避免一开始就把底盘速度打到极端值。
4.2 目标网络与软更新:为什么tau要设成0.005而不是1.0
目标网络是DDPG能稳定训练的核心机制。Critic在计算TD目标时需要用到r + gamma * Q_target(s', a'),其中a'来自目标Actor网络。如果直接用当前Critic网络计算目标值,训练参数的更新会同时改变Q值的当前估计和目标估计,两边互相追赶,训练很容易发散。目标网络的作用是"冻结"一份稍微旧一点的网络参数,让TD目标在一段时间内保持稳定,网络更新才有明确方向。
def soft_update(target_net, current_net, tau=0.005): # 把当前网络参数按tau的比例合并进目标网络 for target_param, current_param in zip(target_net.parameters(), current_net.parameters()): target_param.data.copy_( tau * current_param.data + (1.0 - tau) * target_param.data)这个tau的取值直接决定了训练稳定性。tau=1.0等于每步都硬拷贝当前网络参数,目标网络形同虚设;tau=0.5目标网络跟着当前网络快速漂移,TD目标变化太快;我常用的0.005意味着每一步目标网络只往当前网络方向移动0.5%,一整个episode下来约100步,目标网络的变化也才累积约40%,既跟得上策略提升进度,又足够平滑。训练中如果你发现Critic loss曲线在某个阶段突然规律性震荡,优先检查tau是不是设大了。
目标网络更新频率值得单独说。有人说"每步软更新",有人说"每N步更新一次"。我的做法是每个训练step都执行软更新,因为0.005的tau已经足够温和,不需要再增加人为延迟。但如果你观察到训练过程中Q值高估现象严重,可以把tau降到0.002,同时把更新频率改成每2个step一次,效果类似,调起来更细腻。
4.3 训练监控指标与调参顺序:看什么、改什么、什么时候停
训练DDPG时,很多新手只盯着reward曲线看,我看的通常是四个东西:Critic loss、Actor loss、Q值均值、评估成功率。Q值均值是个很有意思的指标——Critic训练正常时,Q值均值应该和近期episode的平均reward量级接近。如果Q值均值远高于实际reward,说明Critic在高估,要检查奖励缩放是否合理、tau是否太大;如果Q值均值离谱地低,可能是初始化的输出层权重过大,导致初始Q值偏离。
调参顺序我建议固定成一套固定流程,并且每次只动一个变量:
- 如果无法收敛或loss爆炸,先把奖励缩放系数整体除以10,再看曲线。奖励量级过大是DDPG发散最常见的原因。
- 如果收敛速度慢,先调Actor和Critic的学习率。常见配置是Actor为0.0001、Critic为0.001。Critic学习率比Actor快10倍,这个配比能让Q值先稳定下来,再引导Actor更新。
- 如果机器人路径长但能到达,把距离差奖励系数调大;如果路径短但碰撞多,把碰撞惩罚调大。
- 如果训练后期成功率停滞在某个值不动,把OU噪声的底噪sigma调大一点,让它重新探索。
训练长度的判断标准,不同环境规模差异很大。单目标导航、目标距离在2到8米随机生成的地图,通常60到100万步训练就能看到成功率超过八成;如果目标距离拉到15米,或者地图里障碍物动态移动,训练步数要翻倍到200万步。判断"能不能停"的标准不是训练步数,而是连续10个评估周期(每周期20个episode)的成功率方差小于5%。
下面是完整的训练步核心实现,把目标网络更新和batch采样串在一起:
def train_step(actor, critic, target_actor, target_critic, replay, args): s, a, r, s2, done = replay.sample(args.batch_size) # 用目标网络计算TD目标 a2 = target_actor(s2) q_target = r + args.gamma * (1.0 - done) * target_critic(s2, a2) # 当前Critic输出与目标的差距 q_current = critic(s, a) critic_loss = mse_loss(q_current, q_target) # 更新Critic:只让输出层的误差反传 critic.optimizer.zero_grad() critic_loss.backward() critic.optimizer.step() # Actor的更新目标是让Critic对当前策略选出的动作给更高分 a_policy = actor(s) actor_loss = -critic(s, a_policy).mean() actor.optimizer.zero_grad() actor_loss.backward() actor.optimizer.step() # 软更新两个目标网络 soft_update(target_critic, critic, args.tau) soft_update(target_actor, actor, args.tau)注意done的处理:1.0 - done乘以Q值,让终止状态的TD目标不再包含未来奖励。这个细节很多人漏写,漏写之后Critic会持续高估Q值,训练结果是"看起来reward在涨,实际评估时机器人到不了目标点"。Actor的损失是负的Q值均值——因为梯度下降要最小化loss,而Q值越大越好,所以取负号。这个写法是DDPG的标准形式,如果你看到别的实现里Actor loss是-critic(...).mean(),就说明它采用了相同思路。
5. 训练与部署中的关键问题排查:DDPG导航常见问题与避坑记录
DDPG在导航任务里最让人头疼的不是算法本身,而是一堆"仿真能跑、真机翻车""训练发散、不知道改哪里"的问题。这一章写几条我实践中最常遇到的坑,每条按"现象→原因→解决"给你可操作的排查路径。
5.1 Critic loss变成NaN:奖励过大、梯度爆炸、学习率失控
现象:训练到几千步后,Critic loss突然从两位数跳到NaN,随后整个训练崩溃,reward曲线直接从正常变成一条横线或空白。重启训练后同样的步骤还会复现。
原因:绝大多数情况是奖励信号量级太大。DDPG的Critic目标是连续累加的奖励,如果单步奖励达到几十甚至上百,TD目标在episode后期会指数级放大,梯度回传时出现数值溢出。另一种可能是Actor或Critic的学习率设置过高,网络参数更新步长过大,直接跳过有效区域。
解决:先把所有奖励分量统一缩放,让单个episode的总reward控制在[-20, 20]区间内,然后给网络更新加梯度裁剪,把梯度的L2范数限制在1.0以内。我在某图像处理Demo上排查这类问题时,把碰撞惩罚从-100改成-10,NaN现象立刻消失。如果缩放后仍发nan,逐步降低解锁率,每次除以2,直到训练稳定。
5.2 机器人原地打转或路径明显绕远:噪声衰减过快、奖励塑形失衡
现象:训练中期,评估时机器人到达目标点的成功率还行,但轨迹明显绕路——明明障碍物左边有空隙,它偏要绕到右边;甚至出现原地转圈几秒钟再出发的诡异行为。
原因:这是典型的探索与利用失衡。OU噪声衰减太快,机器人过早进入"利用"阶段,策略还停在某个局部最优点。奖励塑形方面,如果距离差奖励权重过高,机器人为了每一步都缩小距离,会在靠近障碍物时选择绕行,因为它发现"绕行虽然远,但每一步距离奖励都为正"。
解决:把噪声衰减周期拉长,让sigma在训练的中段保持峰值更久。检查奖励分量比例,把距离差奖励系数从0.5降到0.2,把到达奖励从+10提高到+15,让机器人更在意"最终能不能到达"而不是"每一步走多远"。同时可以引入路径长度惩罚——如果当前轨迹明显绕路,用-0.02 * 已用步数加大时间惩罚力度,逼策略找捷径。
5.3 仿真训练收敛良好、真机测试完全失效:动力学差异、传感器噪声、分布外状态
现象:这是最让人崩溃的坑。仿真环境里成功率95%,放到真实底盘上,机器人前几秒还能走,遇到第一个转弯就撞上去,或者干脆原地发抖不动。
原因:仿真和真机的gap来自多个层面:底盘的真实加速度、轮胎打滑、激光传感器的反射噪声、目标定位误差,这些在仿真里都没有。策略在仿真里学到了精确规避特定距离的"过拟合"行为,真机的激光读数噪声稍微变化一点,状态就落到训练分布之外,策略就不知道该怎么动。
解决:在仿真训练时叠加域随机化——每次episode随机化机器人的质量、摩擦系数、传感器噪声、激光的最大量程误差,让策略适应"看起来不太一样"的观测。输出动作前加一个低通滤波器,把DDPG输出的高频抖动平滑掉,例如对动作做一阶惯性滤波,系数取0.6。真机评估时的速度上限设为仿真速度的70%,留出控制裕量。你可能会觉得这样保守导致路径变差,但真机环境下"慢一点能到"永远比"快一点撞飞"好。
5.4 训练前期成功率始终为零:状态归一化遗漏、初始探索不足
现象:训练跑了20万步,评估成功率始终是0%,机器人一启动就往墙上撞,target_actor和actor的输出看起来一直在变化,但评估就是零成功。
原因:最隐蔽的坑是状态向量里某个维度忘记归一化。比如目标距离直接用原始米数,目标距离10米和0.5米在数值上差了20倍,Critic对距离维度的梯度分量会压过其他维度,网络几乎"看不见"激光数据,自然学不会避障。另一个常见原因是噪声sigma初始太小,策略完全沿着随机初始化的Actor输出走,而初始化输出又偏向某个固定方向。
解决:把状态向量打印出来,检查每个分量的量纲和范围。我习惯在训练刚开始时打印前100个状态的min/max,一眼就能看出哪个维度没归一化。噪声方面,把初始sigma提高到0.5以上,让动作在早期几乎完全被噪声主导,确保探索覆盖到所有方向。
5.5 目标网络更新被跳过:软更新写进了错误的作用域
现象:训练曲线看起来稳定,但评估结果长时间毫无进步,检查代码发现target_actor和target_critic的参数值从头到尾没变化。
原因:代码结构问题。常见的错误是把soft_update调用写在if len(replay) > batch_size的判断之外,导致训练早期缓冲区不够大时不更新目标网络;或者把target网络在初始化后用了错误的赋值方式,目标网络参数没有真正初始化为当前网络参数的拷贝。
解决:在训练开始时打印target_actor和actor第一层权重的均值,确认初始化同步。在训练循环里加一个简单的断言,每100步检查target网络参数和当前网络参数的差值是否在变化(差值应该小但非零)。如果差值恒为零,说明soft_update没被调用或者调用位置写错了。
6. 从训练到验证:评估指标体系与多目标导航的进阶落地
训练结束不等于系统可用。导航系统的价值最终体现在真实任务里,所以训练完成后的第一件事不是部署,而是搭一套评估流程。我常用的评估指标有四项:成功率、平均路径长度、平均到达时长、碰撞率。单跑一个episode看不出来,要在多个随机起点和随机目标点的组合下跑50次以上才有统计意义。
| 指标 | 计算方式 | 可用范围参考 | 说明 |
|---|---|---|---|
| 成功率 | 到达目标episode数 / 总episode数 | 90%以上可用 | 核心指标,低于70%建议继续训练 |
| 路径长度比 | 实际路径长度 / 两点间直线距离 | 1.0~1.3优秀 | 超过1.5说明绕路明显 |
| 平均到达时长 | 成功episode的平均步数 × 步长 | 由任务时限决定 | 反映策略效率 |
| 碰撞率 | 碰撞episode数 / 总episode数 | 5%以下可用 | 真机部署时要求更苛刻 |
评估时还有一个关键习惯:评估用的episode不要加任何探索噪声,用纯确定性策略跑。很多人在训练时加噪声看reward,评估时忘了摘掉,导致结果忽高忽低。评估环境可以比训练环境稍微严苛一点,比如把目标半径从0.3米改成0.25米,看策略是否还有余量。
从单目标导航进阶到多目标导航,直接重训会出现目标距离过长、奖励稀疏的问题。我会用课程学习的方式过渡:先在一个goal附近训练到成功率稳定,再逐步扩大目标范围。训练时每个episode从多个候选目标中随机选一个,距离近的目标权重高,远的权重低,策略就能在不同难度之间平滑过渡。这个思路也适用于动态障碍物地图——先用静态障碍训练,再逐步引入移动障碍物的移动速度作为随机变量。
另外做导航策略验证时,我把"人工检查轨迹"当成必做环节。每训练完一阶段,保存几条典型轨迹的坐标序列,肉眼看一下机器人在狭窄通道里是贴边绕行还是大转弯绕远、在目标点附近是平稳减速还是反复横摆。这些视觉反馈比任何指标都直观,也最能暴露奖励塑形里那个"说不清哪里不对劲"的隐藏问题。我自己见过一次诡异轨迹——机器人到了目标点附近还不停,绕了一圈才停,查代码发现到达判定用的目标系坐标和训练时用的坐标系差了90度,这类问题指标测不出来,但是轨迹一眼就能看出来。
仿真导航做到能跑通、成功率达标,只是第一道坎。建议你在验收时给自己加一道"扰动测试":把环境里所有障碍物位置随机平移0.2米再看评估结果。这套系统能不能扛住这种级别的干扰,决定了它有没有资格从模拟项目X走向真实机器人平台。希望这篇笔记能帮你少走几段弯路。
本文还有配套的精品资源,点击获取