036、RT-1真实世界机器人Transformer动作Token化与模仿学习实战调试间里那台UR5又撞了。不是撞桌子是撞它自己——夹爪在接近目标点的时候突然抽搐了一下然后整个轨迹像喝醉了酒一样甩出去。我看了一眼终端loss还在往下掉验证集准确率0.87看起来一切正常。但机器人就是不听使唤。后来我把那一段的action序列打印出来发现了一个让人后背发凉的事实模型输出的末端执行器位移在连续两个时间步之间跳变了将近8厘米。8厘米在真实机器人上就是一次事故。而训练数据里人类示教时相邻两步的位移从来没超过2厘米。这就是RT-1论文里没有明说、但你在真实机器人上一定会撞见的问题动作空间连续回归在Transformer的离散token世界里根本行不通。动作Token化把连续空间切成能数得清的格子RT-1的核心改动是把原本连续的7维动作向量x, y, z, roll, pitch, yaw, gripper做了一次离散化。每个维度单独切成256个bin然后拼成一个token序列。听起来简单但这里有个细节你八成会忽略切分方式决定了机器人是稳还是疯。我当时第一版实现用的是均等切分——从动作最小值到最大值均匀分256份。训练loss降得飞快验证集准确率0.9以上一上真机就露馅。原因在于真实示教数据里末端执行器在大部分时间里只有微小位移偶尔才有大幅度移动。均等切分把大量bin浪费在那些极少出现的大位移上而小位移区域的分辨率严重不足。模型学到的其实是猜个大概反正loss已经够低了。正确做法是按数据分位数切分。先统计训练集里每个动作维度的分布然后按分位数把256个bin的边界定下来让每个bin里落的数据量大致相等。这样小位移区域有足够多的token去区分模型输出微小调整的能力就出来了。改完之后真机上那种抽搐式跳变基本消失。代码上分位数切分用np.percentile就能搞定# 这里踩过坑别用min-max均等切分真机上会疯# 用训练集所有动作数据算分位数边界all_actionsnp.concatenate([ep[actions]forepintrain_episodes],axis0)# [N, 7]bin_edges[]fordiminrange(7):# 256个bin需要257个边界0到100分位edgesnp.percentile(all_actions[:,dim],np.linspace(0,100,257))bin_edges.append(edges)推理时模型输出的是256个类别的logits取argmax得到bin索引再映射回连续值。映射的时候别直接取bin中心点用bin内数据的均值会更稳。因为分位数切分后每个bin内的数据分布不一定对称取均值比取中心点更接近真实示教数据的期望值。# 推理时把token映射回连续动作deftoken_to_action(token_idx,dim,bin_edges,bin_means):# token_idx: [seq_len, 7] 每个维度的bin索引# bin_means: [7, 256] 每个bin内训练数据的均值iftoken_idx0:returnbin_edges[dim][0]# 边界情况iftoken_idx256:returnbin_edges[dim][-1]returnbin_means[dim][token_idx]架构细节别把RT-1当成普通TransformerRT-1的backbone是EfficientNet FiLM条件化 Transformer encoder。很多人照着论文搭结果训练出来效果差一大截。我复盘下来问题出在三个地方。第一FiLM层的位置。论文里FiLM是对EfficientNet每个block的输出做条件化不是只在最后接一层。这意味着语言指令从浅层就开始影响视觉特征提取。如果你只在最后加FiLM模型学到的其实是先看全局再根据指令找目标而不是带着指令去看。这两种策略在简单场景下差别不大但一旦场景里有多个相似物体前者就会频繁抓错。第二token序列的长度。RT-1把EfficientNet输出的特征图展平成token序列这个序列长度是固定的。但实际操作中不同任务需要的视觉注意力范围不一样。比如抓红色杯子和把杯子放到抽屉里前者需要关注局部细节后者需要理解空间关系。固定长度token序列会让模型被迫在两者之间妥协。我的做法是在特征图展平之前加一个可学习的空间注意力mask让模型自己决定每个位置的特征重要程度。这个mask的初始化用全1训练过程中它会自动学会聚焦。# 空间注意力mask别小看这个真机成功率能差10个点classSpatialAttention(nn.Module):def__init__(self,feat_h,feat_w):super().__init__()# 可学习的mask初始化为全1self.masknn.Parameter(torch.ones(1,1,feat_h,feat_w))defforward(self,x):# x: [B, C, H, W]# 用sigmoid把mask限制在(0, 2)范围避免过大的缩放mask1torch.sigmoid(self.mask)# 范围(1, 2)returnx*mask第三动作token的序列组织。论文里把7维动作离散化后拼成一个token序列但顺序是固定的。我试过调整维度顺序发现gripper状态放在最后一位效果最好。原因可能是gripper只有开/关两种状态放在最后可以让Transformer在预测时先想清楚位置再决定抓不抓。如果你把gripper放前面模型会过早决定抓取动作导致位置预测被带偏。模仿学习实战数据增强是救命稻草RT-1的训练数据来自真实示教但真实数据的多样性永远不够。我跑过一个实验只用原始数据训练验证集准确率0.82但真机成功率只有40%。加了数据增强之后验证集准确率0.85真机成功率直接跳到75%。数据增强的关键不是多而是像。颜色抖动要有但幅度不能大——真实机器人看到的场景不会突然变色。随机裁剪要有但裁剪范围要控制在10%以内——否则模型会学到物体在画面中央这个假规律。平移增强要有但平移量要对应真实机器人工作空间的范围——你不可能让机器人去抓画面外的物体。# 数据增强这里踩过坑增强太猛模型学废了defaugment_image(image,action):# image: [H, W, 3] 相机原始图# action: [7] 对应的动作# 颜色抖动幅度要小ifrandom.random()0.5:imageadjust_brightness(image,delta0.1)# 别超过0.1imageadjust_contrast(image,factor1.1)# 别超过1.1# 随机裁剪范围控制在10%ifrandom.random()0.5:h,wimage.shape[:2]crop_hint(h*random.uniform(0.9,1.0))crop_wint(w*random.uniform(0.9,1.0))yrandom.randint(0,h-crop_h)xrandom.randint(0,w-crop_w)imageimage[y:ycrop_h,x:xcrop_w]imageresize(image,(h,w))# 动作也要跟着平移这里容易忘# 假设相机内参已知把裁剪偏移换算成机器人坐标系的位移action[0](x-w/2)*pixel_to_world_scale action[1](y-h/2)*pixel_to_world_scalereturnimage,action动作噪声注入也是关键。示教数据里的人类动作太完美了每一步都是平滑的。但模型在推理时由于token离散化输出天然带有量化噪声。如果训练时不给模型看带噪声的动作它遇到真实推理时的量化误差就会手足无措。我的做法是训练时以20%的概率给动作加上高斯噪声标准差设为该维度动作标准差的5%。这个幅度刚好模拟token量化误差又不会让模型学歪。训练策略batch size和learning rate的博弈RT-1的参数量不大约35M但训练起来很吃显存。因为输入是图像序列一般取6帧batch size稍微一大就OOM。我试过batch size 16和64发现batch size 32是个甜点——再小训练不稳定再大显存扛不住。learning rate方面RT-1论文用的是AdamW cosine schedule初始lr 1e-4。但我发现warmup阶段特别重要。因为动作token的类别分布极度不均匀——有些bin出现频率高有些几乎不出现。如果一开始就用大lr模型会迅速偏向高频bin陷入局部最优。我的做法是前2000步用线性warmuplr从0升到1e-4然后cosine decay到1e-5。这个改动让训练收敛速度慢了10%但最终准确率高了3个点。# 训练循环里的warmup别省这一步optimizerAdamW(model.parameters(),lr1e-4,weight_decay0.01)schedulerCosineAnnealingLR(optimizer,T_maxtotal_steps)forstepinrange(total_steps):ifstep2000:lr1e-4*step/2000forparam_groupinoptimizer.param_groups:param_group[lr]lrelse:scheduler.step()真机部署那些论文没告诉你的坑模型训练完验证集准确率0.9你以为就完事了真机部署才是真正的战场。第一个坑推理延迟。RT-1的Transformer encoder处理6帧图像在A100上大概需要30ms。但你的机器人控制器可能跑在工控机上只有一块RTX 3060。实测推理延迟可能到150ms这已经超过了机器人控制周期的容忍范围。我的解决方案是把图像编码器EfficientNet和Transformer encoder分开部署——图像编码器跑在GPU上Transformer encoder跑在CPU上两者通过共享内存通信。这样延迟能压到80ms左右。第二个坑动作平滑。即使做了token化模型输出的动作序列仍然可能有不自然的跳变。我加了一个指数移动平均滤波器对连续两步的动作做平滑# 动作平滑别用简单平均会滞后# 用EMAalpha0.7兼顾响应速度和平滑度smoothed_action0.7*new_action0.3*prev_smoothed_action这个滤波器让机器人动作看起来更人类但代价是响应速度变慢。如果你的任务需要快速抓取alpha可以调到0.8如果需要精细操作调到0.6。第三个坑失败恢复。模型预测的token可能落在不可能的区域——比如夹爪已经闭合了模型还预测继续闭合。我在部署时加了一个动作合法性检查如果模型输出的动作与当前机器人状态冲突比如夹爪已闭合但预测gripper1就强制用上一次的合法动作替代。这个检查在真机上避免了至少30%的抽风行为。经验之谈RT-1的边界在哪里RT-1不是万能的。我跑了半年真机实验总结出它的三个明显短板。第一长时序任务会崩。RT-1的Transformer encoder只处理6帧图像上下文窗口很短。对于先抓A再放B最后按C这种多步任务模型会迷失。我试过把窗口加到12帧效果有提升但显存翻倍。更实用的方案是任务分解——把长任务拆成多个RT-1子任务每个子任务单独训练用状态机切换。第二对光照变化敏感。训练数据里如果光照条件单一模型在真实环境里遇到阴影或反光就会瞎。我试过在训练时随机调整图像亮度、对比度但效果有限。更靠谱的方案是在部署时做图像归一化——用固定的参考白平衡校正图像让输入分布尽量接近训练集。第三动作token化牺牲了精度。256个bin听起来很多但对于需要毫米级精度的操作比如插拔连接器这个分辨率远远不够。我的经验是RT-1适合粗放型操作抓取、放置、推拉不适合精密装配。如果你要做精密操作要么增加bin数量但训练难度会上升要么改用混合方案——RT-1输出粗粒度动作再用一个小的回归模型做精调。最后说一句RT-1的价值不在于它本身有多强而在于它证明了离散化动作 模仿学习这条路走得通。后来的RT-2、Octo、OpenVLA都在这个框架上演进。但无论模型多先进真机调试的底层逻辑没变——先让动作稳再让动作准。稳都做不到准就是空中楼阁。