# 视觉-语言-动作模型

*视觉-语言-动作模型（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()
```
