VLA:视觉-语言-动作
把"看、听、动"三条通路合为一体。这就是具身智能的终局形态——让机器人看着场景、听懂指令、自主完成动作序列。
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)
3. Diffusion Policy:处理多模态
同一张图片,你可以抓杯子的把手,也可以抓杯身——两者都是正确的。BC用MSE会学出均值(无效动作),而Diffusion Policy学习整个动作分布 $p(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→用机器人数据微调→输入图像+指令→输出动作序列。一个模型同时"看懂"和"动手"。
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 仓库
- tonyzhaozh/act — ACT 官方实现 (Action Chunking Transformer)
- real-stanford/diffusion_policy — Diffusion Policy 官方实现
- tonyzhaozh/aloha — ALOHA 低成本双臂遥操作
- ARISE-Initiative/robomimic — 模仿学习基准框架
- haosulab/ManiSkill — 操作任务基准和训练环境
- google-research/robotics_transformer — RT-1/RT-2 参考实现
视频
- ACT: Learning Fine-Grained Bimanual Manipulation
- Diffusion Policy 项目主页 — 含视频和论文
- RT-2: Vision-Language-Action Models — Google DeepMind
具身智能基础模型:2024-2025 前沿
这个领域发展极快
2024-2025年,具身智能基础模型呈现爆发式增长。以下是面试和工作中必须了解的模型:
| 模型 | 机构 | 核心贡献 | 关键思想 |
|---|---|---|---|
| RT-2 | Google DeepMind | VLM→动作token | 大规模图文预训练+机器人微调,首次展示VLA的规模化可行性 |
| Octo | UC Berkeley/Stanford/CMU | 开源通用机器人模型 | Transformer-based,跨机器人、跨任务、跨场景泛化 |
| π₀ (Pi Zero) | Physical Intelligence | 通用机器人基础模型 | 从互联网数据和机器人数据联合训练,处理各种操作任务 |
| GROOT | NVIDIA | 人形机器人基础模型 | 多模态(语言+视频+动作),从人类演示直接学习 |
| 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=...)
真机部署:从仿真到实物
真机部署的隐藏成本
仿真中 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-2 | VLM→动作 | 互联网+机器人 | 否 |
| Octo | 图像+语言→动作 | Open X-Embodiment | 是 |
| π₀ | 多模态→动作 | 多种机器人 | 部分 |
| GROOT | 仿真→Sim2Real | 大规模仿真 | 是 |
验收实验
- 下载Octo预训练模型,在新任务上零样本测试
- 用自己采集的数据微调Octo(LoRA),对比微调前后成功率
6. ACT深度解析——Attention与Chunking的数学
为什么预测单步动作不够
行为克隆(BC)预测单步动作的最大问题:错误会累积(compounding error)。如果第一步偏了1mm,由于训练数据中没有"偏了1mm后"的状态,策略不知道该怎么做。ACT的解决方案:一次预测未来K步的动作序列——用Transformer的自注意力机制建模动作间的时序依赖。
ACT的核心公式
其中$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$:逐步向数据添加高斯噪声(可解析计算任意步$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:
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在同一任务上的成功率