# 视觉-语言-动作模型 *视觉-语言-动作模型(VLA)将视觉理解、语言理解和行动控制统一到单个神经网络中。本章涵盖VLA架构、动作标记化、RT-2、Octo、OpenVLA、预训练策略、泛化能力、与具体形态无关的模型以及基准测试。* - 在前面的文件中,我们涵盖了感知(感知世界)和机器人学习(控制身体)。传统上,这些是独立的流程:感知模块检测物体,语言模块解释指令,控制模块生成动作。每个模块独立设计、训练和调试。 - **视觉-语言-动作模型(VLA)**将这一流程压缩为单个神经网络。模型接收图像(视觉)和自然语言指令(语言),并输出电机命令(动作)。一个模型,端到端。 - 这沿袭了我们在第10章看到的统一趋势:正如多模态模型将视觉和语言理解合并到一个架构中一样,VLA将这一趋势扩展到物理行动。关键洞见在于,语言为指定任务提供了自然、灵活的接口("拿起红色杯子放到架子上"),而大规模预训练的视觉-语言模型已经理解图像和指令。 ## 从视觉-语言到行动 - 回顾第10章,**视觉-语言模型(VLM)**如LLaVA和Flamingo接收图像和文本作为输入,并生成文本作为输出。它们理解场景、回答问题、遵循指令——全部通过语言完成。 - VLA提出的问题是:如果输出不是文本而是**机器人动作**呢?模型不再生成"红色杯子在桌子的左侧",而是生成一系列电机命令,驱动手臂去抓取那个杯子。 - 关键的架构洞见是,动作可以像单词一样表示为标记。如果VLM使用下一个标记预测逐个生成语言标记,那么VLA以同样的方式生成动作标记。Transformer从根本上并不关心输出标记表示"杯子"还是"将夹爪向前移动2厘米"。 - 这重新定义了机器人控制为序列建模问题,这正是transformer擅长的(第7章)。模型学习映射:(图像观测,语言指令)$\\to$(动作标记序列)。 ## VLA架构 ![VLA架构:相机图像和语言指令被编码为标记,由LLM主干网络处理,并解码为机器人动作](../images/vla_architecture.svg) - 典型的VLA有三个组件: - **视觉编码器**:将相机图像处理为视觉标记。通常是预训练的ViT(第8章)或SigLIP编码器(第10章)。图像被分割成块,每个块嵌入为一个标记,与标准视觉transformer完全一样。 - **语言模型主干网络**:一个预训练的LLM(例如LLaMA、PaLM),处理交错的视觉标记和语言标记序列。这就是推理发生的地方:模型通过同时关注指令和视觉特征来理解"拿起**红色**杯子"。 - **动作头**:将LLM的输出映射到机器人动作。可以是一个简单的MLP,将最后的隐藏状态映射到连续动作值,或者是一种将动作转换为离散标记的方案,由LLM的现有词汇表来预测。 - 架构看起来像: $$\\text{图像} \\xrightarrow{\\text{ViT}} \\text{视觉标记} \\quad + \\quad \\text{指令} \\xrightarrow{\\text{分词器}} \\text{语言标记} \\quad \\xrightarrow{\\text{LLM}} \\quad \\text{动作标记}$$ - 视觉标记和语言标记被拼接(或交错)并输入到transformer主干网络,后者自回归地生成动作标记。这与VLM(第10章)的架构相同,但输出模态是动作而非文本。 ## 动作标记化 - 机器人动作是连续的:关节速度、末端执行器位置、夹爪宽度。这些必须转换为离散标记才能让LLM生成。 ![动作标记化:连续动作值被分箱为离散索引,LLM将其作为标记生成](../images/action_tokenisation.svg) - 最简单的方法是**均匀离散化**。每个动作维度被划分为$N$个箱,覆盖有效值范围。例如,如果x方向速度范围从-0.1到0.1米/秒,使用256个箱,每个箱代表$\\frac{0.2}{256} \\approx 0.8$毫米/秒。动作值被映射到最近的箱索引,该索引成为一个标记。 - 对于7个动作维度(6自由度+夹爪)和每个维度256个箱,动作词汇表有$7 \\times 256 = 1792$个标记。这些被添加到LLM现有的文本词汇表中。模型每个维度生成一个动作标记,自回归地,就像生成单词一样。 - **动作分块**一次预测多个未来时间步,而不是单个动作。如果块大小为$H$,模型输出$H \\times d$个标记(其中$d$是动作维度)。这对于平滑、时间连贯的运动至关重要。一次预测一步会产生抖动行为,因为每次预测都是独立的。分块迫使模型规划一个短轨迹,捕获时间结构。 - 更复杂的方法使用**学习型标记化**,通过VQ-VAE(第10章)。VQ-VAE编码器将连续动作序列映射到离散码本索引序列,解码器从这些索引重建连续动作。LLM然后生成码本索引,而不是均匀分箱的值。这类似于图像分词器(第10章)如何将视觉信息压缩为紧凑的离散编码。 ## 关键VLA模型 - **RT-2**(机器人Transformer 2,Google DeepMind)是第一个大规模VLA。它使用预训练的VLM(PaLM-E或PaLI-X,参数高达55B)并在机器人示范数据上微调。动作表示为文本字符串:标记序列"1 128 91 241 5 101 127"编码了一个7维动作(每个数字是箱索引)。 - RT-2展示了一个显著特性:来自VLM主干网络的**涌现能力**迁移到了机器人领域。模型可以遵循涉及从未在机器人数据中见过的概念的指令(例如,"将香蕉移动到以A开头的国家"需要视觉物体识别+世界知识+行动)。VLM的语言理解和视觉推理"免费"获得。 - RT-2的局限性在于它是在单个机器人形态(特定的手臂和特定的夹爪)的数据上训练的。它不能泛化到不同的机器人。 - **Octo**(加州大学伯克利分校)是一个开源的、**与具体形态无关**的VLA,设计用于跨不同机器人平台工作。关键创新包括: - **扩散动作头**,而不是自回归标记预测。动作头获取transformer的输出,并通过去噪扩散过程(第8章)生成动作。这自然地处理了多模态动作分布(见下图),即存在多个有效的任务完成方式。 ![多模态动作分布:回归将两条有效路径平均为一条穿过障碍物的无效路径](../images/multimodal_action_distribution.svg) - **灵活的观测和动作空间**:Octo为不同的机器人配置使用特定于任务的标记化器。它在Open X-Embodiment数据集上预训练,该数据集包含来自22种不同机器人形态的示范。 - **高效微调**:Octo只需100个示范就可以微调到新机器人,使其适用于数据有限的实验室。 - **OpenVLA**(斯坦福大学、加州大学伯克利分校)采用微调现有开源VLM(基于Llama)用于机器人技术的方法。它使用7B参数主干网络、均匀动作标记化(每个维度256个箱),并在Open X-Embodiment数据上训练。其优势在于简单性:架构是标准的VLM,动作标记被附加到词汇表中,使其易于使用现有的LLM基础设施进行训练和部署。 - **$\\pi_0$**(Physical Intelligence)代表了当前最高水平。它使用预训练的VLM主干网络和**流匹配**动作头(第8章)。流匹配通过学习一个速度场将噪声传输到动作分布来生成动作,产生平滑、时间连贯的动作轨迹。$\\pi_0$展示了卓越的通用性,在多种机器人形态(包括双臂操作和灵巧手控制)上执行任务。 ## 预训练配方 - VLA极大地受益于预训练的VLM主干网络,这些网络已经理解视觉场景和语言。训练流程通常分为几个阶段: 1. **VLM预训练**:在数十亿来自互联网的图像-文本对(CLIP、SigLIP、LLaVA风格的训练,如第10章所述)上训练(或使用现成的)视觉-语言模型。 2. **机器人数据协同训练**:在互联网数据和机器人示范数据的混合上微调VLM。互联网数据防止视觉和语言理解的灾难性遗忘,而机器人数据教授动作生成。混合比例很重要:机器人数据过多会降低语言理解,过少则无法学习动作。 3. **特定任务微调**:可选地在特定任务或机器人的示范上进行微调,通常使用LoRA(第10章)保持可训练参数数量较少。 - 机器人数据的数量比互联网数据少数个数量级。VLM可能在上数十亿张图像上预训练,但最大的机器人数据集(Open X-Embodiment)在所有形态上只有数百万帧。这种数据稀缺性正是从预训练VLM开始至关重要的原因:视觉和语言表示可以迁移,只有动作映射需要从有限的机器人数据中学习。 ## 泛化能力 - VLA的承诺是**泛化**:执行训练中未见的任务,使用未见过的物体,在未见过的环境中,遵循未见过的指令。 - VLA沿多个轴进行泛化: - **新颖物体**:VLM主干网络从互联网预训练中识别物体。如果模型从网络图像中知道"螺丝刀"长什么样,即使没有机器人示范涉及螺丝刀,它也能操作螺丝刀。 - **新颖指令**:组合语言理解使模型能够遵循已知概念的新组合。"将蓝色方块堆叠在绿色方块上"即使训练只展示了堆叠红色方块也能工作,因为模型从语言预训练中理解了颜色形容词。 - **新颖环境**:在一定程度上,VLA跨视觉域(不同的桌子、光照、背景)迁移,因为视觉编码器在多样化的网络图像上预训练。但这有局限性:在实验室训练的机器人可能在杂乱厨房中遇到困难。 - **新颖形态**:这是最难的轴。不同机器人有不同的动作空间(关节角度 vs. 末端执行器速度)、不同的传感器(腕部相机 vs. 头顶相机)和不同的物理能力。与形态无关的模型如Octo和$\\pi_0$通过灵活的标记化器和跨多种机器人类型的预训练来解决这一问题。 - 泛化能力通过**保留任务**进行评估:机器人被要求执行从未训练过的任务。在新颖任务上50-80%的成功率被认为是强劲的结果,而在分布内任务上成功率通常>90%。随着模型规模扩大和机器人数据集增长,这一差距正在缩小。 ## 与形态无关的模型 - 该领域正朝着"一个模型,多种机器人"的方向发展。不再为每个机器人训练单独的策略,而是单个VLA处理多种形态。 - 这需要解决**动作空间不匹配**问题。一个7自由度手臂带平行夹爪有7个动作维度。双臂设置是14个。四足机器人有12个。类人机器人有30个以上。动作标记化必须足够灵活以处理所有这些。 - 解决方案包括: - **填充动作向量**:使用最大的动作空间,较小的用零填充。 - **每种形态的动作头**:共享的transformer主干网络,每种机器人类型有单独的小型MLP。 - **归一化动作表示**:在共同框架中表示所有动作(如世界坐标系中的末端执行器速度),使产生类似末端执行器运动的不同机器人共享相同的动作标记。 - 共享主干网络学习通用的视觉和语言理解,加上通用的操作策略(从上方接近、对齐物体、闭合夹爪)。特定于形态的组件只需要将这些高层策略转化为具体的电机命令。 ## 基准测试与评估 - 评估VLA具有独特的挑战性,因为它需要物理机器人实验(或高保真仿真)。 - **SIMPLER**(机器人学习模拟操作策略评估)提供了标准化的仿真环境,无需物理硬件即可比较VLA性能。它与现实世界的成功率相关性良好,并实现了可复现的基准测试。 - **现实世界评估**仍然是金标准。典型协议: 1. 定义一组具有明确成功标准的任务(物体到达目标位置、选择正确物体、在时限内完成任务)。 2. 每次任务运行$N$次试验(通常10-50次)。 3. 报告成功率及置信区间。 4. 包括保留任务(从未训练过的)以衡量泛化能力。 - **Open X-Embodiment**数据集和基准测试汇总了来自22个机构、跨越多个机器人平台的机器人数据。它提供了共享示范的标准格式和用于跨形态迁移的通用评估套件。 ## 编程任务(使用CoLab或notebook) 1. 实现动作标记化:将连续动作离散化为箱并重建。观察量化误差随箱数量的变化。 ```python import jax.numpy as jnp # 连续动作:7个维度(6自由度+夹爪) action_true = jnp.array([0.023, -0.051, 0.012, 0.1, -0.03, 0.005, 0.8]) action_min = jnp.array([-0.1, -0.1, -0.1, -0.5, -0.5, -0.5, 0.0]) action_max = jnp.array([ 0.1, 0.1, 0.1, 0.5, 0.5, 0.5, 1.0]) for n_bins in [16, 64, 256, 1024]: # 标记化:将连续值映射为箱索引 normalised = (action_true - action_min) / (action_max - action_min) tokens = jnp.clip((normalised * n_bins).astype(int), 0, n_bins - 1) # 去标记化:将箱索引映射回连续值 reconstructed = (tokens + 0.5) / n_bins * (action_max - action_min) + action_min error = jnp.linalg.norm(action_true - reconstructed) print(f"箱数={n_bins:4d} 标记={tokens} 误差={error:.6f}") ``` 2. 模拟动作分块与单步预测的比较。生成平滑轨迹,向单步预测添加噪声,并与分块预测比较。 ```python import jax import jax.numpy as jnp import matplotlib.pyplot as plt # 真实平滑轨迹(例如,伸手动作) t = jnp.linspace(0, 2 * jnp.pi, 100) gt_x = jnp.sin(t) gt_y = 1 - jnp.cos(t) # 单步:每次预测有独立噪声 rng = jax.random.PRNGKey(42) noise_ss = jax.random.normal(rng, (100, 2)) * 0.05 single_step = jnp.stack([gt_x, gt_y], axis=1) + noise_ss # 单步误差累积漂移 single_step_cumulative = jnp.cumsum(noise_ss, axis=0) * 0.3 + jnp.stack([gt_x, gt_y], axis=1) # 分块(块大小=10):块内噪声关联,更平滑 chunk_size = 10 rng2 = jax.random.PRNGKey(7) chunks = [] for i in range(0, 100, chunk_size): chunk_noise = jax.random.normal(jax.random.fold_in(rng2, i), (2,)) * 0.05 chunk = jnp.stack([gt_x[i:i+chunk_size], gt_y[i:i+chunk_size]], axis=1) chunks.append(chunk + chunk_noise) chunked = jnp.concatenate(chunks, axis=0) plt.figure(figsize=(8, 4)) plt.plot(gt_x, gt_y, "k-", linewidth=2, label="真实轨迹") plt.plot(single_step_cumulative[:, 0], single_step_cumulative[:, 1], "r-", alpha=0.7, label="单步(漂移)") plt.plot(chunked[:, 0], chunked[:, 1], "b-", alpha=0.7, label="分块(稳定)") plt.legend(); plt.axis("equal"); plt.grid(True) plt.title("动作分块 vs 单步预测") plt.show() ``` 3. 可视化VLA动作分布如何是多模态的。使用简单的2D高斯混合来展示为什么扩散/流匹配动作头优于回归。 ```python import jax import jax.numpy as jnp import matplotlib.pyplot as plt # 绕过障碍物的两种有效方式:左边或右边 rng = jax.random.PRNGKey(0) k1, k2 = jax.random.split(rng) mode1 = jax.random.normal(k1, (200, 2)) * 0.15 + jnp.array([-1.0, 0.5]) mode2 = jax.random.normal(k2, (200, 2)) * 0.15 + jnp.array([ 1.0, 0.5]) samples = jnp.concatenate([mode1, mode2]) # 回归预测均值 = 模态的均值(无效!) mean_pred = samples.mean(axis=0) plt.figure(figsize=(6, 5)) plt.scatter(samples[:, 0], samples[:, 1], s=5, alpha=0.5, label="真实动作分布") plt.plot(*mean_pred, "rx", markersize=15, markeredgewidth=3, label="回归均值(无效!)") plt.plot(-1, 0.5, "g^", markersize=12, label="模态1(向左)") plt.plot(1, 0.5, "b^", markersize=12, label="模态2(向右)") plt.legend(); plt.grid(True) plt.title("多模态动作:为什么回归失败") plt.xlabel("动作维度1"); plt.ylabel("动作维度2") plt.show() ```