简介:这份PDF文档面向机器人控制、强化学习方向的开发者与研究者,系统讲解如何用PyTorch实现DDPG算法,完成六轴机械臂轨迹规划的实时仿真。内容从六轴机械臂运动学基础、轨迹规划传统方法切入,逐步过渡到强化学习与DDPG原理,再落到PyTorch框架下的Actor-Critic网络设计、经验回放、软更新与训练调试,最后给出仿真环境搭建、实时性保障与实验结果分析,形成完整的技术链路。资源共1个PDF文件,压缩包约1.89MB,支持目录跳转与阅读器大纲定位,30页篇幅条理清晰,图表与目录显示正常。已有98人学习下载,适合希望把深度强化学习落地到机械臂控制场景的读者,可据此理解算法实现细节、复现仿真流程并借鉴调参排错思路。
1. 六轴机械臂轨迹规划为什么需要 DDPG:从 PyTorch 强化学习说起
六轴机械臂做轨迹规划,传统做法是逆运动学解算加多项式插值,路径点给定后反解关节角,再用五次多项式或 B 样条平滑。这套流程在结构化场景里够用,但一旦目标点动态变化、避障约束时变,或者末端执行器要在运动中保持姿态连续,逆解的多解性和奇异点问题就会让整条轨迹抖得没法看。DDPG(Deep Deterministic Policy Gradient)属于深度强化学习里的确定性策略梯度算法,它把 Actor-Critic 架构和离线策略学习结合起来,专门处理连续动作空间——机械臂六个关节的角速度或力矩正好是六维连续量,这就是它比 DQN 更适合这类任务的根本原因。用 PyTorch 实现 DDPG,配合 MuJoCo 或 PyBullet 做实时仿真,能在不依赖精确动力学模型的前提下,让机械臂通过试错学出一条平滑且满足约束的轨迹。这套方案适合已经掌握 PyTorch 基础、做过简单强化学习实验、想往机器人控制方向落地的工程师,也适合做多 AGV 路径规划强化学习想迁移到机械臂场景的人。下面从算法选型、环境搭建、网络实现、训练调参到避坑,一步步拆开讲。
2. DDPG 在六轴机械臂上的算法选型与网络结构设计
2.1 为什么是 DDPG 而不是 DQN 或 TD3
六轴机械臂的关节控制量是连续值,DQN 的输出是离散动作的 Q 值,要做连续控制只能把每个关节离散成几十个档位,六轴组合起来动作空间爆炸,训练根本收敛不了。DDPG 用一个确定性 Actor 网络直接输出六维动作,Critic 网络评估这个动作的 Q 值,Actor 根据 Critic 的梯度更新,天然适配连续控制。TD3 是 DDPG 的改进版,加了双 Critic 和延迟更新,能缓解 Q 值高估,但代码复杂度高一截。我一般建议先用 DDPG 跑通 baseline,确认环境、奖励函数、超参没问题,再换 TD3 对比提升幅度。如果任务对样本效率要求极高,可以看 IQL 离线强化学习或基于模型强化学习的方向,但那是另一条路,入门阶段别贪多。
DDPG 的核心结构是四个网络:Actor 在线网络、Actor 目标网络、Critic 在线网络、Critic 目标网络。Actor 输入状态,输出动作;Critic 输入状态和动作,输出 Q 值。目标网络用软更新(Polyak averaging)缓慢跟踪在线网络,系数通常取 0.001 到 0.005。经验回放池存(s, a, r, s', done)五元组,训练时随机采样打破时间相关性。探索靠给 Actor 输出加高斯噪声或 Ornstein-Uhlenbeck 噪声,前者简单够用,后者在惯性系统里更平滑。
2.2 状态空间、动作空间与奖励函数的定义
状态空间要包含机械臂当前六个关节角、六个关节角速度,如果做视觉伺服还要加末端位姿。我一般用 12 维状态:[q1..q6, dq1..dq6],归一化到[-1, 1]或零均值单位方差。动作空间是六个关节的角速度增量或力矩,输出范围用tanh压到[-1, 1],再乘最大角速度限制。奖励函数是成败关键,常见设计是:
def compute_reward(self, state, action, next_state): # 末端到目标点的距离惩罚 dist = np.linalg.norm(self.end_effector_pos(next_state) - self.target_pos) reward = -dist # 动作平滑惩罚,抑制关节抖动 reward -= 0.01 * np.sum(np.square(action)) # 到达目标给稀疏奖励 if dist < 0.01: reward += 10.0 # 碰撞或超限给大惩罚 if self.check_collision(next_state) or self.check_joint_limit(next_state): reward -= 50.0 done = True return reward, done这段奖励里,距离惩罚提供稠密梯度,动作平滑项抑制高频抖动,稀疏奖励加速收敛,碰撞惩罚保证安全。参数上,动作平滑系数 0.01 是经验值,太大机械臂会变懒不动,太小关节会抖。碰撞惩罚要足够大,否则策略会学会用撞墙来抄近路。注意奖励尺度要统一,别让某一项量级压过其他项,否则 Critic 学出来的 Q 值曲面会畸形。
2.3 PyTorch 网络定义与目标网络软更新
Actor 和 Critic 都用全连接网络就够,隐藏层 256 或 400 神经元,激活函数用 ReLU,输出层 Actor 用 tanh,Critic 线性输出。下面是一个可直接抄的 PyTorch 实现:
import torch import torch.nn as nn import torch.nn.functional as F class Actor(nn.Module): def __init__(self, state_dim, action_dim, max_action): super(Actor, self).__init__() self.l1 = nn.Linear(state_dim, 256) self.l2 = nn.Linear(256, 256) self.l3 = nn.Linear(256, action_dim) self.max_action = max_action def forward(self, state): x = F.relu(self.l1(state)) x = F.relu(self.l2(x)) # tanh 压到 [-1,1] 再乘最大动作幅值 return self.max_action * torch.tanh(self.l3(x)) class Critic(nn.Module): def __init__(self, state_dim, action_dim): super(Critic, self).__init__() self.l1 = nn.Linear(state_dim + action_dim, 256) self.l2 = nn.Linear(256, 256) self.l3 = nn.Linear(256, 1) def forward(self, state, action): x = torch.cat([state, action], dim=1) x = F.relu(self.l1(x)) x = F.relu(self.l2(x)) return self.l3(x)Actor 输出乘max_action是让网络输出直接对应物理量纲,省去外部缩放。Critic 把状态和动作拼接后输入,这是 DDPG 的标准做法。软更新代码如下:
def soft_update(target, source, tau): for target_param, param in zip(target.parameters(), source.parameters()): target_param.data.copy_(target_param.data * (1.0 - tau) + param.data * tau)tau取 0.005 是常见起点,太小目标网络跟不上,太大就失去稳定作用。注意目标网络参数更新要用torch.no_grad()包住,别把梯度带进去。
3. 从零搭一套 PyTorch DDPG 训练六轴机械臂的仿真环境
3.1 PyTorch 环境搭建与 CUDA 适配
环境搭建是第一步,也是新手最容易翻车的地方。我一般用 Anaconda 建独立环境,Python 3.9 或 3.10,PyTorch 选稳定版。Ubuntu 下装 GPU 版:
conda create -n ddpg_arm python=3.10 conda activate ddpg_arm # 根据 CUDA 版本选对应命令,这里以 CUDA 11.8 为例 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install gymnasium pybullet numpy matplotlib装完必须验证 CUDA 和 PyTorch 是否匹配:
import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果cuda.is_available()返回 False,先查显卡驱动版本,再查 CUDA 运行时版本,最后确认 PyTorch 装的是不是 GPU 版。常见坑是 pip 默认装了 CPU 版,或者 conda 和 pip 混装导致版本冲突。WSL 下装 PyTorch 环境搭建也能跑,但 PyBullet 的图形渲染要配 X Server,嫌麻烦就直接用无头模式。
3.2 PyBullet 六轴机械臂仿真环境封装
PyBullet 自带 KUKA iiwa、Franka Panda 等六轴模型,直接加载就行。下面封装一个 Gym 风格的环境:
import pybullet as p import pybullet_data import gymnasium as gym import numpy as np class ArmEnv(gym.Env): def __init__(self): self.physics_client = p.connect(p.DIRECT) # 无头模式 p.setAdditionalSearchPath(pybullet_data.getDataPath()) p.setGravity(0, 0, -9.81) self.robot = p.loadURDF("kuka_iiwa/model.urdf", useFixedBase=True) self.num_joints = 6 self.target_pos = np.array([0.5, 0.3, 0.4]) self.max_steps = 200 self.step_count = 0 def reset(self): p.resetSimulation() p.setGravity(0, 0, -9.81) self.robot = p.loadURDF("kuka_iiwa/model.urdf", useFixedBase=True) self.step_count = 0 # 随机初始化关节角 for j in range(self.num_joints): p.resetJointState(self.robot, j, np.random.uniform(-0.5, 0.5)) return self._get_state() def _get_state(self): joints = [p.getJointState(self.robot, j) for j in range(self.num_joints)] q = [j[0] for j in joints] dq = [j[1] for j in joints] return np.array(q + dq, dtype=np.float32) def step(self, action): # action 是六维角速度增量 for j in range(self.num_joints): p.setJointMotorControl2( self.robot, j, p.VELOCITY_CONTROL, targetVelocity=action[j], force=500 ) p.stepSimulation() self.step_count += 1 next_state = self._get_state() reward, done = self._compute_reward(next_state, action) truncated = self.step_count >= self.max_steps return next_state, reward, done, truncated, {}p.connect(p.DIRECT)是无头模式,训练时不开图形界面,速度快很多。调试阶段可以换成p.GUI看机械臂动起来。setJointMotorControl2用速度控制模式,force参数限制最大力矩,设太小机械臂带不动,设太大仿真会不稳定。状态取关节角和角速度共 12 维,和前面网络输入对齐。
3.3 经验回放池与训练主循环
经验回放池用 numpy 数组或 deque 实现,容量一般 100 万条。采样时随机抽 batch,batch size 128 或 256。训练主循环结构:
class ReplayBuffer: def __init__(self, capacity=1_000_000): self.buffer = [] self.capacity = capacity self.position = 0 def push(self, state, action, reward, next_state, done): if len(self.buffer) < self.capacity: self.buffer.append(None) self.buffer[self.position] = (state, action, reward, next_state, done) self.position = (self.position + 1) % self.capacity def sample(self, batch_size): batch = np.random.choice(len(self.buffer), batch_size, replace=False) state, action, reward, next_state, done = zip(*[self.buffer[i] for i in batch]) return (np.array(state), np.array(action), np.array(reward), np.array(next_state), np.array(done))主循环里,每个 episode 先 reset,然后循环 step:Actor 输出动作加噪声,环境执行,存回放池,采样 batch 更新网络。噪声用高斯噪声,标准差从 0.2 线性衰减到 0.02。Critic 的损失是 TD 误差均方:
target_action = target_actor(next_state) target_q = reward + gamma * (1 - done) * target_critic(next_state, target_action) current_q = critic(state, action) critic_loss = F.mse_loss(current_q, target_q.detach())Actor 的损失是最大化 Critic 对 Actor 输出的 Q 值:
actor_loss = -critic(state, actor(state)).mean()gamma取 0.99,学习率 Actor 用 1e-4,Critic 用 1e-3,这是 DDPG 原论文的推荐值。注意target_q要detach(),否则梯度会回传到目标网络。每步训练完做软更新,每 50 步或每个 episode 存一次模型。
4. 训练调参与实时仿真中的避坑记录
4.1 奖励不收敛、Q 值爆炸的排查
现象:训练几千 episode 后奖励曲线震荡不上升,Critic loss 越来越大。原因通常是奖励尺度没归一化,或者学习率太大导致 Q 值发散。解决:先把奖励裁剪到[-10, 10],Critic 学习率降到 1e-4,加梯度裁剪torch.nn.utils.clip_grad_norm_(critic.parameters(), 1.0)。如果还不行,检查目标网络软更新是否生效,tau设 0.001 试试。
4.2 机械臂抖动剧烈、动作不平滑
现象:仿真里机械臂高频抖动,关节角速度来回跳。原因是动作平滑惩罚系数太小,或者噪声标准差衰减太慢。解决:把动作平滑项系数从 0.01 提到 0.05,噪声标准差从 0.2 降到 0.1 并加快衰减。另外检查setJointMotorControl2的force是否过大,力太大会让仿真步进不稳定,适当降到 200 到 300。
4.3 仿真步进与实时性不匹配
现象:训练时仿真跑得飞快,但部署到真实机械臂上动作完全对不上。原因是仿真步进频率和真实控制频率不一致。PyBullet 默认 240Hz,真实机械臂控制周期可能是 10ms 即 100Hz。解决:在 step 里用p.setTimeStep(1/100)对齐,并且每个控制周期只执行一次动作,别在一个周期里多次stepSimulation。实时仿真方案里,控制频率必须和硬件对齐,否则学出来的策略迁移不过去。
4.4 经验回放池内存溢出
现象:训练几小时后程序被系统杀掉,或者报内存错误。原因是回放池存了太多高维状态,100 万条 12 维 float32 大约 48MB,但如果状态里加了图像就爆炸。解决:图像状态用 uint8 存,采样时再转 float32;或者把回放池容量降到 20 万条,优先保证训练速度。另外注意np.random.choice在大池子上采样很慢,换成random.sample或预生成索引。
4.5 目标网络更新频率与训练稳定性
现象:训练前期还行,中期突然崩掉,奖励断崖式下跌。原因是目标网络更新太快,或者 Critic 过拟合当前 batch。解决:tau从 0.005 降到 0.001,Critic 加 L2 正则或 dropout,batch size 从 128 提到 256。如果还崩,检查回放池里 done 标志是否正确,episode 结束时没置 done 会让 Q 值无限累积。
5. 进阶技巧:用 ONNX 导出与验证 DDPG 策略的实时性
训练完的 Actor 网络要部署到实时仿真或真实控制器上,PyTorch 模型直接跑有 Python 解释器开销,我一般导出成 ONNX 再用 C++ 或 TensorRT 推理。导出代码如下:
import torch.onnx actor.eval() dummy_input = torch.randn(1, 12) # 12 维状态 torch.onnx.export( actor, dummy_input, "ddpg_actor.onnx", input_names=["state"], output_names=["action"], dynamic_axes={"state": {0: "batch"}, "action": {0: "batch"}}, opset_version=11 )导出后必须验证 ONNX 和 PyTorch 输出一致:
import onnxruntime as ort import numpy as np sess = ort.InferenceSession("ddpg_actor.onnx") state = np.random.randn(1, 12).astype(np.float32) onnx_action = sess.run(None, {"state": state})[0] torch_action = actor(torch.FloatTensor(state)).detach().numpy() print(np.max(np.abs(onnx_action - torch_action))) # 应小于 1e-5如果误差大于 1e-5,检查opset_version和算子兼容性,tanh 和 Linear 一般没问题,自定义层才容易翻车。实时性验证用time.perf_counter()测单次推理耗时,ONNX Runtime 在 CPU 上通常 1ms 以内,GPU 上更快。控制周期 10ms 的话,推理加通信加执行器响应要留够余量,别卡在 9.9ms 这种边缘。
一个我踩过的坑:导出 ONNX 时忘了actor.eval(),BatchNorm 或 Dropout 层在训练模式下行为不一致,导致推理结果随机。DDPG 网络一般没这些层,但养成eval()习惯没坏处。另外 ONNX 的输入维度要和训练时完全一致,状态归一化参数也要一起导出或硬编码到预处理里,否则部署端忘了归一化,策略输出直接废掉。
最后说个验证策略好坏的土办法:固定几个目标点,让机械臂从不同初始位形出发,统计到达成功率和平均步数。成功率低于 80% 就别急着上真机,回仿真里继续调奖励和超参。强化学习这玩意儿玄学成分不少,但每一步的输入输出对齐了,结果就可复现。希望帮到你。
本文还有配套的精品资源,点击获取