资讯动态

LeRobot扩散模型实战:SO-ARM100机械臂动作生成全解析

发布时间:2026/9/17 15:52:42 来源:尧图企业网站定制
这一篇接着上一篇的进度记录。上一次已经把 LeRobot 环境、SO-ARM100 的固件和采集流程都跑通了这次专门啃 diffusion module——也就是 LeRobot 里用扩散模型做动作生成的那套东西。如果你已经跑过 ACT 或者想试试更“年轻”的策略这篇笔记应该能帮你把扩散模型在真实机械臂上的完整链路理清楚从概率生成原理到 LeRobot 代码里那些关键类怎么串起来再到真机训练部署时最容易踩的坑。刚接触 SO-ARM100 的朋友可能会问一个这么便宜的 6 轴机械臂有必要上扩散模型吗我的回答是如果有条件非常值得。SO-ARM100 本身的成本低、结构简单很适合做算法验证平台而 Diffusion Policy 恰恰是目前把“多模态动作生成”和“长时程轨迹预测”结合得比较好的方案之一。这篇笔记不打算整篇复述论文而是站在“我要在真机上把它跑起来”的角度把模块拆开给你看。1. SO-ARM100 与 Diffusion Policy 的匹配逻辑1.1 这个机械臂平台是怎么一回事SO-ARM100 是 LeRobot 社区里非常出名的一个低成本机械臂项目整机用到的是 SO-100 系列的 6 个串行总线舵机加上 3D 打印结构件成本可以压到很低。它的自由度是 6 个其中第 6 个自由度是夹爪。左右两个广角 USB 摄像头负责给视觉观察夹具的开合由舵机带动整体定位就是“桌面级、可复现、适合算法研究”。这个硬件平台的优点是便宜、开源、社区资料多缺点是精度和重复性不如工业机械臂舵机有回差和延迟码盘反馈也不是绝对位置。这意味着你在算法上稍微偷懒比如直接对动作序列做平均真机上就会出现抖动、轨迹偏离甚至夹爪没对准的情况。所以 SO-ARM100 这种低成本平台对策略模型的鲁棒性要求反而更高。1.2 为什么第二篇就专门讲 DiffusionLeRobot 目前支持的策略里ACT 是 Transformer VAE 的思路Diffusion Policy 是基于扩散模型的思路还有一个简单的 MLP baseline。我自己把三个都试过之后体感上 Diffusion 对“长序列、多模态轨迹”的表达能力最强。ACT 的优势是训练稳定、部署简单但它在隐变量空间里做的其实是单峰建模遇到一个任务有多个合理解法时容易把不同轨迹平均成一条不可执行的折中动作。扩散模型就不一样。它把动作生成建模成一个从噪声逐步去噪的过程每个合理轨迹都是概率空间里的一个采样结果天然适合处理“同样一个任务人类演示里存在多种不同走法”的情况。对 SO-ARM100 这种经常需要抓取、推动不同位置物体的场景来说这个特性非常实用。所以这一篇笔记会比较深入地把 diffusion module 的模型结构、训练逻辑、推理时序讲清楚也会把我自己跑真机时的参数和配置贴出来。2. Diffusion 动作生成的核心思路2.1 把动作序列当成“图像”来去噪扩散模型最初火起来是在图像生成里原理可以这么理解你先有一张清晰的照片然后不断往里面加噪声直到变成一帧雪花点再去训练一个网络学会把噪声一点点去掉还原出照片。生成的时候给网络一帧随机噪声它就能迭代出近似真实的图像。在机器人动作生成里被“加噪”和“去噪”的对象不是图像而是一段动作序列。比如机械臂未来 15 步的 6 个关节角度形状是[15, 6]。训练时我们先从数据集里采样一段真实动作再随机选择一个扩散时间步t给它叠加对应强度的高斯噪声然后让网络去预测“我加的噪声是什么”。网络预测得越准训练 Loss 就越低。推理时的过程正好反过来先随机生成一段[15, 6]的纯高斯噪声然后从t1000开始逐步去噪到t0每一步都让网络预测并扣除一部分噪声。最终得到的这段“去完噪”的序列就是可以直接发给机械臂执行的动作轨迹。2.2 观察条件是怎么“加”进去的机械臂不能凭空生成动作它必须“看着”当前状态来决策。所以在扩散模型里我们需要把图像和关节角度作为条件让网络在去噪时知道当前世界长什么样。LeRobot 在实现里用了一种叫 FiLMFeature-wise Linear Modulation的方式做条件注入。简单来说观察编码器把当前图像和状态编码成一个条件特征之后这个特征不会被拼接进动作序列里而是被映射成一组缩放系数scale和平移系数shift。UNet1D 在每一层处理动作特征时会先做feature * scale shift用条件来调制动作表征。这个思路就像调音台上的推子你不需要去重写乐谱只需要调节每个频段的音量和偏移就能让整首歌变成想要的风格。LeRobot 里对应的网络叫ConditionalUnet1D。它接收的是动作序列中间通过 1D 卷积做下采样和上采样在关键的层用条件特征做 FiLM 调制。因为动作维度不高通常只有 6 个自由度所以这个网络整体体量不大消费级显卡就能训练。2.3 为什么 Diffusion 比直接回归更适合动作生成这里有个特别关键的点我想单独展开讲。如果我们用传统监督回归来预测动作比如直接输入图像、输出动作序列然后计算 MSE Loss那模型学到的是“所有演示轨迹的平均值”。假设训练数据里任务 A 有时从左上方推动物体有时从右上方推动模型大概率会预测出一条从正中间插过去的轨迹——既不左也不右看起来像“和稀泥”真机上一执行就失败。扩散模型则不存在这个问题。它的训练目标不是直接回归到某个确定答案而是学会把噪声反过来映射到数据分布上。每次推理都是一次独立采样可能这次生成左侧绕行轨迹下次生成右侧绕行轨迹但每次都是完整的、可执行的轨迹。这个特性对机械臂操作非常重要因为机器人动作本来就存在多模态解抓同一个杯子手可以从左边拿也可以从右边拿。3. LeRobot 中 Diffusion 模块的代码结构3.1 DiffusionPolicy 类的主流程LeRobot 里 diffusion 策略的入口在lerobot/policies/diffusion.py核心类就是DiffusionPolicy。它的主要职责是把训练数据和当前观察转成条件特征调用ConditionalUnet1D做噪声预测并在推理时调度去噪过程。简化后的训练思路大致是这样# 简化版展示扩散策略的训练核心 def forward(batch): # 从数据集取一段动作序列: [batch, action_len, action_dim] action_seq batch[action] # 随机采样扩散时间步 noise torch.randn_like(action_seq) timestep torch.randint(0, num_train_timesteps, (batch_size,)) # 正向加噪根据 timestep 把动作序列变成带噪版本 noisy_action noise_scheduler.add_noise(action_seq, noise, timestep) # 编码观察条件图像 机械臂状态 cond encode_observation(batch) # 让 UNet 预测噪声 noise_pred unet1d(noisy_action, timestep, cond) # 用预测噪声和真实噪声算 MSE loss F.mse_loss(noise_pred, noise) return loss推理时则是从纯噪声开始用训练好的网络反复去噪# 简化版展示推理时的去噪循环 def select_action(observation): # 随机初始化一段动作序列 action torch.randn(1, action_len, action_dim) # 从高噪声步数逐步走向低噪声步数 for t in reversed(range(num_inference_steps)): action ddim_scheduler.step( unet1d, t, action, conditionencode_observation(observation) ) # 最后的 action 就是未来若干步的关节角度 return action这里要提醒一下训练时用的是完整的 1000 步扩散加噪过程但推理时通常不会跑满 1000 次网络推理而会用 DDIM 采样器加速默认配置一般是 50 步。50 次网络推理对 GPU 来说压力不大但如果你想部署到非常边缘的设备上可以把步数再压缩只是生成质量会有所下降。3.2 从配置项看懂训练推理LeRobot 的 diffusion 配置可以在lerobot/configs/policies/diffusion/config.yaml里看到。下面这些参数是理解整个模块的钥匙参数作用我的常用值num_train_timesteps训练时扩散总步数1000num_inference_steps推理时实际去噪步数50num_conditioning_steps一次预测未来多少步动作15unet_dimUNet 基础通道数256down_dims下采样各层通道数[512, 1024, 2048]delta_timestamps.action动作序列对应的时间戳范围[0.0, 1.4]num_conditioning_steps决定了模型一次“看到”多远的未来。SO-ARM100 默认的控制频率大概是 10Hz因此如果预测 15 步动作相当于模型生成未来 1.5 秒的运动。步数太少容易短视步数太长则模型学习压力大而且一旦预测偏差末端执行器可能会偏离正确位置很久等重新收敛时已经来不及了。delta_timestamps是一个比较细的概念。LeRobot 训练时会从完整的 episode 中构造“当前观测 未来动作”的配对delta_timestamps.action里定义了需要哪些时间点的动作。默认就是 0 到 1.4 秒之间按 0.1 秒间隔取 15 个点和num_conditioning_steps对齐。3.3 数据入口HDF5 和 MCAP 里发生了什么LeRobot 的数据集默认以 HDF5 格式存储。采集完真机数据后目录结构大概是这样的dataset/ ├── meta/ │ ├── info.json │ └── statistics.json └── data/ └── chunk-00001.hdf5 ├── observation.image/episode_000001 ├── observation.state/episode_000001 └── action/episode_000001训练时LeRobot 会先读取statistics.json里每个维度的均值和标准差把动作和状态都归一化到接近标准正态分布。这一步非常关键我在后面踩坑部分会再展开。MCAP 格式则是另一种数据记录格式主要用在采集原始日志、可视化和回放场景里方便你用 Foxglove 之类的工具观察采集质量。如果数据量大MCAP 在流式写入上比 HDF5 友好一些但最终进训练流程前一般还是会转换成 HDF5或者直接兼容读取。4. 真实硬件上的训练部署全流程4.1 采集数据时要注意的几个细节我自己录制 SO-ARM100 数据时最开始犯过一个错误摄像头位置和光源没固定导致同一任务在训练集里“看起来”差异特别大。扩散模型对观察很敏感如果你的图像条件里混入了大量无关变化网络会把精力浪费在拟合这些噪声上。建议采集时做到三件事第一机械臂底座用夹具固定在桌面上别让它有轻微位移第二两个摄像头用固定支架锁死别每次开机都重新掰角度第三光源尽量用固定的室内灯避开窗外自然光变化。采集的演示尽量覆盖多种走法比如推杯子时既有从左绕的也有从右绕的这样多模态训练才有意义。采集命令大致是python lerobot/scripts/control_robot.py \ --robot.typeso100 \ --control.typeteleoperate \ --control.armsleft_main \ --control.fps10 \ --dataset.repo_id你的用户名/so100-push-toy \ --dataset.num_episodes50具体参数和你的源码版本有关但流程就是开遥控模式手动带着机械臂做任务让系统把图像、状态和动作一起记录下来。4.2 训练命令和关键参数数据采集完成后先把数据集上传到 Hugging Face Hub或者直接在本地建一个 dataset 目录然后启动训练。训练命令长这样python lerobot/scripts/train.py \ --policy.pathlerobot/configs/policies/diffusion/config.yaml \ --dataset.repo_id你的用户名/so100-push-toy \ --env.typereal \ --output_diroutputs/train/so100_diffusion如果你想快速验证可以把num_epochs调小一点。我一般先跑 10 个 epoch 看 Loss 趋势Loss 能顺利降到比较低的水平后再继续跑满 50 个 epoch。训练过程中 LeRobot 会定期把 checkpoint 存下来并默认在训练结束后把最终模型推到 Hugging Face Hub。如果你不想公开模型可以关闭 push 相关参数本地保存即可。4.3 部署到真机验证训练完成后在真机上加载模型推理的命令也不复杂python lerobot/scripts/control_robot.py \ --robot.typeso100 \ --control.typedeploy \ --control.armsleft_main \ --policy.path你的模型目录 \ --policy.typediffusion部署时我强烈建议先不启动机械臂执行只观察模型输出的动作序列曲线看看是不是平滑、有没有突变。LeRobot 里可以直接在部署模式里可视化待执行动作这一步能帮你省下不少反复重启机械臂的时间。如果模型生成的轨迹看着没问题再真正闭环让机械臂去执行。每次执行时策略会根据当前图像和关节状态重新推理然后只执行未来动作序列的第一个动作下一帧再重新观测类似于滚动优化。这样可以避免开环误差累积也是扩散策略能在低成本机械臂上跑稳的重要原因。5. 仿真环境下快速调技能5.1 为什么先碰一遍仿真如果你不是第一次搞机器人算法可能已经体会过真机调试的痛机械臂稍微没对准重新采集一轮数据就要半天。LeRobot 自带了一些仿真环境比如aloha_sim、push_t这类 Gym 环境它们同样能接 diffusion 策略。先在仿真里把数据量、训练超参、去噪步数这些关键变量摸清楚再搬去真机效率会高很多。仿真环境里我通常关注两件事一是看策略在理想状态下的成功率二是观察动作曲线是否平滑。很多时候真机上卡住的问题在仿真里就已经有苗头了比如高频抖动、末端往返摇摆这些都是去噪不彻底或条件编码不稳的表现。5.2 从仿真迁移到真机的几个关键点仿真和真机之间的差距主要体现在观测空间和动作范围上。仿真图像干净、光照稳定、背景固定而真机图像可能有噪声、反光和遮挡。迁移时不要把仿真里 100% 成功率的那套超参原封不动搬到真机一般需要把输入图像分辨率、归一化统计量、动作执行频率这些对齐到真机设置。另一个容易忽略的是动作范围。仿真环境里动作空间通常是归一化后的而真机舵机有自己的角度上下限而且不同电机存在死区。LeRobot 会在数据集统计阶段处理这个问题但你在真机部署前仍然要检查模型输出的动作是否超出机械臂的安全限位必要时在控制层做限幅否则一个异常动作就可能撞坏 3D 打印结构件。6. 踩坑实录Diffusion 在 SO-ARM100 上的典型问题6.1 Loss 曲线稳定下降模型却不干活这是我第一次训练时遇到的情况Loss 掉得挺漂亮但是部署到真机上机械臂完全不动或者乱动。后来排查发现问题出在数据集里的动作序列本身有大量“原地不动”的帧——人在操作时机械臂已经到位了手还在按遥控器于是录进了很多动作全零的片段。扩散模型学会了“按数据分布生成动作”如果你的数据里 70% 都是零动作它当然更愿意输出零动作。解决办法是采集时尽量精确机械臂到位后马上结束 episode另外训练前我会看一眼每个 episode 的动作均值如果某条轨迹明显异常直接删掉。6.2 真机动作高频抖动动作抖动最常见的原因是推理时采样噪声过大也就是去噪步数不够。默认 50 步在绝大多数场景够用但如果你发现动作曲线像锯齿一样可以考虑调到 100 步看看是否缓解。还有一个容易被忽略的点执行动作时一定要只取当前这一步不要贪心把整段预测都执行掉否则观测和动作会产生错位控制循环就乱了。另外SO-ARM100 的舵机控制本身有回差即便模型输出很平滑到舵机层面也可能因为负载和齿隙产生轻微抖动。这时可以适当在控制层加一点低通滤波但注意滤波太强会让动作显得迟钝。6.3 Ubuntu 环境与依赖相关的坑很多人在 Ubuntu 上装 LeRobot 时卡住其实大多数问题出在 Python 环境、USB 权限和摄像头驱动上。LeRobot 官方推荐用uv管理依赖这条路径比较省心。真机控制时USB 转串口设备可能需要授权否则控制脚本会提示找不到舵机总线。你可以在 udev 规则里给对应设备加权限或者临时用sudo跑但我不建议长期用 sudo 跑控制程序。摄像头方面先确认系统能正常读取两个 USB 摄像头的视频流再启动 LeRobot。有些采集问题不是算法问题而是摄像头被系统识别成了单一设备图像采集频率跟不上控制频率导致记录到的图像和动作错位。6.4 个人实操中的一点体会跑完整个 diffusion 流程之后我的感受是LeRobot 的抽象做得比较好你把配置调通代码层面几乎不用改但这不代表原理不重要。恰恰相反如果你不清楚num_conditioning_steps和delta_timestamps之间的关系不清楚训练和推理在扩散步数上的差异遇到异常时就会无从下手。所以这篇笔记花了很大篇幅讲原理也是希望你在“能跑通”之外还知道每个参数背后在改什么。如果后续想继续深入我建议你可以试试把unet_dim加大或者换成更轻量的骨干网络看看在 SO-ARM100 上推理延迟和任务成功率的变化。另外采集数据时尝试更多演示轨迹会让扩散模型的多模态优势体现得更明显。下一篇笔记我打算记录一下多任务数据混训的尝试也就是一个模型同时学会推杯子和抓方块这个方向对数据组织和策略容量都提出了更高的要求。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价