外观
7.6 动作分块与基于 Transformer 的动作生成 (ACT)
在前面的章节中,我们深入剖析了单步行为克隆中的协变量偏移与累积误差问题。当我们将目光转向真实世界中最严苛的双臂精细操作(例如用两把镊子穿针引线、给拉链穿扣、组装极小的电路元件)时,单步决策策略往往会暴露出致命的缺陷——高频颤抖、动作不连贯与长程目标迷失。
人类在系鞋带或用双筷夹菜时,大脑绝不是在每隔
2023 年,斯坦福大学 Tony Z. Zhao、Chelsea Finn 等人联合推出了 ALOHA 低成本双臂示教平台,并提出了 ACT(Action Chunking with Transformers,动作分块 Transformer) 模型。ACT 通过引入动作分块(Action Chunking)、条件变分自编码器(CVAE) 与 时序集成(Temporal Ensembling),开辟了双臂长程精细操作的崭新格局。

图 7.6-1:ALOHA 的六类真实双臂任务展示动作分块所针对的长时精细操作。 出处:Learning Fine-Grained Bimanual Manipulation with Low-Cost Hardware,Tony Z. Zhao et al.,2023。
7.6.1 物理与生理基石:人类双臂协同与长程精细操作
要理解动作分块的设计哲学,我们首先必须回到人类神经生理学中的运动协同机制。
1. 运动协同元(Motor Synergies)与动作分块
神经科学研究表明,人类的大脑皮层与脊髓中预存了大量的运动协同元(Motor Synergies)。
- 当一个体操运动员做后空翻时,其小脑在腾空的瞬间就已经激活了一个长达数百毫秒的固定肌肉运动神经冲动程序;
- 这种将连续多个时间步的动作打包为一个整体原子单元的机制,在机器人学中被称为动作分块(Action Chunking)。
动作分块将机器人与环境的有效交互步长缩短了

图 7.6-2:SMILe 与传统监督模仿的赛道表现差异展示训练分布外误差如何累积。 出处:SMILe: Scalable Meta-Inverse Reinforcement Learning,Stéphane Ross et al.,2010。
7.6.2 核心数学推导一:CVAE 风格隐变量与动作生成
在面对同一个双臂装配任务时,即便面对完全相同的起始画面,人类专家也有可能采用不同的细微操作习惯(例如先抬高左臂再抬右臂,或者双臂齐平抬起)。
为了捕捉专家动作的多样性,ACT 采用了**条件变分自编码器(Conditional VAE, CVAE)**架构。

图 7.6-3:ACT 的 CVAE 编码器、Transformer 编解码器与动作查询共同生成动作块。 出处:Learning Fine-Grained Bimanual Manipulation with Low-Cost Hardware,Tony Z. Zhao et al.,2023。
1. CVAE 编码器与风格隐变量采样
在训练阶段,CVAE 编码器接收当前的真实专家动作块
2. Transformer 解码器与动作重构
Transformer 解码器接收环境视觉特征、本体感觉向量以及隐变量
ACT 的联合优化损失函数由 L1 动作重构损失与 KL 散度正则项组成:
其中 KL 散度的初等解析闭式解为:
手算代入算例: 设隐变量维度
我们代入 KL 散度公式手算:
通过初等代数的几步推导,系统迫使隐空间在训练中向标准正态分布对齐;在推理部署时,我们只需直接令
深入推导:连续动作序列高斯变分证据下界(ELBO)与凸松弛优化(点击展开查看完整推导)
对条件动作序列的边缘对数似然
当假设重构误差满足拉普拉斯分布
7.6.3 核心数学推导二:时序集成(Temporal Ensembling)对角线平滑滤波
在实际部署时,如果我们每隔
为了实现绝对丝滑的运动,ACT 引入了**时序集成(Temporal Ensembling)**平滑滤波机制。

图 7.6-4:重叠动作块按时间权重集成,缓解单次预测切换造成的抖动。 出处:Learning Fine-Grained Bimanual Manipulation with Low-Cost Hardware,Tony Z. Zhao et al.,2023。

图 7.6-5:从重叠动作块的同一物理时刻对角线取值并按预测年龄加权。
1. 重叠动作块的对角线提取与指数衰减加权
系统在每一个控制时间步
因此,在当前的物理时刻
系统采用指数衰减权重对这
公式符号逐一拆解:
:预测发生的“年龄”( 表示刚才最新做出的预测, 表示 个时间步之前做出的老预测); :时序折扣率(Temporal Discount Factor),通常取 ; :衰减权重。最新的预测获得最大的权重( ),较早的预测权重微弱衰减,但仍然参与加权,从而将不同周期的动作曲线完美“粘合”为一条处处光滑的动力学轨迹。
深入推导:时序指数加权滤波器的频域传递函数与群延迟(Group Delay)分析(点击展开查看完整推导)
时序集成可形式化为离散有限冲激响应(FIR)低通滤波器:
其 Z 域系统传递函数为:
该系统在高频段具有
7.6.4 纯底层 PyTorch 代码实现:ACT 动作分块与时序集成引擎
下面我们使用纯底层 PyTorch 算子手写实现 ACT 的核心模块,包括 CVAE 编码器、动作查询 Transformer 解码器与时序集成平滑器。
python
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class ACTCVAEEncoder(nn.Module):
"""
ACT 条件变分自编码器编码器
将专家动作块 A 与当前观测压缩为低维隐变量 z ~ N(mu, sigma^2)
"""
def __init__(self, action_dim: int = 14, chunk_size: int = 16, obs_dim: int = 64, latent_dim: int = 32):
super().__init__()
input_dim = chunk_size * action_dim + obs_dim
self.mlp = nn.Sequential(
nn.Linear(input_dim, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU()
)
self.fc_mu = nn.Linear(64, latent_dim)
self.fc_logvar = nn.Linear(64, latent_dim)
def forward(self, actions: torch.Tensor, obs: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""
:param actions: (B, chunk_size, action_dim)
:param obs: (B, obs_dim)
:return: (z, mu, logvar)
"""
B = actions.size(0)
flat_actions = actions.flatten(1)
x = torch.cat([flat_actions, obs], dim=-1)
feat = self.mlp(x)
mu = self.fc_mu(feat)
logvar = self.fc_logvar(feat)
# 重参数化采样
std = torch.exp(0.5 * logvar)
eps = torch.randn_like(std)
z = mu + eps * std
return z, mu, logvar
class ACTTransformerDecoder(nn.Module):
"""
基于可学习动作查询 (Action Queries) 的 Transformer 解码器
"""
def __init__(self, action_dim: int = 14, chunk_size: int = 16, obs_dim: int = 64, latent_dim: int = 32, d_model: int = 128):
super().__init__()
self.chunk_size = chunk_size
self.action_dim = action_dim
# 投影观测与隐变量
self.cond_proj = nn.Linear(obs_dim + latent_dim, d_model)
# 可学习动作查询序列: (chunk_size, d_model)
self.action_queries = nn.Parameter(torch.randn(chunk_size, d_model) * 0.02)
# Transformer 解码器层
decoder_layer = nn.TransformerDecoderLayer(d_model=d_model, nhead=4, dim_feedforward=256, batch_first=True)
self.transformer_decoder = nn.TransformerDecoder(decoder_layer, num_layers=2)
# 动作回归头
self.action_head = nn.Linear(d_model, action_dim)
def forward(self, z: torch.Tensor, obs: torch.Tensor) -> torch.Tensor:
"""
:param z: (B, latent_dim)
:param obs: (B, obs_dim)
:return: (B, chunk_size, action_dim) 预测动作块
"""
B = z.size(0)
cond = self.cond_proj(torch.cat([obs, z], dim=-1)).unsqueeze(1) # (B, 1, d_model)
# 扩展动作查询: (B, chunk_size, d_model)
queries = self.action_queries.unsqueeze(0).expand(B, -1, -1)
# 解码并投影
dec_out = self.transformer_decoder(tgt=queries, memory=cond)
pred_actions = self.action_head(dec_out)
return pred_actions
class TemporalEnsembler:
"""
时序集成平滑滤波器
在时间轴上维护重叠动作块的指数加权衰减平均
"""
def __init__(self, chunk_size: int = 16, action_dim: int = 14, m: float = 0.01):
self.chunk_size = chunk_size
self.action_dim = action_dim
# 计算权重 w_i = exp(-m * i)
indices = torch.arange(chunk_size, dtype=torch.float32)
self.weights = torch.exp(-m * indices) # (chunk_size,)
self.history_buffer = []
def update(self, new_chunk: torch.Tensor) -> torch.Tensor:
"""
输入当前步预测出的动作块 (chunk_size, action_dim),输出单步平滑融合动作 (action_dim,)
"""
self.history_buffer.append(new_chunk.cpu())
if len(self.history_buffer) > self.chunk_size:
self.history_buffer.pop(0)
# 提取当前时刻的多份预测并加权平均
k = len(self.history_buffer)
curr_preds = []
curr_weights = []
for age in range(k):
# 第 age 个历史块中对应当前物理时刻的动作索引为 age
chunk = self.history_buffer[-(age + 1)]
curr_preds.append(chunk[age])
curr_weights.append(self.weights[age])
stacked_preds = torch.stack(curr_preds, dim=0) # (k, action_dim)
stacked_weights = torch.tensor(curr_weights).unsqueeze(-1) # (k, 1)
fused_action = (stacked_preds * stacked_weights).sum(dim=0) / stacked_weights.sum()
return fused_action
# ===================================================================
# 单元测试:CVAE 训练损失与时序集成平滑度校验
# ===================================================================
if __name__ == "__main__":
batch_size = 4
chunk_size = 16
action_dim = 14
obs_dim = 64
latent_dim = 32
encoder = ACTCVAEEncoder(action_dim=action_dim, chunk_size=chunk_size, obs_dim=obs_dim, latent_dim=latent_dim)
decoder = ACTTransformerDecoder(action_dim=action_dim, chunk_size=chunk_size, obs_dim=obs_dim, latent_dim=latent_dim)
dummy_actions = torch.randn(batch_size, chunk_size, action_dim)
dummy_obs = torch.randn(batch_size, obs_dim)
# 1. 测试 CVAE 编码与解码
z, mu, logvar = encoder(dummy_actions, dummy_obs)
pred_actions = decoder(z, dummy_obs)
# 2. 计算联合损失
l1_loss = F.l1_loss(pred_actions, dummy_actions)
kl_loss = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp(), dim=-1).mean()
total_loss = l1_loss + 10.0 * kl_loss
print(f"[ACT Test] 动作重构 L1 损失: {l1_loss.item():.4f}")
print(f"[ACT Test] 隐变量分布 KL 散度: {kl_loss.item():.4f}")
print(f"[ACT Test] 预测动作块形状: {pred_actions.shape}")
assert pred_actions.shape == (batch_size, chunk_size, action_dim), "解码动作块形状不符!"
# 3. 测试时序集成平滑器
ensembler = TemporalEnsembler(chunk_size=chunk_size, action_dim=action_dim, m=0.01)
for _ in range(20):
single_chunk = torch.randn(chunk_size, action_dim)
smooth_act = ensembler.update(single_chunk)
print(f"[Temporal Ensemble Test] 最终单步融合动作形状: {smooth_act.shape}")
assert smooth_act.shape == (action_dim,), "时序集成输出维度不符!"
print("✓ ACT 动作分块、CVAE 损失与时序集成平滑引擎单测全部通过!")7.6.5 本节小结
回顾本节内容,我们建立了动作分块在长程双臂精细操作中的完整技术体系:
- 运动协同与动作分块:将多步连续动作打包为原子块,从物理机制上将交互步数缩短
倍,大幅削减了累积误差的增长速度; - CVAE 隐变量表征:通过高斯隐变量建模人类操作风格的多样性,利用 L1 重构损失与 KL 正则化实现高保真度轨迹拟合;
- 时序集成平滑滤波:利用指数衰减权重融合多重重叠预测,彻底消除了周期切换顿挫,输出高阶光滑的关节控制信号。

