【Action Chunking with Transformers 精读】:低成本双臂机器人的精细操作革命

Action Chunking with Transformers 精读:低成本双臂机器人的精细操作革命


论文信息

标题 :Learning Fine-Grained Bimanual Manipulation with Low-Cost Hardware

会议 :Robotics: Science and Systems (RSS) 2023

单位 :Stanford University、UC Berkeley、Meta

代码github.com/tonyzhaozh/act

论文arxiv.org/abs/2304.13705


一、研究背景:精细操作的"高端病"

机器人做抓取、搬运这种粗活已经很成熟了,但一碰到毫米级的精细操作就"手残"------比如穿扎带、撬杯盖、装电池,既要精准的手眼协调,又要细腻的力接触配合,还要实时闭环反馈。

过去解决这个问题的思路很简单粗暴:堆钱。用几十万的工业机械臂、高精度力传感器、激光跟踪仪,再加上专人校准调试,才能勉强搞定。这就像做精密零件全靠进口五轴机床,普通实验室和小公司根本玩不起。

那能不能换个思路:硬件不够,算法来凑?用几千块的廉价机械臂,靠优秀的模仿学习算法,补上硬件精度的短板,也能完成精细操作?

这篇论文就干成了这件事:

  1. 搭了一套总成本不到2万美元的双臂遥操作平台 ALOHA,普通人2小时就能组装完成;
  2. 提出了全新的模仿学习算法 ACT(Action Chunking with Transformers),只用10分钟左右的人类演示数据,就能让这套廉价硬件完成6种精细操作,成功率高达80%-90%。

打个通俗的比方:这就像以前你打电竞必须买上万的旗舰外设,现在有人告诉你,用两百块的入门键鼠,配上优化到极致的操作逻辑,也能打上王者段位。


二、ALOHA:两万刀打造的双臂遥操作神器

要做模仿学习,首先得有高质量的演示数据。论文团队没有用昂贵的动捕设备或者力反馈手套,而是做了一套"主从机械臂"的遥操作方案:你拖动机型更小的主臂,大一点的从臂就会跟着复刻动作。

2.1 设计五大原则

整个硬件设计围绕5个核心目标:

  • 低成本:总价对标单台科研机械臂,普通实验室也能负担
  • 多功能:能覆盖绝大多数日常精细操作场景
  • 易上手:不用复杂培训,普通人很快就能熟练操作
  • 易维修:零件都是现成货,坏了自己就能换
  • 搭建快:全是现成模块+3D打印件,2小时就能拼完

2.2 硬件配置详情

系统采用"两主两从"共4台机械臂的方案:

  • 从臂(干活的):2台 ViperX 6自由度机械臂,单台约5600美元,重复精度1mm,绝对精度5-8mm,负载750g。搭配自研3D打印透明夹爪,既能看清操作细节,又能夹稳薄塑料件。
  • 主臂(遥控的):2台更小的 WidowX 机械臂,单台约3300美元,人可以直接拖动,关节和从臂一一对应。
  • 视觉系统:4台罗技C922x摄像头,2台装在从臂手腕上(近距离看细节),2台固定在正面和顶部(全局视角),分辨率480×640,30帧。
  • 控制频率:50Hz,也就是每秒更新50次动作,保证操作丝滑。

整个系统算上框架、3D打印件、摄像头,总价不到2万美元,和一台Franka Panda科研机械臂差不多,但能完成的精细任务多得多。

通俗备注:为什么不用VR手柄或者视觉动捕?因为廉价机械臂很容易碰到奇异点(类似你胳膊拧到极限的状态),逆运动学算不出来直接卡死。用关节空间一一映射,相当于直接"抄作业",不用算复杂的运动学,延迟低、不翻车,还能自然过滤人手的轻微抖动。

2.3 系统能力展示

图1是ALOHA系统的概览,左边是用户遥操作主臂的场景,右边是系统能完成的任务示例。

图1 ALOHA双臂遥操作系统概览(出自原文Figure 1)

从图中可以看到,这套系统不仅能做穿扎带、NIST装配板这种接触丰富的高精度任务,甚至还能颠乒乓球这种动态任务。论文里提到,其中不少任务是以往10倍价格的遥操作系统才能实现的。


三、ACT算法:用动作分块根治模仿学习的"累积误差病"

硬件搞定了,接下来是核心难题:模仿学习的累积误差

普通的行为克隆(BC)是"走一步看一步":当前观测输入模型,输出下一步动作。但只要有一步预测偏了一点点,下一个观测就不在训练数据分布里了,误差会越滚越大,最后彻底跑偏------就像新手开车走直线,方向盘稍微歪一点,车就越来越偏,最后冲出车道。精细操作对误差容忍度只有几毫米,这个问题尤其致命。

ACT的核心思路就是:别一步一步预测,一次性预测未来k步的动作序列(动作分块),再通过时序融合让动作丝滑起来,相当于开车的时候提前看好几百米的路,一次性规划好方向盘操作,自然不容易跑偏。

3.1 核心设计1:动作分块(Action Chunking)

公式定义

普通单步策略的建模方式:

πθ(at∣st)\pi_\theta(a_t \mid s_t)πθ(at∣st)

ACT的动作分块策略建模方式:

πθ(at:t+k∣st)\pi_\theta(a_{t:t+k} \mid s_t)πθ(at:t+k∣st)

字母逐项解释

  • πθ\pi_\thetaπθ:参数为θ\thetaθ的策略神经网络
  • ata_tat:第ttt时刻的动作,本文中是双臂14个关节的目标角度,14维向量
  • sts_tst:第ttt时刻的观测,包含4路RGB图像+双臂当前关节位置
  • kkk:分块大小,也就是一次预测的未来动作步数
  • at:t+ka_{t:t+k}at:t+k:从第ttt时刻到第t+kt+kt+k时刻,连续kkk个动作组成的序列

通俗备注:这就像你玩闯关游戏,普通BC是走一步看一步,前面有坑才反应跳,经常来不及掉下去;动作分块是你一眼扫完前面的地形,一次性规划好接下来10步的跳跃、移动、转向,容错率直接拉满。任务的有效长度相当于缩短到原来的1/k1/k1/k,累积误差自然大幅降低。

除此之外,动作分块还能解决人类演示里的"非马尔可夫问题"------比如人操作中间会停顿、会微调,单步模型搞不懂为什么不动,动作分块就能把停顿包含在一个块里,不会乱输出。

3.2 核心设计2:时序集成(Temporal Ensembling)

如果每kkk步才观测一次环境、更新一次动作,机器人的动作会一顿一顿的,像卡帧了一样。为了解决这个问题,ACT提出了时序集成:

每一个时间步都预测一次完整的动作块,这样同一个时刻的动作会被多次预测到,把这些预测结果加权平均,就能得到既精准又平滑的动作。

权重计算公式

wi=exp⁡(−m⋅i)w_i = \exp(-m \cdot i)wi=exp(−m⋅i)

字母逐项解释

  • wiw_iwi:第iii个历史预测对应的权重
  • exp⁡\expexp:自然指数函数
  • mmm:衰减系数,控制新观测的融入速度,mmm越小,新预测的权重占比越高,动作响应越快
  • iii:预测的时间偏移量,i=0i=0i=0对应最早的预测,iii越大对应越新的预测

加权平均的时候,所有权重会做归一化,保证总和为1。和普通的时域平滑不同,这里平均的是不同时刻预测的同一个时间步的动作,不会引入滞后偏差。

通俗备注:这就像你开车的时候,不会只盯着眼前这一秒的路打方向,而是会结合前几秒对路线的判断,综合调整方向盘,开出来的轨迹又稳又顺,不会一顿一顿的。

3.3 核心设计3:CVAE建模人类演示的多样性

人类演示不是标准答案------同一个任务,不同的人做轨迹不一样,同一个人做两次也不一样。如果用普通的均方误差损失训练,模型会学一个"平均轨迹",结果就是哪都不挨哪,精度直接崩。

ACT用条件变分自编码器(CVAE) 把策略建模成生成模型,专门处理人类演示的多模态性。

总损失函数

L=Lreconst+β⋅Lreg\mathcal{L} = \mathcal{L}{\text{reconst}} + \beta \cdot \mathcal{L}{\text{reg}}L=Lreconst+β⋅Lreg

字母逐项解释

  • L\mathcal{L}L:模型总训练损失
  • Lreconst\mathcal{L}_{\text{reconst}}Lreconst:重构损失,衡量预测动作序列和真实演示序列的误差,论文用L1损失(平均绝对误差),比L2对离群点更不敏感,更适合精细操作
  • β\betaβ:正则项权重超参数,控制隐空间的"信息瓶颈",β\betaβ越大,隐变量携带的信息越少,模型输出越保守
  • Lreg\mathcal{L}{\text{reg}}Lreg:KL散度正则项,约束隐变量的分布接近标准正态分布,公式如下:
    Lreg=DKL(qϕ(z∣at:t+k,oˉt)∥N(0,I))\mathcal{L}
    {\text{reg}} = D_{\text{KL}}\left(q_\phi(z \mid a_{t:t+k}, \bar{o}_t) \parallel \mathcal{N}(0, I)\right)Lreg=DKL(qϕ(z∣at:t+k,oˉt)∥N(0,I))
    • DKLD_{\text{KL}}DKL:KL散度,衡量两个概率分布的差异程度
    • qϕq_\phiqϕ:CVAE编码器网络,参数为ϕ\phiϕ,输入真实动作序列和本体觉观测,输出隐变量的概率分布
    • zzz:32维的风格隐变量,用来编码人类演示的操作风格差异(比如快慢、左右手偏好)
    • oˉt\bar{o}_toˉt:不含图像的观测,也就是双臂的关节位置
    • N(0,I)\mathcal{N}(0, I)N(0,I):标准正态分布,均值为0,协方差为单位矩阵III

训练的时候,编码器从真实演示里提取风格隐变量zzz,解码器根据当前观测+zzz生成对应的动作序列。测试的时候,直接把zzz设为0(标准正态分布的均值,也就是平均风格),就能输出最稳定、精度最高的动作。

通俗备注:CVAE相当于给模型装了个"风格调节器"。训练的时候它会看懂"原来这个任务有好几种操作方式",不会硬把所有轨迹揉成一团四不像;测试的时候用默认的"稳妥风格",输出最靠谱的动作,不会乱飘。

3.4 Transformer架构实现

动作序列建模天然适合用Transformer,论文用Transformer实现了CVAE的编码器和解码器,完整架构如图2所示。

图2 ACT网络架构详情(出自原文Figure 11)

整个网络的推理流程可以分成三步:

  1. 视觉特征提取:4路图像分别过ResNet18 backbone,提取空间特征,加上二维正弦位置编码,拉平成序列。
  2. Transformer编码器 :把4路图像特征、关节位置特征、隐变量zzz拼接成一个长序列,通过自注意力融合所有模态的信息。
  3. Transformer解码器 :输入固定长度的动作位置编码(对应kkk个动作步),通过交叉注意力关注编码器的输出,一次性生成未来kkk步的动作序列。

整个模型约8000万参数,单张11G显存的RTX 2080Ti训练5小时就能完成一个任务,单步推理仅需0.01秒,完全满足50Hz的实时控制要求。


四、实验结果:碾压级的性能提升

论文一共做了8个任务:2个模拟任务+6个真实世界任务,每个任务仅用50条演示(约10分钟操作)训练,和4个主流基线方法做对比。

4.1 任务设置

真实世界的6个任务全是需要毫米级精度的双手操作:

  • 开密封袋、装电池、开调料杯、穿魔术贴扎带、剪胶带粘贴、给假脚穿鞋
    每个任务都拆成2-4个子步骤,分别统计成功率,更能看出误差累积的影响。

4.2 基线方法

  • BC-ConvMLP:最基础的行为克隆,卷积网络提图像特征+MLP输出单步动作
  • BeT:Behavior Transformer,当时的模仿学习SOTA,用Transformer+离散动作空间
  • RT-1:谷歌的机器人Transformer,大规模预训练的单步预测模型
  • VINN:非参数近邻方法,靠检索最相似的观测输出动作

4.3 核心结果分析

表1是2个模拟任务+2个真实任务的完整成功率对比。

表1 模拟与真实任务成功率对比(出自原文Table I)

方法 模拟方块传递 模拟轴孔插入 真实开密封袋 真实装电池
BC-ConvMLP 17% 0% 1% 0%
BeT 51% 1% 4% 0%
RT-1 33% 0% 0% 0%
VINN 9% 0% 1% 0%
ACT(本文) 90% 50% 90% 96%

从数据里能看到非常夸张的差距:

  1. 基线方法几乎全崩:真实任务里,所有基线方法基本卡在第一个子步骤,后续步骤成功率全是0,就是因为累积误差让机器人很快就偏离了正常状态。
  2. ACT碾压式领先:模拟任务比最好的基线高20%-59%,真实任务直接从个位数拉到90%+。
  3. 对人类数据鲁棒性强:从脚本数据换成人类演示数据,所有方法都掉点,但ACT掉点最少,说明CVAE确实有效处理了人类演示的噪声。

表2是剩下4个更难的真实任务的结果,只和表现最好的基线BeT对比。

表2 剩余真实任务成功率对比(出自原文Table II)

方法 开调料杯 穿魔术贴扎带 剪胶带粘贴 穿鞋
BeT 0% 0% 0% 0%
ACT(本文) 84% 20% 64% 92%

可以看到,难度更高的任务里,基线方法直接零成功,ACT依然能拿到不错的成功率。其中穿扎带最难,只有20%------因为黑色扎带在黑色背景上占比极小,视觉感知难度太大,每一步的小误差都会累积到最后插不进去。


五、消融实验:拆开看看每个模块到底有用没用

为了验证每个设计的必要性,论文做了详细的消融实验,结果如图3所示。

图3 消融实验结果(出自原文Figure 8)

5.1 动作分块大小k的影响(图a)

  • k=1k=1k=1就是普通单步预测,成功率只有1%,几乎全失败;
  • 随着kkk增大,成功率快速上升,到k=100k=100k=100时达到峰值44%;
  • kkk继续增大到200、400,成功率反而轻微下降------因为分块太大就接近开环控制了,失去了闭环反馈的修正能力。

更有意思的是,把动作分块加到BC和VINN这两个基线方法上,它们的性能也大幅提升。说明动作分块是个通用技巧,不是ACT专属,只要用了就能缓解累积误差。

5.2 时序集成的作用(图b)

  • ACT加上时序集成,成功率涨了3.3%,动作也更平滑;
  • BC-ConvMLP涨了4%,收益更明显;
  • VINN反而掉了20%------因为VINN是直接从数据集里取真实动作,本身没有预测噪声,加权平均反而引入了偏差。

结论:参数化模型有预测噪声,时序集成能平滑掉噪声;非参数方法本身精度高,就不需要了。

5.3 CVAE的必要性(图c)

  • 用脚本生成的确定性数据训练时,加不加CVAE性能差不多;
  • 用人类演示数据训练时,去掉CVAE成功率直接从35.3%掉到2%,几乎完全失效。

这直接证明了:CVAE是处理人类演示多模态性的核心,没有它,模型根本学不准人类的操作。

5.4 控制频率的影响(图d)

论文找了6个受试者,分别用5Hz和50Hz的控制频率做穿扎带、分杯子两个任务。结果50Hz比5Hz的完成时间快了62%,统计检验p值<0.001,差异极其显著。

通俗备注:这就像打游戏,60帧就是比30帧好操作,帧率越高,反馈越及时,精细操作越容易。高频控制是精细操作的基础,很多模仿学习工作只用到5Hz左右,自然做不了高精度任务。


六、核心代码实现

6.1 时序集成推理代码

这是ACT推理时最核心的逻辑,负责缓存历史动作块并加权输出平滑动作:

python 复制代码
import numpy as np
import torch

class ACTTemporalEnsembler:
    """
    ACT时序集成器:缓存历史预测的动作块,指数加权平均得到平滑动作
    """
    def __init__(self, chunk_size: int, decay_m: float = 0.1):
        self.chunk_size = chunk_size  # 动作分块长度k
        self.decay_m = decay_m        # 指数衰减系数m
        self.action_buffer = []       # 动作块缓存,每个元素是[chunk_size, action_dim]的张量

    def push_chunk(self, action_chunk: torch.Tensor):
        """
        存入新预测的动作块
        action_chunk: 未来k步的动作序列,shape [chunk_size, action_dim]
        """
        self.action_buffer.append(action_chunk)
        # 缓存最多保留chunk_size个块,超出的丢弃最老的
        if len(self.action_buffer) > self.chunk_size:
            self.action_buffer.pop(0)

    def get_current_action(self) -> torch.Tensor:
        """
        计算当前时刻的加权平均动作
        """
        current_preds = []
        weights = []

        buffer_len = len(self.action_buffer)
        for idx_in_buffer, chunk in enumerate(self.action_buffer):
            # 计算当前时刻动作在该块中的位置
            step_in_chunk = buffer_len - 1 - idx_in_buffer
            if step_in_chunk < len(chunk):
                current_preds.append(chunk[step_in_chunk])
                # 指数权重:越新的预测权重越高
                weight = np.exp(-self.decay_m * (buffer_len - 1 - idx_in_buffer))
                weights.append(weight)

        # 权重归一化
        weights = np.array(weights) / np.sum(weights)
        weights = torch.tensor(weights, dtype=action_chunk.dtype, device=action_chunk.device)

        # 加权平均得到最终动作
        stacked_preds = torch.stack(current_preds, dim=0)
        weighted_action = torch.einsum('i,id->d', weights, stacked_preds)
        return weighted_action

6.2 ACT策略网络核心结构

简化版的ACT解码器网络,展示Transformer生成动作序列的核心逻辑:

python 复制代码
import torch
import torch.nn as nn
from torchvision import models

class ACTPolicy(nn.Module):
    """
    ACT策略网络(CVAE解码器):输入多视角图像与关节角,输出未来k步动作序列
    """
    def __init__(self, action_dim=14, chunk_size=100, hidden_dim=512, num_heads=8):
        super().__init__()
        self.action_dim = action_dim
        self.chunk_size = chunk_size
        self.hidden_dim = hidden_dim

        # 图像特征提取 backbone
        self.img_backbone = models.resnet18(pretrained=False)
        self.img_backbone.fc = nn.Identity()  # 去掉最后一层分类头
        self.img_proj = nn.Linear(512, hidden_dim)

        # Transformer 编码器:融合多视角图像、关节位置、隐变量z
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=hidden_dim, nhead=num_heads, dim_feedforward=3200, dropout=0.1
        )
        self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=4)

        # Transformer 解码器:生成动作序列
        decoder_layer = nn.TransformerDecoderLayer(
            d_model=hidden_dim, nhead=num_heads, dim_feedforward=3200, dropout=0.1
        )
        self.transformer_decoder = nn.TransformerDecoder(decoder_layer, num_layers=7)

        # 动作查询位置编码(可学习)
        self.action_queries = nn.Parameter(torch.randn(chunk_size, hidden_dim))

        # 输出头:映射到动作维度
        self.action_head = nn.Linear(hidden_dim, action_dim)

        # 模态投影层
        self.joint_proj = nn.Linear(14, hidden_dim)
        self.z_proj = nn.Linear(32, hidden_dim)

    def forward(self, imgs: torch.Tensor, joint_pos: torch.Tensor, z: torch.Tensor) -> torch.Tensor:
        """
        前向传播
        imgs: [batch, 4, 3, 480, 640] 4个摄像头的RGB图像
        joint_pos: [batch, 14] 双臂关节角度
        z: [batch, 32] 风格隐变量
        return: [batch, chunk_size, 14] 预测的未来k步动作序列
        """
        batch_size = imgs.shape[0]

        # 1. 提取4路图像特征并拼接
        img_feats = []
        for cam_idx in range(4):
            cam_img = imgs[:, cam_idx]
            feat = self.img_backbone(cam_img)  # [batch, 512]
            # 注:完整实现会保留空间维度并加2D位置编码,此处做简化
            feat = self.img_proj(feat).unsqueeze(1)  # [batch, 1, hidden_dim]
            img_feats.append(feat)
        img_seq = torch.cat(img_feats, dim=1)  # [batch, 4, hidden_dim]

        # 2. 投影关节位置与隐变量
        joint_emb = self.joint_proj(joint_pos).unsqueeze(1)  # [batch, 1, hidden_dim]
        z_emb = self.z_proj(z).unsqueeze(1)                  # [batch, 1, hidden_dim]

        # 3. 拼接所有序列输入编码器
        encoder_input = torch.cat([img_seq, joint_emb, z_emb], dim=1)  # [batch, 6, hidden_dim]
        # Transformer要求序列维度在前
        encoder_input = encoder_input.transpose(0, 1)  # [6, batch, hidden_dim]
        memory = self.transformer_encoder(encoder_input)

        # 4. 解码器生成动作序列
        decoder_input = self.action_queries.unsqueeze(1).repeat(1, batch_size, 1)  # [chunk_size, batch, hidden_dim]
        decoder_output = self.transformer_decoder(decoder_input, memory)          # [chunk_size, batch, hidden_dim]

        # 5. 输出最终动作
        action_seq = self.action_head(decoder_output.transpose(0, 1))  # [batch, chunk_size, action_dim]
        return action_seq

七、局限性与未来方向

再好的方案也有边界,论文也坦诚了当前的局限:

硬件层面

  1. 只有平行夹爪,没有灵巧手,做不了需要多手指配合的任务,比如开带卡扣的儿童药瓶;
  2. 电机扭矩有限,干不了需要大力气的活,比如拧很紧的密封瓶盖;
  3. 没有力觉传感器,全靠视觉,对需要精确力控制的任务(比如拧螺丝)支持不好。

算法层面

  1. 强依赖视觉感知,透明、反光、低对比度的物体容易翻车,比如拆糖纸找不到撕口;
  2. 单任务训练,每个任务要单独收集数据、单独训模型,不能零样本泛化新任务;
  3. 还是需要人类演示,不能自主探索学习。

八、全文总结

这篇论文是近年具身智能领域非常有影响力的工作,核心贡献可以归纳为两点:

  1. 把精细操作的硬件门槛打了下来:不到2万美元的开源方案,就能做以往几十万系统才能完成的任务,让更多实验室能参与到双臂操作研究里来。
  2. 用动作分块极大缓解了模仿学习的老大难问题:结合Transformer的序列建模能力和CVAE的生成式建模,用极少的演示数据就能实现高精度闭环操作,为机器人模仿学习提供了一个非常实用的范式。

后来的Mobile ALOHA、ACT++等工作,都是在这个基础上迭代出来的。某种意义上,它证明了一件事:不用堆昂贵的硬件,靠算法设计也能让廉价机器人拥有灵巧的操作能力------这正是机器人走进真实家庭、走进千行百业的必经之路。