ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

机械臂避障路径规划:深度强化学习从MDP设计到仿真落地

机械臂避障路径规划:深度强化学习从MDP设计到仿真落地 简介这份PDF是一篇公开发表的学术论文聚焦基于深度强化学习的机械臂避障路径规划研究适合机器人、自动化与智能制造领域的工程师、科研人员及高年级学生阅读。资源只包含1个PDF文件压缩包大小1.44MB内容为《软件工程》期刊2019年第22卷第3期全文含中英文摘要、引言、算法原理、仿真实验与参考文献。论文从机械臂焊接系统调整动作难度大、缺乏灵活性出发指出现有A*、RRT、人工势场、遗传算法等传统方法依赖精确环境建模、场景适应性有限的问题随后详细阐述基于深度强化学习DQN的解决方案采用三层DNN网络将机械臂状态映射为关节角度经离线训练自主生成接近最优的无碰撞轨迹并在三自由度点焊机器人平台上完成避障仿真验证。文中对马尔科夫决策过程、q-learning与DQN的关系也有清晰梳理便于读者快速建立知识框架。该资源已有487人学习对做路径规划相关课题或技术调研具有实用价值。1. 为什么机械臂避障绕不开深度强化学习大多数做机械臂控制的人听到避障路径规划第一反应还是 RRT 或 A* 全家桶。这套组合在静态、低自由度的场景里确实够用可一旦障碍物会动、构型空间高维、末端还要保持轨迹平滑每帧重规划的计算量会让控制器越来越紧。深度强化学习的思路跟传统方法不同它不在路径空间里搜轨迹而是直接学一个从「观测状态」到「关节动作」的映射策略把避障从反复规划变成一次前向推理。这就是「基于深度强化学习的机械臂避障路径规划研究」这类课题最值钱的地方。本文按我做过这类项目的完整链路来讲怎么把问题改写成 MDP、算法选 TD3 还是 SAC、仿真环境怎么搭、还有那些我踩过的坑。2. 把避障改写成 MDP状态、动作、奖励函数怎么定才不学歪深度强化学习落地机械臂避障第一步不是写网络而是定义马尔可夫决策过程。状态空间决定策略能看见什么动作空间决定策略能改变什么奖励函数决定策略往哪个方向优化。这三件事的顺序不能反。我见过不少项目网络很标准、训练框架也没问题最后策略不收敛回头一查是奖励里有一个数值巨大的常数项把有用的梯度信号全淹了。2.1 状态空间策略必须「看得见」障碍而不是靠猜常见的状态设计会把两类信息拼在一起机械臂自身状态和任务相关状态。自身状态是各个关节的关节角、角速度以及末端执行器的位姿任务状态是目标点相对末端的偏差、障碍物相对关键连杆的距离或方向。这里有一个容易被新手忽略的原则尽量用「相对量」而不是「绝对量」。目标点的世界坐标放进状态里等于强迫策略记住不同坐标系下的同一布局而换成目标点相对末端的偏差向量策略就具备平移不变性换一张地图也能用。障碍物信息同理比较稳妥的做法是取障碍物表面离末端最近的那个点存成相对末端的向量和距离标量距离用于奖励判断向量用于给出避让方向。如果场景里有运动障碍只给当前位置会导致状态不满足马尔可夫性——策略判断不了这个障碍是在靠近还是在远离。常见做法是把最近 3 到 5 帧的障碍相对位置堆叠进状态让策略从历史帧里隐式估计障碍速度。状态维度会上升但对训练稳定性帮助明显。另外每一维特征都要做归一化关节角映射到 [-1, 1]距离量按工作空间的尺度缩放到同量级不要让某一维的数值范围天然压过其他维。2.2 动作空间关节速度做输出位置环留给底层控制器动作空间的选择比状态更隐蔽。直接输出关节位置的方案看着自然但仿真器或真机的底层位置环会平滑掉小幅度的位置指令策略明明输出了一格微调末端实际动作却几乎为零梯度信号被控制系统吞掉了。输出末端笛卡尔速度需要实时解逆运动学奇异位形附近的 IK 结果会突变还要额外处理多解问题复杂度转移到控制层。我一般会用关节速度作为动作输出连续且平稳幅值限制容易底层只做一个简单的速度伺服。这样做还有一个额外好处把动作空间每个维度归一化到 [-1, 1]策略网络最后一层用 Tanh 激活天然满足范围约束。训练前期随机探索的动作会被限制在合理的速度范围内不会出现一上来关节就猛冲的现象。动作空间的维度和自由度数一致UR5 这类六轴臂就是 6 维带夹爪再加维度。2.3 奖励函数稀疏奖励在机械臂上基本不可用稠密奖励也不能堆满惩罚机械臂避障的任务空间连续、维度高只给「到达目标 1、碰撞 -1」的稀疏奖励随机探索找到第一个正样本的概率极低训练基本学不出来。实际项目里必须做稠密奖励但稠密奖励也有讲究。首先是目标引导项。直接拿「当前距离」做奖励会引入一个巨大的常数偏置策略对距离的微小变化不敏感更好的做法是拿「两帧之间距离的缩短量」奖励的是进步本身。其次是避障项可以把碰撞重罚、接近障碍物轻罚结合起来。再次是关节限位和平滑项关节角超出限位范围要惩罚动作变化量过大会导致末端抖动加一项很小的动作平滑惩罚能明显改善轨迹质量。下面是这类项目中我会先写的一版奖励函数import numpy as np class ReachAvoidReward: def __init__(self, w_goal2.0, w_collision20.0, w_limit5.0, w_smooth1e-3, obs_thresh0.05): self.w_goal w_goal self.w_collision w_collision self.w_limit w_limit self.w_smooth w_smooth self.obs_thresh obs_thresh self.prev_goal_dist None def __call__(self, q, q_dot, end_pos, goal_pos, obs_dist, q_limits, dt): # 目标项两帧之间到目标距离的缩短量 goal_dist np.linalg.norm(end_pos - goal_pos) delta_d 0.0 if self.prev_goal_dist is not None: delta_d (self.prev_goal_dist - goal_dist) / dt self.prev_goal_dist goal_dist # 碰撞项进入阈值就给大惩罚不用等真正穿透 collision_pen 0.0 if obs_dist self.obs_thresh: collision_pen 1.0 # 关节限位项统计越界关节个数 limit_pen 0.0 for i in range(len(q)): if q[i] q_limits[i, 0] 1e-3 or q[i] q_limits[i, 1] - 1e-3: limit_pen 1.0 # 平滑项抑制末端高频抖动 smooth_pen float(np.sum(q_dot ** 2)) reward (self.w_goal * delta_d - self.w_collision * collision_pen - self.w_limit * limit_pen - self.w_smooth * smooth_pen * dt) return reward, goal_dist这里有两个参数需要重点说明。第一dt是决策周期不是物理仿真步长它决定了delta_d的量纲是「距离/秒」这样奖励和决策频率解耦换一套仿真环境不用重调系数。第二碰撞惩罚的权重我习惯给到目标项的十倍以上因为避障是硬约束宁可让策略绕远路也不能让它在碰撞边缘试探。奖励数值的整体尺度控制在 1 到 20 之间如果单步奖励经常超过这个范围训练过程会不稳后面的 Q 网络更新会变得像玄学。3. 算法选型TD3 和 SAC 是主力PPO 只做对照基线MDP 定义清楚之后进入算法选择。机械臂避障是连续状态、连续动作的控制任务能直接用的深度强化学习算法其实就那几类。很多做机械臂强化学习实战的人第一次接触 DQN但在这个任务上DQN 从一开始就不该进入候选名单。3.1 DQN 系为什么先出局离散化的组合爆炸DQN 的基础是动作价值函数 Q(s, a)它要求动作集合是有限离散的。把每个关节速度离散成 11 个档位六个关节就是 11 的六次方超过一百七十万个组合Q 网络根本学不过来。把档位减少到 3 档动作分辨率又低到末端在目标附近来回振荡永远停不稳。有人尝试把连续动作做成离散动作组合比如每个关节只取「正转、停止、反转」三档末端轨迹会变成锯齿状避障任务里稍微需要精细接近的场景全部翻车。结论很直接连续控制任务直接上连续动作算法。3.2 TD3 / SAC / PPO 三选一采样效率、调参手感、部署差异表格列出我个人的选型习惯算法样本效率调参稳定性超参敏感度适用定位TD3高较稳中低首选主力默认配置SAC更高波动大高reward scale 影响明显追求极限效率时选PPO低稳中在线更新可控留作对照在仿真环境里跑一遍避障任务TD3 通常是最省心的选择。它用双 Critic 取最小值来抑制 Q 值过估计再配上目标策略平滑和延迟更新专门解决连续控制里的价值高估问题。SAC 加了熵正则探索更充分样本效率上限更高但那个熵系数和 reward 尺度强耦合换一次奖励函数可能就要重新调一轮训练曲线容易忽高忽低。PPO 是 on-policy 算法样本利用率低机械臂任务里每一步交互都贵它跑一轮需要的样本量比 TD3 多好几倍我不太会把预算花在它身上最多作为对照基线。3.3 一个 TD3 机械臂避障训练骨架网络与关键超参直接看代码。这个类包含了 TD3 的核心更新逻辑import torch import torch.nn as nn import numpy as np class Actor(nn.Module): def __init__(self, state_dim, action_dim, hidden256): super().__init__() self.net nn.Sequential( nn.Linear(state_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, action_dim), nn.Tanh() # 输出限制在 [-1, 1] ) def forward(self, s): return self.net(s) class Critic(nn.Module): def __init__(self, state_dim, action_dim, hidden256): super().__init__() # 状态和动作在输入层就拼接 self.net nn.Sequential( nn.Linear(state_dim action_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, 1) ) def forward(self, s, a): return self.net(torch.cat([s, a], dim-1)) def td3_update(actor, critic1, critic2, target_actor, target_critic1, target_critic2, replay_buffer, gamma0.99, tau0.005, policy_delay2, batch_size256, noise_std0.2, noise_clip0.5): s, a, r, s2, done replay_buffer.sample(batch_size) with torch.no_grad(): # 目标策略平滑给目标动作加截断噪声抑制 Q 值对动作的过拟合 a2 target_actor(s2) noise torch.randn_like(a2) * noise_std noise noise.clamp(-noise_clip, noise_clip) a2 (a2 noise).clamp(-1.0, 1.0) y r gamma * (1 - done) * torch.min( target_critic1(s2, a2), target_critic2(s2, a2)) # 两个 Critic 都更新取 min 作为价值估计 loss_c1 nn.MSELoss()(critic1(s, a), y) loss_c2 nn.MSELoss()(critic2(s, a), y) # 延迟更新 Actor每 policy_delay 步更新一次 if step % policy_delay 0: loss_a -critic1(s, actor(s)).mean() actor_optimizer.zero_grad() loss_a.backward() actor_optimizer.step() # 软更新目标网络 for target, source in zip( [target_actor, target_critic1, target_critic2], [actor, critic1, critic2]): for tp, sp in zip(target.parameters(), source.parameters()): tp.data.mul_(1 - tau).add_(sp.data, alphatau)这段代码有四个关键点。一是用min(Q1, Q2)而不是单个 Q 值作为目标直接压低 Q 函数的高估倾向。二是目标动作加入了 0.2 标准差的高斯噪声并截断到 0.5让 Critic 对相近的动作输出平滑的价值面。三是 Actor 延迟到每两步更新一次给 Critic 更多时间收敛避免策略和价值网络互相追逐。四是目标网络走软更新tau0.005让目标值缓慢跟随训练过程更稳。超参不是拍脑袋定的我按下面的默认值起手再根据训练曲线微调参数推荐值调整说明gamma0.99决策周期 0.05 秒时对应约 5 秒的有效视野tau0.005软更新系数振荡大就调小到 0.001actor / critic 学习率3e-4机械臂类任务我很少用超过 1e-3探索噪声标准差0.1训练中期可以衰减到 0.05replay buffer1e6至少能装下几十个完整 episodebatch size256显存允许可以加到 512policy delay2样本效率下降就调到 3这些参数都不是「越极端越好」。gamma 太大策略只看远期目标对瞬时碰撞惩罚不敏感噪声太大学到的策略会被探索动作污染噪声太小动作空间又有大片区域没见过。我习惯把训练曲线分成前中后三段看前段看奖励是否上升中段看方差是否收敛后段看测试成功率而不是训练奖励。4. 仿真环境落地CoppeliaSim、MuJoCo、Gazebo 各自的脾气算法写完下一步把机械臂放进仿真环境。这一步决定训练速度和最终策略的可迁移性。仿真是个放大器物理引擎的误差会在强化学习的反复试错中被放大环境选错了后面所有调参都白费。4.1 三角权衡训练速度、物理精度、ROS 集成环境训练速度物理精度ROS 集成适合阶段CoppeliaSim中中上碰撞与距离 API 完善好原生支持 ROS 接口首选主力训练环境MuJoCo快中上接触模型稳定一般大规模并行采样Gazebo慢较高传感器模型全极好真机前的系统验证CopSim 是这类课题的主力选择它在仿真器内部提供距离查询接口不用自己实现包围盒碰撞检测机械臂的每个连杆与障碍物的最近距离直接能拿到这对奖励函数和状态设计都是巨大便利。MuJoCo 的特点是快适合批量跑随机场景但机械臂模型的建模要求更苛刻网上拷来的模型经常出现关节不受控的问题这个下面单独说。Gazebo 物理模型完整能挂真实感的传感器和控制器但仿真负载高深度强化学习动辄百万步的采样量在里面跑时间成本很难接受。4.2 CoppeliaSim 最小闭环读状态、发关节速度、同步步长跑通 CopSim 的最小闭环思路很简单Python 端负责训练仿真器端负责物理。每步训练循环做三件事从仿真器读回关节和末端状态把策略网络的动作下发成关节速度指令等一个决策周期后再重复。class CoppeliaSimArmEnv: 仿真器交互的最小骨架不同版本 Remote API 的函数名会变接口不变 def __init__(self, joint_names, dt0.05): self.dt dt # 连接仿真器获取关节和机械臂句柄 self.joint_handles [] for name in joint_names: handle sim.getObjectHandle(name) # 按版本替换为对应 API self.joint_handles.append(handle) self.reward_fn ReachAvoidReward() def reset(self): # 把关节设回随机或固定初始构型 q0 np.random.uniform(-0.5, 0.5, sizelen(self.joint_handles)) for h, q in zip(self.joint_handles, q0): sim.setJointPosition(h, q) return self._read_state() def step(self, action): # action 是 [-1, 1] 的关节速度先映射到真实速度限幅 vel_limits np.array([2.0] * len(self.joint_handles)) # rad/s vels action * vel_limits for h, v in zip(self.joint_handles, vels): sim.setJointTargetVelocity(h, v) # 等待一个决策周期仿真推进 sim.waitForSimulationStep(self.dt) state self._read_state() reward, info self.reward_fn(q, q_dot, end_pos, goal_pos, obs_dist, q_limits, self.dt) return state, reward, done, info这段骨架里的dt是决策周期我一般设 0.05 秒对应 20Hz 的控制频率机械臂的大部分避障动作在这个频率下足够平滑。仿真器的物理步长单独设 1 到 5 毫秒物理步长越小碰撞检测越准但训练越慢先拿 5 毫秒跑通流程再往小调。这里最容易犯的错是让仿真器开实时模式强化学习训练时不关心墙钟时间是否和仿真时间一致实时同步只会白白等掉大量时间一定要让 Python 端用步进模式驱动仿真器前进。4.3 MuJoCo 的坑与 Gazebo ROS2 的验证定位如果训练规模需要压榨采样效率用 MuJoCo 是合理的但模型文件要自己会改。热词里那个「mujoco 加载机械臂乱动」的现象十有八九是 XML 里的惯量、电机 gear 比和阻尼参数跟实际臂不匹配或者单位系统没对齐。CopSim 和 Gazebo 里模型自带这些参数MuJoCo 里只能自己对着机械臂手册核数。Gazebo 不适合大规模强化学习训练但适合做真机前的最后验证。用 panda 或 ur5 这类现成机械臂模型在 Gazebo 里跑一版 ROS2 机械臂仿真把训练好的策略包装成动作节点验证控制器接口、话题频率和 TF 变换是否正确。这一步能暴露一堆 CopSim 里不存在的问题指令延迟、关节限位冗余、控制器 PID 参数和仿真器频率不匹配。策略本身在 Gazebo 里不需要重训重训成本太高验证接口才是它的用途。5. 避坑机械臂 DRL 训练里最常见的五个翻车现场这部分是我最想写的。深度强化学习做机械臂避障真正劝退人的不是算法数学而是训练过程中那些看起来毫无规律的翻车。下面五条都是我在项目里实际踩过的坑每条按现象、原因、解决三步拆开按顺序检查能省下大量盲目调参时间。5.1 MuJoCo 加载机械臂后模型乱动现象模型加载完一跑整个机械臂像触电一样抖动关节角度在日志里跳变末端位置完全不受控。原因绝大多数情况是模型文件里的驱动参数和刚体参数不匹配。gear值决定电机力矩经过减速后到关节的等效输出阻尼damping决定关节自身阻力这两个值和body的质量、惯量不匹配时仿真在重力和接触力下会数值发散。还有一类原因是用错单位系统MuJoCo 默认米、千克、秒从 SolidWorks 导出的模型如果带着毫米或克单位整个动力学全部错掉。解决逐个关节核对body的mass和inertia再对actuator的gear和ctrlrange做单关节空载测试固定其他关节只给一个关节加阶跃力矩观察加速度是否在一个合理量级。所有关节测试通过后再加载完整模型。5.2 训练前期完全不动或朝一个方向撞现象前几千步奖励曲线纹丝不动随机探索的轨迹一直朝障碍物方向走末端基本上没出过起始区域。原因第一可能是动作空间没有归一化随机动作映射到真实速度后幅度太小末端移动速度不足以在 episode 内到达目标附近第二是奖励太稀疏走了五秒没有一次正反馈第三是探索噪声初始值设得太小策略一开始就几乎确定性输出。解决先用一个完全随机的策略跑几百步打印末端每步位移和距离变化确认「环境本身有反应」。然后检查随机动作实际产生的关节速度覆盖范围如果连起始点附近都出不去直接调大速度限幅或提高探索噪声。还有一个办法初始几万个 step 里把目标点放在离末端 0.1 米以内的位置让策略先学会「伸手」再逐步拉大目标距离。5.3 CoppeliaSim 训练奇慢现象训练跑了很久wall time 消耗巨大但训练步子数才几千步平均每步耗时几百毫秒。原因仿真器开了实时模式每一步都在等实时时钟同步或者每一步之间 Python 端有隐式延时再或者没有禁用渲染每帧画面渲染开销占了训练时间的大头。解决训练时把仿真器切到步进模式waitForSimulationStep用仿真步数控制不要用time.sleep。关闭所有无关渲染窗口和传感器可视化collision 检测保留即可。还有一个容易被忽略的点决策周期取得太短动作频率被迫升高而物理步长又很小每步要算几十次动力学整体计算量线性上升。先确认决策周期在 0.05 秒以上再谈优化物理步长。5.4 末端到位率高但避障经常失败现象训练曲线里目标距离在缩小最终到达率很高但避障成功率始终上不去轨迹经常穿过障碍物边缘。原因奖励函数里目标项和碰撞惩罚的量级失衡。目标引导项是每步都在累积的正反馈碰撞惩罚只在一瞬间触发如果碰撞惩罚权重不够大策略学到的局部最优就是「贴着障碍物蹭过去」。解决把碰撞惩罚权重提高到目标项的十倍以上并且不要等真正碰撞才给惩罚进入障碍物外围阈值就开始计入。同时检查状态里是否包含了障碍物的有效特征——如果状态里只有目标偏差没有障碍相对位置或距离策略在数学上就不可能学会避障怎么调权重都没用。5.5 学出来的策略末端高频抖动现象测试时末端到目标附近后持续小幅振荡稳定不下来轨迹不光滑。原因动作平滑惩罚太弱策略网络对相邻状态输出了变化剧烈的动作或者 Critic 对相近状态动作的 Q 值不平滑策略钻了价值的空子也可能是决策频率过高控制器本身就处于不稳定边缘。解决加大平滑惩罚项权重或者在动作输出后做一阶低通滤波让相邻步的动作变化率受限。另一个动作是降低 Actor 网络容量隐藏层从 256 降到 128参数少了反而更容易学到平滑策略。训练曲线全部收敛后如果抖动仍然存在把决策频率从 20Hz 降到 10Hz 试试机械臂的惯性本身就能滤掉一部分高频成分。6. 训练收尾的验收方法泛化测试比 reward 曲线诚实得多训练曲线好看不代表策略能交付。我在这个项目上最大的教训是训练奖励只能说明这个策略在它见过的那一批初始条件下表现不错完全不能证明它有避障能力。真正应该花时间的是泛化测试。6.1 换障碍、换起点、换目标成功率比 reward 曲线诚实训练停止后把策略固定住在仿真环境里随机生成五十到一百组测试场景改变机械臂的初始构型、障碍物的位置和形状、目标点的方位。逐个跑完整 episode统计成功率而不是看平均奖励。成功率过九成才说明策略学到了泛化的避障能力如果失败场景集中在某个区域比如障碍物靠近机械臂基座时总出问题就针对那一类初始状态补数据重训。我习惯把失败 episode 的关节轨迹打点画出来看失败时机械臂卡在哪个位形附近。很多避障失败根本不是碰撞检测的问题而是关节限位把最优路径堵死了策略在限位附近反复试探。这种场景加奖励惩罚没有用得把限位信息更明确地写进状态。6.2 给策略加扰动再上真机仿真和真机之间的差距是绕不过去的。上真机前我一般会给状态加高斯噪声、给动作加执行器偏差模拟真实传感器噪声和电机响应误差。策略在受扰环境下依旧保持高成功率再考虑移植。真机测试先从低速限幅开始只开放三分之一的速度范围确认轨迹稳定再逐步放开。上个月我还在一个项目里踩了同样的坑训练时把奖励权重调得过于激进策略为了追求距离缩短量学会了让末端走一条贴障碍物很近的捷径仿真里成功率高得惊人真机一跑直接撞上去。后来我养成了一个习惯每次训练结束先看策略在「障碍物偏移 10 厘米」的条件下还有多少成功率这个数字比任何训练曲线都诚实。希望这篇笔记里的路径、参数和坑能帮你把这个方向做得更顺。本文还有配套的精品资源点击获取
返回列表