做强化学习的人,十有八九都经历过这种绝望:环境搭好了,算法写好了,训练脚本启动了,结果跑了几十万步,reward 曲线像一条死人的心电图,动都不动。我之前调一个机械臂抓取任务,用 PPO 硬跑了三个通宵,每次 episode 的奖励都稳定在 -0.1 左右,策略基本就是在原地抖。后来把 Hindsight Experience Replay(后见经验回放,简称 HER)加进去,一个晚上成功率就上了八成。这个 "hindsight"(事后诸葛亮)的思想,是 OpenAI 那篇 NeurIPS 2017 论文的核心,后来被无数个机器人、导航、游戏项目拿过来当急救包。
这篇文章我会把 HER 是什么、为什么它能成为稀疏奖励场景下的标配、以及实际落地时那些论文里不写的细节,一次性讲透。不管你是刚入门的 RL 新手,还是已经跑过几个环境的在职工程师,只要你的任务里有"多目标"这层结构,这篇内容都值得认真看一遍。
1. 先搞清楚 HER 到底解决的是什么问题
1.1 稀疏奖励:多少项目死在"永远学不会"这一步
先别急着看代码,得先把问题的本质掰扯清楚。强化学习本质上靠奖励信号驱动,奖励就像老师批改作业,学生(策略)根据对错调整自己的行为。但如果奖励是稀疏的——比如机器人任务里,只有末端执行器精确到达目标位置才给 reward,其他情况全是 0——那整个训练过程就相当于让一个学生做一份全是填空题、而且只有全对才给分的试卷。他试了无数次,每次都只能看到零分,根本不知道哪一步接近了正确答案,最后只能瞎蒙。
这种情况下,策略学到的东西几乎为零。更麻烦的是,探索本身也会陷入死循环:刚开始策略是随机的,它大概率到不了目标,于是拿不到任何正反馈,梯度信号接近于零,网络参数几乎不更新。你使劲加熵权重、加探索噪声,结果只是让策略在状态空间里横冲直撞,偶尔蒙到一次成功,但概率太低,根本不足以形成稳定的学习信号。我见过很多项目就在这一步把时间全耗光了,然后归咎于"算法不行"。
这里就体现出"目标条件化"的价值了。如果我们把策略写成 π(a|s, g),也就是把当前想要达到的目标 g 一起喂给网络,那么同一个状态 s 下,因为目标不同,策略可以执行完全不同的动作。这个小小的改动,让"从经验中学习"这件事有了新的可能性——因为你可以人为地换目标,从而把一次"失败"的轨迹变成一条"成功"的轨迹。
所谓失败,往往只是相对于你预设的那个目标而言的。换个角度看,这条轨迹里的每一步,其实都在向某个实际到达过的状态靠近——如果以那个状态作为目标,这些经验不仅不失败,反而是完美的教科书。HER 的核心也就在这:重新标注目标的视角,把死局盘活。
1.2 HER 的思路:把失败轨迹改造成"伪成功"样本
HER 的做法一点也不玄乎。假设你在训练一个机器人推物体的小任务:目标是物体从位置 A 推到位置 B。一条 episode 里机器人把物体从 A 推到了 C,但 C 不是 B,所以这条轨迹里所有 transition 的奖励都是 0,没有任何学习信号。
传统 RL 算法到此就放弃了这条数据。HER 的做法是:既然轨迹结束时物体实际在 C,那我就把这条轨迹的目标从 B 改成 C,然后重新计算奖励——此时,每一步的 reward 都变成 1 了,因为目标 C 最终确实被达到了。这条轨迹从"全零奖励的废数据"变成了"全成功奖励的金数据",被重新扔进 replay buffer,供策略学习。
理解了吗?它没有改变环境,没有伪造任何物理过程,只是换了一个"评判标准"。这个思想特别像射箭:你想射十环,结果射偏了,落在八环。传统做法是这一箭白射了,HER 的做法是把八环当作目标重新复盘这一箭——咦,这一箭其实轨迹很稳,如果目标就是八环,那这就是一次完美的射箭。多复盘几次,你至少学会了"如何稳定地射中某个位置",再从中泛化到十环就容易多了。
这种事后诸葛亮式的学习,正是稀疏奖励环境里的一剂强心针。因为它不依赖运气,不依赖探索到真正目标,只要轨迹里出现了任何有价值的状态,都会被充分利用。你不需要真正完成任务,也能学到完成任务所需的技能片段。
1.3 适合什么任务,不适合什么任务
当然,HER 不是万能的药。你得先认清它的适用边界,否则硬套只会浪费算力。
适合 HER 的任务都有一个共性:存在"目标"和"状态"之间的可分离结构。经典的适用场景包括:
- 机器人运动控制(手臂到达某个坐标、抓取物体、推动物体到指定位置)
- 导航任务(到达某个坐标点)
- 离散游戏中的状态匹配问题(比如解开某个特定布局的魔方)
- 任何可以定义"目标空间"、并且目标可以事后被修改的环境
这种环境下,你总能很自然地问一句:"这条轨迹最终到达了什么状态?把这个状态当作目标是否合理?"如果答案是肯定的,HER 就能用。
不太适合 HER 的任务也有明显的特征:目标本身无法事后替换,或者奖励和"是否达成某个目标"的关联太弱。比如训练一个倒立摆平衡的任务,目标就是"保持直立",你没法把轨迹中某个时刻的倾角当作新目标去重标注,因为"平衡"是一个连续维持的过程,不是某个状态快照。再比如训练棋类对弈,目标是赢棋,而一条输掉的棋局里没有哪个中间局面能当作"胜利目标"来重写奖励。这类任务硬用 HER,只会引入错误的监督信号,让策略越学越歪。
判断方法其实很简单:拿一条失败轨迹出来,问自己一句——"如果我把目标改成轨迹中某个实际经过的状态,这条轨迹是否就能被认为是成功的?"如果答案是肯定的,那你的任务就是 HER 的菜;如果不确定,可能要找找其他稀疏奖励解法。
2. 核心机制拆解:目标重标注是怎么改变学习信号的
2.1 一次完整的重标注过程演示
只讲思想不讲细节等于没讲。我们用一个最简单的 2D 连续空间例子,把 HER 重标注的手续一步步走一遍。
环境设定:一个点在 2D 平面里运动,状态是坐标 (x, y),动作是位移,目标也是坐标 (x_g, y_g)。奖励规则:如果点和目标的欧氏距离小于 0.1,reward=0;否则 reward=-1。一次 episode 从 (0.0, 0.0) 出发,目标设为 (1.0, 1.0),最多跑 50 步。
这条 episode 实际跑出来的轨迹大概是:从 (0.0, 0.0) 依次经过 (0.1, 0.2)、(0.3, 0.5)、(0.6, 0.7)、(0.9, 1.1),最后停在 (0.8, 0.9)。因为终点离目标 (1.0, 1.0) 还有约 0.22 的距离,大于阈值 0.1,所以每一步的 reward 都是 -1。假如没有 HER,这条轨迹对策略唯一的教训就是"全程都是错的",梯度方向混乱,基本学不到东西。
现在做 HER。随机挑终点 (0.8, 0.9) 作为新目标,命名为 g' = (0.8, 0.9)。然后把轨迹里的每一条 transition 的 goal 字段从 (1.0, 1.0) 改成 (0.8, 0.9),重新计算奖励:
- 第 49 步(最后一步):状态 (0.8, 0.9) 与目标 (0.8, 0.9) 距离为 0,reward=0(成功)
- 第 48 步:状态 (0.9, 1.1) 与目标 (0.8, 0.9) 距离约 0.28,reward=-1
- 更早的步骤同理:都在 -1
你看,最后一步从 -1 变成了 0,形成了一条"成功信号"从后往前传导的轨迹。虽然大部分步骤奖励仍然是 -1,但至少存在一个明确的、可达的正向反馈点。强化学习的时序差分机制就会沿着这条轨迹反向传播,让前面所有步骤都获得"朝这个方向走是对的"的梯度信号。
这还不是全部。实际操作中,一条轨迹通常不止重标注成一条新轨迹。以 OpenAI 论文里的做法,往往对每条轨迹额外加入多条重标注轨迹(默认 k=4),每个都挑轨迹中不同的状态作为新目标。这样一条 50 步的失败轨迹,能变成 4 条带成功信号的轨迹,数据利用率提升好几倍。
2.2 为什么"事后目标"能提供有效梯度
有些同学可能会疑惑:把目标改成轨迹实际到达的状态,这不等于自欺欺人吗?策略学到的其实是"无论我想要什么,我都停在某个地方就行",这有什么意义?
这里就要回到"目标条件化策略"的泛化能力上来了。策略网络 π(a|s, g) 的输入同时包含当前状态 s 和期望目标 g。在重标注后的轨迹里,策略在状态 s_t 下要朝 g' 前进,而这个 g' 恰好是轨迹中未来的某个状态。这种数据在告诉策略:如果你现在在 s_t,而且你希望到达 g',那么执行接下来这一步动作是合理的、会导致成功。
训练过程中,策略会不断看到诸如此类的"当前状态-目标-下一步最佳动作"三元组。由于重标注的目标来自轨迹中实际发生的状态,这些三元组在状态-目标空间里的覆盖范围非常广——不是只有一个固定的 (1.0, 1.0) 目标,而是整个可达状态空间里密密麻麻的点。策略相当于在大量"虚拟目标"上练习如何接近终点,这就像射箭的人不只是反复练射十环,而是反复练"把箭射向自己选定的、刚好够得着的目标",基本功扎实了,再去挑战十环,自然容易得多。
更重要的是,重标注后的轨迹不会引入"不可能完成的幻觉"。因为新目标 g' 是这条轨迹中实际到达过的状态,从 s_t 出发是真实可达的,所以策略学到的动作-目标配对永远是可实现的,不会学到"朝着不可能目标飞"这种脱离物理规律的行为。
从数学角度解释也很顺。HER 本质上是在改写价值函数的学习目标:普通方法只在一个目标 g 上回归 Q 值,HER 则在大量目标 g' 上回归。这正好契合了通用价值函数的思想——用一个函数逼近器同时预测无数个目标下的价值,参数共享让它们互相促进、互相泛化。目标越多,函数逼近越平滑,学习效率越高。
2.3 目标空间与奖励函数之间的关系
你可能已经注意到,HER 的成败很大程度上取决于"目标空间"怎么定义。目标空间和状态空间是同一个空间,还是存在映射关系?这直接影响重标注的难易。
最简单的设计是目标空间 = 状态空间。比如上面那个 2D 点运动例子,目标就是一个坐标,和状态完全一致。这种情况下,一条轨迹里任何一步的状态都能直接作为新目标,不需要额外处理。
但更多时候,目标只是状态的一部分,或者需要经过变换。比如机器人抓取任务,状态包含机械臂关节角、物体位置、物体姿态,而目标往往只是"物体位置"。此时重标注就要小心:选一个新目标 g' 时,必须保证它和原始轨迹里的"achieved goal"对应得上。也就是说,你需要从 transition 数据里单独记录achieved_goal字段——这是 HER 工程实现里一个极其容易踩坑的地方。
奖励函数在重标注后也要保持一致。原环境里如何判断"成功"的,重标注后就用同一套规则判断。假设环境给了一个稀疏奖励函数compute_reward(achieved, desired),那重标注后的奖励就应该是compute_reward(achieved, new_desired),而不是想当然地赋一个固定值。很多复现代码跑出来效果不对,十有八九是这里出了问题。
这里也顺便说明一下目标空间连续和离散的区别:离散目标空间(比如把 2D 平面离散成 10×10 网格)重标注简单粗暴,两个状态相等就奖励 0,不等就是 -1;连续空间通常要定义距离阈值或者用负距离作为稠密奖励。阈值设大了,策略糊弄一下就达标;设小了,成功信号极其罕见,HER 的威力就大打折扣。我通常的做法是先用环境本身提供的默认阈值,跑通以后再去微调。
3. 从论文到代码:实操落地的关键环节
3.1 replay buffer 里到底要存什么,很多人第一步就错了
很多新手把 HER 想象成一种高大上的算法,觉得要改网络结构、要改损失函数。其实 HER 最核心的改动就在数据流上,而数据流的第一步就是 replay buffer 的存储格式。
普通 DDPG、DQN 的 buffer 里存一条 transition,大概是四元组(s, a, r, s'),有的加一个 done 标志。但 HER 必须在此基础上增加两个字段:desired_goal(这条数据原本的目标)和achieved_goal(执行动作后环境实际达到的目标状态)。也就是说,一条完整的 HER transition 长这样:
transition = { 'observation': obs, # 当前状态 'action': action, # 执行的动作 'reward': reward, # 基于原始目标计算出的奖励 'next_observation': next_obs, # 下一状态 'done': done, # 是否终止 'desired_goal': desired_goal, # 原始目标/重标注后的目标 'achieved_goal': achieved_goal # 实际达到的目标,用于重标注 }注意,achieved_goal通常不能直接从next_observation里截取一段维度就行。它必须来自环境返回的info字典,因为在很多环境里,目标状态可能不是状态的原始形态,而是经过某种归一化或变换后的表示。你截错了维度,后面重标注算出的奖励全是错的,而且这种错在训练曲线上非常隐蔽——它不会让 loss 爆炸,只会让学习效率极低。
一个坑中坑是 done 标志。原始轨迹里目标没达成,done 是 False,重标注之后目标达成或者到达终点了,done 该不该改成 True?如果你用的是带截止回合的强化学习,建议重标注后仍保持原始的 done 标志不变,因为 episode 在物理上确实没有真正完成最终目标,把它标成 True 会让价值函数误以为"到达任意状态就等于终止",导致后续训练把"随便停在某个地方"当成最优策略。等到你真正想让策略停在目标点时,它反而变得孱弱。
3.2 重标注采样策略:future 为什么是默认选项
确定存储结构之后,下一个关键点是:如何从这条轨迹中选择状态作为新目标。OpenAI 论文里总结了几种不同的策略,效果差异很大:
| 策略 | 做法 | 特点 |
|---|---|---|
| final | 只取轨迹最后一个状态作为新目标 | 简单,但每个 episode 只生成一条新轨迹,信号有限 |
| episode | 从整条轨迹里随机取一个状态作为新目标 | 覆盖面广,但有些状态在时间上离当前步骤太远,学习信号偏弱 |
| random | 从所有历史轨迹里随机取一个状态作为新目标 | 适用范围广,但目标可能与当前轨迹几乎无关,引入较大噪声 |
| future | 从当前 transition 之后的未来时间步里随机取一个状态作为新目标 | 时间上因果一致,目标总是"可达的",效率最高 |
实际操作下来,我几乎只推 future 策略,也就是每个 transition 都从其所在的 episode 的后续时间步里随机挑 k 个状态作为候选新目标。为什么 future 效果最好?因为它保持了一个隐含的时间一致性:在 t 时刻,你设定的新目标 g' 来自未来某个时刻 t'(t' > t)的状态。这保证从 s_t 出发,到 g' 是真实可行的,而且轨迹中从 t 到 t' 的动作序列就是一条天然的、通向 g' 的示范。策略学起来既不费劲也不误导。
k 值怎么选?论文里最常用的是 k=4,也就是每一条原始轨迹额外补充 4 条不同目标的重标注轨迹。这个数字不是拍脑袋定的,它是在多个任务上调出来的平衡点。k 太小,重标注信号太少,效果不明显;k 太大,replay buffer 里伪成功样本占比过高,策略可能过度拟合"近期状态可达到"的模式,对远距离目标的泛化反而变差。我自己在机械臂任务上对比过 k=1、k=4、k=8,k=4 在收敛速度和最终成功率上综合最优,k=8 在训练前期略快一点但后期稳定性差一些。
一个小细节:future 采样时要不要排除 t'=t 这种同一时刻?建议不要选当前时刻本身,因为那样新目标和当前状态一致,对学习毫无增量。务必要让 t' > t。
3.3 核心训练循环伪代码:能跑通的最小实现
讲完理论,直接上一段能负责"把 HER 跑起来"的伪代码。以最常见的 DDPG + HER 组合为例,逻辑对 DQN、TD3、SAC 同样适用:
import random import numpy as np # 假设 env 返回 obs, reward, done, info,其中 info 必须包含 achieved_goal # replay 是自定义的 ReplayBuffer,支持按轨迹(episode)存储 for episode_idx in range(total_episodes): obs = env.reset() episode_transitions = [] achieved = env.goal # 初始的 achieved_goal for t in range(max_episode_steps): # 目标条件化策略:输入包含 obs 和当前 desired_goal action = policy.select_action(obs, desired_goal) + exploration_noise next_obs, reward, done, info = env.step(action) achieved = info['achieved_goal'] # 注意这里取到的才是真正目标空间表示 episode_transitions.append({ 'obs': obs, 'action': action, 'reward': reward, 'next_obs': next_obs, 'done': done, 'desired_goal': desired_goal, 'achieved_goal': achieved, }) obs = next_obs if done: break # —— 核心:HER 重标注 —— her_extra = [] for i, trans in enumerate(episode_transitions): # 对每个 transition,采样 k 个 future 状态作为新目标 future_states = random.sample( episode_transitions[i+1:], # 只能从未来取 min(k, len(episode_transitions) - i - 1) ) for future_trans in future_states: new_goal = future_trans['achieved_goal'] new_reward = compute_reward(new_goal, trans['next_obs']) her_extra.append({ 'obs': trans['obs'], 'action': trans['action'], 'reward': new_reward, 'next_obs': trans['next_obs'], 'done': False, # 保持 done 为 False,前文已解释 'desired_goal': new_goal, 'achieved_goal': trans['achieved_goal'], }) # 原始轨迹和重标注轨迹一起入 buffer for trans in episode_transitions + her_extra: replay.add(trans) # —— 训练阶段:和普通 off-policy RL 没有区别 —— for _ in range(updates_per_episode): batch = replay.sample(batch_size) policy.update(batch)这段代码的核心就两件事:一是把每条轨迹里的所有 transition 都额外生成 k 条重标注版本;二是重标注后的 done 保持 False。你把这两件事做好了,DER 的 70% 功效就到手了。
还有几个容易被忽略的实现细节。第一,重标注的奖励计算必须复用环境的compute_reward函数,不要自己另写一套,否则环境和策略的评判标准不一致,训练会撕裂。第二,如果环境自带的就是稠密奖励(比如负距离),重标注后仍然用同样的稠密公式,只是把目标换成新目标;HER 不限定只能用稀疏奖励。第三,建议在重标注之后对整条轨迹做一次肉眼检查——打印几个新目标的坐标,看看是否合理可解释,这一步能帮你避免很多隐性问题。
3.4 超参数速查:直接把能用的值贴给你
参数这东西,光看论文不给具体值等于大海捞针。我根据自己复现和跑工程的经验,把 HER 相关的关键参数整理成一张速查表:
| 参数 | 常见取值 | 说明 |
|---|---|---|
| 重标注数量 k | 4 | 每条 transition 额外生成的样本数 |
| 采样策略 | future | 从同一轨迹的未来时间步中采样 |
| buffer 大小 | 10^6 | 尽量大,因为重标注会成倍增加样本 |
| 训练频率 | 1 次 update / 1 个 episode | 如果 episode 很长可改成每步更新 |
| 探索噪声 | 0.1~0.3 | 太大会掩盖学习信号,太小探索不足 |
| 目标阈值 | 环境默认 | 别急着调,先跑通再优化 |
| 网络结构 | 两层 256 或 128 | 小任务 128,复杂任务 256,别一上来就超大网络 |
特别提醒 buffer 大小这一点:HER 会额外生成 k 倍的伪成功样本,如果你 buffer 设得小,这些样本很快会被后续数据冲刷掉,策略的"泛化地基"就打不稳。我在一个推箱子任务上把 buffer 从 10^5 扩到 10^6,最终成功率直接提升了 20 个百分点。很多人复现 HER 效果不好,buffer 容量不够往往是第一元凶。
4. 训练那些坑:失败模式排查与调参实录
4.1 训练曲线纹丝不动时,按这个顺序排查
HER 听起来美好,但工程实现里翻车的概率一点都不低。我自己在多个项目里踩过不少坑,把最常见的失败模式整理成了一套排查顺序。
第一步,检查重标注后的奖励是否真的有变化。很多人在 buffer 里存数据没问题,但重标注逻辑写错了——比如拿next_obs做新目标时取错了维度,导致新目标和旧目标几乎一样,伪成功样本根本没产生。排查方法很简单:单独跑一条 episode,手动调 HER 函数,打印重标注前后的 reward 对比。如果重标注后的轨迹里一条 reward=0 的样本都没有,问题出在重标注本身。
第二步,检查缓冲区里伪成功样本的比例。理想情况下,训练初期的采样 batch 里应有明显比例的伪成功样本——因为伪成功样本对应策略过去曾经达到过的状态,这些状态是策略的"已知成功区"。如果这个比例太低(比如不足 5%),说明 future 采样的逻辑有问题,或者 episode 太短导致可选未来状态太少。
第三步,看 critic loss 是否正常。HER 场景下 critic loss 通常不会爆炸,但如果你发现它在训练初期就震荡得厉害,那可能是奖励尺度的问题。伪成功样本的 reward 从 0 到 -1 不等,原始样本和重标注样本的 ratio 如果不平衡,价值函数会被"伪成功"主导。此时可以尝试把重标注样本的优先级调低(如果用了优先回放),或者简单地在采样时对两类样本做个 1:1 的混合。
这些排查步骤走一遍,大部分"训练无动静"的情况都能定位到具体原因。我自己的经验是,90% 的问题出在数据格式和重标注逻辑上,算法本身反而很少背锅。
4.2 稳定性问题:训练后期成功率来回跳
另一种常见现象是训练前期效果不错,后期成功率在 60%~80% 之间反复横跳,始终上不去。这种问题多半和两个因素有关:一是探索噪声过大,二是重标注目标的范围太窄。
探索噪声大这个好理解:训练后期策略已经有一个不错的 baseline,如果 noise 还保持初期那么大,策略的动作会被噪声干扰,好不容易学到的精细控制被破坏。解决方法是老生常谈的噪声退火(annealing)。但注意别退得太快,否则策略又回到稀疏奖励的困境里。
重标注目标范围太窄这个问题隐蔽得多。future 采样的目标全都来自同一 episode 的后半段,如果环境初始状态每次差不多,轨迹形状也比较集中,那么重标注出的目标就会长时间聚集在某个状态子空间里,策略对远距离目标的泛化能力就一直没被锻炼。一个有效的技巧是在采样 future 状态时,稍微放宽限制:比如以 70% 的概率选未来的状态,30% 的概率选整条轨迹里任意状态(等于混合 episode 策略)。这样既不破坏因果一致性,又能扩大目标的覆盖范围。我在车上做过实验,这个混合策略对最终的泛化稳定很有帮助。
4.3 对比实验不靠谱?先看看这几个变量是否对齐
如果你要做 HER 和某 baseline 的对比实验,或者同一环境不同配置的消融实验,有几个隐蔽变量最容易让结果失真。
第一是评价指标。HER 训练的是"多目标策略",你不能只拿最初始的目标去测试成功率。正确的测法是随机采样一批目标(要和训练时候的目标分布一致),让策略分别去完成,然后统计平均成功率。否则你很可能只看到策略在初始目标任务上的表现,而忽略它在泛化上的优势。
第二是随机种子。HER 的重标注过程本身是随机的,同一配置下不同 seed 的结果差异可以很大。做对比实验时,至少跑 3~5 个 seed,取均值和方差再下结论。只跑一个 seed 就宣布方法有效或无效,在 RL 里没有任何说服力。
第三是训练步数和 buffer 状态的公平性。HER 因为额外生成样本,同样的环境交互步数下,buffer 里的样本更多、训练更新更频繁。如果你只是在"总交互步数"上对齐,HER 天然占便宜。这时要说明清楚,你比较的是"同交互预算下的真实性能",还是"同更新次数下的算法优劣",两种比较有完全不同的结论。
5. HER 之后:从离线数据到更复杂的任务
5.1 与主流算法的适配,远不止 DDPG
很多人以为 HER 只能配 DDPG,这是最大的误解。HER 是一个数据重标注模块,不是算法本身,它可以嵌入任何 off-policy RL 框架。我在项目里分别试过 DDPG + HER、TD3 + HER、SAC + HER,后两者在连续控制任务里效果明显更好,尤其 SAC 的熵正则化会让策略在早期探索更充分,配合 HER 的伪成功样本,收敛速度和稳定性比 DDPG + HER 高一个档次。
配 TD3 或者 SAC 时有一点需要注意:actor 和 critic 的输入都必须包含目标向量 g,而且目标向量最好经过归一化处理后再进网络。HER 重标注出的目标分布可能与原始目标分布不同,如果不做归一化,网络输入的标准差会非常大,影响训练稳定性。我通常的做法是在 buffer 里记录目标空间每个维度的均值方差,然后做 running normalization。
PPO 这类 on-policy 算法和 HER 的适配要麻烦很多。因为 HER 重标注的样本来自历史策略,而 PPO 对 off-policy 数据的容忍度极差。理论上可以重标注后当作行为克隆的辅助损失来用,但实现复杂度高,效果也不稳定。如果你就在用 PPO 做稀疏奖励任务,我会建议先换成 off-policy 算法再考虑 HER,别硬凹。
5.2 离线场景里的目标重标注:潜力与风险并存
近两年离线强化学习越来越火,HER 在这个方向上的应用也被重新挖了出来。如果你手上有一堆离线数据(比如柔性制造产线上采集的机器人操作日志),里面绝大多数轨迹都是失败的,怎么利用?HER 的思路依然有效:把轨迹实际到达的姿态作为目标重新标注,就能从看似无用的失败数据里提炼出大量"成功片段"。
但离线场景比在线多一层风险:数据分布偏移。在线训练时,重标注样本会被策略实时尝试并修正偏差;离线时,重标注样本里的动作-状态分布是固定的,如果策略学到的是"把所有轨迹都标成成功"这种捷径,它可能会产生幻觉——生成一个价值虚高的策略,而真正部署到环境时立刻崩盘。我在离线项目中应对这个问题,主要靠两条:一是限制重标注目标只在轨迹末端附近的小邻域内取,二是同时用原始目标样本给策略加一个保守正则项(类似 CQL 的思路),防止价值函数被伪成功样本撑爆。
5.3 下一个值得关注的方向:自动课程与子目标生成
HER 本身已经是"自动课程学习"的一种朴素形态——它不断把目标替换成更容易实现的状态,让策略阶段性地从易到难。但更进一步的做法是主动生成具有阶梯难度的课程目标,而不是被动地从轨迹中采样。
比如分层强化学习场景:上层策略负责生成子目标,下层策略负责执行。这种情况下,下层策略依然可以用 HER 来学习"如何达到任意给出的子目标",上层策略再学习"如何制定合理的子目标序列"。HER 就像一个万能的下层执行模块,给上层提供了可靠的基础能力。
再比如和自动课程学习结合:把 HER 重标注出的目标按照难度聚类,训练早期多采样容易目标,后期逐渐增加难目标比例。我在一个 open 环境的抓取任务里试过这种动态难度调度,相比纯 uniform 采样,最终成功率提升约 15%,而且训练方差明显减小。这个方法不需要额外网络组件,只是在采样时加了点权重调度,成本很低,值得一试。
说了这么多,最后聊聊我自己的实际操作体会。第一次跑 HER 时的感受是震惊的:同样一个机械臂 reach 环境,naive 版本策略 5000 个 episode 过去,成功率几乎为 0;加上 HER 以后,500 个 episode 左右成功率就开始爬升,2000 个 episode 时已经接近 90%。那种"从死局里活过来"的体验,是做 RL 工程最爽的时刻之一。
如果你打算在自己的项目里尝试 HER,我建议你先做一个小验证再上大任务:挑一个带achieved_goal字段的简单 gym 环境,手动跑一条失败轨迹,调用重标注函数,亲眼确认奖励从全负变成含正样本。这个验证只需要十分钟,但能帮你排除掉数据格式和奖励函数这两个最大的隐性杀手。等这一步确认无误,再去跑完整的稀疏奖励任务,你会发现自己少走了至少一半的弯路。这个技巧我几乎每次带人都要强调一遍,因为它在实操中救过我好几次。