本文面向具备深度学习基础、但不熟悉机器人控制的读者,介绍 VLA 中最容易被忽略、却最重要的概念------Action。
摘要
在计算机视觉任务中,模型通常输出类别、检测框或文本;而在 VLA 中,模型最终需要输出机器人能够执行的动作。
但"动作"并不是一句"拿起杯子",而是由位置、旋转、关节角度和夹爪状态组成的连续数值。
本文将介绍:
-
关节空间和笛卡尔空间;
-
机械臂末端位姿;
-
绝对动作和相对动作;
-
夹爪动作;
-
控制频率;
-
Action Chunk;
-
动作归一化;
-
动作如何真正驱动机器人。
关键词
VLA、机器人动作、关节空间、笛卡尔空间、Action Chunk、机械臂控制
目录
TOC
一、为什么理解 Action 很重要
初学 VLA 时,很多人会把主要注意力放在视觉编码器、语言模型和 Transformer 上。
但真正运行代码时,最容易出问题的往往不是模型结构,而是动作定义。
例如,一个模型输出:
action = [
0.12,
-0.03,
0.08,
0.01,
0.00,
-0.02,
1.00,
]
这 7 个数字可能表示:
[x, y, z, roll, pitch, yaw, gripper]
也可能表示:
[Δx, Δy, Δz, Δrx, Δry, Δrz, gripper]
还可能表示七个机械臂关节的目标角度。
如果误解了动作含义,即使模型预测完全正确,机器人也无法正常执行。
因此,阅读一个 VLA 项目时,首先应该确认:
observation 是什么,action 又是什么?
二、机器人动作的两个主要空间
机械臂动作通常可以在两个空间中表示:
-
关节空间;
-
笛卡尔空间。
三、什么是关节空间
机械臂通常由多个可以旋转的关节组成。
例如,一台六轴机械臂可能包含六个旋转关节:
Joint 1
Joint 2
Joint 3
Joint 4
Joint 5
Joint 6
每个关节都有一个角度。
因此,机械臂当前状态可以表示为:
q = [q₁, q₂, q₃, q₄, q₅, q₆]
其中,每个 qᵢ 表示一个关节角度。
关节空间动作可能直接预测各个关节的目标角度:
target_joint_position = [
0.32,
-0.81,
1.24,
-0.42,
0.63,
0.15,
]
也可以预测关节角度变化量:
delta_joint_position = [
0.01,
-0.02,
0.00,
0.03,
0.00,
-0.01,
]
还可以预测关节速度:
joint_velocity = [
0.10,
-0.05,
0.02,
0.00,
0.03,
-0.04,
]
关节空间的优点
-
可以直接对应机械臂关节;
-
底层执行比较直接;
-
不需要每一步都求逆运动学;
-
适合底层机器人控制。
关节空间的缺点
同样的"向前移动夹爪"动作,在不同机械臂上可能对应完全不同的关节变化。
因此,关节空间动作通常与具体机器人结构绑定,不容易跨机器人迁移。
四、什么是笛卡尔空间
笛卡尔空间描述的是机械臂末端执行器在三维空间中的位置和姿态。
末端执行器通常是:
-
夹爪;
-
吸盘;
-
机械手;
-
其他操作工具。
一个末端位姿通常包含两部分:
位置 + 旋转
位置可以表示为:
[x, y, z]
旋转可以表示为:
[roll, pitch, yaw]
因此,一个常见的 6 维末端位姿是:
[x, y, z, roll, pitch, yaw]
如果再加入夹爪动作,就形成常见的 7 维动作:
[x, y, z, roll, pitch, yaw, gripper]
在 VLA 中,也经常使用位置变化量:
[Δx, Δy, Δz, Δroll, Δpitch, Δyaw, gripper]
例如:
action = [
0.02,
-0.01,
0.00,
0.00,
0.03,
0.00,
1.00,
]
可以解释为:
末端沿 x 方向移动 2 cm
沿 y 方向移动 -1 cm
z 方向保持不变
改变部分旋转姿态
闭合夹爪
五、机械臂末端位姿是什么
位姿是"位置"和"姿态"的组合。
位置
位置表示机械臂末端在三维空间中的坐标:
p = [x, y, z]
例如:
[0.45, 0.12, 0.28]
姿态
姿态表示末端执行器朝向哪个方向。
常见表示方式包括:
-
欧拉角;
-
四元数;
-
旋转矩阵;
-
Axis-Angle。
VLA 数据集中经常出现以下形式:
[x, y, z, roll, pitch, yaw]
或者:
[x, y, z, qx, qy, qz, qw]
其中:
[qx, qy, qz, qw]
是四元数。
四元数使用四个数表示三维旋转,可以避免部分欧拉角表示中的奇异问题。
但对于初学者来说,最重要的不是立即推导四元数公式,而是确认:
当前数据中的旋转量到底使用什么表示方式?
六、绝对动作和相对动作
VLA 中常见两种动作类型:
-
Absolute Action;
-
Delta Action。
6.1 绝对动作
绝对动作直接描述机器人需要到达的目标状态。
例如:
action = [
0.45,
0.12,
0.28,
0.00,
1.57,
0.00,
1.00,
]
其中前三维可能表示机械臂末端需要移动到:
x = 0.45
y = 0.12
z = 0.28
绝对动作类似于:
把夹爪移动到这个坐标。
优点
-
目标清晰;
-
不会因为连续增量累积产生较大漂移;
-
适合已有稳定坐标系的控制环境。
缺点
-
对坐标系定义比较敏感;
-
不同机器人工作空间差别很大;
-
跨机器人动作迁移比较困难。
6.2 相对动作
相对动作描述机械臂相对于当前位置的变化量:
[Δx, Δy, Δz, Δroll, Δpitch, Δyaw]
例如:
action = [
0.01,
0.00,
-0.02,
0.00,
0.00,
0.01,
0.00,
]
可以表示:
从当前位置向 x 方向移动 1 cm,向下移动 2 cm,并轻微旋转。
优点
-
更适合局部闭环控制;
-
数值范围通常较稳定;
-
更容易学习重复操作模式;
-
不依赖完整的全局目标坐标。
缺点
-
误差可能逐步累积;
-
需要频繁重新观测;
-
控制频率变化会影响动作含义。
七、夹爪动作如何表示
机械臂除了移动,还需要控制夹爪。
夹爪动作常见表示方式包括:
二值表示
0:张开
1:闭合
或者相反:
-1:闭合
1:张开
不同项目的定义可能完全相反。
连续表示
0.0:完全张开
1.0:完全闭合
也可能直接表示夹爪宽度:
0.08 米:张开
0.00 米:闭合
因此,阅读数据时不能仅根据数值猜测夹爪含义。
必须查看:
-
数据集说明;
-
环境中的
step函数; -
动作反归一化代码;
-
夹爪控制器实现。
八、控制频率是什么
控制频率表示机器人每秒执行多少次动作。
例如:
10 Hz:每秒执行 10 次动作
20 Hz:每秒执行 20 次动作
50 Hz:每秒执行 50 次动作
假设控制频率为 20 Hz,那么每个动作大约持续:
1 / 20 = 0.05 秒
同一个相对动作:
Δx = 0.01
在不同控制频率下,实际运动效果可能不同。
如果训练数据以 10 Hz 采集,但推理时以 50 Hz 执行,机器人可能移动得过快或行为异常。
所以 VLA 不仅需要匹配动作维度,还需要匹配:
-
控制频率;
-
动作单位;
-
动作范围;
-
执行持续时间。
九、什么是 Action Chunk
单步策略每次只预测一个动作:
oₜ → aₜ
其中:
-
oₜ是当前观测; -
aₜ是当前动作。
Action Chunk 策略则一次预测未来多个动作:
oₜ → [aₜ, aₜ₊₁, ..., aₜ₊H₋₁]
其中 H 表示 Action Horizon。
例如:
action_chunk.shape = [
batch_size,
50,
7,
]
表示模型一次预测:
-
50 个时间步;
-
每个时间步为 7 维动作。
十、为什么要一次预测多个动作
Action Chunk 具有以下优势。
1. 动作更加连续
如果每个时间步独立预测,机械臂容易出现抖动。
一次预测完整动作块,可以让模型学习一段连续运动模式。
2. 减少推理次数
模型不需要每执行一步都完整推理一次。
3. 更容易学习局部技能
例如,"接近杯子并闭合夹爪"本身是一个连续过程。
Action Chunk 能够将这段过程整体建模。
4. 降低单步噪声影响
即使某一个动作稍有偏差,整体动作轨迹仍可能保持连贯。
十一、为什么不一次执行整个 Action Chunk
假设模型预测未来 50 个动作。
如果机器人一次性全部执行,就相当于在较长时间内不再观察环境。
但执行过程中可能出现:
-
杯子发生移动;
-
夹爪没有抓稳;
-
机械臂产生误差;
-
环境中出现障碍;
-
前几个动作已经偏离目标。
因此,常见做法是:
预测 50 步
只执行前 5~10 步
重新观察
再次预测
这叫:
Receding Horizon Control
即滚动时域控制。
伪代码如下:
while not done:
observation = env.get_observation()
action_chunk = policy.predict(
observation
)
for action in action_chunk[:8]:
env.step(action)
这种方式兼顾:
-
动作连续性;
-
模型推理效率;
-
闭环纠错能力。
十二、动作归一化是什么
机器人动作不同维度的数值范围可能差别很大。
例如:
位置变化:[-0.05, 0.05]
旋转变化:[-0.5, 0.5]
夹爪动作:[-1, 1]
关节角度:[-3.14, 3.14]
如果直接训练,数值范围较大的维度可能主导损失。
因此,VLA 通常会对动作进行归一化。
常见方式包括:
均值方差归一化
a_norm = (a - mean) / std
Min-Max 归一化
a_norm = 2 × (a - min) / (max - min) - 1
分位数归一化
使用动作的低分位数和高分位数,减少异常值影响。
推理时,模型输出归一化动作后,还需要进行反归一化:
模型输出
↓
反归一化
↓
真实动作单位
↓
发送给机器人控制器
如果归一化统计量与训练数据不匹配,机器人动作可能完全失控。
十三、动作如何真正驱动机器人
VLA 一般不会直接控制电机。
完整流程通常是:
VLA 输出动作
↓
动作反归一化
↓
安全范围裁剪
↓
机器人控制器
↓
逆运动学或关节控制
↓
电机执行
如果 VLA 输出笛卡尔空间动作,系统可能需要使用逆运动学将其转换为关节动作。
例如:
目标末端位姿
↓
逆运动学
↓
目标关节角度
↓
关节控制器
因此,VLA 只是整个机器人系统中的策略层。
它下方通常还有:
-
运动学求解器;
-
位置控制器;
-
速度控制器;
-
力控制器;
-
碰撞检测;
-
安全限制。
十四、阅读 VLA 代码时应该检查什么
面对一个新的 VLA 项目,可以依次检查以下内容。
1. 动作维度
action_dim = ?
2. 动作定义
关节角度?
末端位姿?
位置变化量?
速度?
3. 旋转表示
欧拉角?
四元数?
Axis-Angle?
4. 夹爪定义
1 是张开还是闭合?
5. 动作单位
米?
厘米?
弧度?
角度?
6. 动作是否归一化
训练时如何归一化?
推理时如何反归一化?
7. 控制频率
每秒执行多少步?
8. Action Chunk 长度
模型预测多少步?
实际执行多少步?
十五、总结
VLA 中的 Action 并不是一句高级指令,而是机器人能够执行的数值控制信号。
常见动作表示包括:
关节空间动作
笛卡尔空间动作
绝对动作
相对动作
夹爪动作
现代 VLA 通常会一次预测多个未来动作,也就是 Action Chunk。
但机器人一般不会一次执行完整动作块,而是采用:
预测一段
执行一部分
重新观察
再次预测
这构成了闭环控制。
理解一个 VLA 项目时,最重要的是弄清楚:
每个动作维度代表什么、使用什么单位、如何归一化,以及最终如何被机器人执行。
只有理解了 Action,才能真正看懂 VLA。
VLA 入门(三):Diffusion 与 Flow Matching,到底是怎样生成机器人动作的?
本文从机器人动作生成角度介绍 Diffusion 和 Flow Matching,帮助初学者理解 π0、Diffusion Policy 等模型为什么能够"从噪声中生成动作"。
摘要
在现代 VLA 和机器人策略模型中,动作生成不再局限于普通回归和离散 Token 预测。
Diffusion 和 Flow Matching 可以从随机噪声出发,逐渐生成一段连续、平滑的机器人动作。
二者看起来非常相似,但学习目标和生成过程并不完全相同。
本文将介绍:
-
为什么直接回归动作可能存在问题;
-
Diffusion 如何对动作加噪和去噪;
-
Flow Matching 如何学习速度场;
-
ODE 数值积分是什么;
-
Diffusion 与 Flow Matching 的主要区别;
-
它们如何用于 Action Chunk 生成。
关键词
Diffusion Policy、Flow Matching、VLA、Action Chunk、机器人动作生成
目录
TOC
一、为什么不直接回归机器人动作
最简单的机器人策略可以直接预测动作:
观测 o → 动作 a
训练时使用均方误差:
L = ||a_pred - a_gt||²
这种方法结构简单,但可能遇到一个问题:
同一个场景可能存在多种正确动作。
例如,机械臂需要绕过一个障碍物抓取杯子。
它可能:
-
从左侧绕行;
-
从右侧绕行;
-
先抬高再向前;
-
先旋转夹爪再接近。
如果训练数据中同时存在多种方案,普通均方误差容易对不同动作取平均。
假设两个正确动作分别是:
向左移动
向右移动
它们的平均结果可能是:
保持不动
但保持不动并不是正确动作。
这类存在多个可能答案的分布称为:
Multi-modal Distribution
Diffusion 和 Flow Matching 的优势之一,就是可以更好地建模这种多峰动作分布。
二、Diffusion 的核心思想
Diffusion 的核心过程包括:
-
前向加噪;
-
反向去噪。
对于机器人任务,被加噪的对象不是图片,而是一段动作序列。
假设真实动作块为:
a₀ ∈ R^(H×D)
其中:
-
H是动作序列长度; -
D是每个动作的维度。
例如:
actions.shape = [50, 7]
表示未来 50 步,每步 7 维动作。
三、Diffusion 的前向加噪过程
训练时,首先从数据集中获取真实动作:
a₀
然后采样高斯噪声:
ε ~ N(0, I)
再将真实动作和噪声进行混合:
aₜ = αₜa₀ + σₜε
其中:
-
a₀是真实动作; -
aₜ是时间t下的带噪动作; -
αₜ控制保留多少真实动作; -
σₜ控制加入多少噪声。
当 t 较小时:
aₜ 更接近真实动作
当 t 较大时:
aₜ 更接近随机噪声
可以将整个过程理解为:
真实动作
↓ 加少量噪声
略微模糊的动作
↓ 继续加噪声
严重扰动的动作
↓
随机噪声
四、Diffusion 模型学习什么
Diffusion 模型通常接收:
带噪动作 aₜ
时间步 t
视觉条件
语言条件
机器人状态
然后预测噪声:
εθ(aₜ, t, c)
其中 c 表示条件信息。
训练目标可以写成:
L = ||εθ(aₜ, t, c) - ε||²
也就是说:
模型需要判断当前带噪动作中包含了什么噪声。
训练伪代码如下:
actions = batch["actions"]
condition = encode_observation(batch)
noise = torch.randn_like(actions)
t = sample_random_timestep()
noisy_actions = add_noise(
actions,
noise,
t,
)
predicted_noise = model(
noisy_actions,
t,
condition,
)
loss = ((predicted_noise - noise) ** 2).mean()
五、Diffusion 推理时如何生成动作
推理时没有真实动作。
模型首先随机初始化一个噪声动作块:
actions = torch.randn(
batch_size,
horizon,
action_dim,
)
然后从高噪声时间步逐渐向低噪声时间步更新:
for t in reversed(timesteps):
noise_pred = model(
actions,
t,
condition,
)
actions = scheduler.step(
noise_pred,
t,
actions,
)
完整过程可以理解为:
随机动作噪声
↓
第一次去噪
↓
出现粗略动作结构
↓
继续去噪
↓
动作逐渐平滑
↓
得到最终 Action Chunk
模型并不是一次直接输出动作,而是多次修正动作。
六、为什么 Diffusion 适合生成机器人动作
1. 可以生成连续动作
Diffusion 直接在连续动作空间中进行建模,不需要把动作离散化为 Token。
2. 可以表示多种正确方案
由于推理从随机噪声开始,不同随机种子可能生成不同可行动作。
3. 可以生成整个 Action Chunk
模型可以一次对完整动作序列进行去噪:
[50, 7]
而不是逐个动作自回归生成。
4. 动作通常比较平滑
动作序列被整体建模,相邻时间步之间容易形成连续轨迹。
七、Diffusion 的主要问题
推理需要多步迭代
如果去噪 100 次,模型就需要前向计算 100 次。
机器人实时控制对延迟比较敏感,因此通常需要:
-
减少去噪步数;
-
使用更快的采样器;
-
对模型进行蒸馏;
-
降低模型规模;
-
提前预测 Action Chunk。
采样结果存在随机性
随机性可以带来多样性,但也可能让机器人动作不稳定。
因此,实际部署时经常固定随机种子,或者使用较稳定的采样策略。
八、Flow Matching 的核心思想
Flow Matching 同样可以从随机噪声生成真实动作。
但它学习的不是"噪声是什么",而是:
当前样本应该朝哪个方向移动。
这个方向由速度场表示:
vθ(aₜ, t, c)
可以将生成过程写成常微分方程:
da/dt = vθ(a, t, c)
其中:
-
a是当前动作样本; -
t是连续时间; -
vθ是模型预测的速度。
九、Flow Matching 如何构造训练样本
假设:
a₀:噪声样本
a₁:真实动作
可以定义一条从噪声到真实动作的插值路径:
aₜ = (1 - t)a₀ + ta₁
其中:
t ∈ [0, 1]
当:
t = 0
有:
aₜ = a₀
也就是随机噪声。
当:
t = 1
有:
aₜ = a₁
也就是真实动作。
对于这条简单直线路径,其目标速度为:
v_target = a₁ - a₀
模型训练目标为:
L = ||vθ(aₜ, t, c) - v_target||²
伪代码如下:
target_actions = batch["actions"]
noise_actions = torch.randn_like(target_actions)
t = torch.rand(
target_actions.shape[0],
1,
1,
)
interpolated_actions = (
(1 - t) * noise_actions
+ t * target_actions
)
target_velocity = (
target_actions
- noise_actions
)
predicted_velocity = model(
interpolated_actions,
t,
condition,
)
loss = (
(
predicted_velocity
- target_velocity
) ** 2
).mean()
十、Flow Matching 推理时如何生成动作
推理从随机噪声开始:
actions = torch.randn(
batch_size,
horizon,
action_dim,
)
然后使用速度场进行积分:
dt = 1.0 / num_steps
for i in range(num_steps):
t = i / num_steps
velocity = model(
actions,
t,
condition,
)
actions = actions + dt * velocity
这段代码使用的是最简单的欧拉积分。
可以将其理解为:
当前位置
+
当前速度 × 时间间隔
=
下一时刻位置
对于动作生成来说:
当前动作样本
+
模型预测的移动方向
=
更接近真实动作的新样本
不断更新后,噪声就会逐渐流向真实动作分布。
十一、什么是 Learned Vector Field
Flow Matching 中经常出现:
Learned Vector Field
即学习到的向量场或速度场。
可以把动作空间想象成一个地图。
地图上的每一个位置,都有一个箭头:
当前位置 → 应该往哪里移动
模型接收当前动作、时间和条件后,输出一个同维度向量:
velocity.shape == actions.shape
例如:
actions.shape = [B, 50, 7]
velocity.shape = [B, 50, 7]
这个速度告诉所有动作位置下一步应该如何变化。
通过多次积分,完整 Action Chunk 会从噪声逐渐变成合理动作。
十二、Diffusion 与 Flow Matching 的直观区别
可以使用两个类比理解。
Diffusion
Diffusion 更像修复一张被噪声污染的图片:
当前动作中有很多噪声
↓
模型预测哪些部分是噪声
↓
逐渐去除噪声
↓
得到干净动作
Flow Matching
Flow Matching 更像控制水流方向:
动作当前位于噪声区域
↓
模型告诉它应该向哪里流动
↓
不断沿速度场移动
↓
到达真实动作区域
十三、Diffusion 和 Flow Matching 的技术对比
| 对比项 | Diffusion | Flow Matching |
|---|---|---|
| 初始状态 | 随机噪声 | 随机噪声 |
| 学习目标 | 噪声、分数或相关目标 | 速度场 |
| 推理过程 | 反向去噪 | ODE 积分 |
| 动作空间 | 连续 | 连续 |
| 是否支持 Action Chunk | 支持 | 支持 |
| 是否需要多步采样 | 通常需要 | 通常需要 |
| 直观理解 | 擦除噪声 | 沿流场移动 |
需要注意的是,真实论文中的 Diffusion 和 Flow Matching 可能采用不同路径、参数化方式和采样器。
但对于初学者,先理解:
Diffusion 学去噪
Flow Matching 学方向
已经能够帮助阅读大多数代码。
十四、条件信息是如何加入模型的
无论 Diffusion 还是 Flow Matching,模型都不能只看动作噪声。
它还需要知道:
当前看到了什么
用户要求做什么
机器人当前在哪里
因此模型实际预测的是:
εθ(aₜ, t | image, language, state)
或者:
vθ(aₜ, t | image, language, state)
条件信息可以通过以下方式加入:
-
Cross-Attention;
-
拼接 Token;
-
FiLM;
-
AdaLN;
-
Prefix Embedding;
-
条件 Transformer。
例如:
condition_tokens = torch.cat(
[
vision_tokens,
language_tokens,
state_tokens,
],
dim=1,
)
动作网络再通过注意力读取这些条件。
十五、DiT 在动作生成中做什么
DiT 是 Diffusion Transformer。
它并不是一种新的 Diffusion 原理,而是:
使用 Transformer 作为去噪网络或速度预测网络。
传统图像 Diffusion 常使用 U-Net。
动作序列具有明显的时间结构,因此可以使用 Transformer:
带噪 Action Tokens
时间编码
视觉语言条件
↓
Transformer
↓
预测噪声或速度
需要注意:
ViT 主要编码图像 Patch
DiT 主要处理扩散过程中的带噪样本
二者都使用 Transformer,但任务不同。
十六、Action Chunk 如何通过 Flow Matching 生成
假设模型需要生成:
50 步 × 7 维
模型首先初始化:
actions = torch.randn(
batch_size,
50,
7,
)
这 50 个动作一开始全部是噪声。
随后模型多次预测:
velocity = model(
actions,
time,
observation_condition,
)
每次都更新整个动作块:
actions = actions + dt * velocity
最终得到:
[aₜ, aₜ₊₁, ..., aₜ₊₄₉]
机器人可能只执行前 8 步:
execute_actions = actions[:, :8]
然后重新观察环境,再生成新的动作块。
十七、常见误区
误区一:Flow Matching 一次前向就能生成动作
Flow Matching 通常仍然需要进行多次数值积分。
只是积分步数可以根据模型和实现进行调整。
误区二:Diffusion 只能生成图像
Diffusion 可以生成任何可以表示为连续向量的数据,包括:
-
图像;
-
音频;
-
视频;
-
三维结构;
-
机器人动作。
误区三:随机采样等于随机控制
最终动作受到视觉、语言和机器人状态强约束。
随机噪声只是生成过程的初始状态,不代表最终动作是任意的。
误区四:Flow Matching 一定比 Diffusion 快
实际速度取决于:
-
采样步数;
-
模型大小;
-
数值求解器;
-
硬件;
-
Action Chunk 长度;
-
实现优化。
不能只根据方法名称判断速度。
十八、总结
Diffusion 和 Flow Matching 都可以从随机噪声出发生成连续机器人动作。
Diffusion 的核心是:
给动作加噪
训练模型识别噪声
推理时逐步去噪
Flow Matching 的核心是:
构造噪声到真实动作的路径
训练模型预测速度
推理时沿速度场积分
二者都适合生成 Action Chunk,并能够处理多种可行动作方案。
理解它们时,可以先记住:
Diffusion 学习如何去掉噪声,Flow Matching 学习样本应该朝哪里移动。