DDPG算法在机械臂轨迹规划中的应用:从PyTorch仿真到真机部署

发布时间:2026/10/12 1:25:08
DDPG算法在机械臂轨迹规划中的应用:从PyTorch仿真到真机部署
简介文档《机器人运动控制新范式DDPG算法在工业机械臂轨迹规划中的PyTorch实现》面向机械臂轨迹规划研究者和深度强化学习入门者围绕工业场景中的轨迹规划难题讲解如何用DDPG算法替代传统几何、运动学与动力学方法提升复杂环境下的适应能力。全文档共29页以“问题建模—算法讲解—环境搭建—代码实现—实验分析”为主线先从工业机械臂结构、运动学基础与传统方法挑战讲起再依次覆盖DDPG核心组件、确定性策略梯度数学原理、PyTorch安装与验证以及状态空间定义、动作空间设计、奖励函数构建、网络结构实现、经验回放与目标网络更新、训练循环、评估指标和实验对比最后给出结论与展望。压缩包内含1个PDF文件大小约2.07MB目录共8个章节支持大纲定位与章节跳转可快速查阅。文中包含大量模型设计思路、代码实现细节与调试优化建议适合有Python基础、希望系统掌握DDPG在机械臂控制中应用的研究者参考也可作为相关课程的补充材料。目前已有86人学习。1. DDPG算法在工业机械臂轨迹规划中的应用从仿真训练到落地思路工业机械臂的轨迹规划在很多产线场景里仍然靠人工示教或离线插补。一旦工装位置漂移、目标工件变更就得重新改路径点连换料停线都要半天。DDPGDeep Deterministic Policy Gradient是深度强化学习里专门为连续控制设计的一种Actor-Critic算法正好用在机械臂这类“动作本质上是连续关节速度/力矩”的问题上。在PyTorch里实现一套DDPG先在仿真环境里训练策略网络再迁移到真机是目前比较主流且可控的新范式。这篇文章面向想从传统规划转向强化学习的工程师也面向被“DDPG代码能跑但训练不出效果”卡住的新手给出从环境搭建、网络设计到参数排错的完整落地路径。2. 连续动作空间与机械臂轨迹规划DDPG原理和选型依据在抓取场景里机械臂末端要从A点走到B点还要避开一个突然出现的障碍物。传统方法一般先做路径搜索RRT*再做轨迹插补梯形或S型速度规划最后用PID跟踪。但每一步都需要重新计算运动学逆解和碰撞检测遇到动态障碍就得整条重规划。如果改为强化学习可以把“感知状态-输出动作”变成单步推理策略网络在训练期见过大量随机起止点和障碍布局推理期就能直接给出接近实时的关节速度指令。2.1 机械臂轨迹规划为什么要引入强化学习传统规划器的三个局限传统规划器的第一个局限是依赖精确运动学模型模型参数一变末端精度就掉。第二个局限是重规划频率低普通工控机上做一次RRT*碰撞检测可能要几十毫秒在高速搬运场景很难平滑衔接。第三个局限是难以处理“避开障碍后还要最小化能耗/时间”这类多目标优化参数权重全靠人工调。而强化学习把轨迹规划变成马尔可夫决策过程状态里放进关节角度、角速度、末端位姿误差动作就是关节速度增量奖励里同时写距离项和能耗惩罚让策略自己学会折中。对比项传统规划器RRT*插补DDPG强化学习模型依赖精确运动学与动力学模型仅需状态和奖励模型学到数据中动态环境应对需要重规划延迟高单步推理策略网络直接输出动作多目标优化需要人工调权重奖励函数可以同时编码距离、能耗、平滑性部署形态工控机在线计算复杂几何训练后是轻量全连接网络便于ONNX部署DDPG之所以适合机械臂是因为它的动作输出是连续的确定性策略。理论上DQN只能处理离散动作比如向左、向右、抓、放但机械臂每个关节的速度是一个实数6个关节就是6维连续向量。如果用DQN把连续空间离散化维度灾难会让网络根本学不动。DDPG的Actor网络直接输出连续动作Critic网络评估这个动作在当前状态下的长期回报两个网络协同正好覆盖机械臂控制的需求。2.2 Actor-Critic结构与确定性策略两个网络如何分工DDPG里Actor网络学习的是一个确定性策略函数 μ(s)输入当前状态s输出动作a。Critic网络学习的是动作价值函数Q(s,a)用来评判“在这个状态下执行这个动作到底有多好”。训练时Actor看到Critic的评价值朝着能让Q值变大的方向调整自己的输出Critic则不断拟合真实的折扣回报。这种结构很像老师给学生打分学生根据分数调整答案老师根据结果反馈更新评分标准。和PPO这类随机策略相比确定性策略的优点是采样效率高同样一批经验可以直接用于梯度更新不用做重要性采样。缺点是探索能力弱因为网络一旦收敛到某个动作就很难尝试别的动作。所以在DDPG训练里必须额外加探索噪声常见做法是使用Ornstein-UhlenbeckOU噪声或高斯噪声。对于机械臂高斯噪声更简单sigma从0.1开始衰减到0.02即可OU噪声的强相关性适合产生“有惯性”的连续探索但调参数更容易翻车。我一般不建议在机械臂任务上一上来就用OU噪声因为OU噪声有两个额外参数theta和mu需要调而它带来的平滑探索优势在50毫秒控制周期下并不明显。先用高斯噪声把训练流程跑通再回头试OU也不迟。如果你发现训练后期动作几乎不动可以把噪声标准差衰减到0.01以下给网络一段“纯利用”的收敛期很多指标会在这段时间突然变好。2.3 经验回放和软更新让训练稳定下来的两个关键机制DDPG最容易被吐槽的是训练不稳定反复起伏不收敛。稳定性的关键其实不在网络结构而在两个机制经验回放Experience Replay和软更新Soft Update。经验回放把每一步转移(s, a, r, s)存进一个buffer每次随机采样一批来更新打破样本之间的时序相关性。真实产线数据是一段时间序列前后高度相关如果直接按顺序训练网络会被最近的样本带偏。我一般把buffer大小设为100000到200000条机械臂步长50毫秒存满一次相当于跑了1到2小时仿真够用。软更新则是让目标网络缓慢跟随在线网络而不是直接复制参数。通常设为τ0.005每步更新时目标网络参数用“当前网络参数的τ倍 旧目标参数的(1-τ)倍”混合。这能减少过估计风险代价是收敛变慢。如果发现训练过程loss震荡厉害可以把τ降到0.001如果收敛太慢可以升到0.01但不要超过这个范围否则目标网络会被在线网络带着剧烈波动。这里有一个常见误用很多人直接把DDPG和TD3当成一回事。TD3是DDPG的改进版针对Critic过高估计问题做了三处修正双Critic网络、目标策略平滑、延迟更新。我建议先把DDPG跑通理解软更新和经验回放的交互再改TD3否则一上来就上TD3出了bug根本分不清是网络问题还是参数问题。从“能跑”到“收敛”DDPG本身的机制已经给了足够多的旋钮优先调它们比换算法划算。3. 搭建PyTorch与PyBullet机械臂环境安装、URDF加载和奖励定义在写DDPG代码之前先要把运行环境准备好。我自己常用组合是Anaconda PyTorch PyBullet。PyBullet的好处是体量小、Python直接调、自带URDF解析不需要像Gazebo那样启一堆服务对训练机资源不高的场景更友好。PyTorch则负责网络训练和模型导出两者之间用numpy数组交换状态和动作边界清晰。3.1 安装PyTorchCPU版和GPU版的适配PyTorch的安装版本取决于你的训练机有没有NVIDIA GPU。如果没有GPU先装CPU版本把代码跑通训练速度慢一点但不会报CUDA错误。如果装了GPU需要先确认CUDA版本再选对应的PyTorch。常见做法是用Anaconda建一个干净的Python 3.8或3.9虚拟环境避免之后和系统Python抢包。# 创建并激活虚拟环境 conda create -n ddpg python3.8 -y conda activate ddpg # CPU版安装 pip install torch torchvision torchaudio # GPU版安装根据你的CUDA版本从PyTorch官方whl索引安装 # CUDA 11.8版本示例其他版本对应修改cuXXX pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118安装完一定要做一次验证很多后续报错都来自这一步选错了版本。python -c import torch; print(torch.__version__, torch.cuda.is_available())输出里torch.__version__至少有2.x版本号torch.cuda.is_available()如果是TrueGPU版才正常如果是False即使你装了GPU版训练也会掉回CPU动作反馈慢得让你误以为算法发散。这里最常见的坑是用conda install pytorch默认装的是CPU版必须用pip装官方whl才能拿到CUDA支持。如果你看到网上各种“安装pytorch教程gpu”核心其实只在确认CUDA版本和选择对应index-url这两步。3.2 用PyBullet建立机械臂仿真环境加载URDF和驱动关节PyBullet本身不带机械臂模型通常需要加载一个URDF文件。URDF是机器人模型的统一描述格式包含连杆、关节、惯量和碰撞体积。如果你有自己的机械臂模型可以直接把URDF路径换掉没有的话可以用KUKA IIWA或UR5的开源URDF做原型验证。我一般会把模型放在项目urdf/目录下统一管理。import pybullet as p import pybullet_data import time # 连接GUI模式方便调试时观察机械臂动作训练时请改成p.DIRECT physics_client p.connect(p.GUI) p.setAdditionalSearchPath(pybullet_data.getDataPath()) # 加载机械臂URDFuseFixedBaseTrue表示底座固定适合固定工位机械臂 robot_id p.loadURDF(kuka_iiwa/model.urdf, [0, 0, 0], useFixedBaseTrue) # 获取所有关节信息打印关节索引和关节类型 for joint_index in range(p.getNumJoints(robot_id)): info p.getJointInfo(robot_id, joint_index) print(fjoint {joint_index}: {info[1].decode()} type{info[2]}) # 控制所有关节的方式位置控制设置目标位置为当前角度 num_joints p.getNumJoints(robot_id) for joint in range(num_joints): p.setJointMotorControl2( bodyUniqueIdrobot_id, jointIndexjoint, controlModep.POSITION_CONTROL, targetPosition0.0, maxForce100.0, ) # 运行仿真循环 for _ in range(1000): p.stepSimulation() time.sleep(1./240.)这段代码里p.connect(p.GUI)会弹出一个可视化窗口适合看运动效果但训练DDPG时建议改成p.DIRECT不渲染图形速度快很多。setJointMotorControl2中targetPosition是目标关节角度maxForce限制关节力矩过大容易让机械臂动作太猛过小则难以到位一般从额定力矩的40%开始调。p.stepSimulation()的默认仿真步长是1/240秒对DDPG训练来说控制周期可以放宽到50毫秒也就是每个训练步执行12次仿真步后采一次状态。3.3 定义状态空间、动作空间与奖励函数直接影响收敛速度的部分写DDPG代码前对状态、动作、奖励的定义越明确后面训练越省事。对6自由度机械臂我一般把状态设计成6个关节角度、6个关节角速度、末端当前位姿(x, y, z)与目标位姿的差、以及目标位姿本身共约24维。动作是6个关节角度增量范围限制在[-0.01, 0.01]弧度/步避免机械臂一步转太多导致碰撞。奖励函数是关键。如果只给“到达目标得1分”大部分训练时间拿不到任何奖励网络学不动。我通常用“稀疏奖励密集惩罚”组合让网络每一步都有反馈。def compute_reward(state, action, target_pose, prev_distance): # state包含关节角度/角速度这里提取末端位置和姿态 end_pos get_end_effector_position(state) # 末端到目标的距离 distance np.linalg.norm(end_pos - target_pose) # 距离惩罚项权重weighted_distance决定引导强度 weighted_distance -distance * 2.0 # 动作变化惩罚防止关节抖动 action_penalty -0.1 * np.sum(np.square(action)) # 到达奖励距离小于1厘米时给一个大奖励 reach_reward 50.0 if distance 0.01 else 0.0 # 剩余项距离减小则给正向激励距离增大则惩罚 progress_reward 5.0 * (prev_distance - distance) return weighted_distance action_penalty reach_reward progress_reward这个函数里weighted_distance的权重控制“引导到目标”的强度太大会让机械臂贪图直线接近而忽略避障太小则训练慢。action_penalty防止动作抖动如果训练中关节速度剧烈波动把系数从0.1提高到0.5。progress_reward利用前一步距离差引导机械臂逐步逼近可以显著缓解稀疏奖励问题。实际训练时还需要把状态归一化到[-1, 1]之间关节角度除以π速度除以最大关节速度这样网络不会把数值大的维度当成了更大权重。4. 用PyTorch实现DDPGActor-Critic网络、经验回放与训练循环网络结构设计、经验回放和训练循环是DDPG实现的三件套。我先把代码拆开写清楚再展示完整的训练迭代方便你直接抄作业。DDPG代码的PyTorch实现比算法描述看起来简单但有几处细节很容易写错下面直接给出可用版本。4.1 Actor-Critic网络结构三层全连接加ReLU在PyTorch里定义DDPG网络不需要复杂的封装继承nn.Module写forward即可。Actor网络输入状态维度输出动作维度中间用两层256维全连接层加ReLU输出层用Tanh把动作压缩到[-1,1]再映射到实际关节速度范围。Critic网络则同时接收状态和动作输出一个Q值。import torch import torch.nn as nn import torch.nn.functional as F class Actor(nn.Module): def __init__(self, state_dim, action_dim, hidden_dim256): super(Actor, self).__init__() self.fc1 nn.Linear(state_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.fc3 nn.Linear(hidden_dim, action_dim) def forward(self, state): x F.relu(self.fc1(state)) x F.relu(self.fc2(x)) # tanh把动作限制在[-1,1]之后再到环境里做scale return torch.tanh(self.fc3(x)) class Critic(nn.Module): def __init__(self, state_dim, action_dim, hidden_dim256): super(Critic, self).__init__() self.fc1 nn.Linear(state_dim action_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.fc3 nn.Linear(hidden_dim, 1) def forward(self, state, action): x torch.cat([state, action], dim-1) x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) return self.fc3(x)这里Critic把状态和动作在输入层直接拼接是DDPG最常见的做法。也可以把动作放到第二层再融合但对于机械臂6维动作这种规模输入层拼接已经足够。隐藏层维度256在大多数机械臂任务里是安全选择如果你的状态包含视觉特征或激光雷达点云可以升到512。关键是要把状态和动作归一化到大致相同的数值范围否则Critic会很快过拟合到数值大的特征上。4.2 经验回放和软更新两个必须手写的组件单步训练数据强烈依赖前一时刻状态如果直接按时间顺序训练策略会被最近的样本带偏。经验回放的实现非常简单用list或双端队列即可。from collections import deque import random import numpy as np class ReplayBuffer: def __init__(self, capacity100000): self.buffer deque(maxlencapacity) def push(self, state, action, reward, next_state, done): # state和next_state统一转成numpy数组方便后续转tensor self.buffer.append(( np.array(state, dtypenp.float32), np.array(action, dtypenp.float32), float(reward), np.array(next_state, dtypenp.float32), float(done), )) def sample(self, batch_size): batch random.sample(self.buffer, batch_size) states, actions, rewards, next_states, dones map(np.asarray, zip(*batch)) # 按批维度转成PyTorch tensor return ( torch.FloatTensor(states), torch.FloatTensor(actions), torch.FloatTensor(rewards).unsqueeze(1), torch.FloatTensor(next_states), torch.FloatTensor(dones).unsqueeze(1), ) def __len__(self): return len(self.buffer)deque(maxlencapacity)存满后会自动丢掉最旧的数据很适合持续采集的训练环境。采样时随机抽取batch_size条通常取256。容量和批量大小直接决定训练稳定性和速度容量太小样本重复率高学到的策略偏向近期数据容量太大旧样本与当前策略差异太大梯度方向混乱机械臂任务里10万到20万条足够。软更新作为DDPG的核心机制代码如下def soft_update(target_net, online_net, tau0.005): for target_param, online_param in zip(target_net.parameters(), online_net.parameters()): target_param.data.copy_(tau * online_param.data (1.0 - tau) * target_param.data)软更新的时机是每一步训练后都做。tau控制目标网络跟随速度过大会让目标网络快速接近在线网络失去稳定作用过小则目标网络几乎不动Critic更新用的目标Q值长时间失真。在机械臂轨迹规划任务里我习惯先固定tau0.005如果训练后期出现loss震荡再调小到0.001不要一开始就过度调参。4.3 训练循环从探索噪声到策略更新的一整段流程以下是一个完整的训练迭代代码。它假设你已经有了env.step(action)接口和一个初始状态。import torch.optim as optim state_dim 24 action_dim 6 actor Actor(state_dim, action_dim) critic Critic(state_dim, action_dim) actor_target Actor(state_dim, action_dim) critic_target Critic(state_dim, action_dim) soft_update(actor_target, actor, tau1.0) # 初始化目标网络 soft_update(critic_target, critic, tau1.0) actor_optimizer optim.Adam(actor.parameters(), lr1e-4) critic_optimizer optim.Adam(critic.parameters(), lr1e-3) buffer ReplayBuffer(100000) sigma 0.1 # 探索噪声初始标准差 sigma_end 0.02 add_noise_decay 0.998 # 每个episode衰减一次 for episode in range(500): state env.reset() sigma max(sigma_end, sigma * add_noise_decay) episode_reward 0.0 done False while not done: # 1. 选择动作并加上探索噪声 state_tensor torch.FloatTensor(state).unsqueeze(0) action actor(state_tensor).detach().numpy()[0] # 高斯噪声乘上sigma使其随训练衰减 action np.clip(action np.random.normal(0, sigma, sizeaction_dim), -1.0, 1.0) # 2. 执行动作 next_state, reward, done env.step(action) buffer.push(state, action, reward, next_state, done) state next_state episode_reward reward # 3. 经验足够后开始更新网络 if len(buffer) batch_size: states, actions, rewards, next_states, dones buffer.sample(batch_size) # 计算目标Q值 with torch.no_grad(): target_actions actor_target(next_states) target_q critic_target(next_states, target_actions) target_q rewards 0.99 * (1 - dones) * target_q # 更新Critic current_q critic(states, actions) critic_loss nn.MSELoss()(current_q, target_q) critic_optimizer.zero_grad() critic_loss.backward() critic_optimizer.step() # 更新Actor最大化当前Q值 actor_loss -critic(states, actor(states)).mean() actor_optimizer.zero_grad() actor_loss.backward() actor_optimizer.step() # 软更新目标网络 soft_update(actor_target, actor, tau0.005) soft_update(critic_target, critic, tau0.005) print(fepisode {episode}: reward{episode_reward:.2f} sigma{sigma:.3f})这个循环中隐藏了三个容易犯的错。第一是初始化软更新时tau必须设为1.0让目标网络直接复制在线网络参数否则一开始目标网络就是错的。第二是dones必须参与Q值计算机械臂到达终点或越界都会终止当前episode如果不乘以(1-dones)目标Q值会无限累积。第三是Actor的更新频率DDPG原始论文里是每步都更新但实践中改成“每更新两次Critic再更新一次Actor”更稳定这就是TD3里延迟更新思想的雏形如果你发现Actor更新导致loss爆炸改成延迟更新即可。如果训练中遇到回报一直不涨先不要盲目加噪声。我的排查顺序是看Critic的loss是否在下降下降则问题在Actor的探索范围或奖励设计不下降则问题在经验回放或归一化。把训练日志里的episode reward、critic loss、actor loss同时打出来远比只盯reward容易诊断。5. DDPG训练避坑指南收敛慢、动作抖动和奖励稀疏的排查方向训练DDPG时会遇到各种“看起来正常但就是学不好”的奇怪现象。下面五条踩坑记录每一组都是现象、原因、解决按顺序排查能省下大量时间。这些坑覆盖了我自己从仿真到半实物测试的全过程比网络结构更值得花时间研究。5.1 训练loss变成NaN或者Critic loss下降但Actor不动先说现象训练前100个episode里Critic的loss偶尔跳到NaN之后整个训练直接崩溃或者是Actor输出一直不变只有Critic在动。原因通常是学习率太高导致参数更新过大把数值打出浮点上限状态维度里混入过大数值比如末端坐标的毫米值和关节角度的弧度值量级相差太大梯度被大数值维度主导。补充一个常见诱因奖励函数里如果出现除以距离距离为零时会直接NaN。解决方法是先做状态归一化和奖励裁剪。把每个状态特征除以它的最大绝对值奖励限制在[-10, 10]之间。学习率方面Actor用1e-4Critic可以用1e-3但不能更高。最后在环境step里对距离做max(distance, 1e-6)保护避免零除。如果你是全连接网络不要用ReLU之后直接输出先看Actor输出层是否有tanh。经历了这个坑之后我把“先查数值范围”写进了自己的调试清单90%的NaN都是数值尺度问题不是算法实现错。5.2 动作抖动机械臂在目标点附近高频振荡现象已经看到机械臂末端到达了目标附近但关节一直小幅高频振荡轨迹不平滑看起来像“帕金森”。原因是Critic对细微动作的Q值预测不够精确导致Actor在高频动作上“试探”同时训练后期探索噪声衰减不彻底残余噪声持续扰动策略。解决分两步。第一步把奖励函数里的action_penalty系数从0.1提到0.5让大动作受到更重惩罚。第二步对动作输出做一阶低通滤波也就是每一步把“原始策略动作和上一时刻实际动作”按系数混合filtered_action alpha * raw_action (1 - alpha) * prev_actionalpha取0.6到0.8可以显著平滑轨迹。如果你已经加了滤波还抖考虑直接换成TD3用目标策略平滑机制彻底消除过估计。不过在换算法前先确认控制周期和仿真步长是否匹配我见过太多高频振荡其实是控制周期设置得太快让策略来不及反馈。5.3 奖励稀疏训练几百个episode奖励一直没有任何提升现象episode奖励一直保持在一个小幅度负值没有任何上升趋势偶尔有正奖励但立刻又掉回去。原因是初始状态设置离目标太远动作空间限制太小导致机械臂需要几千步才能靠近目标而progress_reward权重太低引导信号完全被噪声淹没。我的解决办法是“课程学习”。先让目标点出现在机械臂当前末端前方10厘米内训练收敛后再逐步把目标距离拉远。同时把progress_reward的系数从5.0提高到10.0让“距离缩短”的正反馈更明显。如果还不行检查你给的动作范围[-0.01, 0.01]弧度/步在6关节上听起来很小实际每步只能动0.5度左右对于超过30厘米的初始误差需要大约600步才能接近训练效率极低把动作范围放宽到[-0.05, 0.05]弧度/步并降低action_penalty系数可以加快探索。5.4 训练稳定但轨迹偏离机械臂学会了“绕路”而不是“直走”现象训练loss正常回报也在增长但把训练好的策略拿去回放发现末端轨迹绕了一个大弯不优雅甚至可能与障碍物擦边。原因是奖励函数里“到达奖励”权重过高机械臂学会了先直线冲被障碍物挡住后滑向前方某一安全点再绕过去而这在Q值函数看来属于“局部最优”。解决时先把weighted_distance的权重从2.0降到1.0让人工势场式的引导不要太强势同时增加碰撞惩罚封装一个collision_penalty当末端与障碍物距离小于5厘米时每一步扣除50分。另外可以在状态里额外加入“最近障碍物距离”让策略能显式感知避障需求。比较微妙的是有时候奖励改完之后训练变慢别急着回滚先跑50个episode再看趋势因为策略需要重新适应新的奖励曲面。5.5 CUDA和PyTorch版本不匹配训练速度偶尔极慢或者直接报错现象代码完全一致在一台机器上GPU训练飞快换一台却提示CUDA error: no kernel image is available for execution on the device或者GPU利用率只有0%。原因是PyTorch的CUDA编译只针对特定SM架构以及安装PyTorch时选择了与显卡驱动不匹配的cu版本。解决方式先按本机显卡的算力选择对应的PyTorch轮子。常见的做法是到NVIDIA官网查算力然后选cu118或cu121对应的安装包。如果已经装错的直接pip uninstall torch torchvision torchaudio后重装。另外在代码里加上torch.set_default_tensor_type(torch.cuda.FloatTensor if torch.cuda.is_available() else torch.FloatTensor)可以避免训练过程中CPU和GPU张量混用导致的隐式设备转换。实践里我发现很多人为了图方便用conda install pytorch默认装了CPU版导致训练慢了10倍还以为是算法问题这种情况在DDPG代码调参时尤其容易误诊先验证torch.cuda.is_available()永远是第一优先级。6. 从PyTorch到真机部署Actor模型导出与轨迹验证的三个实战技巧训练收敛只是第一步真正的考验是把模型搬到真机控制器上。PyTorch环境通常不适合直接跑在工控机上所以我会把训练好的Actor网络先转成ONNX再接进机器人控制器或边缘计算单元。这个导出流程短但能避开许多奇怪的环境依赖。import torch import onnx actor.eval() dummy_input torch.randn(1, state_dim) torch.onnx.export( actor, dummy_input, actor.onnx, input_names[state], output_names[action], dynamic_axes{state: {0: batch_size}, action: {0: batch_size}}, ) onnx.checker.check_model(onnx.load(actor.onnx))三个实战技巧如下。第一个技巧部署前先做轨迹回放对比。用PyBullet加载训练好的Actor随机生成10组起点和目标把每一步的动作记录下来画出关节速度曲线。重点看两条一是在起点和终点位置是否有速度突变二是相邻两步动作差值是否超过预设阈值。如果动作差值超过20%说明策略本身不稳定需要回到训练阶段加动作平滑。第二个技巧验证模拟轨迹和真机轨迹的一致性。先用仿真记录末端位置随时间的变化再到真机上用激光跟踪仪或编码器记录同样动作计算末端位置偏差。偏差来源往往不是策略不对而是控制周期不匹配。仿真里控制周期20Hz真机却用250Hz插补关节速度指令会被频繁重采样产生额外抖动。正确做法是把控制周期设成与仿真一致再跑一次对比。第三个技巧给真机部署加一个动作安全限幅层。即使训练时做了动作范围限制推理时的状态分布仍然可能超出训练分布导致Actor输出异常大。我一般会在Actor输出后串联一个limiter把动作限制在原训练范围的1.2倍并对超限动作做一个温和的衰减。这样就算新状态没有见过机械臂也不会瞬间冲爆。这个安全层最好用纯numpy实现便于在RTOS和PLC里复写。我第一次把DDPG模型直接搬到真机时就是忽略了控制周期不一致的问题结果机械臂在到达点附近抖了许久才稳定差点触发急停。后来每次部署前先做轨迹回放对比才把这个问题彻底压下来。希望帮到你。本文还有配套的精品资源点击获取