近半年来圈子里冒出一个高频词叫"物理AI",和ChatGPT这种在文本里打转的AI不同,它要求模型真正走进现实世界,用传感器感知、用电机执行、在混乱的动态环境里闭环。我第一次完整跑通"用LeRobot训练SO-ARM101机械臂做物体自动分拣"这个项目时,最大的感受是:模仿学习这条路比想象中更工程化,也比想象中更有意思。整个过程不需要写一行控制算法,数据采集加上模型训练就能让机械臂学会"抓取-分类-放置"的完整动作链条。
这篇文章把我从零开始的完整实操经验写下来,包括Ubuntu环境怎么搭、LeRobot怎么安装、SO-ARM101怎么驱动、数据怎么采集、ACT策略怎么训练、模型怎么部署到真机做分拣,以及我踩过的各种坑。适合手里正好有SO-ARM系列或者其他支持LeRobot的桌面机械臂、想入门模仿学习和具身智能的朋友参考,也适合想了解物理AI落地路线的人当作案例分析来读。
1. 项目脉络:为什么用模仿学习教机械臂分拣
1.1 物理AI和别的AI到底差在哪
最近总有人问"人工智能有物理AI还有别的AI吗",这类问题背后其实是对AI分类的困惑。目前大家接触到的AI大致可以分成几类:一类是感知和语言类模型,比如图像识别、语音识别、大语言模型;一类是生成类模型,比如画图、生成视频;还有一类是智能体类,比如能调用工具、查阅资料的Agent系统。物理AI是这里面非常特殊的一支,它往后退了一步,不是去处理已经数字化好的数据,而是直接和物理世界打交道,输入是相机图像、力觉、关节角度,输出是电机电流、关节力矩,整个推理必须在一个不完美的、充满噪声的真实环境里实时完成。
机械臂自动分拣就是一个标准的物理AI场景。物体摆放不整齐、光照会变、夹爪抓取有滑移、舵机响应有延迟,这些不确定性很难用传统规则全部覆盖。而模仿学习的思路很直接:既然规则写不全,那就让模型看人类怎么操作,自己总结规律。SO-ARM101这类桌面六轴臂成本低、响应快、支持外部控制,天然适合做这种实验平台。我选择这个项目,就是想验证一条问题:不写任何运动规划代码,纯靠"示教数据+神经网络",能不能让一台机械臂稳定完成一个需要视觉判断和运动控制协同的任务。
1.2 模仿学习的本质是学"映射关系"
很多人第一次听到模仿学习,会误以为是把人的操作录成视频,然后让机器人照着视频做。这个理解只对了一半。模仿学习学的不是"播放视频",而是一个从观测到动作的映射函数:输入是当前这一刻能看到的一切(相机图像、关节角、夹爪状态),输出是下一步应该怎么动(关节速度、末端位移),训练数据是人或遥操作设备实际操作时同步记录下来的"观测-动作"对。
这种行为克隆的思想最早可以追溯到很久以前的机器人示教研究,但真正让它实用化,靠的是近年来的两个突破:一是低成本遥操作设备让普通人也能采集大量高质量示教数据;二是Transformer、扩散模型这类网络结构能稳定地学习高维的视觉-运动策略。LeRobot是Hugging Face开源的机器人学习框架,它把数据采集、数据集管理、模型训练、真机部署这几个环节串成了一条标准流水线,支持ACT、Diffusion Policy等多种端到端策略,社区里已经有大量开源数据集可以直接下载研究,这也是我选它而不是自己手写一套pipeline的核心原因。
1.3 为什么组合是SO-ARM101 + LeRobot + ACT
先说硬件。SO-ARM101是SO-ARM系列里定位桌面教育的低成本六轴机械臂,整机重量轻、臂展适中、舵机驱动,常规桌面分拣场景完全够用,而且它的通信协议和LeRobot社区里已有的SO-ARM驱动能对上,省去了自己写底层驱动的痛苦。
再说算法。ACT(Action Chunking with Transformers)是斯坦福ALOHA项目提出的一种端到端模仿学习策略,在精细操作任务上的表现非常稳定,尤其适合"抓取-放置"这类需要连续协调动作的任务。相比逐帧预测动作的传统行为克隆,ACT的核心创新是让模型一次预测未来一段时间内的一整段动作序列,这个"动作分块"机制大幅减少了误差累积,训练收敛速度和最终成功率都比老方法有明显优势。
最后是框架。LeRobot把三者黏合在一起,它内置了数据记录器、仿真环境、模型训练脚本、评估工具和真机推理接口,而且配置体系非常灵活。我用它实现从零训练到部署只花了几天时间,这个效率对个人开发者和小型实验室来说非常关键。
2. 硬件与软件环境准备
2.1 这套方案需要哪些硬件
我这次的实验环境很简单,核心设备就四样:一台SO-ARM101机械臂本体、一个USB摄像头、一台运行Ubuntu系统且带NVIDIA显卡的电脑、一些用于分拣的道具(不同颜色的积木块和对应的收纳盒)。
这里重点提醒一下摄像头的选型。我一开始用的是普通720P的USB摄像头,画面在采集端看着还行,但模型训练时图片会缩放到84x84或96x96,过低的原始分辨率会直接损失物体边缘细节,导致抓取定位精度上不去。后来换成一款1080P的免驱摄像头,情况立刻好转。如果你预算允许,直接用Intel RealSense D435i这类深度相机更好,LeRobot对它的支持很成熟,以后还能扩展深度信息输入。相机架设位置也有讲究,我建议固定在机械臂斜上方45度左右,能够同时看到整个工作台面和机械臂末端,这样模型能学到"物体在哪里"和"手在哪里"的关系。分拣道具我用的是三种颜色的木质积木块,形状统一、颜色区分明显,第一版场景尽量降低视觉识别难度,把问题集中在"运动控制是否稳定"这件事上。
2.2 Ubuntu环境下安装LeRobot
系统方面我用的Ubuntu 22.04,显卡驱动和CUDA提前装好,Python环境用conda管理。LeRobot的安装步骤在官方仓库里写得很清楚,但有几个细节容易出问题,我这里给出一个我实测稳定通过的流程。
conda create -n lerobot python=3.10 conda activate lerobot pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/huggingface/lerobot cd lerobot pip install -e .注意几点:一是Python版本必须3.10及以上,我之前用3.8踩过依赖解析失败的坑;二是如果电脑没有NVIDIA显卡,可以装CPU版PyTorch,但训练ACT策略会很慢,我个人建议至少有一张6GB以上显存的显卡;三是LeRobot还在快速迭代,不同版本的命令行参数略有差异,我后续给出的命令都以我用的v0.2.x版本为准,你实际操作时如果遇到参数不识别的情况,优先查看自己本地版本的lerobot --help。
安装完成后,用一条命令验证一下框架是否就绪:
python -c "import lerobot; print('lerobot ok')"2.3 SO-ARM101接线、驱动与控制方式
SO-ARM101的物理接线比较简单,自带的串口控制板通过USB连接到电脑,机械臂的电源适配器单独供电。这里要特别提醒:先接控制线、再开电源,我因为反着操作导致串口识别失败过两次。连接好之后,先确认系统是否识别到设备:
ls -l /dev/ttyUSB*如果能看到/dev/ttyUSB0之类的设备,说明控制板已经被识别。但普通用户默认没有访问串口的权限,必须把当前用户加入dialout组,然后重新登录系统:
sudo usermod -aG dialout $USERLeRobot的SO-ARM驱动是通过串口通信下发关节角度指令的,控制频率通常能到几十赫兹,对于桌面分拣这种非高速场景完全够用。成功连接后,先跑一下LeRobot自带的控制脚本,用手柄或者UI界面遥控机械臂做几个简单的动作,确认每个关节的响应方向和控制频率正常。这一步千万别跳过,它同时验证了串口通信、固件、驱动映射三层是否正常,后面所有数据采集都建立在这个基础上。
3. 数据采集:决定模型上限的第一步
3.1 三种示教采集方式怎么选
模仿学习有一句行话叫"垃圾进,垃圾出",模型上限完全由数据质量决定。LeRobot支持三种主流示教方式:第一种是使用同型号的领航臂(leader arm)做遥操作,人和领航臂物理交互,从动臂完全跟随,手感最自然;第二种是使用游戏手柄或键盘控制,精度稍差但上手简单;第三种是拖动示教,直接用手掰动机械臂记录轨迹,只适用于本身具备力矩回馈或者轻量化关节的机型。
SO-ARM101这个型号,我实测下来最顺手的方式是同轴领航臂遥操作。如果暂时没有领航臂,用手柄控制也不是不行,但录制分拣动作时容易因为遥控精度不足导致夹爪对位不准,数据里会混入大量"失败示范",后期模型学到的动作会很拧巴。所以我强烈建议:既然决定走模仿学习路线,领航臂这笔投入不要省,它直接决定了数据采集的上限。
3.2 LeRobot的数据集里到底存了什么
在开始录制之前,有必要先搞清楚LeRobot数据集的结构。每一个episode代表一次完整的示教过程,比如"从区域A抓一个红积木放到红盒子里"。一个episode里包含按时间顺序对齐的三类数据:相机图像(以MP4视频帧的形式存储)、机器人状态(每个关节的当前角度、夹爪状态)、目标动作(操作者实际下达的关节指令)。所有这些结构化信息会汇总成一张parquet格式的表格,配合视频文件一起组织在数据集目录里。
我们可以用LeRobot自带的数据集工具查看信息,代码大致是这样的:
from lerobot.common.datasets.lerobot_dataset import LeRobotDataset dataset = LeRobotDataset("local_dir", root="data/your_dataset") print(dataset.meta) print(len(dataset.episodes))这套设计的好处是训练时不需要关心数据是怎么录的,框架会统一把图像、状态、动作打包成batch喂给模型。理解了这个结构,你就知道为什么采集时动作和图像必须严格同步——如果图像和关节指令错位了哪怕零点几秒,模型学到的映射关系就是乱的。
3.3 采集分拣数据的实操细节
采集命令我使用的是LeRobot的record接口。以遥操作为例,核心参数如下:
lerobot record \ --robot.type=so_arm \ --control.mode=leader \ --fps=30 \ --num_episodes=60 \ --tags sorting_v1执行之后,程序会进入录制状态,我拿着领航臂控制SO-ARM101完成一个完整的"抓取-移动-放置"动作,再按录制键结束这一个episode。一共录了60个episode,用了大概一个半小时。
这里有几个我总结的实操细节,直接影响训练效果:
一是每个episode的起始状态要有变化。我每次录制前都会手动把积木的位置稍微挪一点,夹爪初始角度也刻意错开,这样数据集里就包含了物体位置和机械臂初始姿态的多样性,模型才不会过拟合到某一条固定轨迹上。
二是动作速度要比手写轨迹操作稍慢一档。神经网络默认你给的所有动作都是"正确示范",快而糙的操作会导致轨迹抖动被学进去,推理时机械臂会很飘。我录制时刻意保持匀速顺滑,夹爪闭合后稍微停顿0.3秒再移动,这个停顿能让模型学到稳定的抓取姿态。
三是固定场景要素。我在采集阶段就把工作台、盒子的相对位置固定下来,光照尽量用室内顶灯不拉窗帘。桌面反光对图像的影响很大,我后来铺了一张哑光桌垫,采集阶段识别不稳的问题立刻少了很多。
四是数据量不是越多越好,而是覆盖度越高越好。60个episode听起来不多,但每个episode约30到40秒,已经覆盖了我能想到的各种积木摆放情况。LeRobot社区里很多任务的公开数据集也就50到200个episode。如果采集了重复度过高的数据,模型反而会陷入过拟合。
4. ACT策略:训练核心环节拆解
4.1 一个动作块为什么比一个动作点稳
ACT全称Action Chunking with Transformers,核心是"动作分块"。传统行为克隆的做法是模型每看到一帧观测就预测一个动作,执行完再重新推理,这有两个致命问题:一是推理频率高,部署时必须有极低延迟的推理链路;二是误差会逐帧累积,一旦某一帧预测偏了,机械臂就进入了训练数据里从未出现过的状态,模型越努力纠正越跑偏,这就是所谓的复合误差问题。
ACT的做法是让模型一次性预测未来一段时间的连续动作,比如100个控制步的动作序列,然后机械臂按照这个序列去执行。执行过程中模型不用反复推理,自然避免了高频推理链路的压力;而且整段动作是在一个全局视野下生成的,前后动作天然协调,不会出现单帧预测时的抖动。论文里真正的杀手锏还有两个:一是用CVAE(条件变分自编码器)引入隐变量,让模型能表达"同一个任务有多个合理做法",学到的动作分布更多样,不容易被平均出一堆犹豫不决的动作;二是Transformer的编码器-解码器结构能很好地建模视觉特征和历史动作之间的长程依赖关系。我在实际训练和部署中验证下来,ACT确实是当前模仿学习策略里最稳的之一,非常适合抓取-放置这类需要精细协调的任务。
4.2 关键训练参数怎么调
ACT的训练配置文件在LeRobot里以Hydra配置的形式组织,核心参数不少,但不是每个都需要手动调。我把自己实验中验证过的关键参数整理成了一张表,方便大家直接抄作业:
| 参数名 | 推荐值 | 说明 |
|---|---|---|
| policy.chunk_size | 100 | 每次推理预测的动作步数,越大越平稳,但过大容易导致反应迟钝 |
| policy.n_obs_steps | 1 | 模型输入的历史观测帧数,单帧观测对静态分拣任务够用 |
| policy.n_action_steps | 100 | 实际执行的动作步数,一般与chunk_size一致 |
| policy.hidden_dim | 512 | Transformer隐藏维度,显存不够可以降到256 |
| policy.dim_feedforward | 3200 | 前馈网络维度,与hidden_dim配套调整 |
| policy.n_encoder_layers | 4 | 编码器层数 |
| policy.n_decoder_layers | 4 | 解码器层数 |
| policy.vision_backbone | resnet18 | 视觉编码器,优先resnet18,速度快且足够 |
| training.batch_size | 64 | 显存不够就降到32 |
| training.lr | 1e-5 | ACT对学习率很敏感,不建议超过5e-5 |
| training.num_steps | 100000 | 总训练步数,小数据集50000步也能收敛 |
这里我特别想强调两个参数的取舍。chunk_size是ACT的灵魂参数,我试过50和100两个值,50的响应更快但动作明显毛糙,100在分拣任务中动作更顺滑、抓取成功率更高,但我身边有做快速反应任务的朋友把这个值调到了20,所以要根据任务节奏取舍。training.lr是另一个重点,ACT的学习率比普通模型小一个数量级,我一开始用默认的1e-4训练,loss曲线一直在震荡但就是降不下去,后来改成1e-5才稳定收敛。
4.3 训练过程观察与模型评估
训练命令很简洁,把数据集ID和策略名称替换成自己的就行:
lerobot train \ policy=act \ robot=so_arm \ env=so_arm \ dataset_repo_id=your_name/so101_sort \ env.task=SortBlocks \ training.num_steps=100000 \ training.batch_size=64 \ policy.chunk_size=100 \ training.wandb.enable=true我习惯开启wandb记录训练曲线,没有账号也可以关掉这个配置本地观察。训练过程中重点看两个指标:loss/action_loss和loss/kl_loss。action_loss代表模型预测动作与真实动作的差距,训练正常的话它会稳步下降;kl_loss是CVAE隐变量正则项,它不会归零,但会维持在一个较小的稳定水平。如果action_loss下降很快但kl_loss异常飙升,说明模型把多样性全压在隐变量上了,实际部署时动作会很不稳定。
训练结束后,LeRobot会自动保存多个checkpoint,分别对应不同训练阶段。我的经验是不要只看最后一个checkpoint,训练前期、中期、后期的checkpoint都各自跑一轮评估再选。60000步到80000步之间的checkpoint往往在测试场景里表现得比100000步的还好,因为更长的训练可能开始记忆训练集里的噪声。评估命令如下:
lerobot eval \ policy=act \ robot=so_arm \ env=so_arm \ dataset_repo_id=your_name/so101_sort \ pretrained_path=outputs/train/act_sort/checkpoints/006000/pretrained_model5. 部署到SO-ARM101上做真实分拣
5.1 从checkpoint到真机推理
训练完成不等于任务完成,把checkpoint部署到真机上推理才是真正见真章的一步。LeRobot的评估脚本本质上就是真机推理,它会加载训练好的模型,从相机读取图像、从机械臂读取关节状态,组装成观测输入,然后让模型输出动作序列,再下发到机械臂执行。
部署时我关注的第一个问题是推理频率和动作执行的匹配。SO-ARM101的串口控制频率约30到50Hz,而ACT模型推理一次需要几十毫秒。由于action chunking机制,模型推理一次生成100个动作,机械臂按30Hz逐步执行,这意味着每推理一次就能覆盖3秒多的操作,完全不用实时推理,实际部署中机械臂动作非常连贯。
比较麻烦的是模型输入图像和真实相机画面的对齐问题。LeRobot对图像做了裁剪、缩放、归一化处理,训练时模型看到的是84x84的小图,而真实相机采集是1080P原始画面。如果相机位置和采集时不一致,模型看到的小图和训练数据分布差异很大,成功率会断崖式下跌。所以部署阶段相机的安装位置一定不能动,角度、高度、焦距都应该和采集阶段保持一致。
5.2 分拣场景的完整推理流程
我的分拣场景是这样设计的:工作台上有一个取料区,放三种颜色的积木块,取料区旁边固定三个收纳盒,分别对应三种颜色。机械臂的任务是自主判断积木颜色,抓起来放进对应的盒子里,循环执行直到取料区清空。
实际部署时,我写了一个简单的循环控制脚本,流程如下:
# 1. LeRobot加载预训练模型 # 2. 机械臂返回home位姿并等待 # 3. 相机采集当前画面,模型判断"哪个积木、怎么抓、放哪里" # 4. 机械臂执行100步动作序列,完成一次抓取-放置 # 5. 回到home位姿,循环步骤3这里有一个值得说的设计细节:我让机械臂每完成一次分拣就回到一个固定的home位姿,而不是连续不停地操作。这样做有两个好处,一是模型每次面对的场景初始状态都比较接近训练数据分布,推理成功率更稳定;二是给系统留出了状态重置的时间,避免了连续操作导致的累积误差。最终我用三个测试序列,每个序列包含10个随机摆放的积木,测试结果是:第一次跑成功率在75%左右,优化后稳定在88%到92%之间。这个成绩在真实物理环境中已经算很可用了。
5.3 用DAGGER思路迭代修正失败案例
第一次部署后,我统计了失败案例,发现主要集中在两类:一类是积木位于取料区边缘时夹爪会轻微擦到桌面,导致抓取偏位;另一类是蓝紫色积木在特定光照下被识别成红色。对于这种真实场景里的分布偏移问题,传统做法是继续录更多数据重训,但更高效的做法是借鉴DAGGER(Dataset Aggregation)的思路。
DAGGER的核心思想是:不要只在专家示范的数据上训练,要让模型自己在真实环境里跑,跑出问题后由专家接管并给出纠正动作,把这些"模型犯错状态下的专家纠正"数据合并进训练集,然后重新训练。这比单纯增加专家示范更有针对性,因为它直接覆盖了模型最容易出错的状态空间。
我在LeRobot里实现DAGGER思路的流程很简单:先让模型自主跑分拣,一旦发现机械臂动作要偏,立刻切换到领航臂遥操作模式接管控制权,手动纠正抓取动作并继续完成放置;系统记录下这个episode,其中前半段是模型的错误状态、后半段是专家的纠正动作;然后把新录制的纠正数据与原数据集合并,重训一轮。两轮迭代之后,边缘抓取和颜色误判这两个问题都明显改善。对于没有领航臂的读者,也可以用手柄在出错瞬间介入控制,效果稍差但思路是一样的。
6. 复盘:常见问题与成功率提升经验
6.1 问题速查表
这几天实验下来,我记录了一些高频问题,整理成速查表,方便遇到类似情况的朋友快速排查:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 串口无法识别机械臂 | USB线接触不良或权限问题 | 换USB口,执行usermod加入dialout组并重登 |
| 训练loss不下降 | 学习率过高 | 把training.lr从1e-4降到1e-5 |
| 部署时机械臂动作抖动 | chunk_size过小 | 增大policy.chunk_size到100或更高 |
| 同一位置反复抓不到 | 相机或机械臂位置发生过偏移 | 检查工作台固定螺丝和相机支架 |
| 颜色识别经常错 | 采集时颜色样本太少 | 每种颜色录制时尽量调整角度,保证颜色信息多样性 |
| 夹爪对不准物体 | 数据采集动作太快太糙 | 重录数据,动作放慢,抓取后停顿0.3秒以上 |
| 手写脚本分拣很慢 | 每次完整执行100步动作 | 适当降低chunk_size或增加提前终止条件 |
6.2 数据层面提效的四个技巧
第一个技巧是"宁多录,不漏录"。每次录制尽量涵盖不同物体位置、不同初始姿态、不同光照条件,哪怕有些episode最后抓取失败了,只要失败原因不是操作者手抖,也可以作为负样本保留。LeRobot支持对episode做标注,把这些负样本标记出来,训练时框架可以选择性地弱化它们的权重。
第二个技巧是定期做数据可视化检查。LeRobot提供了查看数据集的工具,把录制好的episode渲染成视频逐帧检查。我发现很多问题在看视频的一瞬间就能发现:图像和动作对不齐、夹爪其实没有完全闭合、某一段轨迹动作跳变异常。这个检查成本很低,但远比训练完再部署调试来得高效。
第三个技巧是控制每个episode的长度。我最初录制时习惯一口气操作很久,结果一个episode里包含了两到三次完整分拣,模型学到的是长程混乱的动作序列。后来我把每次录制控制在一次分拣动作内,按下开始、完成抓取放置、立刻结束,episode结构干净,训练收敛速度快了不少。
第四个技巧是统一数据分辨率。如果你像我一样后面会从社区下载公开数据集做预训练或混合训练,一定要注意图片分辨率是否和本地采集的一致。LeRobot本身会做resize,但不同相机采集的画面比例差异会影响最终的裁剪效果,让模型看到奇怪的内容。
6.3 结构层面与算法层面的优化建议
如果基础方案已经能跑通但成功率还达不到要求,我建议从三个方向优化。视觉层面,可以升级到深度相机,把深度图像作为额外的观测通道,模型对物体空间位置的判断会稳健很多。结构层面,ACT的n_obs_steps可以适当增大,让模型看到过去几帧图像,对运动中的物体和动态环境更鲁棒。算法层面,LeRobot还支持Diffusion Policy,它是一种基于扩散模型的模仿学习策略,在高维动作空间上表达力更强,如果ACT遇到表达瓶颈,可以尝试对比。
另外,如果手头有多个数据集,LeRobot支持把多个数据集混合训练,甚至可以加载社区里现有的预训练模型做微调。我在第二版实验里用了这个方法,先用公开的通用操作数据预训练视觉编码器,再用自己的SO-ARM101分拣数据微调,训练收敛速度快了不少,最终的抓取成功率也比从零训练高了三到五个百分点。
最后再分享一个我个人的体会:这类项目真正难的不是训练模型,而是把数据采集、系统集成、故障排查这些"脏活累活"做扎实。第一次跑通之后,整个过程会变得非常顺手。如果你也在做类似的事情,遇到问题不要急着换模型换框架,先把数据打开逐帧看一遍,把机械臂的物理连接和相机固定检查一遍,大多数"模型不好使"的真相都在这些细节里。