简介:这份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_goal=2.0, w_collision=20.0, w_limit=5.0, w_smooth=1e-3, obs_thresh=0.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, hidden=256): 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, hidden=256): 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, gamma=0.99, tau=0.005, policy_delay=2, batch_size=256, noise_std=0.2, noise_clip=0.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, alpha=tau)这段代码有四个关键点。一是用min(Q1, Q2)而不是单个 Q 值作为目标,直接压低 Q 函数的高估倾向。二是目标动作加入了 0.2 标准差的高斯噪声并截断到 0.5,让 Critic 对相近的动作输出平滑的价值面。三是 Actor 延迟到每两步更新一次,给 Critic 更多时间收敛,避免策略和价值网络互相追逐。四是目标网络走软更新,tau=0.005,让目标值缓慢跟随,训练过程更稳。
超参不是拍脑袋定的,我按下面的默认值起手,再根据训练曲线微调:
| 参数 | 推荐值 | 调整说明 |
|---|---|---|
| gamma | 0.99 | 决策周期 0.05 秒时,对应约 5 秒的有效视野 |
| tau | 0.005 | 软更新系数,振荡大就调小到 0.001 |
| actor / critic 学习率 | 3e-4 | 机械臂类任务我很少用超过 1e-3 |
| 探索噪声标准差 | 0.1 | 训练中期可以衰减到 0.05 |
| replay buffer | 1e6 | 至少能装下几十个完整 episode |
| batch size | 256 | 显存允许可以加到 512 |
| policy delay | 2 | 样本效率下降就调到 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, dt=0.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, size=len(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 厘米」的条件下还有多少成功率,这个数字比任何训练曲线都诚实。希望这篇笔记里的路径、参数和坑,能帮你把这个方向做得更顺。
本文还有配套的精品资源,点击获取