VLA:视觉-语言-动作

把"看、听、动"三条通路合为一体。这就是具身智能的终局形态——让机器人看着场景、听懂指令、自主完成动作序列。

4-6 周
8 个章节
6 个代码示例
4 个验收实验

本阶段目录

  1. 行为克隆:示教即编程
  2. ACT:一次预测未来多步
  3. Diffusion Policy:处理多模态
  4. 遥操作数据采集
  5. VLA端到端:RT-2路线
  6. 具身智能基础模型前沿
  7. 真机部署实战

1. 行为克隆:示教即编程

对照已有知识

示教编程你用了多年——手动引导机器人做一遍,它记住路径回放。行为克隆是它的AI版本:网络不仅记忆,还能泛化到未见过的场景。

人类遥控机器人完成任务→记录(图像,关节)对→训练网络从图像预测动作。

class BCPolicy(nn.Module):
    """图像+本体感知 → 关节动作"""
    def __init__(self):
        super().__init__()
        self.vision = nn.Sequential(  # 简化CNN
            nn.Conv2d(3,32,3,2), nn.ReLU(),
            nn.Conv2d(32,64,3,2), nn.ReLU(),
            nn.AdaptiveAvgPool2d((1,1)), nn.Flatten()
        )
        self.proprio = nn.Linear(14, 64)  # 7关节角度+7速度
        self.head = nn.Sequential(
            nn.Linear(128+64,256), nn.ReLU(),
            nn.Linear(256,7)  # 7维关节动作
        )
    def forward(self, img, prop):
        return self.head(torch.cat([self.vision(img), self.proprio(prop)], -1))

BC的致命缺陷

分布偏移:训练时见过的轨迹都是"好轨迹",但推理时一旦偏离,小误差就会滚雪球。因为BC从未见过偏离后的状态,无法纠正。这就是为什么单纯BC通常不够。

2. ACT:一次预测未来多步

ACT (Action Chunking Transformer) 不预测单步动作,而是一次预测未来K步的动作序列(称为"动作块"),然后执行前几步后重新预测。这大幅减少了累积误差。

# ACT核心结构
class ACT(nn.Module):
    def __init__(self, chunk_size=20, d_model=256):
        super().__init__()
        self.encoder = nn.TransformerEncoder(...)  # 编码观测
        self.decoder = nn.TransformerDecoder(...)  # 自回归生成动作
        self.query = nn.Parameter(torch.randn(chunk_size, d_model))
    def forward(self, obs):
        mem = self.encoder(obs); return self.decoder(self.query, mem)
🔧 工程直觉:ACT就像给你未来20个时间步的控制量一起优化。你先执行前5步,利用新的观测重新预测。这种"滚动时域"策略让系统对环境变化和自身误差都有更强的鲁棒性。

3. Diffusion Policy:处理多模态

同一张图片,你可以抓杯子的把手,也可以抓杯身——两者都是正确的。BC用MSE会学出均值(无效动作),而Diffusion Policy学习整个动作分布 $p(a|s)$,自然地处理了多模态问题。

$a_t \sim p_\theta(a | s)$ —— 从学习到的条件分布中采样
class DiffusionPolicy(nn.Module):
    def __init__(self, n_steps=100):
        super().__init__()
        self.noise_net = UNet1D(...)  # 从噪声动作预测噪声
        self.betas = self._cosine_schedule(n_steps)  # 噪声调度

    def sample(self, obs):
        """从纯噪声逐步去噪,生成动作"""
        x = torch.randn(obs.shape[0], horizon, act_dim)
        for t in reversed(range(self.n_steps)):
            noise_pred = self.noise_net(x, obs, t)
            x = self._denoise_step(x, noise_pred, t)  # 去噪一步
        return x

4. 遥操作数据采集

公开数据集覆盖的任务有限。你必须自己采数据。

方案成本精度适用
键盘控制末端0元2D推送、简单抓取
SpaceMouse 3D鼠标~500元3D操作入门
ALOHA 双臂遥操作~2万元双臂精细操作
VR手柄 + IK映射~3000元中高通用3D操作
# 数据记录伪代码
class DemoRecorder:
    def record(self, task):
        obs = env.reset()
        while not done:
            action = self.get_input()  # 键盘/SpaceMouse/VR
            next_obs, _, done = env.step(action)
            self.buffer.append((obs, action, next_obs))
            obs = next_obs
        np.savez_compressed(f'{task}_demo.npz', **buffer)

5. VLA端到端:RT-2路线

RT-2 核心思路

用大规模图文数据预训练VLM→把动作离散化为文本token→用机器人数据微调→输入图像+指令→输出动作序列。一个模型同时"看懂"和"动手"。

图像 + "pick up the red apple" → VLM → "Δx=+3 Δy=-2 Δz=+1 grip=close" → 反离散化 → 连续动作
def discretize(action, bins=256):
    """连续动作→离散token"""
    norm = (action - lo)/(hi - lo)
    return int(np.clip(norm*bins, 0,bins-1))

# 7维动作 × 256 bins = 7个vocab=256的"词"
# VLM生成这7个token → 反离散化 → 机器人执行

验收实验

  • 键盘遥操作采集50条"推送方块"演示,训练BC,成功率 > 60%
  • 用ACT在仿真Franka上完成方块堆叠,3块成功率 > 50%
  • 对比BC和Diffusion Policy在"插入"任务上的成功率差异
  • 分析演示数量(10/50/200条)对策略质量的影响,产出实验报告

上一阶段:← 机器人控制与仿真  |  返回首页

拓展资源:VLA与模仿学习

GitHub 仓库

具身智能基础模型:2024-2025 前沿

这个领域发展极快

2024-2025年,具身智能基础模型呈现爆发式增长。以下是面试和工作中必须了解的模型:

模型机构核心贡献关键思想
RT-2Google DeepMindVLM→动作token大规模图文预训练+机器人微调,首次展示VLA的规模化可行性
OctoUC Berkeley/Stanford/CMU开源通用机器人模型Transformer-based,跨机器人、跨任务、跨场景泛化
π₀ (Pi Zero)Physical Intelligence通用机器人基础模型从互联网数据和机器人数据联合训练,处理各种操作任务
GROOTNVIDIA人形机器人基础模型多模态(语言+视频+动作),从人类演示直接学习
RDT-1B清华1B参数扩散操作模型最大规模的扩散策略,在双臂操作上表现优异
# 使用 Octo 进行零样本机器人控制
# Octo 是一个预训练的通用机器人策略模型
# 输入:图像 + 任务描述 → 输出:动作

from octo import OctoModel
model = OctoModel.from_pretrained("octo-small")

# 给定一张桌面图片和自然语言指令
observation = {"image_primary": img, "timestep_pad_mask": mask}
task = model.create_tasks(texts=["pick up the red block"])

# 模型直接输出机器人动作
action = model.sample_actions(observation, task, rng=...)
🔧 转行者建议:不要试图从零训练 VLA 模型(那需要数百万美元的计算资源)。学习使用这些预训练模型——理解它们的局限性,知道如何微调,知道何时需要自己采集领域数据。

真机部署:从仿真到实物

真机部署的隐藏成本

仿真中 90% 成功率 → 真机上可能不到 50%。这不是你的模型有问题,而是真实世界有太多未知:延迟、标定误差、柔性、摩擦变化、传感器死区。你需要学会诊断和修复这些问题。

部署检查清单

类别检查项常见问题
通信ROS话题延迟、带宽图像传输延迟 > 100ms → 控制失稳
标定手眼标定、相机内参5mm标定误差 → 抓取失败率翻倍
控制频率策略推理速度Diffusion Policy 推理可能 > 500ms
安全关节限位、力矩限制RL策略可能产生超限指令
初始化每次实验的起始条件物体位置微小变化导致策略完全失效

调试策略

# 真机调试的口袋方法
def debug_deployment(policy, env, n_trials=10):
    failures = {'collision': 0, 'timeout': 0, 'wrong_action': 0,
                'perception': 0, 'undetected': 0}
    for i in range(n_trials):
        obs = env.reset()
        while not done:
            action = policy(obs)
            obs, reward, done, info = env.step(action)
            if info['collision']: failures['collision'] += 1
        # 分类失败原因
        categorize_failure(failures)
    return failures

# 经验法则:
# collision >30% → 加力/力矩限幅
# timeout >40% → 检查策略探索是否不足
# undetected >20% → 检查感知模块(照明/背景)

真机部署经验

  • 先在仿真中把成功率做到 > 95%,再上真机
  • 真机上先做"慢速模式"(action scaling),逐步加速
  • 每个实验都录制视频和传感器日志,失败后能复盘
  • 保持一个"已知可工作"的baseline,每次改动后对比

4. 遥操作数据采集实战

为什么需要遥操作

模仿学习需要大量高质量的机器人操作数据。强化学习奖励函数设计困难且训练慢。最实用的路径:人遥操作采集演示→模仿学习训练策略→策略部署

数据格式

# 每条演示 = (观测历史, 动作历史)
demo = {{
    "observations": [  # T个时步,每步含:
        {{"joint_pos": [0.1, -0.5, ...],  # 关节位置
         "images": [cam1, cam2, ...],   # 多视角图像
         "ee_pose": [x,y,z,rx,ry,rz]}}, # 末端位姿
    ],
    "actions": [                    # T个时步,每步含:
        [0.02, -0.01, 0.0, ...],   # 关节增量 或 末端速度
    ],
    "task": "pick_red_block",
}}

ALOHA硬件方案

ALOHA (A Low-cost Open-source Hardware system for bimanual teleoperation): 两个6轴从臂执行操作,两个6轴主臂用于遥操作。成本约$20K——远低于Franka Research 3的$25K+。

验收实验

  • 采集50+条有效演示数据(单任务)
  • 数据包含至少2个视角的RGB图像和关节状态
  • 验证数据质量:回放演示,观察机器人能否复现

5. VLA基础模型深度解析

RT-2架构

RT-2的核心创新:将机器人动作token化,与文本token一起输入VLM进行联合训练。不再是"先理解再执行"的两步,而是端到端的"看到图像+听到指令→输出动作"。

Octo: 开源通用机器人策略

Octo在Open X-Embodiment数据集上预训练(100万+条机器人轨迹),可以作为下游任务的初始化权重。它的设计理念:像GPT-4可以零样本完成各种NLP任务一样,Octo可以零样本或小样本完成各种操作任务。

模型模态训练数据开源
RT-2VLM→动作互联网+机器人
Octo图像+语言→动作Open X-Embodiment
π₀多模态→动作多种机器人部分
GROOT仿真→Sim2Real大规模仿真

验收实验

  • 下载Octo预训练模型,在新任务上零样本测试
  • 用自己采集的数据微调Octo(LoRA),对比微调前后成功率

6. ACT深度解析——Attention与Chunking的数学

为什么预测单步动作不够

行为克隆(BC)预测单步动作的最大问题:错误会累积(compounding error)。如果第一步偏了1mm,由于训练数据中没有"偏了1mm后"的状态,策略不知道该怎么做。ACT的解决方案:一次预测未来K步的动作序列——用Transformer的自注意力机制建模动作间的时序依赖。

ACT的核心公式

$$\hat{a}_{t:t+K} = \text{TransformerDecoder}(\text{Encoder}(o_t), \text{QueryChunk})$$

其中$o_t$是当前观测(图像+本体感知),$\hat{a}_{t:t+K}$是预测的未来K个动作。QueryChunk是可学习的位置编码向量,告诉Decoder"请生成第0...K-1步的动作"。

Chunk overlapping——训练和推理的桥接

训练时:从演示数据中随机截取$K$步动作作为ground truth
推理时:预测$K$步,但只执行前$k_{exec} < K$步,然后重新预测。$k_{exec}$越小越安全(纠正机会多),但推理开销越大。

class ACTPolicy(nn.Module):
    """完整的ACT (Action Chunking Transformer) 实现"""
    
    def __init__(self, obs_dim, act_dim, chunk_size=100, d_model=512,
                 n_heads=8, n_layers=6, n_encoder_layers=4):
        super().__init__()
        self.chunk_size = chunk_size
        
        # 观测编码器(CNN+MLP混合)
        self.encoder = ACTEncoder(obs_dim, d_model, n_encoder_layers)
        
        # 动作解码器(生成整个chunk的动作)
        decoder_layer = nn.TransformerDecoderLayer(
            d_model=d_model, nhead=n_heads,
            dim_feedforward=2048, dropout=0.1, batch_first=True)
        self.decoder = nn.TransformerDecoder(decoder_layer, n_layers)
        
        # 位置编码(可学习的query chunk)
        self.query_chunk = nn.Parameter(torch.randn(1, chunk_size, d_model))
        
        # 输出头
        self.action_head = nn.Linear(d_model, act_dim)
    
    def forward(self, obs):
        # obs: (B, T_obs, obs_dim) — 历史观测序列
        B = obs.shape[0]
        memory = self.encoder(obs)  # (B, T_obs, d_model)
        
        # 扩展query chunk到batch维度
        query = self.query_chunk.expand(B, -1, -1)  # (B, chunk_size, d_model)
        
        # 交叉注意力:query(chunk) attends to memory(observations)
        output = self.decoder(tgt=query, memory=memory)  # (B, chunk_size, d_model)
        
        # 每个chunk位置预测一个动作
        actions = self.action_head(output)  # (B, chunk_size, act_dim)
        return actions
    
    def predict_action(self, obs_history, exec_steps=10):
        """推理:预测chunk,执行部分步,然后滚动窗口"""
        chunk = self(obs_history)  # (1, chunk_size, act_dim)
        return chunk[0, :exec_steps]  # 只执行前exec_steps个动作

训练ACT的完整pipeline

def train_act(policy, dataloader, optimizer, epochs=200, device='cuda'):
    """ACT训练循环——L1 Loss + 数据增强"""
    policy.train()
    
    for epoch in range(epochs):
        total_loss = 0
        for batch in dataloader:
            obs, gt_actions = batch  # gt_actions: (B, chunk_size, act_dim)
            obs, gt_actions = obs.to(device), gt_actions.to(device)
            
            # 数据增强(时序)
            if np.random.random() > 0.5:
                # 随机截取部分chunk进行训练
                start = np.random.randint(0, gt_actions.shape[1] // 2)
                gt_actions = gt_actions[:, start:start + policy.chunk_size]
            
            pred_actions = policy(obs)
            loss = nn.L1Loss()(pred_actions, gt_actions)
            
            optimizer.zero_grad()
            loss.backward()
            torch.nn.utils.clip_grad_norm_(policy.parameters(), 1.0)
            optimizer.step()
            total_loss += loss.item()
        
        if epoch % 20 == 0:
            print(f"Epoch {epoch}: Avg Loss = {total_loss / len(dataloader):.4f}")

对比:为什么ACT > BC

BC每步独立预测——误差复合增长($O(T^2)$);ACT预测时序相关的多步——

Transformer注意力机制隐式建模了动作间的平滑约束,显著降低compounding error。

7. Diffusion Policy深入——去噪扩散如何生成动作

从图像生成到动作生成

扩散模型在图像生成领域取得了巨大成功(Stable Diffusion, DALL-E)。核心思想是一样的:学习逆向去噪过程——从纯噪声逐步去噪,最终得到干净的动作序列

DDPM的核心公式回顾

$$q(x_t | x_{t-1}) = \mathcal{N}(x_t; \sqrt{1-\beta_t}x_{t-1}, \beta_t I)$$
$$p_\theta(x_{t-1} | x_t) = \mathcal{N}(x_{t-1}; \mu_\theta(x_t, t), \Sigma_\theta(x_t, t))$$

前向过程$q$:逐步向数据添加高斯噪声(可解析计算任意步$t$的$x_t$)。
逆向过程$p_\theta$:神经网络学习从$x_t$预测$x_{t-1}$——等价于预测添加的噪声$\epsilon$。

Diffusion Policy的三种噪声调度

调度方案公式特点适用场景
线性调度$\beta_t = \beta_{start} + t(\beta_{end} - \beta_{start})/T$简单均匀通用
余弦调度$\bar{\alpha}_t = \cos^2(\frac{t/T + s}{1+s} \cdot \frac{\pi}{2})$早期噪声少,后期噪声多需要更多去噪步的精细任务
Squaredcosine余弦调度的变体更平滑灵巧操作
class DiffusionPolicy(nn.Module):
    """基于DDPM的Diffusion Policy实现"""
    
    def __init__(self, obs_dim, act_dim, act_horizon=16, n_diffusion_steps=100):
        super().__init__()
        self.act_horizon = act_horizon
        self.n_diffusion_steps = n_diffusion_steps
        
        # 噪声预测网络(UNet 1D或Transformer)
        self.noise_pred_net = ConditionalUnet1D(
            input_dim=act_horizon * act_dim,
            global_cond_dim=obs_dim,
            diffusion_step_embed_dim=128,
            down_dims=[512, 1024, 2048],
            kernel_size=5,
            n_groups=8,
        )
        
        # 噪声调度
        betas = self._cosine_beta_schedule(n_diffusion_steps)
        alphas = 1.0 - betas
        self.register_buffer('betas', betas)
        self.register_buffer('alphas', alphas)
        self.register_buffer('alphas_cumprod', torch.cumprod(alphas, dim=0))
    
    def _cosine_beta_schedule(self, timesteps, s=0.008):
        """余弦噪声调度——最优的默认选择"""
        steps = timesteps + 1
        x = torch.linspace(0, timesteps, steps)
        alphas_cumprod = torch.cos(((x / timesteps) + s) / (1 + s) * torch.pi * 0.5) ** 2
        alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
        betas = 1 - (alphas_cumprod[1:] / alphas_cumprod[:-1])
        return torch.clamp(betas, 0.0001, 0.02)
    
    def forward(self, obs_cond, gt_actions=None):
        """训练时:给定真实动作,采样噪声水平t,预测噪声"""
        B = obs_cond.shape[0]
        if gt_actions is not None:
            # 训练模式
            # 1. 随机采样噪声水平
            t = torch.randint(0, self.n_diffusion_steps, (B,), device=obs_cond.device)
            
            # 2. 采样噪声并添加到动作
            noise = torch.randn_like(gt_actions)
            noisy_actions = self._add_noise(gt_actions, noise, t)
            
            # 3. 预测噪声
            noise_pred = self.noise_pred_net(noisy_actions, t, global_cond=obs_cond)
            
            # 4. MSE Loss
            loss = nn.MSELoss()(noise_pred, noise)
            return loss
        
        # 推理模式: DDPM采样
        return self._ddpm_sample(obs_cond)
    
    def _ddpm_sample(self, obs_cond):
        """DDPM逆向过程:从纯噪声逐步去噪生成动作"""
        B = obs_cond.shape[0]
        device = obs_cond.device
        x = torch.randn(B, self.act_horizon * act_dim, device=device)
        
        for t in reversed(range(self.n_diffusion_steps)):
            t_batch = torch.full((B,), t, device=device, dtype=torch.long)
            noise_pred = self.noise_pred_net(x, t_batch, global_cond=obs_cond)
            
            alpha = self.alphas[t]
            alpha_cumprod = self.alphas_cumprod[t]
            beta = self.betas[t]
            
            # DDPM后验均值
            x = (1 / torch.sqrt(alpha)) * (
                x - (beta / torch.sqrt(1 - alpha_cumprod)) * noise_pred
            )
            
            # 加回方差项(t > 0时)
            if t > 0:
                noise = torch.randn_like(x)
                sigma = torch.sqrt(beta)
                x = x + sigma * noise
        
        return x.reshape(B, self.act_horizon, -1)
🔧 工程连接:扩散模型的噪声调度和你做热处理工艺的温度曲线类似——加热速率(噪声水平)和保温时间(去噪步数)决定了最终材料性能(生成质量)。余弦调度就是你熟悉的"缓慢升温+快速冷却"曲线。

8. RT-2动作Token化——把连续动作变成语言模型能理解的token

核心挑战:连续值 vs 离散token

大语言模型(LLM)只能处理离散token。但机器人动作是连续值(关节角度0.523rad、末端速度0.1m/s...)。RT-2通过离散化来解决这个gap。

RT-2的动作离散化方案

将每个动作维度(6D末端位姿)均匀离散化为256个bin:

$$\text{token\_id}_i = \lfloor \frac{a_i - a_{min}}{a_{max} - a_{min}} \times 255 \rfloor$$
class ActionTokenizer:
    """连续动作 ↔ 离散token的互相转换"""
    
    def __init__(self, action_min, action_max, n_bins=256):
        self.action_min = np.array(action_min)
        self.action_max = np.array(action_max)
        self.n_bins = n_bins
        self.action_dim = len(action_min)
    
    def encode(self, actions):
        """连续动作 → token IDs (0-255)"""
        # 归一化到[0, 1]
        norm_actions = (actions - self.action_min) / (self.action_max - self.action_min)
        norm_actions = np.clip(norm_actions, 0, 1)
        
        # 量化为bin索引
        token_ids = (norm_actions * (self.n_bins - 1)).astype(int)
        
        # 转换为LLM词汇表中的token ID
        # 动作token从词表末尾分配: e.g., 32000-32255
        action_vocab_start = 32000
        llm_token_ids = action_vocab_start + token_ids
        return llm_token_ids
    
    def decode(self, token_ids, action_vocab_start=32000):
        """token IDs → 连续动作"""
        bin_ids = token_ids - action_vocab_start
        norm_actions = bin_ids / (self.n_bins - 1)
        actions = norm_actions * (self.action_max - self.action_min) + self.action_min
        return actions

# 示例:6-DOF机械臂末端位姿
action_min = [-0.5, -0.5, 0.0, -np.pi, -np.pi, -np.pi]  # xyz + rpy
action_max = [0.5, 0.5, 0.8, np.pi, np.pi, np.pi]
tokenizer = ActionTokenizer(action_min, action_max)

# 动作 → token → 文本 → LLM → token → 动作
action = np.array([0.3, -0.1, 0.5, 0.0, 0.0, 1.57])
tokens = tokenizer.encode(action)
decoded_action = tokenizer.decode(tokens)
print(f"量化误差: {np.abs(action - decoded_action).max():.4f}")
# 输出: 量化误差: ~0.003 (256 bins → 精度约为量程的0.4%)

RT-2的完整推理流程

输入:"Pick up the red can" + 当前图像(通过ViT编码为token)
LLM前向:图像token + 文本token → 自回归生成
输出:检测到特殊动作token → 提取连续6组token → decode为6D位姿
执行:控制器将位姿发送到机械臂

验收实验

  • 实现完整的ACT训练pipeline,用模拟数据验证chunk预测的有效性
  • 实现Diffusion Policy的DDPM采样,可视化去噪过程的动作演化
  • 实现动作tokenizer,量化分析256 bin方案的精度损失
  • 对比BC / ACT / Diffusion Policy在同一任务上的成功率