ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

动作Token化器怎么做才优秀?从连续回归到离散序列的关键设计

动作Token化器怎么做才优秀?从连续回归到离散序列的关键设计

最近我在做动作生成和具身操作相关的工作时,最常被问到的问题是:为什么非得先把动作token化,直接让模型输出连续坐标不行吗?每次我都得从“多模态动作分布”讲起,讲着讲着对方就会意识到,token化并不只是工程上的妥协,而是一种把连续控制问题翻译成序列决策问题的关键设计。ActionCodec 就是在这个背景下折腾起来的一个动作token化器,这篇内容想把它的核心问题聊透:什么才算得上优秀。

如果你最近也在做机器人的模仿学习、动作预测、游戏AI生成,或者只是对“动作进大模型”这条路感兴趣,这篇文章应该能帮你少踩几个坑。我尽量不堆公式,但会保留必要的量化指标,因为动作token化器这个东西,感觉和直觉都容易骗人,最后能信的还是那几组具体数字。

1. 对连续动作直接建模,为什么总是不得劲

先别急着聊token化器本身。搞懂了“为什么直接回归连续动作不舒服”,你才能明白优秀动作token化器到底在解决什么问题。

1.1 回归会逼模型去预测“平均动作”

真实动作数据是典型的多模态分布。同样一个倒水任务,有人习惯先抬肘再转腕,有人喜欢直接平移手臂;遇到障碍物,从左绕和从右绕都合理。如果让神经网络去回归一个连续的末端坐标或者关节角序列,它的损失函数默认把所有可行轨迹都当作同一个目标去逼近。

用L2损失做回归,网络会输出所有可行模式在数值空间里的均值。学过聚类的人都知道,两个合理轨迹的均值往往是一个不合理的轨迹。放到动作空间里,表现就是手伸到一半停住、越过障碍物的路径跑到障碍物中间、一个快速挥拍动作被压成慢吞吞的中间态。这不是网络容量不够,而是回归目标本身和任务的多模态性质不匹配。

1.2 高维动作空间里,损失函数很难说出“人话”

机械臂或者人形机器人的动作一般由几十个自由度组成:关节角度、末端六维位姿、速度、加速度、力矩,有时候还有夹爪开合。把这些量拼成一个向量直接回归,你会立刻遇到量纲和坐标耦合的麻烦。

关节角度的误差和末端位置的误差不呈线性关系,一个很小的腕部关节角变化,到末端可能放大成几厘米的偏移。欧拉角在奇异点附近数值突变,四元数在回归时又有单位约束的问题。你可以给不同量纲手动配权重,但配来配去你会发现,这个权重在轨迹A上合理,换到轨迹B就失灵。token化把连续的高维向量先压缩成有限个“动作符号”,相当于逼模型先做分辨,再去关心数值精度,反而避开了很多这种麻烦。

1.3 一步错,步步错的误差累积

另一个痛点出现在自回归生成里。动作预测模型常按时间步逐帧输出下一个动作,再把自己刚预测的动作拼到历史里继续预测。连续动作回归每一步都会带一点小噪声;如果这个噪声落在真实动作流形之外,下一步模型会把噪声当成输入,误差会被一步步放大。

离散token有一个隐蔽优势:它把输出空间限制在一组有限的符号里。模型出错时,出错的是“选择了错误的token”,而不是生成了一个飘到流形外的非法数值。错误性质从英寸级漂移变成可枚举的符号替换,这对长序列的稳定性影响非常大。

1.4 token为动作装上了“语言接口”

再往大了说,动作一旦变成离散token,就可以复用语言模型里那套序列建模框架。你可以把文本token、图像token和动作token放在同一个字典空间里,用同一个Transformer去建模;可以靠因果注意力做动作预测;也可以用指令文本去条件生成一串动作token。ActionCodec 想做的其实也是这件事:把原始动作轨迹变成一套干净、紧凑、可控的离散表示,让上层模型有资格去“读”动作。

2. ActionCodec 的骨架:编码器、码本、解码器,职责不能乱

聊完必要性,我们把ActionCodec拆开看。它的基本结构和常见的向量量化模型一致,三段式:编码器、码本、解码器。每个部分解决一个不同的问题,合在一起才是token化器。

2.1 一条动作数据变成token索引的完整路径

假设你有长度为T的动作序列,每个时间步的维度是D_action。编码器先对这段序列做时间维度的压缩,通常会把序列长度缩短到T/R,R是时间降采样率。压缩后的每个时间步是一个D_z维的潜向量,叫z_e。然后每个z_e去码本codebook里找一个最近的码字向量z_q,记录的只是一串整数索引。解码器再根据这些码字向量把序列还原成动作,和原始轨迹算重构损失。

这里有一个概念要分清:token不等于原始数据点,而是“潜空间中的一个码字”。时间降采样率R决定了每秒动作被切成多少个token;码本大小K决定了这棵“动作词汇表”里一共有多少个符号。R和K是token化器身上最核心的两个旋钮。

2.2 编码器最少要管到“邻域上下文”

编码器该用什么结构?按我自己的经验,时间卷积是最顺手的,比普通Transformer更容易控制感受野,也更容易约束因果性。动作序列是强时间相关的,单个时间步的关节角度没有任何意义,得靠前后几帧才知道这个动作的意图是加速还是减速。

如果你要做在线预测或者自回归生成,编码器一定要保证因果性:生成第t个token时,不能看到t之后的动作。否则训练时效果很好,一部署就穿帮。比较常见的做法是因果卷积加膨胀率,感受野覆盖大概200毫秒到500毫秒的动作窗口。手头没有特殊理由时,我会把感受野控制在“看得懂局部动作、又不至于被全局规划淹没”的尺度。

2.3 码本更新:别拿码本向量当普通参数训练

码本是一个大小为K×D_z的可学习矩阵。直觉上你会想把码本向量直接扔进反向传播里更新,大部分工业级实现却不这么做,大多走EMA更新。原因是码本向量本质上是“聚类中心”,更稳的更新方式是对每个码字维护一个指数滑动平均,只更新被选中码字对应的聚类中心。

训练loss一般分三块:重构loss、commitment loss、codebook loss。

  • 重构loss:解码器还原出的动作和原始动作之间的误差。
  • commitment loss:让编码器输出不要飘得太远,保证它稳定地往最近的码字贴。
  • codebook loss:让码字去接近编码器输出。

一个常见写法是:β * ||z_e - sg(z_q)||²,代表commitment的力度。β太小,编码器输出会乱跑;β太大,编码器会被压成只有一个激活模式,码本利用率反而崩掉。这个β我一般从0.25开始调,跑一版看码本利用率再决定加大还是减小。

2.4 解码器决定了token携带信息的“下限”

很多人评估token化器时只盯着码本,其实解码器的能力同样关键。因为码本只需要存储“离散要素”,那些数值细节靠解码器在还原时补出来。如果解码器太弱,码本就必须承担更多细节,结果码本里的码字会变得互相重叠,token的可分辨性下降。

如果你在做一个完整的ActionCodec,我的建议是先把重构这环打通:编码器-码本-解码器一起训到重构误差落在一个可接受范围,再冻结token化器,去训练下游的预测或者决策模型。这样动作token化器成为一个固定的“表示层”,后面怎么折腾都不容易再污染它的质量。

3. 我的评价清单:重构精度并不是全部

一个动作token化器好不好,第一个蹦出来的指标通常是重构误差。重构精度当然重要,但只看它会出大问题。下面这份清单我实际用下来比较靠谱。

3.1 重构精度怎么测量才公平

最简单的指标是关节角度平均绝对误差和末端位置平均误差。测量时要分训练集和验证集分别报,验证集上的误差才有参考价值。另外还要注意动作数据的频率,同样是1厘米末端误差,在50Hz控制频率和500Hz高频轨迹里含义完全不同。

不过我会提醒一句:不是误差越小越好。token化器本质上在做有损压缩,压缩到一定精度之后,继续提高重构精度会消耗码本容量,反而让下游模型更难学。更合理的参考线是“任务允许误差”,比如末端接近目标到2厘米内就算成功,那重构误差降到1厘米附近就够用,没必要硬压到毫米级。

3.2 码本利用率:率先看死码率

码本利用率可能是最容易被忽略、又最能暴露问题的指标。定义很简单:一段验证集轨迹里实际被触达的码字数量除以码本总大小。如果码本有1024个token,实际只用了120个,那死码率就高达88%,这通常意味着大量动作被无意义地映射到了同一批token上。

还可以看码字使用分布的熵。均匀分布熵最高,说明动作空间被比较均匀地切分;熵太低说明只有少数几个码字被反复使用,其他码字成了摆设。你可以每次训练迭代里统计一下活跃码字数量,一旦发现活跃数量突然下降,基本就是在预警码本崩溃。

3.3 时序一致性:码本里藏着多少“跳变邻居”

连续动作被分成离散token后,有一个被低估的风险:相邻时间步的token在码本里的欧氏距离不一定小。因为每个z_e都是独立去做最近邻匹配的,量化过程完全不关心前一个token是哪个。结果就是解码出来的动作,在一个时刻还是平滑的,下一时刻突然跳到另一个码字的动作模式,表现为抖动或者卡顿。

评价时序一致性,我会看重构动作序列在相邻时间步的位移/速度跳变比例。把这个比例和原始动作序列的跳变比例放在一起对比,如果token化之后跳变明显更剧烈,那就是量化引入的伪运动。出现这种情况时,要么调低R让时间粒度更细,要么在重构loss里加一个平滑项,或者考虑用多级残差量化来缓解。

3.4 语义可迁移性:token是不是“一招鲜”

最后一个维度偏“大模型思维”:好的动作token最好是有语义的,能够在不同任务、不同场景间复用。比如一个“向前抓取”的动作token,在家里桌面能用,在货架前也应该能用;一个“走路启动”的token,在硬地和地毯上不应该差别太大。

定量看,可以统计多个子任务之间动作token的重叠率。如果每个任务的token分布都完全不同,说明token化器只是在记忆特定轨迹,不是在学习通用的动作要素。这种token化器在下游做跨任务泛化时基本指望不上。

评价维度常用量化方式只盯着看会踩什么坑
重构精度关节MAE、末端位置误差容易陷入无意义的有损压缩竞赛
码本利用率活跃码字比例、使用熵利用率高不等于语义可用
时序一致性相邻重构动作跳变率单独看每帧误差会忽略抖动
语义可迁移性跨任务的token重叠率需要多任务数据,成本高

4. 训练动作token化器时最容易踩的三个坑

我把自己实际调参过程中遇到过的问题总结成三类,基本覆盖了我见过的绝大部分翻车场景。

4.1 码本崩溃:死码率不知不觉爬升

码本崩溃最典型的表现是:loss还在降,重构输出却越来越单一,活跃码字数从700慢慢掉到200。你去看编码器输出,会发现它集中在一个很小的区域里打转,最近邻总是落在附近几个码字上。

根因通常是训练初期编码器还没有稳定,码本的聚类中心就被某些高频动作带偏了,之后越来越偏。我常用的修复方案按顺序来:先用EMA更新替代直接梯度更新;再把commitment loss的β稍微调大一点,让编码器输出更早地稳定;最后每几百个迭代做一次死码检查,超过阈值就把长期不被触达的码字重新初始化成当前编码器输出的随机采样。

这个坑最阴险的地方在于,它不一定一开始就出现。我见过训了三分之一才崩的情况,所以每500次迭代打印一次活跃码字数,是个成本很低的保健习惯。

4.2 过平滑重构:动作变成“温吞水”

第二种常见问题是重构出来的动作看起来很合理,但缺少节奏。速度曲线像被磨平了,本该干脆停顿的动作变成缓起缓停,体操动作被还原成太极。

过平滑一般有两个来源。一个是码本容量太小,大量高动态动作被分到同一个码字,解码器只能还一个平均值,高速度部分自然被抹掉了。另一个是loss函数只盯动作数值,对运动学特征如速度、加速度不敏感,模型觉得“差不多”就行了,真实动作里的瞬时变化就被放弃了。

解决办法可以分头试:扩大码本容量;在重构loss里加上速度和加速度的误差项;或者干脆换成残差量化,把一个码字拆成多个层次的组合,把“粗动作”和“精细微调”分给不同层。这比一口气加大单码本要可控得多。

4.3 码本维度与索引序列长度的配平

最后一个坑更隐蔽,牵涉到码本维度D_z和时间降采样率R的配比。D_z太高,码本里的码字在高维空间里几乎是稀疏的,最近邻距离差别很小,量化基本退化成随机映射;D_z太低,单个码字承载不了足够信息,量化误差居高不下。

同样,R决定了每秒钟的动作被压缩成几个token。R太大,动作细节大量丢失;R太小,token序列太长,上层Transformer的上下文窗口一下就被塞满,计算成本暴涨。

给不出一个万能数字,但如果你第一次搭,我建议先落在这些经验区间里:D_z取64到128,K取512到2048,R取4到8,commitment loss的β在0.1到0.5之间。后面再根据码本利用率和下游表现一起调。

5. 怎么验证你的token化器真的优秀:一份可复现的验收流程

夸再多不如跑一套对照实验。下面是我自己会执行的验收流程,数据规模和结构都可以按你自己的任务替换。

5.1 先备一份带“众数”的动作数据

第一步不急着训token化器,先把动作数据整理出来。我建议准备一份包含明显多种走法/策略的数据集,比如同样是从A点取物放到B点,数据里既有绕左侧的轨迹也有绕右侧的轨迹。这样的数据才能暴露“平均动作”问题有没有被token化真正解决。

动作频率、轨迹长度、关节数保持一致,切成固定长度窗口。窗口切分要注意重叠一部分上下文,防止动作时序在边界处被切断。

5.2 训练两件东西:token化器和轻量下游预测头

验收时不能只训ActionCodec本身,还要顺手训一个轻量级下游模型,用来检验token序列是否容易被序列模型利用。我会用一个小型Transformer,输入过去N个动作token,预测下一个动作token,然后解码成连续动作和真实动作对比。

这一步很重要:没有下游验证的重构误差,只能说明信息压缩得好不好,无法说明token表示对决策是否友好。有些token化器重构误差做得很好,但下游预测loss怎么降都降不下来,原因就在于token序列在时间上跳变太剧烈,模型根本找不到规律。

5.3 跑出下面几组对照,结果会说话

我会固定网络结构和训练数据,只改动少数几个变量,把四组配置跑出来对比。

配置重构MAE活跃码字率下游预测误差策略成功率
单码本512,降采样40.03271%0.04168%
单码本2048,降采样40.01842%0.03574%
残差量化2层,每层2560.02189%0.02781%
单码本512,降采样80.05760%0.04461%

上表是我之前按类似流程跑出来的示意数据,不同任务数字会不一样,但趋势通常很稳定:单纯加大码本可能降低重构误差,却不一定提升下游表现;而残差量化往往两头都占便宜。

5.4 我的验收顺序:先盯死码率,再看重构,最后看下游

我会按照这个顺序去判断:首先看活跃码字率,低于50%直接换配置;然后看重构误差,确认没有严重过平滑;最后才看下游预测误差和策略成功率。

如果发现某个配置重构误差更大、下游效果反而更好,我不会惊讶。这说明token化器把“网络难以利用的精细动作噪声”给过滤掉了,只留下对决策有用的结构,这对大模型后续学习是正向的。

6. 下一步我打算在ActionCodec里加的分层方案

本来做到单码本调参已经能应付不少任务,但我对现状还是不太满意,下一步想在ActionCodec里重点尝试两条路。

6.1 残差量化:用多个小码本代替一个大码本

残差量化的思路很简单:第一级码本负责捕获大尺度的动作轮廓,量化之后算出残差,第二级码本负责继续量化残差,第三级再去捕捉更精细的细节。这样每一级码字都可以比较小,整体表达能力却可以叠得很高。

我比较看好它的原因是,每一级码本在时间上的行为不一样。高层码字变化慢,天然带“规划感”;低层码字变化快,负责补运动细节。下游模型可以先读高层token做决策,然后再结合低层token细化,这种层次结构对长时序动作建模很友好。

6.2 让token的语义去对齐观察和语言

另一个让我兴奋的方向是,让动作token的编码过程多多少少参考观察信息和文本指令。比如“把杯子放到左边”和“把杯子放到右边”这两个动作的token序列,前半段可能完全一样,后半段应该出现明显的语义分化。

我打算在这上面加一个对比损失,让相同意图、不同实例的token序列距离更近,不同意图的token序列距离更远。如果这条路径走通,动作token化器就不再只是一个压缩器,它会更接近一个动作词法表,后面接什么任务都更顺手。

最后,回到开头那个问题:什么构成优秀的动作token化器?我现在的答案很朴素——它应该在重构精度、码本利用率、时序一致性、语义可迁移性这四个维度上打好平衡,并且必须在真实的下游任务里经得住检验。好的token化器不一定把重构loss压到最低,但它能给上层模型提供一套稳定、可组合、有语义的动作语法。这个判断标准,我会继续用在自己后面的项目里。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进