hindsight 这个英文词,翻译过来就是“后见之明”,说难听点叫事后诸葛亮。但在强化学习圈子里提到它,十有八九指的都是 OpenAI 2017 年那篇经典论文提出的 Hindsight Experience Replay(HER)。我第一次看到这个思路时愣了一下:把没有完成的目标直接替换成智能体实际到达的状态,然后说这次尝试是成功的?这不就是给自己找台阶下吗?但正是这个“找台阶”的trick,解决了一大批机器人连续控制任务长期以来的老大难问题——稀疏奖励。
这篇文章想跟各位分享一下我对 Hindsight / HER 的理解和实操经验:它到底在解决什么问题、核心机制在代码里怎么落地、调参时踩过哪些坑。适合正准备做强化学习、尤其是想做机器人操作或连续控制方向的朋友,也适合已经跑通 DDPG / SAC 但卡在稀疏奖励任务里出不来的同学。我会尽量讲得直白,公式只保留最必要的那一点,剩下的都是可以直接抄走的实践细节。
1. 先说清楚:hindsight 到底在解决什么问题
1.1 稀疏奖励——强化学习里那道过不去的坎
强化学习有个让人又爱又恨的特点:它不需要人工标注数据,完全靠智能体和环境互动来学。但这句话只说对了一半——当奖励信号密集的时候,确实这样;一旦奖励变得稀疏,互动一万次也未必能撞见一次正反馈。
举个我经常跟朋友举的例子:你要教一个机械臂抓取桌子上的水杯,环境只给一个奖励信号:抓住了给 +1,没抓住给 0。那么在训练刚开始的时候,机械臂的动作基本是随机的,它几乎不可能在几万次尝试里偶然正中目标。于是 replay buffer 里面装的几乎全是“没抓住”的轨迹,每个 transition 的 reward 都是 0。DDPG 这类算法靠 TD 误差来更新 critic,而当你采到一批 reward 全为 0 的数据时,Q 网络的梯度信号弱到几乎无法更新,策略自然也就永远不会变好。这就是稀疏奖励的“死锁”:越没成功越没信号,越没信号越不可能成功。
很多人第一反应是做 reward shaping,在中间过程加一些诱导奖励,比如手指离目标越近就给越高的分。这个思路不能说错,但 reward shaping 太容易引入设计者的主观偏见了。你费尽心思设计一个距离奖惩项,智能体很可能学到的是“把手指挪到离目标 1 厘米的地方刷分”,而不是真正学到如何稳定地完成任务。我在实际项目里见过太多次这种“刷分式学习”,最后还得反过来调 shaping 权重,非常痛苦。
1.2 为什么“事后视角”能成为学习信号
HER 的出发点其实特别朴素:既然这次没抓到杯子,但机械臂最终伸到了某个确定的位置,那这段轨迹是不是至少教会了我们“如何把手臂移动到那个位置”?答案是肯定的。
假设原始目标用 g 表示,比如“手指尖到达 (1.0, 0.5, 0.3) 这个点”。某次 episode 里,机械臂一顿操作,手指最后停在 (0.6, 0.4, 0.2)。对原始目标来说,这毫无疑问是一次失败,reward = 0。但 HER 会做一件事:把这条轨迹的 goal 重新标记成 (0.6, 0.4, 0.2)。由于手指确实在最后到达了这个位置,这条轨迹瞬间变成了一条“成功轨迹”,reward = +1。同样的状态、同样的动作,只是换了目标标签,数据就从无用垃圾变成了高质量教材。
这个操作的理论根基在于:HER 面向的是 goal-conditioned 策略,也就是以“目标”为输入、输出动作的策略网络。它学的不是“抓住那个特定杯子”,而是“给定任意一个目标位置,学会到达它”。所以用轨迹实际到达的状态来当备选目标,完全合法。更直白地说,我们欠智能体一个真相:它在整个 episode 里并不只是在朝原始目标努力,它同时也完成了无数个“隐式目标”,只是我们没告诉它而已。
1.3 HER 的适用场景与不适用场景
HER 不是银弹,它只适用于目标条件强化学习(Goal-Conditioned Reinforcement Learning)的场景。判断标准很简单:你的状态里有没有一个东西可以明确地被当作“目标”,并且能用来判定成功与否?比如机械臂的末端位置、机器人的目的地坐标、迷宫里的出口位置,这些都可以。哪怕目标是一个多维向量(比如“物体在桌子上且机械爪张开”),只要你能写一个判定函数,HER 就能用。
反过来,如果任务是单目标、没有任何可替换的目标概念,或者奖励本身已经足够稠密,HER 的收益就很小。举两个例子:训练智能体玩 Atari 游戏拿最高分,目标就是“得分高”,没有第二个有意义的目标可以替换,HER 就用不上;再比如倒立摆任务,每一步都能拿到连续的距离奖励,那本身就不缺学习信号,HER 更多是锦上添花。还有一种情况也要注意:如果环境状态不可完全观测,比如你只能拿到部分信息而拿不到完整状态,那“事后重标记”也没有可靠的对象。核心原则是:目标空间必须可观测、可判定,否则 HER 会变成瞎标数据。
2. Hindsight Experience Replay 的核心原理拆解
2.1 一次失败里藏着什么:目标重标记的微观视角
为了讲清原理,我拿 OpenAI Gym 里经典的 FetchReach 任务来拆解。环境给智能体的 observation 是一个字典,里面有两个关键字段:achieved_goal(当前实际到达的位置)和desired_goal(期望到达的目标)。每一时刻,智能体根据“当前状态 + 目标”输出一个动作,环境随后返回新的状态和一个稀疏奖励:如果achieved_goal和desired_goal的距离小于某个阈值,奖励为 0(或者 +1),否则为 -1(或者 0)。注意,这个奖励函数本身简单到极致,没有任何中间过程奖励。
现在假设某条轨迹 T 有 50 步,原始目标 g 是 A 点,但整个轨迹走完,末端位置一直停留在 B 点附近。HER 在把这条轨迹存进 replay buffer 的时候,会额外生成 4 条“虚拟轨迹”:每条都把 B 点(或者轨迹中某个未来时刻的实际位置)重新设定为 desired_goal。由于虚拟轨迹里每一步实际状态确实“到达”了虚拟目标,这些 transition 的奖励全部变成了正样本。这样一来,replay buffer 中不再是一潭死水,而是充满了“成功到达某目标”的信号,Q 网络终于有了可以反向传播的梯度。
这里有件事值得强调:重标记后的数据不是真实环境交互产生的,而是我们事后改写的,所以 HER 必须是 off-policy 算法才能用。像 PPO 这种 on-policy 算法,如果用改写后的数据去算策略梯度,会破坏重要性采样的前提,训练曲线会非常诡异。我见过有新手直接把 HER 塞进 PPO 里,结果 loss 一路乱跳,最后连基础任务都学不会。记住,HER 的本质是 replay buffer 层面的数据增强,天然跟 DDPG、TD3、SAC 这类 off-policy 方法搭配。
2.2 目标重标记的关键策略选择:final 还是 future
论文里提出了几种从轨迹中挑选“事后目标”的策略,这个细节直接关系到训练效果,值得单独说:
- final:直接把整条轨迹的最后一个状态当作事后目标。逻辑最简单,但多样性差——一条轨迹只有一种事后目标。
- future:从轨迹中某个时刻 i 之后的时间步里随机抽一个状态当作该时刻的事后目标。这是论文实验中最推荐的方案。
- episode:从整条轨迹的任意时刻随机抽一个状态,不要求它出现在当前时刻之后。
- random:从所有收集到的状态里随机抽一个。
我试下来,future的效果确实最好,原因也不难理解。对于轨迹中间时刻 t,如果取一个在 t 之后才出现的状态作为目标,那么这个目标的难度是“适中”的——它不是太远以至于这条轨迹完全没往那边走,也不是太近以至于毫无学习价值。这种“逐步靠近目标”的数据分布,能让策略学到一种平滑的到达能力。相比之下,final只考虑终点,早期时刻的目标距离太远,学起来噪声很大;random则可能选到完全无关的状态,数据质量太差。
具体实现里,通常会为每条轨迹的每个 transition 额外生成 k 个 hindsight 目标,k 一般取 4 到 8。注意这里不是把原始目标丢掉,而是在保留原始目标的同时,额外增加虚拟目标。这样才能保证智能体既知道真实任务是什么,又能从失败经验中抓住信息。如果你把原始目标全换掉,策略就会彻底跑偏,只顾着追逐那些“容易达成的虚拟目标”,真实任务的成功率会原地踏步。
2.3 为什么它不用改公式也能加速学习
对比一下传统的 reward shaping 和 HER,你会发现一个很有意思的差异:前者是在“奖励函数”上做文章,企图让每一步都给出有意义的信号;后者是在“数据”上做文章,把失败轨迹重新解读成有效样本。所以 HER 的接入成本极低——你完全不需要改动 actor-critic 的更新公式,不需要写复杂的辅助损失,只需要在处理 replay buffer 时加一个重标记函数。
从数学上看,如果我们定义 goal-conditioned 的 Q 函数为 Q(s, a, g),那么 HER 做的事情就是把训练样本里的 g 替换成 g',然后照常计算 TD 误差。因为 g 本身只是函数输入中的一个条件向量,替换它并不会破坏函数拟合的目标,反而让 Q 函数能利用到更多“到达了某状态”的真实数据。这有点像学一门新语言的时候,你不仅听老师布置的“标准对话”,还把平时日常聊天中无意说出的句子也当作学习材料,反复琢磨。本质上,这都是在从看似无用的经历里挖掘训练信号。
3. 实操:在代码里实现 HER
3.1 环境搭建与基准选择
真要动手复现 HER,第一步是选环境。OpenAI Gym 里的 Fetch 系列(FetchReach、FetchPush、FetchSlide、FetchPickAndPlace)是论文的标准测试环境,但依赖 MuJoCo,需要申请 license。如果你不想折腾 license,也可以换成 PyBullet 里类似的机械臂环境,或者干脆自己写一个 2D 点导航环境来验证算法——反正 HER 的核心机制跟具体环境关系不大,甚至在 2D 环境里更容易 debug。
算法骨架我推荐用 DDPG,不是因为 DDPG 好调,而是因为它是 HER 论文的原生搭档,参考资料多,出了问题容易对照。如果你已经熟悉 TD3 或 SAC,也可以直接把 HER 的 relabel 逻辑搬过去,不会有任何冲突。我个人更建议新手从 DDPG + HER 开始,把机制跑通了再换更强的 base learner。
3.2 核心代码逻辑与关键参数
下面这段伪代码是 HER 最核心的部分,也是我在项目里反复用到的一个模式。假设你已经在环境交互阶段收集了一条完整轨迹,每条 transition 包含(obs, achieved_goal, desired_goal, action, reward, next_obs, next_achieved_goal, done),接下来要做的是重标记:
def hindsight_relabel(trajectory, k=4, strategy="future", relabel_prob=0.5): """ trajectory: 一条完整 episode 的 transitions 列表 k: 每个 transition 额外生成的 hindsight 目标数量 strategy: future / final / episode / random relabel_prob: 对一个 transition 执行重标记的概率 """ relabeled = [] horizon = len(trajectory) for i, trans in enumerate(trajectory): # 保留原始目标,确保真实任务不丢失 relabeled.append(trans) if np.random.rand() > relabel_prob: continue for _ in range(k): if strategy == "final": j = horizon - 1 elif strategy == "future": # 在 i 之后随机选一个时间步,保证目标在“未来” j = np.random.randint(i, horizon) elif strategy == "episode": j = np.random.randint(0, horizon) else: # random j = np.random.randint(0, horizon) new_goal = trajectory[j]["achieved_goal"] # 重标记后的 reward 由环境自带的判定函数重新计算 new_reward = compute_reward(new_goal, trans["achieved_goal"]) new_trans = dict(trans) new_trans["desired_goal"] = new_goal new_trans["reward"] = new_reward relabeled.append(new_trans) return relabeled这段代码有几个细节值得注意。第一,compute_reward必须和环境的奖励函数保持一致,一般是距离小于阈值就判成功,这样重标记后的 reward 才可信。第二,future策略里我取的j包括i本身,也就是当前时刻的实际到达状态,这在实操中也能用,但多数情况下取j > i效果更稳定。第三,重标记后的done字段要谨慎处理,尤其是稀疏奖励环境里,虚拟轨迹很可能并没有真的结束,所以通常把done强制设为 False,避免触发 bootstrapping 的终止状态误判。
训练主循环里,每次从 replay buffer 采样一个 batch 时,也要以一定概率对 batch 里的 transition 做同样的重标记,而不是只在数据采集后处理一次。论文和大部分实现里,这个概率取 0.5,也就是说,Q 网络每次更新,大约一半数据来自原始目标,另一半来自 hindsight 目标。这个比例我试过从 0.3 到 0.8,0.5 附近整体最稳,过高会让策略偏向虚拟目标,过低则稀疏奖励问题依旧。
3.3 训练效果对比与参数调优心得
用 DDPG + HER 跑 FetchReach 这种相对简单的任务,成功率通常在训练早期就能快速上升,几千个 episode 之后就能冲到 0.9 以上。FetchPush 要难一些,需要几万个 episode,而 FetchSlide 和 PickAndPlace 则需要更长的时间和更细的超参调整。我自己的经验是,如果某个任务在你的实现里跑了很久成功率还是 0,优先检查重标记逻辑,而不是急着调 actor 的学习率。
超参建议直接抄我这份:replay buffer 容量 1e6,batch size 512,actor 和 critic 的学习率都设 1e-3,discount factor 0.98,soft update 的 tau 取 0.05,动作探索噪声用标准的高斯噪声并在训练中后期逐渐衰减。网络结构不需要太复杂,两层 256 个神经元的 MLP 就够,输入是observation和desired_goal拼接后的向量,输出是动作。有一点很关键:必须对状态和 goal 做归一化。Fetch 环境里机械臂关节角度和位置坐标的量纲不一样,如果不归一化,critic 的输入数值范围差好几个量级,训练过程非常容易震荡。我在 PyBullet 环境里试过不归一化,loss 直接飞上天。
还有一个调参心得是:HER 不是越使劲越好。k 值从 4 加到 16,训练速度并没有线性提升,反而 buffer 里虚拟目标占比太高,增加了采样开销,策略还容易陷入“盯着容易目标练手”的坏习惯。我的习惯是 k=4 起步,任务难度高再调到 8,一般不会超过这个范围。
4. 踩坑记录:HER 不是万能药
4.1 常见问题速查表
我把自己和身边朋友踩过的坑整理成了一张表:
| 问题现象 | 可能原因 | 排查方向 | 解决办法 |
|---|---|---|---|
| 训练曲线完全不动,成功率一直是 0 | 重标记概率设成了 0,或者 buffer 里原始轨迹占比过高 | 检查 relabel 逻辑是否生效 | 确认每个 transition 至少额外生成 4 个 hindsight 目标 |
| Q 值爆炸,loss 出现 NaN | 状态和目标没做归一化 | 打印 critic 输入数值范围 | 对 observation 和 goal 分别做标准化 |
| 策略只追虚拟目标,真实任务成功率不涨 | relabel 概率太高或 k 太大 | 统计 buffer 中虚拟目标占比 | 把 relabel 概率降到 0.3~0.5,k 控制在 4 |
| 训练到中后期波动大,已经学会的任务又忘了 | 动作噪声没有衰减 | 观察动作输出的方差 | 训练进度过半后逐步线性降低噪声 |
| 同一任务换随机种子后效果差异巨大 | 稀疏奖励下随机性被放大 | 多跑几个 seed 取中位数 | 至少跑 5 个随机种子再做对比 |
4.2 我踩过几次坑之后的体会
第一次复现 HER 时,我在一个小型 2D 导航任务上调试,明明逻辑都对,但智能体就是学不会。后来发现,重标记后的 reward 我直接沿用了环境返回的reward,没有重新调用compute_reward计算。因为环境返回的 reward 是针对原始目标的,目标被替换后,旧 reward 完全失效。这个问题特别隐蔽,因为代码不报错,训练也能跑,就是成绩上不去。各位如果发现自己的 HER 实现“看起来没问题但就是没效果”,第一个要去查的地方就是 reward 是否跟着目标一起重算。
另一个让我印象很深的坑是done标志。在稀疏奖励任务里,一旦智能体到达目标附近,环境可能会判定 episode 结束。但重标记后的虚拟目标和真实轨迹的终止条件未必匹配,如果你把原始的done=True直接带给虚拟 transition,Q 更新的 bootstrapping 就会出错,导致价值估计严重偏差。我后来统一在重标记时把done置为 False,只在最后一个真实目标达成时才置 True,训练稳定性立刻好了不少。
4.3 从 hindsight 到 forward thinking:HER 的边界与后续方向
HER 再强,也解决不了所有稀疏奖励问题。它最擅长的是这种场景:智能体已经能在环境中“碰到”各种状态,只是不知道这些状态可以作为目标来学习。但如果任务的探索空间实在太大,比如机器人要从随机位置出发,找到千里之外的稀有物体,智能体连“碰运气碰不到”的时候,HER 就无能为力了——你无法重标记根本不存在的状态。这种时候,方向就变成了如何设计更聪明的探索策略,比如在学习早期用课程学习逐渐提高目标难度,或者用基于模型的 imagination 来生成虚拟数据。不过那已经是另一个故事了,先把 HER 本身用好,就已经能解决一大批实际问题。
还有一个容易被人忽略的点:HER 的思想并不仅限于目标条件强化学习。如果你在做一个多目标优化问题,或者任何“同一个行为可以用多种标签描述”的场景,都可以试着问一句:我能不能从这次失败中,提取出一个“成功”的角度?这个思维模式,才是 hindsight 留给我们的真正财富。我自己后来做项目时,遇到某些稀疏信号的学习问题,第一反应已经不是加奖励项,而是先想想数据里有没有能重新解读的“事后目标”。
写在最后的实操建议
我个人在实际操作中的体会是,HER 最厉害的地方不是它的公式,而是它看待数据的态度。很多强化学习项目卡在稀疏奖励上,第一反应是设计更精美的奖励函数,结果越调越脏。其实不妨先停下来,看看 replay buffer 里那些“失败”轨迹,里面可能全是金子。
最后再分享一个小技巧:训练过程中定期打印一下 buffer 里原始目标和虚拟目标的比例,如果虚拟目标占比超过 70%,策略很可能已经失衡了。这时候不用着急清空 buffer,只需把 relabel 概率调低一点,比如从 0.5 降到 0.3,然后继续训练,真实任务的成功率通常会慢慢回涨。这个小技巧我在 FetchPush 和 PickAndPlace 上都验证过,关键时刻能救回一个眼看要跑偏的训练过程。