简介:这是一份面向机械臂路径规划与自动化焊接应用研究的学术PDF资料,聚焦基于深度强化学习的机械臂避障路径规划课题。资源内含一篇完整的期刊论文,系统阐述了针对现有机械臂焊接系统调整困难、灵活性不足的问题,采用深度强化学习算法进行路径规划的思路。文中提出使用三层DNN网络,以机械臂状态信息为输入、运动关节角度为输出,通过离线训练自行逼近最优运动轨迹,并在三自由度点焊机器人模拟平台上验证了该方法规划无碰撞路径的有效性,同时涵盖马尔科夫决策过程、q-learning算法等强化学习基础概念,适合机器人控制、智能制造相关学习者参考。资源包共1个PDF文件,大小约1.44MB;目前已有487人学习使用。通过阅读该资料,读者可快速了解深度强化学习在机械臂避障中的建模流程、网络设计及仿真验证方法,为相关课题研究或工程应用提供参考。
1. 基于深度强化学习的机械臂避障路径规划:这篇论文到底解决了什么问题
做机械臂路径规划的工程师基本都撞过同一堵墙:A*、RRT、人工势场法这些经典算法,全都依赖精确的环境建模,障碍物稍微挪个位置,整个规划就要重来。这篇论文换了个思路,用深度强化学习(DQN)把机械臂避障问题做成了端到端的控制,输入是机械臂状态,输出是关节角度,离线训练完直接在线上用,不需要为每个场景重新建模。论文的落地背景是集装箱点焊作业,三自由度焊接机械臂要在障碍物密布的空间里自动规划无碰撞路径,仿真平台上训练出的轨迹只用了32步就能绕过两个球形障碍到达目标点。适合正在做机械臂控制、自动化焊接或者刚接触强化学习的工程师读,尤其是对Q表存不下高维状态这个问题头疼的人,这篇论文给出了一个完整的简化落地框架。
2. 强化学习基础:从MDP到DQN的演进逻辑
2.1 马尔科夫决策过程:机械臂避障为什么要先建模成MDP
几乎所有强化学习算法都能放进马尔科夫决策过程的框架里描述。论文里给出的元组是 (S, A, P, R, γ),其中 S 是状态集,A 是动作集,P 是状态转移概率,R 是回报函数,γ 是折扣因子。机械臂避障这个任务,状态可以理解为机械臂当前每个关节的角度、末端位置、障碍物距离;动作就是每个关节要怎么转;环境——也就是仿真平台——会返回新的状态并给出一个即时奖励。
这里有个容易忽略的点:P 状态转移概率在实际问题里往往拿不到,因为机械臂真实的物理环境太复杂。所以机械臂避障的强化学习解法基本都是无模型(model-free)的,也就是跳过 P,直接用智能体和环境的交互数据去更新策略。论文里用的就是这种思路,这也是它能做到“不需要针对每一种场景建模”的核心原因——模型是靠试错试出来的,不是靠公式推出来的。
累计回报的计算公式是 $R_t = r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \dots$,折扣因子 γ 控制在0到1之间,γ 越接近1,智能体越看重长远回报;γ 接近0,就只顾眼前。机械臂避障里 γ 通常取0.9~0.99,要让机械臂不被眼前的奖励诱惑,比如先往障碍物方向蹭一下拿到一个正奖励,而是学会一整条从起点到终点的安全路径。强化学习的目标就是在给定MDP下找到最优策略 π,使得累计回报的期望最大,这也是后面所有算法的共同出发点。
2.2 q-learning的短板:Q表在高维空间根本存不下
q-learning 是把动作价值函数 Q(S,a) 存成一张表,行是状态,列是动作,每个格子存对应的价值。机械臂的状态空间是连续的关节角度,哪怕把每个关节的角度离散成10档,三个自由度的机械臂也有 10³ = 1000 个状态;如果把末端坐标、障碍物距离都算进去,状态维度直接爆炸,Q表占用的内存会大到没法接受,而且迭代收敛极慢。论文明确指出了这个问题,这也是它转向深度Q网络的核心动机。
q-learning 还有一个特性值得注意:它属于时序差分中的离线控制方法,用的是两个策略,一个策略负责选择动作去执行,另一个策略在更新价值函数时用贪婪法选使 Q(S',a) 最大的动作。也就是说,Q表的更新和动作的执行是分离的,当前选择的动作只是用来算更新目标,不真正执行。这个设计上的细节在后面 DQN 的经验回放里会继续用到,理解它对后续写代码有帮助。
2.3 DQN的两个关键技巧:经验回放与目标网络
DQN 做的事很简单:把 Q 表换成神经网络。网络输入是状态的特征向量,输出是动作集合里每个动作对应的 Q 值。中间层用 DNN、CNN、RNN 都可以,论文用的是三层 DNN。但神经网络训练需要带标签的样本,强化学习的交互数据是时序相关的,直接拿来训练网络,样本之间的相关性会导致梯度更新不稳定。
论文里处理这个问题用了两个技巧。第一个是经验回放:把每次交互得到的五元组 (S, A, R, S', is_end) 存进一个经验池 D,训练时从 D 里随机采样 m 个样本做批量梯度下降。这样做打破了样本的时间相关性,也提高了数据利用率——同一条经验可以反复学习。第二个是目标网络:DQN 用一个当前网络 Q 负责选动作和更新参数,另一个结构完全相同的目标网络 Q' 负责计算目标 Q 值。目标网络不每次迭代都更新,而是每隔 C 步把当前网络的参数复制过去。如果不这样做,更新目标一直在变,网络容易震荡甚至发散,这在实践中是个非常常见的翻车点。
目标 Q 值 T 的计算公式我习惯写成:
T = reward + gamma * (1 - is_end) * max(Q_target(s_next))这里用(1 - is_end)是为了让终止状态不计算未来的回报,逻辑上更安全。loss 函数就是当前网络输出的 Q(s,a) 和这个目标 Q 值的均方差,靠反向传播去更新网络参数。论文在第4节的实验部分用这个框架跑出了收敛曲线,后面第三章会讲具体的系统搭建。
3. 机械臂建模与碰撞检测:把三维避障问题变成可计算的距离问题
3.1 用D-H法搭机械臂模型:齐次变换矩阵与正运动学
要把机械臂放进强化学习的环境里,第一步是建立运动学模型。论文采用 D-H 法(Denavit-Hartenberg)建立数学模型,相邻连杆之间的关系用一个 4x4 的齐次变换矩阵描述。旋转关节的齐次变换矩阵一般形式带着 θ 角、d 偏距、a 杆长和 α 扭角四个参数,机械臂末端的位置和姿态就是各个相邻齐次变换矩阵的连乘结果,这个过程叫正运动学。
具体到这篇论文,机械臂是 3-DOF 串联结构,杆长设置为 [50, 100, 100]。每步动作执行后,根据当前关节角算出一组新的连杆位姿,然后交给碰撞检测模块去判断有没有撞到障碍物。这里正运动学是每轮训练都会执行的,跑得慢会直接影响训练效率。我一般会在仿真环境初始化时把变换矩阵里不变的部分先算好缓存,只把随关节角变化的部分放进训练循环里算,能省不少时间。
正运动学在代码里通常写成连乘的形式:
# 已知DH参数:各关节角度 theta_list,杆长 a,偏距 d,扭角 alpha def forward_kinematics(theta_list, a_list, d_list, alpha_list): # 初始化齐次变换矩阵为单位阵 T = np.eye(4) for theta, a, d, alpha in zip(theta_list, a_list, d_list, alpha_list): # 当前关节的齐次变换矩阵 T_i = np.array([ [np.cos(theta), -np.sin(theta) * np.cos(alpha), np.sin(theta) * np.sin(alpha), a * np.cos(theta)], [np.sin(theta), np.cos(theta) * np.cos(alpha), -np.cos(theta) * np.sin(alpha), a * np.sin(theta)], [0, np.sin(alpha), np.cos(alpha), d ], [0, 0, 0, 1 ] ]) T = T @ T_i # 矩阵连乘 # 末端位置是齐次矩阵的前三行第四列 end_effector_pos = T[:3, 3] return end_effector_pos逻辑说明:每一根连杆对应一个齐次变换矩阵,矩阵里同时含了旋转分量(左上3x3)和平移分量(第四列前3行)。末端位置就是所有矩阵连乘后第四列的前三个分量。这个实现里唯一要注意的就是@运算符做矩阵乘法时顺序不能反,D-H 法的约定是后一个关节的变换乘在前一个的右边,顺序错了末端位置会跑到完全不相干的地方去。
参数说明:theta_list是三个关节的当前角度,a_list是杆长,对应论文里的 [50, 100, 100]。d_list和alpha_list取决于机械臂的具体D-H参数表,论文里图2给出了标准参数,实际复现时如果没用到偏心距,这两个可以填0。这个函数会在强化学习环境里被频繁调用,建议用 numpy 向量化一次算完,不要逐元素生成。
3.2 碰撞检测算法:把不规则障碍物简化成球体线段求距
三维空间里的障碍物形状乱七八糟,论文的处理方式是把障碍物统一简化成空间球体,机械臂简化成圆柱体,再进一步点杆化处理。这样碰撞检测就变成了两个几何体之间的距离计算问题——球心到机械臂轴线的距离、球心到端点的距离,跟预设的半径阈值比较就完事了。
具体做法是:设球形障碍物的中心为 O,半径为 R_obs,机械臂的圆柱体半径为 R_arm,当前机械臂某段轴线的两个端点是 A 和 B,障碍物球心到直线 AB 的投影点是 P。先求出投影点坐标,然后判断投影点是否在线段 AB 区域内:如果在,计算球心到线段 AB 的垂直距离 d,如果 d < R_obs + R_arm 就判定碰撞;如果投影点不在线段区域内,计算球心到两个端点的距离,取小的那个跟阈值比较。每个轴都安全才算安全,任何一个轴碰撞就立刻终止当前回合。
论文里机械臂被简化为点杆模型,圆柱半径这层可以省略,碰撞判定变成球心到轴线的距离和障碍物半径直接比较。这样处理损失了一点精度,但换来了计算速度和实现简单,对验证强化学习算法收敛性来说是值得的。真实工程里要更精细,可以把机械臂的每一段杆拆成更短的子段去做碰撞检测,多花不了多少计算量,但精度提升一个档次。
3.3 碰撞检测的Python实现:写清楚再去套强化学习
碰撞检测的代码不复杂,核心就是向量投影和点到线段距离的计算。下面这段实现可以直接嵌进仿真环境里,每次机械臂动作后调用一次:
import numpy as np def point_to_segment_distance(p, a, b): # 计算空间点p到线段ab的最短距离 ab_vec = b - a ap_vec = p - a # 投影参数t: 找出线段上距离p最近的点 t = np.dot(ap_vec, ab_vec) / (np.dot(ab_vec, ab_vec) + 1e-12) t = np.clip(t, 0.0, 1.0) # 限制在线段范围内,防止投影落在线段延长线上 closest_point = a + t * ab_vec return np.linalg.norm(p - closest_point) def check_collision(arm_points, obstacle_center, obstacle_radius): # arm_points: 机械臂各段轴线的端点坐标列表,长度是关节数+1 # obstacle_center: 障碍物球心坐标 [x, y, z] # obstacle_radius: 障碍物半径 for i in range(len(arm_points) - 1): a = arm_points[i] b = arm_points[i + 1] dist = point_to_segment_distance(obstacle_center, a, b) if dist < obstacle_radius: return True # 该段与障碍物碰撞 return False逻辑说明:point_to_segment_distance用向量投影算线段上离球心最近的点,np.clip(t, 0.0, 1.0)是这段代码里最关键的细节——如果不限制 t 的范围,当障碍物投影落在线段延长线时,距离会被算错,明明离端点很远却判定碰撞。然后在每一段连杆上做检查,任何一个距离小于障碍物半径就判定碰撞,终点到一个轴都没碰到才安全。
参数说明:arm_points是机械臂所有关键点的三维坐标列表,对三自由度机构来说,就是底盘基点加三个关节末端,一共4个点,3段杆。obstacle_radius对应论文实验里的20。这里要注意:论文里机械臂是点杆模型,所以不需要考虑机械臂自身的半径;如果你的环境里机械臂有实际粗细,就把阈值改成obstacle_radius + arm_radius。加1e-12在分母上,是为了防止线段长度为零时除以零报错。
这段代码我强烈建议在强化学习之前先单独测试一遍,把障碍物摆几个极端位置,比起点到线段距离是否和手动算的一致。碰撞检测一旦写错,整个训练过程都不会收敛,而且错误很隐蔽。
4. DQN机械臂避障系统设计:状态、动作、奖励三大件的设参细节
4.1 状态与动作的编码:10维状态向量和离散动作集
论文里机械臂与环境的交互状态用一个10维向量表示,具体拆开是:末端点的三维坐标、第三个端点的三维坐标、是否碰撞的标志位、是否到达目标点的标志位、末端点到目标点的距离、第三端点到目标点的距离。这里有个细节值得琢磨:状态里既有连续量(坐标、距离)又有离散量(碰撞标志、到达标志),做DQN时神经网络输入层直接接这个混合向量就行,离散标志位一般不要归一化,保持0/1反而让网络更容易学会“撞了就负反馈”的语义。
选择状态量时有一个原则:真正决定决策边界的信息必须进状态。比如末端点到目标点的距离,它直接告诉网络“你离目标还有多远”,这个量是梯度信号的重要来源;而是否碰撞这个标志位,是为了让网络在撞上障碍物的瞬间能收到一个强负信号。如果只给坐标不给距离,网络很难纯靠坐标学会远近概念。
动作用一个向量表示三个关节的角转动量。论文里允许机械臂转角有一定范围,但没有给出详细的离散化档位。常见的做法是把每个关节的角度增量离散成三档,比如 [-5°, 0°, +5°],三个关节组合起来就是 3³ = 27 个离散动作。这个设计直接决定了网络输出层的节点数——每个动作对应一个输出节点,DQN的最后一层维度就是动作数。
4.2 奖励函数怎么设才不会把网络带偏
奖励函数是DQN里最“玄学”的部分,设置是否合理直接决定算法能不能收敛。论文的核心逻辑很清晰:希望机械臂能顺利避开障碍物到达目标点,同时产生的步数尽量少,所以奖励函数围绕这三个目标展开。具体数值论文里只给出了方向没有给全,我按实操经验补上一个可用的配置。
def compute_reward(done, collision, reach_target, step_penalty, dist_before, dist_after): # done: 本回合是否结束 # collision: 是否发生碰撞 # reach_target: 是否到达目标点 # dist_before, dist_after: 执行动作前后的末端点到目标点距离 reward = 0.0 if collision: reward = -10.0 # 碰撞重罚 elif reach_target: reward = 10.0 # 到达目标给大奖励 else: # 距离差为正说明在靠近目标,给小正反馈 reward = step_penalty + (dist_before - dist_after) * 0.5 return reward逻辑说明:step_penalty是每一步的生存代价,通常填 -0.1 到 -0.05。这一项的作用是让机械臂学会“少绕路”——既不碰撞也不到目标时,走一步扣一点分,逼着智能体用最少的步数完成避障,这和论文里“产生的步数尽量少”的设定是对应的。(dist_before - dist_after) * 0.5是距离形奖励,给网络一个连续的梯度信号:靠近目标有奖励,远离目标被扣除。
参数说明:碰撞和到达的奖励数值要拉开差距。我一般用碰撞 -10、到达 +10 或者 -1/+1 这种对称结构。论文里的实验跑了接近6300轮才收敛,奖励幅度设置得合理的话收敛速度差很多。有一段要注意,步数惩罚不宜过大,否则机械臂会为了省步数直接直线冲过去撞障碍物——碰撞惩罚和步数惩罚的比值最好保持在 100:1 左右,奖励设计这块急不来,要看曲线再调。
4.3 三层DNN网络结构与训练主循环
论文采用三层 DNN 作为 DQN 网络主体,输入是10维状态向量,输出是动作集对应的 Q 值。层数不用深,三层全连接足够拟合这个规模的函数;隐藏层的节点数在64到128之间,激活函数用 ReLU,输出层不加激活函数,因为 Q 值需要的是回归输出而不是分类概率。整个训练流程论文给出了一个清晰的伪代码框架,我把它翻译成可执行的维度设计:
import torch.nn as nn class DQN(nn.Module): def __init__(self, state_dim=10, action_dim=27): super(DQN, self).__init__() # 三层DNN:输入层 -> 隐藏层1 -> 隐藏层2 -> 输出层 self.fc1 = nn.Linear(state_dim, 128) self.fc2 = nn.Linear(128, 64) self.out = nn.Linear(64, action_dim) def forward(self, x): x = torch.relu(self.fc1(x)) x = torch.relu(self.fc2(x)) return self.out(x) # 输出每个动作的Q值,不做softmax逻辑说明:DQN的输出层节点数等于动作数,对应的是每个离散动作的 Q 值估计。训练时用它选出 Q 值最大的动作执行,但只取最大值参与目标 Q 值计算。这里不能对输出做 softmax,因为 Q 值是标量回归值,不是概率分布。
参数说明:state_dim=10对应论文里的10维状态向量,action_dim=27是三个关节各三档离散角度的组合数。当然后续章节会讲到训练主循环,包括从经验池采多少样本、目标网络多久同步一次,这些参数设置不当的后果会在第5章避坑里展开。
4.4 论文实验参数对照表
把论文的实验配置整理成表,复现的时候直接对着填就行:
| 参数项 | 论文取值 | 说明 |
|---|---|---|
| 机械臂杆长 | [50, 100, 100] | 三根杆的长度,单位毫米 |
| 起点坐标 | (118.3, -68.3, 13.3) | 机械臂末端初始位置 |
| 终点坐标 | (50, 70, 30) | 目标点位置 |
| 障碍物1坐标 | (40, 30, 30) | 球形障碍物圆心 |
| 障碍物2坐标 | (70, -20, 60) | 球形障碍物圆心 |
| 障碍物半径 | 20 | 论文设置为统一半径 |
| 最大训练轮数 | 10000 | episode上限 |
| 每轮最大步数 | 250 | 超过则终止当前回合 |
| 碰撞检测模型 | 点杆模型 | 障碍物简化为球体 |
注意障碍物坐标的单位、起始点和终点的相对位置关系要在仿真环境里核对一遍。论文给的这组坐标是可以直接在笛卡尔空间里画出来的,A 到 B 的直线路径正好会穿过障碍物区域,所以算法必须规划出弯折轨迹才能到达。
5. 避坑笔记:DQN机械臂训练里的五个翻车现场
训练了神经网络却一直不走、奖励曲线忽上忽下的问题,在机械臂避障场景里几乎每个人都会碰到一次。这一章把常见的五个坑写清楚,每条都是踩过之后才知道的。
坑一:碰撞检测边界判定不准导致训练不收敛
现象:网络训练了2000多轮,机械臂依然反复撞到障碍物,而且撞的位置总是那一片。
原因:point_to_segment_distance里的投影参数 t 没有做np.clip(t, 0.0, 1.0)限制,障碍物球心投影落在线段延长线上时距离被算大,机械臂贴着障碍物边缘走却没被判碰撞。强化学习环境里的每一步奖励都是按这个距离算的,边界判错等于整个奖励信号都不可信。
解决:把投影限制在线段范围内,然后单独拉一组测试用例,把障碍物摆在端点外侧、正上方、正中间三个位置,手动验证碰撞检测距离是否和几何直觉一致。
坑二:奖励稀疏导致前面几千轮都在瞎逛
现象:累计回报曲线前3000轮几乎是一条直线,机械臂不断做随机动作,完全没有往目标方向运动的趋势,距离形奖励没有起到引导作用。
原因:奖励函数里距离形反馈的权重太小,机械臂就算靠近目标也拿不到明显的奖励信号,学习不到“靠近目标是对的”这个规律。论文里状态包含了“末端点与目标点的距离”,这显然是要用距离来设计奖励的,但如果差值直接乘0.1,信号淹没在步数惩罚里,效果跟没有一样。
解决:在试了从0.1到2.0之间五组权重之后,我把距离奖励的系数提到0.5,同时把步数惩罚降到-0.05,让“每走一步靠近目标”的收益能够正向传导。可以在训练日志里打印每轮前50步机械臂是否在靠近目标,如果方向对了奖励权重就不用再动。
坑三:目标网络同步太频繁,loss震荡不衰减
现象:loss 曲线整体在下降但抖动很大,每到一个节点就突然跳高一次,训练结果时好时坏,动作经常在几个选择之间反复变。
原因:目标网络每隔 C 步才从当前网络复制参数,如果 C 设置太小,目标网络和当前网络几乎同步更新,DQN 里精心设计的“打破目标相关性”就不成立了。我遇到过把 C 设成5步的案例,整个训练过程就像追逐自己的影子,永远追不上。
解决:C 至少100步起步,我一般取200~500。同时记录同一个状态下连续两次更新后目标 Q 值的差异,如果变化超过当前 loss 的3倍,先调大 C 再看网络结构和学习率。
坑四:探索率衰减太快,机械臂学成了“莽夫”
现象:训练后期机械臂每次选择都走同一条弧线,成功率能到80%,但那条路径离障碍物只有几个单位距离,稍微有点扰动就会撞上;而且这路线绕了远路,每轮步数比预期多。
原因:强化学习前期要靠探索找到可行的碰撞避免路径,如果探索率 ε 从1.0迅速衰减到0.1,机械臂只在初期见到过少数成功轨迹,后面就直接按这条脆弱的路径走了,没有机会发现更优路线,这就是典型探索不充分导致的局部最优。
解决:探索率衰减周期拉长到8000轮以上,从1.0线性衰减到0.1。评估模型时把 ε 直接设成0,看机械臂在没有随机扰动下的真实表现——这一步论文里没提,但实际操作中一定要做,否则会高估训练的可靠性。
坑五:仿真和训练环境分离,接口对接反复出错
现象:MATLAB里搭建的碰撞检测模型、Python里的深度强化学习网络,两边各自运行没问题,一连起来训练就报维度错误,接口对不上,训练中断。
原因:论文实验配置里明确写了MATLAB做机械臂及碰撞检测模型搭建、Python做深度强化学习模型搭建,两个平台之间数据交互要通过保存文件或者socket传递。状态向量维度在MATLAB里算出来可能是10,传过去Python端如果少传了一个碰撞标志位,网络输入就变成9维,直接报错。这种跨语言联调的问题特别繁琐,每个维度都要人工对齐。
解决:仿真部分我现在的习惯是封装成统一的环境接口,不管底层是MATLAB还是Python,对外只暴露state, reward, done = env.step(action)这样一个接口,维度对齐在环境内部检查。跨语言传数据时,前100次训练步强制走一次全流程打印,确认状态向量的每个维度含义对得上,再撤掉打印去跑完整实验。
6. 验证技巧:光看loss曲线不够,还得看这两条曲线
训练完成后,模型到底行不行,不能只看DQN的loss降没降。论文实验部分画了两张图——每回合的累计回报和每回合实际走的步数,这两个才是判断策略是否真的学会避障的关键。累计回报曲线应该逐渐上升并最终稳定,收敛点大约在6300轮;步数曲线应该逐渐下降,意味着机械臂在找到安全路径的同时还在优化路径长度,最终稳定在32步附近,这就是接近最优的依据。我自己的习惯是这两个曲线一起看:回报涨而步数降,说明策略在变好;回报涨但步数不变,可能是奖励函数里距离项的功劳被步数惩罚抵消了,路径还能再压缩。
落地的验证手法上,第一步是保存训练完的模型后把探索率设成0,加载模型回放一遍完整轨迹,把每一帧的末端坐标记录下来,在三维空间里和障碍物一起画出来看,不能只看控制台里打的成功率数字。第二步是确定性策略下重跑50轮取平均,看平均步数和碰撞率是否和训练末期一致。如果回放时出现轨迹到一个坐标点突然停止,多半是动作空间边界处理的问题,检查np.clip(t, 0.0, 1.0)这个边界有没有污染到末端坐标的计算。
另外有个小技巧:训练过程中每隔500轮保存一个模型参数快照,全部训练完后从后往前加载每个快照,在同一起点上让机械臂跑一遍,观察中间训练阶段每次迭代轨迹的变化。这样做能直观看到机械臂策略从乱撞到稳定避障的转变过程,也能定位到具体是哪一轮之后策略开始明显改善——这比只盯着最后那个收敛模型要更容易判断网络什么时候真正学到了东西。从那以后我每次训练DQN机械臂,都强制走一遍“看双曲线、零探索率回放、多快照对比”的流程,这三个环节缺一个,我就不敢把模型交给后续的焊接路径执行环节做验证——神经网络的策略在仿真里看着能走,拿到真实机械臂上分分钟翻车,希望帮到你。
本文还有配套的精品资源,点击获取