0x00 概要
本篇主要介绍 DrQ 和 VICE,
- 机器人必须通过像素看世界,DrQ 解决了视觉特征提取的泛化难题。
- VICE 解决了"真实世界没有代码奖励"的问题,让机器人拥有了"成就感"。

注:
- 本系列的最终目标是"通过一系列相关项目/算法的解读,来深入学习/分析/反推 LWD(Learning while Deploying)这篇论文的机理和可能实现"。之所以从SERL入手,是因为 SERL,HIL-SERL,SOP(没有开源)都是罗剑岚博士的一系列论文,可以从中管窥作者的思路脉络。
- 本文依然是从工程/论文进行反推,还请读者不吝指出问题,多谢。
0x01 奖励与自动化:从手写 reward 到 learned reward
真实机器人任务的 reward specification 是 SERL 重点解决的问题之一。
某些任务可以用机器人状态直接定义,例如 PCB 插入可以根据末端或物体位置设计奖励;在这种情况下,奖励函数可以由一个二元分类器提供,该分类器接收状态观测 s 并输出一个二元" 事件"e发生的概率,对应于任务的成功完成。奖励随后由 r(s) = log p(e|s) 给出。
但多数任务需要从图像中判断是否完成。比如"电缆是否正确卡入槽位""物体是否被放到正确 bin 里",往往需要视觉判定,手工设计一个好的奖励函数本身就是一个耗时且需要领域专长的任务。
SERL 支持三类奖励方式:
- 手写奖励:适用于状态足以判断成功的任务
- 二分类成功判别器:用成功/失败图像训练 classifier
- VICE:把 reward classifier 与 RL 过程结合,在训练中利用策略产生的负样本更新分类器

1.1 Success Classifier:用少量图像训练自动裁判
用户可以采集少量成功与失败图片,训练一个轻量视觉分类器。该分类器在训练过程中输出成功概率或二值 reward。即,人只需要定义成功 / 失败样本,之后系统就能自动在训练中判断 reward。这就是自动裁判(Success Classifier):
Success Classifier 是离线训练专用,是简单的端到端结构:
- 架构: 基于轻量级视觉分类器,利用少量正负样本图像快速预训练。
- 职责: 在训练过程中实现 Reward 自动化,使系统具备无人值守的自我优化能力。
- 流程: 先收集正/负样本 pkl → BCE loss 训练 ResNet-10 冻结 + MLP head → 保存 checkpoint → 加载到 env wrapper 中作为固定奖励函数
这里需要注意:SERL 不是完全不需要 reward,而是提供了工具让 reward specification 更容易、更自动化。SERL 的目标不是消除 reward 设计,而是降低 reward engineering 的成本。
1.2 VICE:把奖励分类器也纳入训练
VICE 将奖励学习看成类似 GAN 的过程:策略像 generator,不断产生新样本;reward classifier 像 discriminator,判断哪些状态是成功事件。这样,随着策略分布变化,classifier 可以用策略产生的新负样本继续训练,避免只在初始数据上过拟合。
这种设计让奖励系统能够自适应策略的进化。初始阶段,classifier 在人类标注的样本上训练;随着策略能力的提升,classifier 可以从策略产生的经验中学习更细粒度的成功模式,实现奖励的持续改进。
VICE 是在线训练, 支持 return_encoded / classify_encoded 模式
- serl-main/serl_launcher/serl_launcher/agents/continuous/vice.py
- VICEAgent 继承自 DrQAgent, 分类器嵌入 agent 内部, 与 RL 训练交替在线更新
- 额外正则化: Mixup + Label Smoothing + Gradient Penalty
0x02 DrQ vs VICE
在具身智能和强化学习的语境下,DrQ 和 VICE 是两套针对不同痛点的经典 Agent(代理)架构。
简单来说:DrQ 是为了 "看得更清",而 VICE 是为了 "罚得更准"。
- VICE(上帝之眼):解决"是否到达",通过分类器提供平滑、客观的奖励信号。
- 视觉鲁棒性(DrQ)
- 随机裁剪:对 s 和 s′ 使用不同偏移,强迫网络学习物理特征而非像素坐标。
- 共享编码器:Actor 和 Critic 共用 ResNet-10,加速特征收敛。
这样可以视觉与硬件包装:眼手合一。
2.1 DrQ Agent (Data-regularized Q-learning)
用途:解决 "从图像直接学习动作(Pixel-to-Action)" 时的样本效率和泛化问题。
-
它的痛点: 如果直接把原始图片丢给 RL 模型,机器人很容易 "只看局部",或者因为光线变了一点就完全不会动了。
-
核心特色:图像增强(Data Augmentation)+ 一致性。
- 做法:机器人每看到一张图,DrQ 会在后台生成它的几个 "微调版"(比如随机裁剪 4 个像素、稍微改变亮度)。
- Q-Learning的一致性: DrQ 强制要求模型对这几张 "微调版" 照片预测出的 Q 值必须是一样的。
-
优点:
- 极强的稳定性: DrQ 像是在训练时给机器人戴上了各种滤镜,强迫模型学会看透事物的本质特征。
- 极高的样本效率: 即使数据量很少,通过图像增强,模型也能学到稳健的特征。
2.2 VICE Agent (Variational Inverse Control with Events)
用途: 解决 "自动定义奖励(Reward)" 的问题,特别是针对那些难以用数值描述的 "事件"。
-
它的痛点: 你怎么告诉机器人 "盖子盖紧了"?你很难写代码去算角度。
-
核心特色:反向学习(Inverse RL)+ 事件判定。
- 做法: 开发者给机器人看一堆 "成功的快照"。(比如 20 张盖好盖子的照片)。VICE 会通过这些照片训练一个判别器(Classifier)。
- 特色奖励: 在机器人自己尝试时,VICE 会根据它当前的画面 "多像" 那堆成功快照,自动产生一个 θ 到 1 之间的分值。
-
优点:
- 零代码定义 Reward: 只要有成功的照片,你就拥有了奖励函数。
- "事件" 驱动: 它的逻辑是 "我只要结果那个状态",非常适合解决那些结果明确但过程复杂的任务(如:扣上纽扣)。
2.3 Agent体系
在 SERL 中,SACAgent 是核心基类。
2.3.1 SACAgent 核心机制
| SACAgent 核心机制 | 功能 |
|---|---|
critic_loss_fn() |
Clipped Double Q + ensemble min + 可选 entropy backup |
policy_loss_fn() |
标准 SAC actor 目标:Q−a⋅logπ |
temperature_loss_fn() |
Lagrange 乘子自动调温 |
update_high_utd() |
关键优化:UTD ratio 多次 critic 更新 + 1 次 actor 更新,用 jax.lax.scan 实现 JIT 内循环 |
update() |
支持选择性更新子网络(networks_to_update 参数) |
2.3.2 继承体系
DrQAgent 和 VICEAgent 是 SACAgent 的子类。这意味着它复用了 SAC 的所有稳定机制(双 Q、熵自动调节),只是在输入端加了一层图像处理逻辑。
DrQAgent 增强 (drq.py)
- 对 observations 和 next_observations 做 random crop 数据增强
- 支持 3 种编码器:small (4 层 CNN)、resnet(从零训练)、resnet-pretrained(ImageNet 预训练 ResNet-10 冻结 + 可训练 pooling head)
VICEAgent 奖励学习 (vice.py)
-
用 Binary Classifier 替代手工奖励函数
-
训练技巧:Mixup + Label Smoothing + Gradient Penalty(防 GAN 模式崩塌)
-
推理时:
sigmoid(logit) >= 0.5 → reward=1, 否则 reward=0SACAgent (sac.py:21)
├─ create_pixels() → 像素输入,共享/独立编码器
└─ create_states() → 状态输入,无编码器
└─ DrQAgent (drq.py:23) 继承 SACAgent
├─ create_drq() → 支持 small/resnet/resnet-pretrained 编码器
├─ data_augmentation_fn() → 随机裁剪增强
├─ update_high_utd() → 高 UTD 比率训练(关键优化)
└─ update_critics() → 仅更新 Critic
└─ VICEAgent (vice.py:26) 继承 DrQAgent
├─ create_vice() → 额外 VICE 分类器网络
├─ update_vice() → BCE + Mixup + Label Smoothing + 梯度惩罚
├─ vice_reward() → 用分类器替代手工奖励
└─ update_high_utd() → 用 VICE 奖励替换环境奖励
2.4 深度对比表
| 特性 | DrQ Agent | VICE Agent |
|---|---|---|
| 主要任务 | 视觉表征学习(把图看透)。 | 奖励函数生成(把分打准)。 |
| 核心技术 | 随机裁剪、平移(Augmentation)。 | 基于正负样本的对抗学习 / 判别。 |
| 解决的难题 | 图像噪声、过拟合、泛化差。 | 奖励函数难写、信号稀疏。 |
| 在 SERL 里的位置 | 作为视觉主干(Backbone)的增强。 | 作为自动裁判(Reward Classifier)的原型。 |
0x03 DrQ:让视觉 RL 更稳健
真实机器人通常使用图像观测。如果直接用像素训练,视觉过拟合会非常严重。DrQ 的核心是对图像做数据增强,例如 random crop,让策略不要记住某个固定像素位置,而是学到更鲁棒的视觉特征。
SERL 的 DrQAgent 在 SAC 基础上增加了图像增强和视觉编码器支持。
3.1 数据增强的作用
DrQ 最重要的特性是 random crop 数据增强。这在真实机器人环境中尤为关键:
- 视觉正则化:阻止模型记住绝对像素位置,强迫其学习鲁棒的空间特征
- 对真实世界的适应性:真实环境中光照、背景、物体位置都会有微小变化,数据增强让模型对此具备天然的泛化能力
特色解释:为什么 DrQ 在 SERL 中不可或缺?
- 像素级鲁棒性:如果没有 DrQ 的数据增强,机械臂只要位置偏了一点,或者光照变了一下,RL 就会失败。DrQ 通过随机裁剪,让网络学会关注"物体本身",而不是"物体在图像中的绝对坐标"。
- 极致的性能:在 update_high_utd中,可以看到 DrQ 是在进行高 UTD 更新之前,先做一次数据增强。
- 细节:由于 batched_random_crop 是在 JAX 内部实现的,它运行在 GPU 上。相比于在 CPU 上做增强再传给 GPU,这节省了巨大的数据传输开销。
论文实验中使用了 ImageNet 预训练 ResNet-10 作为视觉 backbone,并连接到 MLP。观测包含相机图像和机器人本体信息,例如末端位姿、速度、力、力矩等。
3.2 视觉编码器体系
3.2.1 EncodingWrapper(特征融合) 设计
EncodingWrapper 是 SERL 处理视觉和本体感知输入的核心封装:
- 多图像键支持(dict of encoders)
- 帧堆叠处理 :
rearrange(image, "T H W C -> H W (T C)")和rearrange(image, "B T H W C -> B H W (T C)") - 本体感知融合:Dense → LayerNorm → tanh → concat
stop_gradient支持:允许冻结视觉编码器- 将视觉像素与本体感知(关节角度)拼接。视觉告诉你物体在哪,本体感知告诉你手在哪。"眼手合一"是精细操作的前提。
编码器选项:
- SmallEncoder:4 层 Conv (32→64→128→256) + Spatial Softmax + bottleneck
- ResNet-10 (from scratch):10 层 ResNet + Spatial Learned Embeddings
- PreTrained ResNet-10:ImageNet 预训练冻结 + 可训练 pooling head
Spatial Softmax / Spatial Learned Embeddings 是一个关键设计:它们将卷积特征图转为空间坐标的加权期望,保留空间信息同时大幅降维。这比全局池化更能保留空间位置信息,对机器人视觉任务至关重要。
3.2.2 视觉 - 本体感知 Fusion 详解
在 SERL 中,观测数据结构如下:
observations = {
"image1": (T, H, W, C), # 相机1图像帧栈
"image2": (T, H, W, C), # 相机2图像帧栈
"state": (T, D), # 本体感受 (tcp_pose, tcp_vel, gripper, force, torque)
}
EncodingWrapper 的处理流程如下:

Late Fusion 设计:视觉和本体感受各自独立编码,最后拼接。这不是 Early Fusion,而是有深刻的设计考量:
- 模态异质性:图像是 2D 空间信号,适合卷积;state 是 1D 向量,适合全连接
- 梯度隔离 :Late Fusion 允许对视觉编码器做
stop_gradient,冻结视觉特征只训练本体感受分支 - 本体感受的 LayerNorm + tanh:state 的各维度量纲差异大(位置 vs 速度 vs 力),归一化到统一尺度,限制范围避免梯度爆炸
3.2.3 多任务接口条件
代码中预留了但未使用的多任务接口:
| 功能 | 状态 | |
|---|---|---|
| GCEncodingWrapper | Goal-Conditioned: obs + goal 图像 fusion | 代码存在但未被任何 Agent 使用 |
| LCEncodingWrapper | Language-Conditioned: obs + 语言指令 fusion | 代码存在但未被任何 Agent 使用 |
这些接口为未来的多任务学习或条件策略扩展预留了空间。
3.3 算法实现
3.3.1 DrQAgent 网络架构
由于 DrQAgent 继承 SACAgent,其网络架构基本相同,但有以下关键差异:
| 特性 | SACAgent | DrQAgent |
|---|---|---|
| Actor架构 | 编码器+MLP256,256 | 相同 |
| Critic架构 | 编码器+Ensemble MLP256,256×2 | 相同 |
| 数据增强 | 无 | 随机裁剪(4像素填充) |
| 训练稳定性 | 标准SAC | 增强提高泛化 |
3.3.2 编码器选择
DrQAgent 支持多种编码器类型:
- SmallEncoder:轻量级4层卷积网络
- ResNet-10:中等深度ResNet
- ResNet-10-Pretrained:ImageNet预训练的ResNet-10
3.3.3 async_drq_sim.py代码细节
以 async_drq_sim.py 为例,SERL 的训练流程是典型的异步 actor-learner 架构:
1. Learner 初始化:创建 TrainerServer,注册 replay buffer,等待数据填充
2. Actor 初始化:创建 TrainerClient,连接 Learner
3. Actor 循环:
- 采样动作(前 N 步随机,之后用策略)
env.step()→ 收集 transition →data_store.insert()- 定期
client.update()→ 推送数据 - 定期
evaluate()→ 发送 stats
4. Learner 循环:
- 等待 buffer 填充到
training_starts - 发送初始网络参数给 Actor
- RLPD 50/50 采样:batch 一半来自 demo_buffer,一半来自 online replay_buffer
update_critics()× (UTD-1) 次 →update_high_utd(utd_ratio=1)× 1 次- 定期
server.publish_network()同步参数 - WandB 日志记录
这种异步架构让机器人控制的高实时性要求和训练的高吞吐量要求解耦,互不干扰。
3.3.4 drq.py 逻辑流程图

3.3.5 drq.py 代码拆解
- 数据增强(data_augmentation_fn):这是 DrQ 的"灵魂"。它对 observations 中的每一个 pixel_key(图像键)执行 batched_random_crop。
-
原理:即使图像只位移了 1-2 个像素,对于神经网络来说也是全新的输入。这相当于有限的数据池注水,极大增加了泛化性。
def data_augmentation_fn(self, rng, observations):
for pixel_key in self.config["image_keys"]:
observations = observations.copy(
add_or_replace={
pixel_key: batched_random_crop(
observations[pixel_key],
rng,
padding=4,
num_batch_dims=2
)
}
)
return observations
-
训练时,会对观测和下一观测进行数据增强。
def update_high_utd(self, batch, utd_ratio, pmap_axis=None):
# 训练时对观测和下一观测进行数据增强
rng, obs_rng, next_obs_rng = jax.random.split(rng, 3)
obs = self.data_augmentation_fn(obs_rng, batch["observations"])
next_obs = self.data_augmentation_fn(next_obs_rng, batch["next_observations"])
batch = batch.copy(
add_or_replace={
"observations": obs,
"next_observations": next_obs,
}
)
# 调用父类SAC的更新
return SACAgent.update_high_utd(new_agent, batch, utd_ratio=utd_ratio, pmap_axis=pmap_axis)
update_critics 方法也有调用如下:
def update_critics(self, batch, pmap_axis=None):
# 数据增强
obs = self.data_augmentation_fn(obs_rng, batch["observations"])
next_obs = self.data_augmentation_fn(next_obs_rng, batch["next_observations"])
# 仅更新Critic网络
new_agent, critic_infos = new_agent.update(
batch,
pmap_axis=pmap_axis,
networks_to_update=frozenset({"critic"}) # 只更新Critic
)
-
编码器选择(create_drq):它提供了灵活的配置:
- small:简单的卷积层,适合简单任务。
- resnet:标准的 ResNet-10,SERL 的主力方案。
- resnet-pretrained:加载预训练权重(通常是 ImageNet 预训练),适合非常复杂的视觉场景。
- 优秀选择较小模型:在高 UTD 场景下,计算机通常在带宽和前向传播受限。如果模型太大,GPU 每一秒处理不完那 20 次更新,整个训练过程就会从"20 分钟学会"变成"2 小时学会"。在机器人领域,实时性(Speed)往往比深度(Depth)更重要。
-
随机种子(随机裁剪偏移量):在计算数据增强时,它对 observations 和 next_observations 使用了两个不同的随机种子(obs_rng 和 next_obs_rng)。
- 用不同种子的好处:如果 s 往左移,s' 往右移,这会产生一种类似"抖动"的效果。这强迫网络明白:无论目标在图像的哪个角落,无论视角怎么晃动,动作与状态之间的物理逻辑是不变的。这极大地增强了策略的"空间不变性"。
- 用同样种子的坏处:如果你对 s 和 s' 使用相同的偏移(比如都往左上角移 4 像素),同样种子会导致虽然位置变了,但 s 到 s' 的相对位置没变,机器人学到的是一种"死板的平移"。
-
共用视觉头(Shared Encoder)在 create_drq 中,shared_encoder 默认是 True。如果设为 False(Actor 和 Critic 各用各的视觉网络),其优势如下:
- 特征一致性:Actor 想知道:"杯子在哪里?"。Critic 想知道:"手抓到杯子的价值是多少?"。它们关心的都是同样的物理特征。让它们共用一个 ResNet,就像是让两个学生共用一本最好的教科书。一个学生(Critic)学得快,他理解的知识点会通过梯度回传,帮助另一个学生(Actor)也快速进步。
- 极速收敛:视觉特征的学习是 RL 中最慢的部分。由于 Critic 接收的是奖励信号(比较明确),它训练视觉头的速度通常比 Actor 快。搭便车效应:Actor 直接使用 Critic 已经练好的视觉特征,只需要学习如何映射到动作即可。这能节省 50% 以上的训练时间。
- 节省显存:ResNet 的显存占用很大。在 UTD=20 时,显存消耗非常惊人。共用一个头可以腾出更多的空间给更大的 Batch Size。