【机器人 / 强化学习】HIL-SERL 算法篇:DQN + SAC 混合架构的实现哲学
0x00 概要
HIL-SERL 不是单一算法,而是一套高度集成的混合 RL 系统。它用 SAC 给机器人灵动的手臂、用 DQN 给机器人果断的夹爪、用二值分类器替代手工奖励设计、用人类干预数据弥合 RL 探索与 BC 模仿之间的鸿沟。这篇我们从算法角度逐层拆解它的设计逻辑。
HIL-SERL-train
0x01 HIL-SERL 的总体算法思路
1.1 HIL-SERL 的算法基因图谱
HIL-SERL 的核心算法血统是:
[MDP 框架]
│
↓
[SAC (Off-policy, 双Q网络)] → [最大熵RL: 探索 + 鲁棒性]
│
│ + 离线专家数据
↓
[Offline RL (BCQ/BEAR/CQL 思想)] → [Q_exp 作为先验: 解决稀疏奖励 + 冷启动]
│
│ SAC + Q_exp 先验
│ + offline demonstration replay = RLPD
↓
[SERL] → [带专家先验的在线RL: 解决离线RL无法超越专家]
│
│ + 人类实时纠正
↓
[DAgger / Interactive Learning] → [在策略分布下收集纠正数据: 解决分布偏移]
│
│ SERL + HIL
↓
[HIL-SERL] → [高精度灵巧操作的完整解决方案]
这不是一条"算法演进"的树形图谱,而是一条明确的工程叠加链——HIL-SERL 的每一个组件都在解决真实世界 RL 的一个具体痛点:
SAC 提供样本高效的连续控制基类,通过熵正则化保证探索稳定性。
RLPD 通过 50/50 混合采样(online + demo)和高 UTD 比率,让 SAC 能同时利用离线专家数据和在线交互数据训练,解决从零开始 RL 样本效率不足的问题。
HIL-SERL 在 RLPD 之上叠加三层工程创新:人类实时纠正(解决 RL 在真实环境中探索成本过高的问题)、混合动作空间(解决夹爪连续输出犹豫不决的问题)、二值分类器奖励(解决复杂视觉任务中奖励工程困难的问题)。
1.2 三类预训练/先验机制
HIL-SERL 包含三类"预训练/先验"机制,每一类解决不同层面的冷启动问题:
视觉 encoder 使用 ImageNet-1K 预训练 ResNet-10:解决真实图像复杂度高、从头训练数据效率太低的问题
每个任务单独训练 reward classifier:解决奖励函数难以手工设计的问题
少量人类 demonstrations 做 BC 预训练 / RLPD 加速:解决 RL 冷启动探索盲目性的问题
但这些都不是"跨机器人形态、跨任务的通用价值函数"。它们是任务特定的、工程导向的先验。
1.3 HIL-SERL 的训练流程
整个训练过程可以分为四个阶段:
阶段一:离线准备
人类通过遥操作收集两类数据:
200 张成功 + 1000 张失败图像 → 训练 Reward Classifier(约 5 分钟)
20-30 条成功演示轨迹 → 初始化 Demo Buffer
阶段二:专家 Q 函数预训练(Offline)
在离线演示数据上执行标准 Bellman Backup 训练 Q_exp(s, a),用于初始化在线 Critic。这防止了在线训练启动时 Critic 从零开始的盲目性。
阶段三:在线 HIL-SERL 训练
策略执行 π(a|s) → 人类观察
├─ 机器人正常执行 → 继续自主
└─ 机器人出错/卡住 → 人类通过 SpaceMouse 接管
↓
干预数据存入 D = D_exp ∪ D_RL ∪ D_HIL(双通道)
↓
SAC 策略更新(含 RLPD 50/50 采样 + Q_exp 先验引导)
↓
干预频率随策略提升逐步下降,直至策略自主完成任务
阶段四:部署
训练好的策略部署到真实机器人。由于策略是通过 RL 而非纯 BC 训练的,它学会了比人类操作更快的动作模式(论文报告约 1.8x faster)。
训练循环
具体训练循环如下:
HIL-SERL-HIL-SERL 训练循环
训练数据流
训练数据流如下:
HIL-SERL-训练数据流
1.4 为什么 HIL-SERL 能超越模仿学习
论文报告 HIL-SERL 相比 imitation learning baseline 有显著提升。我们从机制上拆解原因:
模仿学习的根本局限:BC 训练的是"在专家状态下输出专家动作"。一旦机器人偏离专家状态,BC 没见过这些 OOD 状态,无法恢复。
HIL-SERL 的打破方式:人类纠偏正好发生在策略出错时。这些数据不是普通演示,而是"从错误状态恢复"的演示。这类数据对解决 compounding errors 非常有效。
RL 还能优化速度和路径:论文还强调 HIL-SERL 不只是提高成功率,也能降低 cycle time。RL 不只是模仿人类路径,它可以在任务 reward 驱动下探索更快、更适合机器人的动作模式。人在操作时会有冗余动作(手抖、犹豫、绕路),SAC 的目标是最大化 Q 值,在训练中会发现"更短、更直"的路径。
0x02 核心算法:RLPD + 人类干预
2.1 RLPD 的"干预版"
HIL-SERL 依然以 RLPD 为核心,但数据来源变了。传统的 RLPD 只用开始采集好的 Demo,而 HIL-SERL 在训练过程中通过人类干预不断注入新的高质量数据。人在训练过程中如果发现机器人要搞砸了,可以用 SpaceMouse 实时接管。这些接管的数据会被存入 Buffer。
算法本质依然是带有高 UTD 和 LayerNorm 的 SAC,但它通过人类干预解决了 RL 在复杂任务(如插拔正时皮带、组装仪表)中"探索不到成功状态"的难题。
HIL-SERL-RLPD
为什么 50/50 采样有效? 在线数据提供最新的状态覆盖,让 Critic 学习到当前策略分布下的价值;Demo 数据(含干预)提供高价值 recovery 轨迹,防止 Critic 忘记专家先验。两者缺一不可。
2.2 BC Loss 的作用
在 rlpd.py 中有一行 bc_loss(Behavioral Cloning Loss)。在升级 Actor 时,如果不仅让它最大化 Q 值,还强制让它模仿演示数据里的动作,这对训练初期的稳定性有很大帮助。
为什么 BC Loss 能稳定初期训练?因为模仿学习的"真理信号"来自人类演示,而不是来自不一定靠谱的自奖励网络。
BC 的 Loss:
Loss
(
a
−
a
demo
)
2
——目标很明确,动作要离人类近
SAC 的 Loss:
Loss
α
log
π
(
a
|
s
)
−
Q
(
s
,
a
)
——目标是熵要大且 Q 值要大
当 Q 值萎缩到 0 时:SAC 的 Loss 变成了
α
log
π
(
a
|
s
)
−
0
。为了最小化这个 Loss,智能体必须最大化熵(随机性)。后果是智能体开始胡乱甩动,彻底忘记人类教过什么。BC Loss 的引入正是为了防止这种退化。
2.3 训练数据流的双通道设计
HIL-SERL-双流
0x03 混合动作空间:SAC + DQN
HIL-SERL 作者发现用 SAC 去夹爪的"开关"这种二进制动作动作效率很低,所以单独拆了一个 DQN 出来专门练"抓取评价"。
SAC:负责控制机械臂的 6D 末端位姿(连续动作)。通过正则化,SAC 的探索是"平滑"的。它在动作周围进行微小的、有目的的试探。这也是为什么 SAC 更适合精细动作。
DQN(Deep Q-Network):专门负责控制夹爪的开/关(离散动作)。DQN 的探索是"抽风式"。大部分时间选最好的,小部分时间随机乱选一个动作。这比较生硬。
这解决了机器人操作中一个非常现实的矛盾:手臂需要丝滑的连续移动,而手指(夹爪)通常只需要果断的开关动作。
3.1 为什么要"分而治之"?
机器人操作的动作由两个截然不同的部分组成:
机械臂运动(连续空间):手臂需要在三维空间中精确移动,位置坐标
(
x
,
y
,
z
)
是连续的实数。SAC 的 Gaussian policy 通过输出均值
μ
和方差
σ
,天然适合这类"具有无限可能性的平滑运动"。
夹爪动作(离散空间):夹爪通常只有两到三个离散状态——张开、保持、闭合。如果用 SAC 的连续输出去拟合,会产生类似 0.13、-0.27 的中间值,夹爪执行时被阈值化,学习信号不稳定。更重要的是,"何时闭合夹爪"是一个非常关键的离散决策——闭早了抓空,闭晚了错过物体。
这就引出了一个自然的设计:用 SAC 给机器人灵动的手臂,用 DQN 给机器人果断的夹爪。
3.2 SAC + DQN 如何协同工作
在 HIL-SERL 的网络中,SAC 和 DQN 不是两个独立的进程,而是同一个主干下的两个分支:
HIL-SERL-网络架构
共享主干:所有相机图像经过 ResNet-10(共享权重)编码后,与 proprioception 的 MLP 编码拼接,输入共享的 MLP Head。
SAC 分支:输入特征 → 输出均值 μ 和方差 σ → 采样得到连续的 Delta 位移向量(单臂 6 维,双臂 12 维)。
DQN 分支(GraspCritic):输入特征 → 输出 3 个 Q 值(关/保持/开)→ argmax 选出最大 Q 的动作。
统一更新:虽然算法不同,但它们在同一个训练循环中被同步优化。
具体来说,HIL-SERL 在这些任务中分别求解两个马尔可夫决策过程MDP
M
1
{
S
,
A
1
,
ρ
1
,
P
1
,
r
,
γ
}
M
2
{
S
,
A
2
,
ρ
2
,
P
2
,
r
,
γ
}
其中
A
1
和
A
2
分别是连续和离散动作空间。它们都接收来自环境的相同状态观测,如图像、本体感受、夹爪状态等。对于
M
2
的critic更新,遵循标准的DQN方法,并引入额外的目标网络以稳定训练。
在训练或推理时,HIL-SERL 首先从策略
M
1
中查询连续动作,然后在
M
2
中通过对评论家critic的输出取argmax来获得离散动作,最后作者将连接的动作应用于机器人。
3.3 Rollout 时的配合
在 sample_actions 方法中,两个分支的协作流程非常清晰:
def sample_actions(self, observations, *, seed, argmax=False, **kwargs):
# SAC 部分:生成连续动作(位置+方向)
dist = self.forward_policy(observations, rng=seed, train=False)
if argmax:
ee_actions = dist.mode()
else:
ee_actions = dist.sample(seed=seed)
# DQN 部分:选择离散抓取动作
grasp_q_values = self.forward_grasp_critic(observations, rng=grasp_key, train=False)
grasp_action = grasp_q_values.argmax(axis=-1) # 贪心选择
# 组合动作
return jnp.concatenate([ee_actions, grasp_action[..., None]], axis=-1)
连续控制分支的 Rollout 符合经典 SAC 范式——只依赖 Actor 网络,Critic 不参与推理。
离散控制分支则不同——由于没有独立的 Actor 网络,GraspCritic(DQN)在推理时直接充当"决策者",通过 argmax 选出最优离散动作。
3.4 训练时的配合
训练时,SAC 和 DQN 使用不同的损失函数和不同的回传目标,但共享同一套观测编码:
Critic 训练:只使用连续动作部分
actions = batch[“actions”][…, :-1] # 去掉最后一维夹爪
target_q = rewards + γ · min_i Q_target_i(s’, a’)
critic_loss = MSE(predicted_q, target_q)
GraspCritic 训练:只使用离散夹爪部分 + grasp_penalty
grasp_rewards = batch[“rewards”] + batch[“grasp_penalty”]
target_grasp_q = grasp_rewards + γ · max Q_target(s’, a’_grasp)
grasp_critic_loss = MSE(predicted_q, target_grasp_q)
Actor 训练:采样连续动作,最大化 Q(s,a) - α·log π(a|s)
actor_loss = -mean(predicted_q - temperature * log_probs)
这种设计确保了 reward 的分工:SAC Critic 只关心末端执行器的连续动作价值,GraspCritic 则额外学习"不要做无意义夹爪动作"的惩罚信号。
3.5 输出维度对比
设计者把动作空间拆成了两部分:
前 6 维连续动作:由 SAC actor 输出
最后 1 维夹爪动作:由 GraspCritic(DQN 风格)选择
在 update 中,agent 明确要求单臂动作维度为 7:
chex.assert_shape(batch[“actions”], (batch_size, 7))
具体对比如下:
Agent 类型 SAC 输出 DQN 输出 最终动作维度
SingleArm 6 维 (3位置+3方向) 3 维 (关/保持/开) 7 维
DualArm 12 维 (双臂各6维) 9 维 (3×3双臂组合) 14 维
3.6 SAC vs DQN:靶向设计
两者在 Target 计算上的差异反映了各自面对的问题域完全不同:
DQN 的 Target(硬最大):
Target
r
+
γ
max
a
′
Q
target
(
s
′
,
a
′
)
它假设下一时刻一定选分数最高的动作。因为动作空间是离散且极小的(2-3 个),通过 target_net 的缓慢更新已经能抵消大部分高估问题。
SAC 的 Target(软预期):
Target
r
+
γ
E
a
′
∼
π
(
⋅
|
s
′
)
[
Q
target
(
s
′
,
a
′
)
−
α
log
π
(
a
′
|
s
′
)
]
它不选最高的,而是对当前策略输出的所有可能动作求期望,并加上熵奖励。面对无穷多个连续动作,SAC 必须用"双网络取最小值(Clipped Double-Q)"来暴力压制高估。
把这两个差异总结成一句话:DQN 是"轻量级防守"(离散空间够小,target_net 足矣),SAC 是"重量级防守"(连续空间高估无限放大,必须双 Q + min)。这个区别反映在代码上就是:DQN 找最大值,SAC 则需要从 Actor 里采样一个动作
a
′
出来算 Q 值。
0x04 GraspCritic:夹爪离散决策网络
GraspCritic 是 HIL-SERL 中一个关键但容易被忽视的设计。它不是普通 SAC 里的 Critic(s, a),而是一个只输入 observation、不显式输入 action 的离散动作 Q 网络。
4.1 定位与架构
GraspCritic 不是普通 SAC 里的 Critic(s, a),而是一个只输入 observation、不显式输入 action的离散动作 Q 网络。
GraspCritic 的输入输出:
输入: observation (无 action)
输出: 3 个 Q 值 → 夹爪开(0) / 保持(1) / 关(2)
训练: DQN 式目标(在线网络选动作 + 目标网络评估)
奖励: grasp_rewards = batch[“rewards”] + batch[“grasp_penalty”]
网络结构同样采用 ResNet-10 + MLP,但 MLP 维度比 Critic 小(单臂为 [128, 128]),因为它只需要学习夹爪的离散决策,不需要建模连续动作的精细价值。
class GraspCritic(nn.Module):
encoder: Optional[nn.Module]
network: nn.Module
output_dim: Optional[int] = 3 # 默认3个离散动作
def __call__(self, observations, train=False):
obs_enc = self.encoder(observations) # ResNet编码
outputs = self.network(obs_enc, train) # MLP
value = nn.Dense(self.output_dim)(outputs)
return value # (batch_size, 3)
核心前向逻辑是:
输入 observation;
经过视觉 / proprioception encoder;
经过 MLP;
输出 output_dim 个 Q 值。
默认 output_dim=3,对应单臂夹爪的 3 个离散动作:
0 -> 环境动作 -1
1 -> 环境动作 0
2 -> 环境动作 +1
代码里训练时把环境动作最后一维从 {-1, 0, 1} 映射到 {0, 1, 2}:
grasp_action = jnp.round(batch[“actions”][…, -1]).astype(jnp.int16) + 1
因此,GraspCritic(obs) 的输出可以理解为:
[
Q_grasp(obs, close_or_negative),
Q_grasp(obs, keep),
Q_grasp(obs, open_or_positive)
]
也就是说,它在回答一个很具体的问题:在当前图像和机器人状态下,夹爪应该关、保持,还是开?每个选择未来能带来多大价值?
4.2 为什么需要单独的 GraspCritic?
考虑一个完整的动作向量:[x, y, z, roll, pitch, yaw, gripper]
前 6 维是连续控制,SAC 的 Gaussian policy 天然适合。但最后一维 gripper 本质上不是平滑连续信号。如果把夹爪也塞进连续的 SAC actor 里:
动作语义离散但 policy 输出连续:SAC 会输出类似 0.13、-0.27 这种值,夹爪真实执行时被阈值化,导致学习信号不稳定
夹爪动作稀疏且关键:抓取任务里,"什么时候闭合夹爪"是一个关键离散决策。闭早了抓空,闭晚了错过物体
夹爪误操作需要额外惩罚:代码里有 grasp_penalty,惩罚不必要的开合动作。例如 USB 插入任务中,夹爪已经接近闭合还继续执行关闭动作,会产生惩罚
所以这份实现把动作空间拆开:
连续末端执行器动作:SAC actor + SAC critic 学
离散夹爪动作:GraspCritic 用 DQN 风格学
4.3 学习原理:Double DQN 风格的离散动作价值学习
GraspCritic 的训练采用的是 DQN 式 Bellman 回归
Q
θ
(
s
t
,
a
g
t
)
←
r
g
t
+
γ
⋅
max
a
g
t
+
1
Q
¯
θ
(
s
t
+
1
,
a
g
t
+
1
)
,但代码里更接近 Double DQN:
Step 1:用 online grasp critic 选择下一步最优夹爪动作
next_grasp_qs = self.forward_grasp_critic(batch[“next_observations”], rng=rng)
best_next_grasp_action = next_grasp_qs.argmax(axis=-1)
Step 2:用 target grasp critic 评估这个动作的 Q 值
target_next_grasp_qs = self.forward_target_grasp_critic(…)
target_next_grasp_q = target_next_grasp_qs[jnp.arange(batch_size), best_next_grasp_action]
Step 3:构造目标值(含 grasp_penalty)
grasp_rewards = batch[“rewards”] + batch[“grasp_penalty”]
target_grasp_q = grasp_rewards + discount * masks * target_next_grasp_q
Step 4:当前网络只取实际执行的夹爪动作对应的 Q
predicted_grasp_q = predicted_grasp_qs[jnp.arange(batch_size), grasp_action]
Step 5:用 MSE 做 TD 回归
grasp_critic_loss = jnp.mean((predicted_grasp_q - target_grasp_q) ** 2)
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)