DDPG与PyTorch实战:六轴机械臂轨迹规划的实时仿真

DDPG与PyTorch实战:六轴机械臂轨迹规划的实时仿真 简介面向机器人控制与强化学习研究者的技术文档聚焦 PyTorch 框架下 DDPG 算法在六轴机械臂轨迹规划中的实时仿真方案。文档从六轴机械臂的正逆运动学、传统轨迹规划方法入手逐步引入强化学习基本概念、MDP、策略梯度与 DDPG 原理随后结合 PyTorch 动态计算图、Tensor 与自动求导机制给出 Actor-Critic 网络、目标网络软更新、经验回放和奖励函数设计等实现要点并介绍仿真平台搭建、状态与动作空间定义、实时性保障措施以及累积奖励、轨迹误差、成功率等评价指标。资源为单个 PDF 文件共 30 页约 1.89MB支持大纲显示和章节快速定位内容排版清晰已有 98 人学习。整体结构完整从研究背景、国内外现状到实验分析与总结适合作为相关课题入门和项目实现的系统参考。1. DDPG 与六轴机械臂轨迹规划的实时仿真难点在连续动作输出“机器人动态控制”这个标题真正要解决的问题是让 PyTorch 训练出来的强化学习 DDPG 算法在六轴机械臂轨迹规划任务中输出连续控制量并在仿真环境里以足够快的速度跑通训练闭环。机械臂的动作空间是浮点关节指令DQN 那种离散动作表在这里要么粒度太粗要么维度爆炸PPO 能处理连续动作但 on-policy 的样本利用率低仿真里每一次 reset 都要重新采数据。DDPG 用确定性策略加经验回放一条轨迹跑完就能立刻进入训练队列正好贴合“仿真器推进慢、数据获取贵”的场景。这篇方案面向两类人一类是在 PyBullet 或 Isaac Gym 里验证六轴机械臂轨迹规划算法的工程师另一类是准备把强化学习模型移植到真实控制器、需要先做实时性预研的人。你不必吃透全部控制理论但得清楚正运动学和关节限位这两件事否则后面所有参数都调不明白。2. 六轴机械臂轨迹规划的仿真环境与状态动作空间设计DDPG 不是换上网络结构就能收敛状态空间和动作空间决定了策略能学到什么。机械臂轨迹规划的特殊之处在于状态是关节空间和笛卡尔空间的混合量动作又必须映射成控制柜能接收的指令。先搭环境再定维度顺序不能反。2.1 用 PyBullet 和 URDF 把六轴机械臂装进仿真常见的做法是直接用 PyBullet 自带的 URDF 模型省去自己写正运动学的麻烦。比如ur5e这类六轴机械臂 URDF加载起来就是一套完整的几何、惯量和关节限位信息。import pybullet as p import pybullet_data p.connect(p.GUI) # 训练时可换成 p.DIRECT 无界面 p.setAdditionalSearchPath(pybullet_data.getDataPath()) p.setGravity(0, 0, -9.8) p.loadURDF(plane.urdf) robot p.loadURDF(ur5e/ur5e.urdf, useFixedBaseTrue) num_joints p.getNumJoints(robot) joint_indices [i for i in range(num_joints) if p.getJointInfo(robot, i)[2] ! p.JOINT_FIXED] print(可动关节:, joint_indices)getJointInfo返回的第三个字段是关节类型等于JOINT_FIXED的是固定轴要过滤掉。joint_indices的长度就是 DDPG 动作向量的维度六轴机械臂一般是 6 个旋转关节但有些模型末端带着法兰或夹爪过滤后可能变成 7 个。训练循环里设置速度控制模式时直接用这个列表p.setJointMotorControlArray( robot, joint_indices, p.VELOCITY_CONTROL, forces[200.0] * len(joint_indices) )VELOCITY_CONTROL模式下面是内部 PID 追踪目标速度forces参数是每个关节执行器允许的最大力。DDPG 输出的动作经过缩放后传给targetVelocity比直接发扭矩安全得多。GUI模式适合调试训练大批量回合时建议改为DIRECT否则图形渲染会占掉大量实时仿真时间。2.2 状态空间选取关节角、角速度与末端误差状态空间维度我一般固定为 18 维分成三段见下表。关节角用弧度表示输入网络前统一归一化到[-1, 1]否则Linear层的输入尺度差异会拖慢收敛。状态组维度表示归一化范围关节角 q6当前各关节角度[-pi, pi]映射到[-1, 1]关节角速度 dq6PyBullet 返回的关节速度除以最大角速度末端位置误差 e3目标位置减去当前末端位置按最大工作距离 clip末端姿态误差3轴角形式的角度差[-1, 1]末端姿态误差用轴角而不是欧拉角因为欧拉角存在万向锁DDPG 学出来的策略在奇异点附近会突然跳变。位置误差单独提出来而不混在目标位置里是为了让网络直接看到“我还差多远”减少隐式推理。如果目标是跟踪一条连续轨迹我还会把轨迹下一点的位置也拼进状态维度变 21但如果目标只是到达固定点加上去反而会让策略提前拐弯末端误差震荡更严重。2.3 动作空间速度增量比直接输出力矩更稳DDPG 输出的原始动作是 6 维浮点范围由输出层tanh限制在[-1, 1]。问题是怎么把这些值变成机械臂的指令。这里我推荐速度增量而不是关节力矩action np.clip(ddpg_action, -1.0, 1.0) target_velocity action * max_velocity # max_velocity 0.5 rad/s p.setJointMotorControlArray( robot, joint_indices, p.VELOCITY_CONTROL, targetVelocitiestarget_velocity, forces[200.0] * len(joint_indices) )max_velocity是每个关节允许的极限速度按机械臂型号设定六轴工业臂一般在 0.5 到 1.5 rad/s。速度增量模式相当于让底层 PID 做平滑策略只负责给方向能避免训练初期随机探索时关节直接飞到限位。直接输出力矩的方案也有但那时需要在 reward 里额外加重力补偿项还要对惯性矩阵做估计训练复杂度会高很多。实时仿真方案里先跑通速度控制再考虑力矩模式是更稳妥的路径。3. DDPG 网络结构与 PyTorch 实现要点DDPG 的结构不难难在 Actor 和 Critic 的配合方式、目标网络的更新节奏以及噪声怎么加。这些细节在 PyTorch 里体现得最直接也最容易写错。3.1 为什么是 DDPG 而不是 DQN 或 PPODQN 只能输出离散动作六轴机械臂每个关节给 20 个离散档位动作总数就是 20 的 6 次方完全没法学。PPO 是 on-policy 算法每一步的交互数据用完就丢而 PyBullet 仿真一个回合可能涉及上千步样本效率太低。DDPG 把策略函数直接建模成连续映射状态 - 动作配合经验回放把历史数据反复用交互同样的步数能训练更多次。它不是没有缺点超参数敏感、Q 值高估是出了名的但对机械臂轨迹规划这种低维连续控制问题调参成本仍然在可接受范围内。3.2 PyTorch 中 Actor 和 Critic 的网络定义Actor 输入状态、输出动作输出层必须用tanh把动作限制在[-1, 1]import torch import torch.nn as nn class Actor(nn.Module): def __init__(self, state_dim, action_dim, hidden256): super().__init__() self.fc1 nn.Linear(state_dim, hidden) self.ln1 nn.LayerNorm(hidden) self.fc2 nn.Linear(hidden, hidden) self.ln2 nn.LayerNorm(hidden) self.fc3 nn.Linear(hidden, action_dim) def forward(self, state): x torch.relu(self.ln1(self.fc1(state))) x torch.relu(self.ln2(self.fc2(x))) return torch.tanh(self.fc3(x))Critic 输入状态和动作拼接后的向量输出一个 Q 值class Critic(nn.Module): def __init__(self, state_dim, action_dim, hidden256): super().__init__() self.fc1 nn.Linear(state_dim action_dim, hidden) self.fc2 nn.Linear(hidden, hidden) self.fc3 nn.Linear(hidden, 1) def forward(self, state, action): x torch.cat([state, action], dim-1) x torch.relu(self.fc1(x)) x torch.relu(self.fc2(x)) return self.fc3(x)LayerNorm在这里比BatchNorm更合适。机械臂状态的关节角、角速度值域差异大BatchNorm 依赖 batch 统计量仿真数据一变化就容易抖动LayerNorm 只对单个样本做归一化训练初期更稳定。隐藏层 256 是经验值六轴机械臂状态维度 18、动作维度 6256 足够拟合再加大到 512 在 CPU 上推理延迟会翻倍收益很小。3.3 软更新目标网络与经验回放DDPG 必须有一对目标网络Actor 和 Critic 各一个。目标网络不参与梯度计算而是缓慢跟随在线网络参数def update_target(net, target_net, tau): with torch.no_grad(): for target_param, param in zip(target_net.parameters(), net.parameters()): target_param.data.copy_( tau * param.data (1.0 - tau) * target_param.data )训练时的更新逻辑这样组织def ddpg_update(agent, replay_buffer, batch_size256): state, action, reward, next_state, done replay_buffer.sample(batch_size) with torch.no_grad(): next_action agent.actor_target(next_state) target_q agent.critic_target(next_state, next_action) y reward agent.gamma * (1.0 - done) * target_q current_q agent.critic(state, action) critic_loss nn.functional.mse_loss(current_q, y) agent.critic_optimizer.zero_grad() critic_loss.backward() agent.critic_optimizer.step() actor_loss -agent.critic(state, agent.actor(state)).mean() agent.actor_optimizer.zero_grad() actor_loss.backward() agent.actor_optimizer.step() update_target(agent.actor, agent.actor_target, agent.tau) update_target(agent.critic, agent.critic_target, agent.tau)这段代码的核心点有三个。第一计算target_q时要用目标 Actor 输出下一步动作而不是在线 Actor否则 bootstrap 误差会叠加。第二Actor 的梯度方向是最大化 Critic 给出的 Q 值所以actor_loss取负。第三软更新参数tau必须小目标网络才追不上在线网络训练才稳定。3.4 DDPG 超参数推荐范围超参数推荐值说明actor_lr1e-4比 Critic 学习率低 10 倍避免策略突然跑偏critic_lr1e-3Q 值发散时降到 1e-4gamma0.99PyBullet 控制周期短接近 1 利于长程规划tau0.001目标网络收敛慢时提高到 0.005batch_size256batch 过小梯度方差大replay_buffer1e6约能存 2000 个 500 步回合noise_scale0.1探索不足时调到 0.2DDPG 本身没有内置探索因为 Actor 是确定性策略。需要在输出动作上叠加高斯噪声或 OU 噪声。机械臂这类惯性强的系统OU 噪声更贴近真实扰动但高斯噪声实现简单初始阶段先用它没问题。噪声在训练中按 episode 衰减我常用noise max(0.01, noise * 0.999)大概两百回合后进入稳定利用阶段。4. 轨迹规划实时仿真训练循环reward 设计与主程序网络结构和超参数定了之后真正决定轨迹规划效果的是 reward 函数和训练循环的写法。实时仿真环境下每一步都要尽快完成采样、存储、更新代码写得差一点单回合时间长好几倍。4.1 reward 怎么设才不抖机械臂轨迹规划最常见的失败是末端到了目标点附近但关节还在高频抖动。如果只把位置误差放进 reward策略只要平均误差低就能获得高收益完全不用管动作是否平滑。所以我用三项求和def compute_reward(end_pos, target_pos, prev_action, action, arrived): distance np.linalg.norm(end_pos - target_pos) smoothness np.abs(action - prev_action).sum() reward -0.5 * distance - 0.03 * smoothness if arrived: reward 1.0 return rewarddistance主导策略朝目标走smoothness惩罚相邻两步的动作变化量arrived是关键点奖励。三个系数里最敏感的是0.03它会让动作增量大的轨迹立即获得负回报如果设成0.1策略会缩在原点不敢动设成0.001又会出现末端到位后关节仍然来回摆的现象。奖励整体幅度控制在[-1, 1]量级Critic 的 Q 值更容易学习。4.2 训练主循环采样、存储、更新一步不落主循环写成单进程同步模式每走一步仿真就立刻更新一次网络replay_buffer ReplayBuffer(capacity1000000) agent DDPGAgent(state_dim18, action_dim6, hidden_dim256) noise_scale 0.1 for episode in range(5000): obs env.reset() prev_action np.zeros(6) episode_reward 0.0 done False step 0 while not done: action agent.get_action(obs, noise_scalenoise_scale) action np.clip(action, -1.0, 1.0) next_obs, reward, done, _ env.step(action) replay_buffer.push(obs, action, reward, next_obs, float(done)) if replay_buffer.size() 1000: ddpg_update(agent, replay_buffer, batch_size256) obs next_obs episode_reward reward prev_action action if step % 4 0: env.render() step 1 noise_scale max(0.01, noise_scale * 0.999) print(fepisode {episode}, reward {episode_reward:.2f}, noise {noise_scale:.3f})代码中replay_buffer.size() 1000是 warmup 阶段前 1000 条经验不更新网络因为随机初始化网络产出的 Q target 完全不可信强行更新只会让参数乱跳。每个环境 step 都调用一次ddpg_update比每回合更新一次更符合在线数据流PyTorch 在 CPU 上跑一次 256 batch 的更新大约几毫秒能塞进仿真步进间隙。env.render()每 4 步才渲染一次GUI 模式才不会成为瓶颈。4.3 训练不收敛时从哪几条线排查我见过最多的失败现象是reward 曲线整体上升但末端距离不降。这种情况先别改超参数把action - prev_action的绝对值打印出来如果动作变化率持续大于 0.1说明平滑惩罚权重太小。另一个常见现象是 Critic 的 Q 值在训练中段突然跳到几十这时把critic_lr从 1e-3 降到 1e-4同时把 reward 整体除以 5让目标 Q 值回到合理区间。还有一种情况末端到达目标点但每个回合来回穿越检查arrived的判定距离如果判定阈值是 0.1m策略学到的是“足够近就行”把阈值改成 0.03m再给一个到达后的保持步数奖励会比单纯调学习率有效。5. DDPG 实时性调优推理延迟、安全滤波与轨迹验证仿真训练通过之后要考虑“实时仿真方案”这个标题里的实时性。DDPG 网络本身很小但 PyTorch 的 Python 调用链和控制循环之间会有不少隐藏延迟需要逐段控制。5.1 用torch.inference_mode压推理延迟代码里最容易忽略的是每次推理都创建torch.no_grad()上下文这会增加调用开销。我一般用torch.inference_mode()并固定 CPU 线程数import torch torch.set_num_threads(4) with torch.inference_mode(): action agent.actor(torch.from_numpy(obs).float()).numpy()这里不要每步把 obs 转成 GPU tensor机械臂状态是 18 维小向量GPU 的 PCIe 拷贝延迟反而比 CPU 计算慢。CPU 上 256 隐藏层的 DDPG 单步推理在 0.2 到 0.5ms 之间配合 PyBullet 的 1/240 秒控制步长理论上有很大余量但 Python 取关节状态和设置电机指令会占掉更多时间。5.2 安全滤波DDPG 输出不能直接发给关节训练好的策略必须过一道安全层。我常用的两个过滤动作幅值限制和动作变化率限制。raw_action agent.actor(torch.from_numpy(obs).float()) raw_action np.clip(raw_action, -1.0, 1.0) filtered_action 0.8 * last_action 0.2 * raw_action filtered_action np.clip(filtered_action, -1.0, 1.0) last_action filtered_action低通滤波系数 0.8/0.2 表示当前动作 80% 来自上一帧高频分量被压掉末端轨迹更平滑。滤波会带来几十毫秒的相位延迟如果目标轨迹本身变化很快把系数改成 0.6/0.4 再配合末端位置预测补偿。5.3 用 eval 模式验证轨迹不看总 reward训练最后阶段用无噪声策略跑固定几组初始位置记录末端距离和动作抖动指标def evaluate(agent, env, episodes10): dists, jitters [], [] for _ in range(episodes): obs env.reset() prev_action np.zeros(6) done False while not done: action agent.get_action(obs, noise_scale0.0) obs, _, done, _ env.step(action) dists.append(env.end_effector_distance()) jitters.append(np.abs(action - prev_action).mean()) prev_action action return np.mean(dists[-100:]), np.mean(jitters)如果dists[-100:]小于 0.02m而jitters大于 0.05说明轨迹到位但动作还在跳这时优先调安全滤波里的低通系数而不是继续训练。把这两组指标按 episode 写进 CSV对比不同随机种子的收敛速度能直接看出是哪一次运行卡在了局部最优。本文还有配套的精品资源点击获取