论文信息

  • 标题:RT-1: ROBOTICS TRANSFORMER FOR REAL-WORLD CONTROL AT SCALE
  • 会议:arXiv preprint (2022)
  • 单位:Robotics at Google, Everyday Robots, Google Research, Brain Team
  • 代码:github.com/google-research/robotics_transformer
  • 论文:https://arxiv.org/pdf/2212.06817

一、引言:机器人学习的"数据困境"

想象一下,如果你想教一个机器人学会"把可乐罐放进冰箱",你需要怎么做?在传统的机器人学习方法中,你可能需要收集成百上千次这个特定任务的演示,然后训练一个专门的模型。但如果下次你想让它"把苹果放进碗里",你又得重新收集数据、重新训练模型。这就像教一个孩子学英语,每次只教他一个单词,而且他永远不会举一反三。

这就是机器人学习领域长期面临的"数据困境":真实世界的机器人数据收集极其昂贵且耗时。与计算机视觉和自然语言处理领域动辄数十亿的数据集相比,机器人数据集的规模通常只有几千甚至几百个样本。

那么,我们能不能像NLP和CV领域那样,训练一个通用的机器人大模型,让它能够从大量多样化的数据中学习通用的技能,然后零样本或少样本地泛化到新任务上呢?这正是RT-1(Robotics Transformer 1)想要解决的问题。

二、RT-1整体架构:高效的多模态Transformer

RT-1是一个专为真实世界机器人控制设计的Transformer模型。它的核心设计理念是:在保证实时控制速度的前提下,最大化模型的容量和泛化能力
在这里插入图片描述

图1:RT-1的整体架构概览(来源:论文Figure 1)

从图1可以看出,RT-1的输入是图像序列自然语言指令,输出是离散化的机器人动作。整个架构由三个主要部分组成:

  1. FiLM条件化的EfficientNet:用于处理图像和语言指令的早期融合
  2. TokenLearner:用于压缩视觉token数量,提高推理速度
  3. Decoder-only Transformer:用于序列建模和动作预测

通俗易懂的解释:你可以把RT-1想象成一个"机器人大脑"。EfficientNet是它的"眼睛",负责看清楚周围的环境;FiLM层是它的"注意力开关",根据语言指令告诉"眼睛"应该关注什么;TokenLearner是它的"信息筛选器",只把最重要的视觉信息传递给大脑;最后的Transformer就是它的"大脑皮层",负责思考和决策,告诉机器人应该做什么动作。

三、核心技术细节

3.1 输入与输出表示

3.1.1 输入表示

RT-1接收两种输入:

  • 图像序列:最近6帧300×300的RGB图像
  • 自然语言指令:描述任务的文本,如"pick up the coke can"
3.1.2 输出表示

RT-1输出一个11维的离散化动作向量:

  • 7个手臂维度:x, y, z(位置),roll, pitch, yaw(姿态),gripper(夹爪开合度)
  • 3个底盘维度:x, y(平移),yaw(旋转)
  • 1个模式维度:控制手臂、控制底盘或终止任务

每个维度都被离散化为256个bin。这种离散化的表示方式有两个主要优点:

  1. 能够表示复杂的多模态动作分布
  2. 与Transformer的token化输入输出更加匹配

3.2 图像与语言的早期融合:FiLM-EfficientNet

RT-1没有采用常见的"图像编码器+语言编码器+后期融合"的架构,而是使用了FiLM(Feature-wise Linear Modulation)技术实现了图像和语言的早期融合。

FiLM层的数学公式如下:
FiLM(F,γ,β)=γ⋅F+β\text{FiLM}(F, \gamma, \beta) = \gamma \cdot F + \betaFiLM(F,γ,β)=γF+β

其中:

  • FFF:输入特征图
  • γ\gammaγ:缩放因子,由语言指令嵌入通过全连接层得到
  • β\betaβ:偏移因子,同样由语言指令嵌入通过全连接层得到

通俗易懂的解释:FiLM层就像一个"调光器"。对于不同的语言指令,它会对图像特征图的不同通道进行不同程度的"调亮"或"调暗"。例如,当指令是"pick up the red apple"时,FiLM层会调亮与红色和苹果形状相关的特征通道,让模型更加关注这些信息。

RT-1使用了预训练的EfficientNet-B3作为图像编码器,并在每个MBConv块之后插入了FiLM层。为了避免破坏预训练权重,FiLM层的权重被初始化为:
γ=1,β=0\gamma = 1, \quad \beta = 0γ=1,β=0

这样,在训练初期,FiLM层相当于一个恒等映射,不会影响预训练EfficientNet的功能。随着训练的进行,模型会逐渐学习到如何根据语言指令调整图像特征。

3.3 视觉token压缩:TokenLearner

Transformer的自注意力机制的计算复杂度是O(n2)O(n^2)O(n2),其中nnn是token的数量。如果直接将EfficientNet输出的9×9×512特征图展平成81个token,那么自注意力的计算量会非常大,无法满足实时控制的要求。

为了解决这个问题,RT-1使用了TokenLearner模块来压缩视觉token的数量。TokenLearner的核心思想是:学习一个注意力权重,将大量的输入token加权求和得到少量的输出token

TokenLearner的数学公式如下:
Ti=∑j=1Nai,j⋅VjT_i = \sum_{j=1}^{N} a_{i,j} \cdot V_jTi=j=1Nai,jVj

其中:

  • TiT_iTi:第iii个输出token
  • ai,ja_{i,j}ai,j:第iii个输出token对第jjj个输入token的注意力权重
  • VjV_jVj:第jjj个输入token
  • NNN:输入token的数量

RT-1使用TokenLearner将81个视觉token压缩到了8个,这使得自注意力的计算量减少了一个数量级以上。

通俗易懂的解释:TokenLearner就像一个"新闻编辑"。它从81条"新闻"(视觉token)中挑选出最重要的8条,然后将它们整合成一篇"摘要",传递给Transformer进行处理。这样不仅大大提高了处理速度,还能让模型更加关注最重要的信息。

3.4 Transformer骨干网络

经过TokenLearner压缩后,每帧图像得到8个token。RT-1使用最近6帧图像,因此总共有6×8=486 \times 8 = 486×8=48个视觉token。这些token被加上位置编码后,输入到一个decoder-only Transformer中。

RT-1的Transformer骨干网络有8个自注意力层,总共约19M参数。整个RT-1模型的总参数量约为35M,这在Transformer模型中算是非常小的,但它却能在真实世界机器人上以3Hz的频率运行。

3.5 训练目标

RT-1使用行为克隆(Behavioral Cloning)方法进行训练,训练目标是最小化预测动作与专家演示动作之间的交叉熵损失:
L=−∑t=0T∑d=0Dlog⁡p(at,d∣i,x0:t)\mathcal{L} = -\sum_{t=0}^{T} \sum_{d=0}^{D} \log p(a_{t,d} | i, x_{0:t})L=t=0Td=0Dlogp(at,di,x0:t)

其中:

  • TTT:轨迹长度
  • DDD:动作维度数(11)
  • at,da_{t,d}at,d:第ttt步第ddd维的真实动作
  • iii:语言指令
  • x0:tx_{0:t}x0:t:从第0步到第ttt步的图像序列

四、大规模数据集:130k演示,700+任务

RT-1的成功离不开大规模、多样化的训练数据集。谷歌的研究人员用了17个月的时间,使用13台Everyday Robots移动机械臂,收集了超过130k个成功的演示轨迹,涵盖了744个不同的任务指令

这些任务可以分为以下几类:

技能类别 数量 描述 示例指令
Pick Object 130 从表面拿起物体 pick iced tea can
Move Object Near Object 337 将一个物体移到另一个物体附近 move pepsi can near rxbar blueberry
Place Object Upright 8 将细长物体竖直放置 place water bottle upright
Knock Object Over 6 推倒细长物体 knock redbull can over
Open / Close Drawer 6 打开或关闭橱柜抽屉 open the top drawer
Place Object into Receptacle 84 将物体放入容器 place brown chip bag into white bowl
Pick Object from Receptacle and Place on Counter 162 从容器中取出物体并放在柜台上 pick green jalapeno chip bag from paper bowl and place on counter
Additional tasks 9 其他真实场景任务 pull napkin out of dispenser
Total 744

表1:RT-1训练数据集的任务分布(来源:论文Table 1)

有趣的案例:在数据收集过程中,研究人员发现机器人学会了一些非常有趣的"人类习惯"。例如,当人类演示者在打开抽屉时会稍微向后退一步,机器人也学会了这个动作。这说明模型不仅学习了任务本身,还学习了人类完成任务的方式。

五、实验结果与分析

5.1 整体性能对比

研究人员将RT-1与两个最先进的基线模型进行了对比:

  • Gato:DeepMind提出的通用多模态智能体
  • BC-Z:谷歌之前提出的多任务机器人学习模型,用于SayCan系统

对比结果如下:

Model Seen Tasks Unseen Tasks Distractors Backgrounds
Gato 65 52 47 35
BC-Z 56 43 23 41
BC-Z XL 72 56 43 41
RT-1 (ours) 97 76 83 59

表2:RT-1与基线模型的整体性能对比(来源:论文Table 2)

从表2可以看出,RT-1在所有评估维度上都显著优于基线模型:

  • 已见任务:97%的成功率,比BC-Z高25%,比Gato高32%
  • 未见任务:76%的成功率,比次优基线高24%
  • 抗干扰能力:83%的成功率,比次优基线高36%
  • 背景鲁棒性:59%的成功率,比次优基线高18%

通俗易懂的解释:这意味着RT-1不仅能很好地完成它见过的任务,还能很好地完成它从未见过的新任务。即使在有很多干扰物体或者背景完全不同的情况下,它也能保持很高的成功率。

5.2 真实厨房场景泛化

为了测试RT-1在真实世界中的泛化能力,研究人员在两个完全不同的真实办公室厨房中进行了测试。这些厨房与训练环境在布局、光照、背景等方面都有很大差异。

测试结果如下:

Model L1 (Layout & Lighting) L2 (+ Distractors) L3 (+ New Objects & Settings)
Gato 75 50 0
BC-Z 80 60 20
BC-Z XL 85 65 25
RT-1 (ours) 95 85 60

表3:RT-1在真实厨房场景中的泛化性能(来源:论文Table 3)

L1、L2、L3代表不同难度的泛化级别:

  • L1:仅泛化到新的布局和光照条件
  • L2:在L1的基础上增加了未见的干扰物体
  • L3:在L2的基础上增加了全新的任务设置和物体

从表3可以看出,RT-1在所有难度级别上都表现最好。特别是在最困难的L3级别,RT-1的成功率达到了60%,而Gato的成功率为0%。

5.3 长时程任务性能

RT-1的高成功率和强泛化能力使得它非常适合用于长时程任务。研究人员将RT-1与SayCan系统结合,在真实厨房中执行了一系列长时程任务,如"Bring me two different sodas"、"Throw away all the items on the table"等。

这些任务平均需要9.6步才能完成,涉及平均2.4个操作技能。测试结果如下:

Model Kitchen1 Execution Kitchen2 Execution
Original SayCan 47 -
SayCan w/ Gato 33 0
SayCan w/ BC-Z 53 13
SayCan w/ RT-1 (ours) 67 67

表4:RT-1在长时程任务中的性能(来源:论文Table 6)

令人惊讶的是,RT-1在Kitchen1和Kitchen2中的执行成功率完全相同,都是67%。这说明RT-1具有非常强的环境泛化能力。在补充视频中,研究人员展示了SayCan-RT1系统能够执行长达50步的超长时间任务。

5.4 数据消融实验

为了研究数据量和数据多样性对RT-1性能的影响,研究人员进行了一系列数据消融实验:

Model % Data % Tasks Seen Tasks Unseen Tasks Distractors Backgrounds
RT-1 100 100 97 76 83 59
RT-1 51 100 97 67 42 53
RT-1 37 100 97 55 35 47
RT-1 22 100 73 46 29 41
RT-1 97 75 86 54 42 53

表5:数据消融实验结果(来源:论文Table 7)

从表5可以得出一个非常重要的结论:数据多样性比数据量更重要。减少25%的任务(同时保留97%的数据)对泛化性能的影响,与减少49%的数据(同时保留100%的任务)相当。

5.5 跨域数据吸收能力

RT-1的一个非常强大的特性是它能够吸收来自不同域的数据,包括仿真数据和来自其他机器人的数据。

5.5.1 吸收仿真数据

研究人员在真实数据的基础上,添加了518k个仿真轨迹,这些轨迹包含了一些在真实世界中从未见过的物体。测试结果如下:

Model Training Data Real Objects (Seen Skill) Sim Objects (Seen Skill) Sim Objects (Unseen Skill)
RT-1 Real Only 92 23 7
RT-1 Real + Sim 92 87 33

表6:吸收仿真数据的实验结果(来源:论文Table 9)

从表6可以看出,添加仿真数据后:

  • 真实物体的性能保持不变(92%)
  • 仿真物体的性能从23%提升到了87%
  • 仿真物体上未见技能的性能从7%提升到了33%

这说明RT-1能够有效地从仿真数据中学习,并且不会忘记在真实数据中学到的技能。

5.5.2 吸收其他机器人的数据

研究人员还尝试将来自Kuka IIWA机械臂的209k个抓取轨迹与Everyday Robots的数据混合训练。测试结果如下:

Model Training Data Classroom eval Bin-picking eval
RT-1 EDR only 92 22
RT-1 Kuka only 0 0
RT-1 EDR + Kuka 90 39

表7:吸收其他机器人数据的实验结果(来源:论文Table 5)

从表7可以看出:

  • 仅用Kuka数据训练的模型在EDR机器人上完全无法工作(0%)
  • 混合训练后,原始任务的性能仅下降了2%(从92%到90%)
  • bin-picking任务的性能提升了17%(从22%到39%),几乎翻了一倍

这说明RT-1能够从其他机器人的经验中学习,并且能够将这些经验迁移到自己的身体上。

5.6 模型消融实验

为了研究RT-1各个组件的重要性,研究人员进行了一系列模型消融实验:

Model Seen Tasks Unseen Tasks Distractors Backgrounds Inference Time (ms)
RT-1 (full) 97 76 83 59 15
RT-1 w/o big model 89 (-8) 62 (-14) 77 (-6) 53 (-6) 13.5
RT-1 w/o pre-training 84 (-13) 43 (-33) 60 (-23) 41 (-18) 15
RT-1 w/ continuous actions 43 (-54) 30 (-46) 48 (-35) 35 (-24) 16
RT-1 w/o Transformer 85 (-12) 71 (-5) 67 (-16) 59 (0) 5.9
RT-1 w/ auto-regressive actions 82 (-15) 62 (-14) 67 (-16) 59 (0) 26
RT-1 w/o history 86 (-11) 62 (-14) 50 (-33) 59 (0) 14

表8:模型消融实验结果(来源:论文Table 13)

从表8可以得出以下结论:

  1. ImageNet预训练非常重要:移除预训练后,未见任务的性能下降了33%
  2. 离散动作表示非常重要:使用连续动作后,所有维度的性能都大幅下降
  3. 历史信息对抗干扰能力很重要:移除历史信息后,抗干扰能力下降了33%
  4. 自回归动作表示没有帮助:不仅没有提升性能,还使推理速度慢了一倍

六、核心代码实现

下面是RT-1模型的核心代码实现(基于PyTorch):

import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision.models import efficientnet_b3
from transformers import UniversalSentenceEncoder

class FiLMLayer(nn.Module):
    """Feature-wise Linear Modulation layer"""
    def __init__(self, num_features, embedding_dim=512):
        super().__init__()
        self.gamma = nn.Linear(embedding_dim, num_features)
        self.beta = nn.Linear(embedding_dim, num_features)
        
        # Initialize to identity
        nn.init.zeros_(self.gamma.weight)
        nn.init.zeros_(self.gamma.bias)
        nn.init.zeros_(self.beta.weight)
        nn.init.zeros_(self.beta.bias)
    
    def forward(self, x, embedding):
        """
        Args:
            x: (batch_size, num_features, height, width)
            embedding: (batch_size, embedding_dim)
        Returns:
            modulated_x: (batch_size, num_features, height, width)
        """
        gamma = self.gamma(embedding).unsqueeze(-1).unsqueeze(-1) + 1.0
        beta = self.beta(embedding).unsqueeze(-1).unsqueeze(-1)
        return gamma * x + beta

class TokenLearner(nn.Module):
    """TokenLearner module for compressing visual tokens"""
    def __init__(self, in_channels, num_tokens=8, hidden_dim=128):
        super().__init__()
        self.num_tokens = num_tokens
        self.attention = nn.Sequential(
            nn.Conv2d(in_channels, hidden_dim, kernel_size=3, stride=1, padding=1),
            nn.GELU(),
            nn.Conv2d(hidden_dim, num_tokens, kernel_size=3, stride=1, padding=1)
        )
    
    def forward(self, x):
        """
        Args:
            x: (batch_size, in_channels, height, width)
        Returns:
            tokens: (batch_size, num_tokens, in_channels)
        """
        batch_size, channels, height, width = x.shape
        
        # Compute attention weights: (batch_size, num_tokens, height, width)
        attn = self.attention(x)
        attn = attn.view(batch_size, self.num_tokens, -1)
        attn = F.softmax(attn, dim=-1)
        
        # Reshape input: (batch_size, channels, height*width)
        x_flat = x.view(batch_size, channels, -1)
        
        # Compute tokens: (batch_size, num_tokens, channels)
        tokens = torch.bmm(attn, x_flat.transpose(1, 2))
        return tokens

class RT1(nn.Module):
    """Robotics Transformer 1 model"""
    def __init__(self, num_actions=11, num_bins=256, num_frames=6, num_tokens_per_frame=8):
        super().__init__()
        self.num_actions = num_actions
        self.num_bins = num_bins
        self.num_frames = num_frames
        self.num_tokens_per_frame = num_tokens_per_frame
        
        # Language encoder
        self.use = UniversalSentenceEncoder.from_pretrained("google/universal-sentence-encoder-large-5")
        
        # Image encoder with FiLM layers
        self.efficientnet = efficientnet_b3(pretrained=True)
        self.film_layers = nn.ModuleList()
        
        # Add FiLM layers after each MBConv block
        for i in range(len(self.efficientnet.features)):
            if isinstance(self.efficientnet.features[i], nn.Sequential):
                for j in range(len(self.efficientnet.features[i])):
                    if hasattr(self.efficientnet.features[i][j], 'block'):
                        out_channels = self.efficientnet.features[i][j].block[-1][0].out_channels
                        self.film_layers.append(FiLMLayer(out_channels))
        
        # TokenLearner
        self.token_learner = TokenLearner(in_channels=1536, num_tokens=num_tokens_per_frame)
        
        # Transformer decoder
        self.transformer = nn.TransformerDecoder(
            nn.TransformerDecoderLayer(
                d_model=1536,
                nhead=8,
                dim_feedforward=4096,
                dropout=0.1,
                activation='gelu',
                batch_first=True
            ),
            num_layers=8
        )
        
        # Action head
        self.action_head = nn.Linear(1536, num_actions * num_bins)
        
        # Positional encoding
        self.pos_encoding = nn.Parameter(torch.randn(1, num_frames * num_tokens_per_frame, 1536))
    
    def forward(self, images, instructions):
        """
        Args:
            images: (batch_size, num_frames, 3, 300, 300)
            instructions: list of strings
        Returns:
            action_logits: (batch_size, num_actions, num_bins)
        """
        batch_size = images.shape[0]
        
        # Encode language instructions
        lang_embedding = self.use(instructions)  # (batch_size, 512)
        
        # Process each frame
        all_tokens = []
        film_idx = 0
        
        for t in range(self.num_frames):
            x = images[:, t]  # (batch_size, 3, 300, 300)
            
            # Forward through EfficientNet with FiLM layers
            for i in range(len(self.efficientnet.features)):
                x = self.efficientnet.features[i](x)
                
                # Apply FiLM layer after each MBConv block
                if isinstance(self.efficientnet.features[i], nn.Sequential):
                    for j in range(len(self.efficientnet.features[i])):
                        if hasattr(self.efficientnet.features[i][j], 'block'):
                            x = self.film_layers[film_idx](x, lang_embedding)
                            film_idx += 1
            
            # Apply TokenLearner
            tokens = self.token_learner(x)  # (batch_size, num_tokens_per_frame, 1536)
            all_tokens.append(tokens)
        
        # Concatenate tokens from all frames
        all_tokens = torch.cat(all_tokens, dim=1)  # (batch_size, num_frames*num_tokens_per_frame, 1536)
        
        # Add positional encoding
        all_tokens = all_tokens + self.pos_encoding
        
        # Forward through Transformer decoder
        transformer_output = self.transformer(all_tokens, all_tokens)  # (batch_size, seq_len, 1536)
        
        # Take the last token for action prediction
        last_token = transformer_output[:, -1]  # (batch_size, 1536)
        
        # Predict action logits
        action_logits = self.action_head(last_token)  # (batch_size, num_actions*num_bins)
        action_logits = action_logits.view(batch_size, self.num_actions, self.num_bins)
        
        return action_logits

# Example usage
if __name__ == "__main__":
    model = RT1()
    
    # Dummy input
    batch_size = 2
    num_frames = 6
    images = torch.randn(batch_size, num_frames, 3, 300, 300)
    instructions = ["pick up the coke can", "move the apple to the bowl"]
    
    # Forward pass
    action_logits = model(images, instructions)
    print(f"Action logits shape: {action_logits.shape}")  # Should be (2, 11, 256)
    
    # Get predicted actions
    predicted_actions = torch.argmax(action_logits, dim=-1)
    print(f"Predicted actions: {predicted_actions}")

七、结论与展望

RT-1是机器人学习领域的一个重要里程碑。它证明了:

  1. Transformer架构可以有效地用于真实世界机器人控制
  2. 大规模、多样化的数据集是实现通用机器人学习的关键
  3. 数据多样性比数据量更重要
  4. 模型可以有效地吸收来自不同域的数据,包括仿真数据和其他机器人的数据

RT-1的成功为通用机器人学习指明了方向。未来的研究方向包括:

  • 扩大模型规模和数据集规模
  • 加入更多的模态,如触觉、声音等
  • 实现更好的跨机器人迁移
  • 结合强化学习进一步提升性能

有趣的展望:想象一下,未来你可以用自然语言告诉你的家庭机器人"帮我准备早餐",它会自动打开冰箱,拿出牛奶、面包和鸡蛋,然后为你煎鸡蛋、烤面包、倒牛奶。这一切都不需要你为每个单独的任务收集数据和训练模型。RT-1让我们离这个梦想又近了一步。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐