1. 从“事后诸葛”到强化学习:hindsight 这个词的含金量
英文里 hindsight 指的是“事后才看明白”,中文常调侃成事后诸葛。放到算法优化里看,这其实是一类很被低估的先验:你不需要提前知道正确的目标是什么,只需要在失败之后回头看,发现“如果当初目标换成另一个方向,这次经历其实挺成功”。
我第一次认真琢磨这个词,是在复现 OpenAI 2017 年那篇《Hindsight Experience Replay》的时候。标题直译就是“事后经验回放”,当时觉得这是给稀疏奖励下的强化学习续命的一招。真正跑起来才发现,这个思想比算法本身更值钱:它把“失败样本”重新签名为“其他目标的成功样本”,让一个什么都不会的策略在冷启动阶段也能持续获取学习信号,而不是在探索死区里空转。
这篇文章是我把 HER 完整落地到机械臂抓取与仿真导航任务过程中的总结。如果你正准备训练抓取、让智能体在稀疏奖励迷宫里去达成某个目标,或者想搞懂多目标强化学习中的目标重标记(relabeling)到底怎么实现,这篇文章基本覆盖了从原理到工程实现的全部关键点。看完你应该能自己接上 DDPG,也能避掉我在调参路上踩过的大部分坑。
1.1 我为什么会盯上 hindsight 这个方向
当时的项目背景很简单:仿真环境里有个七自由度机械臂,要从台面上抓取一个方块放到目标位置。任务只给一个稀疏奖励——方块中心与目标点的距离小于阈值就算成功,给 +1,其余情况一律 0。这样一个看似简单的任务,我用普通 DQN 和 DDPG 跑了一周,成功率依然趴在 0% 到 1% 之间。
后来翻论文时看到 HER,才意识到问题不在网络结构,也不在超参数,而在“所有失败经验都被浪费了”。机械臂每一次都能推动方块,只是没推到目标点。如果把这些轨迹标记为“失败”丢弃,策略永远不知道“自己刚才其实完成了另一个目标”。hindsight 的核心就是把这种“失败”重新解释成“对其他目标的成功”,样本利用率立刻就不一样了。
1.2 这篇文章适合谁看
如果你正在做连续控制、机器人操作、稀疏奖励导航,或者准备复现多目标强化学习论文,这篇文章会很对路。我会用偏工程的角度拆解 HER:先讲清楚它解决什么问题,再讲原理和伪代码,然后给出一套可落地的 DDPG+Hindsight 实现方案,最后把训练中遇到的典型问题和排查方式整理成速查表。即便你之前没碰过强化学习,只要懂一点点神经网络和策略梯度的基础概念,也能跟上推导过程。
2. 稀疏奖励与目标条件策略:HER 真正要解决的问题
2.1 目标条件马尔可夫决策过程
HER 面向的不是普通强化学习,而是目标条件强化学习(Goal-Conditioned RL)。这里的核心区别在于:策略的输入除了当前状态,还包括一个目标向量 g,输出动作时要在给定目标的条件下决策,记作 π(a|s, g)。
在目标条件 MDP 中,奖励函数本身就是目标相关的一类函数。以上面提到的机械臂抓取任务为例,状态 s 是关节角度、末端位置、方块位置这些信息,目标 g 是期望的方块落点坐标,动作 a 是各个关节的力矩或位置增量。每一步结束后,智能体要判断当前到位状态是否接近目标,并给出奖励 r_g(s, a, s')。如果到位状态与目标 g 的距离小于阈值,就判定任务成功。
这种设定看起来很自然,但它在网络设计上有一个隐藏要求:状态和目标的表示要分开,不能混为一谈。观测空间通常由三部分构成:自有状态(比如机械臂关节角)、期望目标(desired goal)、实际到位状态(achieved goal)。很多工程新手在这里翻车,把实际到位状态直接当目标塞给网络,结果训练到后面完全分不清“自己当前在哪”和“想让我去哪”。
2.2 稀疏奖励让梯度信号变成“零”
如果任务环境采取稠密奖励,比如每一步按“离目标近了给正分,远了给负分”来反馈,策略还能靠梯度慢慢爬。但很多现实任务根本无法设计稠密奖励,或者设计出来的稠密奖励反而诱导出投机行为。于是大家转过头来用稀疏奖励:只有成功那一瞬间给 +1,其余时刻都是 0。
这样一来,回报信号本身没有坡度,整个动作空间里成功路径又只占极小比例。随机探索几乎不可能在有限步数内撞到成功,智能体就等于在一个全域零奖励的环境里“瞎走”。我见过不少实验曲线横着走了几万步,成功率纹丝不动,原因就在这里:状态空间高维、动作连续、奖励稀疏、探索困难,四个问题叠加在一起。
打个比方,这就像让一个从没摸过方向盘的人在空旷停车场里找唯一一个车位,只告诉他“到了给一分,没到不给分”,但没有告诉他离车位还有多远。他可能转悠一整天,一次都得不到反馈,自然学不到任何东西。
2.3 事后认知为什么恰好能破局
人类解决这种问题的思路很朴素:我这次没把方块放到目标点,但我把方块推到另一个位置了。那个位置虽然不是预定目标,但它本身也是一个合理的落点。如果我把目标改成那个位置,那我这次尝试就是成功的。
hindsight 的价值就在这里:它不要求智能体真的完成指定目标,而是让智能体从“已完成的状态”中反推出“如果这是目标,我该怎么做”。这样每次探索都不白费,因为只要轨迹末端产生了一个可观测状态,就能把这个状态当作替代目标来构造正样本。
从数学上看,替代目标样本配合稀疏奖励,相当于给数据分布加入了一个“隐式课程”。训练初期,策略先把容易达成的目标学会,因为替代目标往往离当前实际轨迹更近;随着策略能力提升,它才能逐步完成更难的原始目标。这种由易到难的过程不是人为设计的,而是重标记机制天然带来的。
3. 拆解 HER 核心机制:让失败经验改签身份
3.1 经验回放与目标重标记
HER 的原始做法非常干净:智能体在训练过程中照常和环境交互,把每一步经验都存进回放缓冲区。区别在于,每次采样一个 batch 用于更新网络时,算法会额外抽取一部分经验,把它们的原始目标 g 替换成同一轨迹中某个未来时刻的“实际到位状态”g',然后再用 g' 重新计算奖励。
关键点在于:替换的从来只有目标向量和奖励,状态转移、动作、下一步状态这些物理事实全部保持不变。我们还是拿同样的状态、同样的动作、同样的结果去训练,只是给这次经历换了一个“解释口径”。原本一个 +0 的失败样本,在稀疏奖励下重新算出来可能变成 +1,因为到位状态恰好和历史某个未来状态重合。
这里有个很容易误解的细节:HER 重标记是在采样阶段做的,不是存储在缓冲区里的物理修改。如果你想当然地把重标记后的样本写回缓冲区,后面再采到同一个原始样本,覆盖关系会乱掉,训练稳定性立刻崩给你看。正确做法是保留原始经验,只在每次采样后生成重标记副本,两者一起参与更新。
3.2 伪代码视角下的一次完整迭代
把算法展开成可执行的步骤,大概是这样:
对每个 episode: 初始化状态 s0,采样一个原始目标 g 执行 rollout,得到轨迹 (s_0, a_0, s_1, ...) 把原始经验存入回放缓冲区 R 针对轨迹中的每个时间步 t: 从同一轨迹的未来状态(t+1 到 T)中随机采样一个 g' 用 g' 重新计算奖励 r' = r_g'(s_t, a_t, s_{t+1}) 把重标记样本 (s_t || g', a_t, r', s_{t+1}) 存入 R 从 R 采样一个 mini-batch 用 DQN 或 DDPG 等离线算法更新 Q 网络和策略网络论文里常见的做法是,原始目标样本和替代目标样本按 1:4 的比例混合。也就是每个经验除了保留原目标版本,还会额外生成 K 个替代目标版本。我在实践中一般取 K=4,这个值在多数任务里表现都不错。如果你任务的目标空间特别大或者分布特别复杂,可以把 K 加大到 8 试试,但不要无限加,否则原始目标的任务信号会被稀释得太厉害。
3.3 HER 为什么能稳定提升样本效率
先看一个最直观的收益:原本一条稀疏奖励轨迹里,几乎只有最后一两步可能产生非零奖励,其余全是 0。重标记之后,每个轨迹点都可以通过“把目标设为未来某个状态”的方式获得新奖励信号,而且这个信号因为状态接近目标,大概率是正样本。数据不再是一潭死水。
更深一层,HER 实际上改变了策略的优化方向。普通强化学习只学“如何达成给定目标”,但在探索初期,给定目标几乎都是不可达的,梯度也因此失效。HER 先把“如何达成可达目标”学好,让策略具备把状态拉近目标的能力,这种能力天然迁移到原始目标上。这也是为什么它能适应从简单到复杂的目标分布,相当于自动课程学习。
这里也要说明白 HER 的边界:它解决的是“奖励信号稀疏但目标可由状态定义”的问题。如果任务根本没有清晰的目标向量,或者成功与否和当前状态没有直接距离度量关系,HER 就不太适用。比如对话生成、图像生成这种目标很难形式化的场景,直接套 HER 很别扭。它最适合的还是机器人控制、导航这类有明确状态空间和距离度量的连续控制任务。
4. HER 落地实操:接入 DDPG 的全过程
4.1 为什么选 off-policy 算法做底座
HER 从原理上就依赖经验回放,天然和 off-policy 算法绑定。你可能第一反应是“那我用 PPO 行不行”,答案是不行。PPO、A3C 这类 on-policy 算法要求更新时使用的样本必须来自当前策略的采样分布,而 HER 的重标记样本本质上来自旧策略,哪怕通过重要性采样修正,也无法妥善处理“目标被替换后回报分布改变”这件事。实测下来,HER+PPO 基本没有效果,成功率和纯 PPO 没啥区别。
所以我最终选的是 DDPG,或者更稳一点可以用 TD3。它们在连续动作空间里表现成熟,而且天然支持大型回放缓冲区。SAC 也可以,但 SAC 的熵正则项在多目标场景下需要额外调参,前期反而容易不稳。如果你只想快速复现 HER 效果,先接 DDPG 最省事。
4.2 网络输入与重放缓冲区的设计
实现 DDPG+Hindsight 时,网络输入结构要设计清楚。Actor 网络的输入是把自有状态 s 和期望目标 g 拼接成一个向量;Critic 网络输入则再拼接动作 a。输出只有动作。到位状态 achieved_goal 不进网络,它只用于重标记时计算替代目标。
回放缓冲区的条目也要分成两部分存:一部分是物理的状态转移 (s, a, r, s', done),另一部分是这条转移对应的原始目标 g。等到采样时,如果需要重标记,就根据轨迹 ID 找到同一轨迹上的未来状态,生成替代目标,再构造新样本进网络。我一开始图省事,把目标直接拼进状态里存,结果重标记时找不到原始目标,还得重新拆,非常折腾。还是老老实实分开存,代码清晰也方便调试。
另外,缓冲区容量不要省。连续控制任务里,DDPG 对缓冲区大小很敏感,我一般设到 1e6 级别,实在不够就降到 5e5,但别低于 1e5。早期策略探索出来的轨迹分布和后期差别很大,缓冲区太小会丢掉很多“早期失败但富含信息”的样本,HER 的收益会打折扣。
4.3 关键超参数和我的初始配置
下面是我跑通抓取任务时用的初始配置,你可以当成起点,不必逐字照抄。
| 超参数 | 初始值 | 说明 |
|---|---|---|
| 未来状态采样数 K | 4 | 每个经验额外生成 4 个重标记目标 |
| 原始目标:替代目标 | 1:4 | 采样时保证原始目标经验保留一定比例 |
| 回放缓冲区容量 | 1e6 | DDPG 类算法需要较大缓冲 |
| 稀疏奖励阈值 | 0.05 | 距离小于该值视为成功,Fetch 系任务常见设置 |
| Actor/Critic 学习率 | 1e-3 / 1e-3 | 偏高容易发散,偏高慎用 |
| 探索噪声 | Ornstein-Uhlenbeck,sigma=0.2 | 冷启动阶段保证随机性 |
| 训练周期数 | 50 | 每个周期含若干 episode 和若干更新步 |
| Batch size | 256 | 重标记会放大样本量,batch 大一点更稳 |
这里我要特别说明奖励阈值。在 HER 的 Fetch 系列任务里,0.05 是论文默认值,它代表到位状态和目标位置的欧氏距离小于 0.05 米就算成功。但如果你换到自己的任务环境,一定要先看看状态量的尺度。比如你的坐标范围是 0 到 100,那 0.05 的阈值几乎不可能触发,重标记后也造不出正样本,整个训练等于白搭。先把阈值定成一个“从历史轨迹统计来看可以偶尔触达”的值,再逐步收紧。
4.4 训练过程中的观察记录
任务环境是基于 Mujoco 的 FetchPickAndPlace 类环境,我用 DDPG+Hindsight 跑下来,前 5 个周期成功率基本是 0,这个阶段全是探索噪声在瞎动。从第 10 个周期开始,重标记样本开始让策略学到“把方块推近某个位置”的基本技能,成功率缓慢爬到 10% 左右。
后面 20 到 40 个周期是性能上涨最快的阶段,成功率从 20% 一路冲到 70%。这时候如果去看 Actor 的动作输出,会发现它已经具备“接近方块—抓取—移动到目标点—松手”的完整行为链。到 50 个周期左右,成功率基本稳定在 80% 上下,不再有明显上涨。
一个很值得注意的现象是:如果用同样的 DDPG 但不加 HER,跑同样周期数,成功率始终保持在 0% 左右。两者差距不是 20% 或 30%,而是“完全学不会”和“学到不错”的区别。这就是稀疏奖励任务里“有信号”和“没信号”的本质差别。
5. 常见问题与排查实录
5.1 我踩过的五个典型坑
第一个坑:用 PPO 跑 HER。我最初看到 HER 框架简单,下意识就把它接在旧代码的 PPO 流程里。结果跑了三天,成功率动都不动。后来回头读论文才发现,HER 这个名字里自带“Experience Replay”,它默认就是给离线回放类算法设计的。换到 TD3 之后问题立刻消失。血的教训:先确认算法族,再动手写代码。
第二个坑:重标记时把状态和目标搞混。有段时间我在构造新样本时,直接把期望目标 g 替换成替代目标,顺手把自有状态 s 也改成到位状态,想着“让目标更接近状态”。理论上看着合理,实际上完全破坏了物理约束,策略被训练去预测一个并不存在的状态转移。后来严格规定:重标记只动目标向量和奖励,状态转移原封不动,训练才恢复正常。
第三个坑:奖励阈值设置不合理。任务环境换成自定义场景后,我忘了调阈值,仍用 0.05。但该场景坐标范围很大,机械臂末端离目标点动辄差好几个单位,导致重标记后奖励全是 0。后来我先跑了几条随机轨迹,统计到位状态和目标位置之间的典型距离,把阈值定在中等距离,很快就出现了可学习信号。
第四个坑:探索噪声过小导致状态覆盖不足。HER 的替代目标是从“未来状态”里采的,如果策略从一开始就非常确定、动作输出近乎于零,那未来状态全挤在起点附近,替代目标也毫无多样性。我一开始把噪声设得很小,结果训练曲线平得像心电图。调大噪声之后,状态空间覆盖度上来了,重标记目标丰富起来,学习才真正开始。
第五个坑:目标空间维度太高。最初我以为 HER 是万能的,直接拿它训一个目标是“末端完整位姿(位置+姿态角)”的任务。结果训练速度直线下降,因为均匀采样出来的替代目标在高维空间里几乎都是不可达的,重标记信号和一维位置目标完全没法比。后来我先把目标降到末端位置三个维度,等策略成熟后再引入姿态维度,难度才可控。
5.2 问题排查速查表
| 症状 | 原因 | 解决方法 |
|---|---|---|
| 成功率一直为 0,loss 也不下降 | 奖励阈值过大或过小,重标记全是 0 | 统计轨迹距离分布,重新设定阈值 |
| PPO 接入 HER 后无效 | 算法族不匹配 | 换 DDPG、TD3、SAC 等 off-policy 算法 |
| 训练后期策略震荡 | 原始目标样本比例过低 | 提高原始目标占比,或减少 K 值 |
| 状态被替换导致 loss 发散 | 重标记把 s 改成了 achieved_goal | 只改目标与奖励,保持状态转移不变 |
| 替代目标没有多样性 | 探索噪声过小 | 提高噪声或增加随机动作概率 |
| 高维目标空间学不动 | 均匀采样无法覆盖目标空间 | 目标降维、按距离加权采样或策略生成目标 |
5.3 调参顺序心得
HER 的参数不多,但千万别一上来就想着把 K、阈值、网络结构一起调。我自己的经验是:先固定一个能跑的配置,再一个一个变量换。优先级顺序参考这样的思路——先确认奖励阈值合理,再确认重标记有没有生成足够多的正样本,然后看探索噪声是否让状态空间有足够覆盖,最后才动网络容量和学习率。
判断重标记有没有生效有一个很简单的办法:训练开始时打印每个 batch 里“重标后奖励非零”的比例。如果这个比例长期低于 5%,说明替代目标离到位状态太远,要从阈值或采样方式下手。如果比例高于 40%,说明任务本身已经不难了,可以开始评估原始目标成功率是否在涨。
另一个经验是保存训练过程中的回放缓冲区状态。HER 的样本分布变化剧烈,早期样本和末期样本差异很大,如果代码突然崩溃,能从保存的缓冲区里接着训,不至于全部白跑。
6. 从 hindsight 向外延伸
6.1 自动课程与目标生成
HER 本质上是一种隐式的自动课程:替代目标大多是可达的,所以策略先学简单目标,再逐步逼近原始目标。顺着这个思路继续走,就是让目标生成变成显式的过程。比如 GoalGAN 这类方法,用 GAN 去生成难度合适的目标,让策略始终被推着走向“跳一跳够得着”的境界。相比 HER 的随机采样,显式生成可以在高维目标空间里更有方向性地选择任务难度。
我之前尝试过在 HER 基础上加一个简单的“距离加权采样”:替代目标不从未来状态里均匀采样,而是优先选那些离当前位置适中、既不算太近又没有远到完全不可达的状态。这样做的直观效果是,训练后期的成功率曲线比纯 HER 更平滑,尤其在高维任务里提升明显。它的原理也好理解,均匀采样在高维空间里会让目标大多落在中远距离范围,而人类学习新技能的节奏本来就是“先近后远”。
6.2 与好奇心、模型预测控制的结合
HER 还有一类很有价值的组合方向:和内在好奇心机制结合。HER 的优势在目标重标记,但它的替代目标仍然依赖智能体实际探索到的状态。如果探索本身特别差,比如机械臂一直缩在角落不动,那未来状态全挤在一起,重标记目标也没啥用。这时给策略加一个内在奖励,鼓励它访问“预测模型认为意外”的状态,就能有效拓宽轨迹散布,为 HER 提供更丰富的历史状态池。
我实际测试过“HER+内在好奇心”的版本,相比纯 HER,在冷启动阶段的状态覆盖率明显更高,达到同样成功率所需的周期数大约缩短了三成。当然代价是要维护一个预测误差网络,训练开销更大。如果你的任务不差那点算力,这是个很推荐的组合。反过来,如果任务本身就容易探索,只是目标不好达成,那加好奇心就是徒增麻烦。
另外,基于模型的强化学习也可以和 hindsight 结合。模型预测控制能给出更多有依据的未来状态作为候选目标,替代目标的“可达成性”会比随机采样更高。学术上这类工作路线已经不少,工程上建议先跑通标准 HER,再逐步叠加。
6.3 最后说点个人体会
我从这个项目里学到的最大一课,不是 HER 的公式或者重标记的细节,而是“不要轻易放弃看似无信号的失败数据”。很多强化学习项目卡住,不是策略网络不够强,是数据利用方式太粗暴。把失败当成垃圾丢掉,和把失败重新解读成经验,训练效果完全是两个世界。
hindsight 这个词也很妙,它提醒我们回头看的时候,总能从过去里挖出本来没注意到的价值。放到算法里,这种智慧变成了数据增强;放到工程里,它意味着每次失败的实验曲线都值得存档和分析,因为换个角度,它们可能正好是某个目标点的成功样本。我现在每次训练新任务前,都会先问一句:这些“失败”数据里,有没有藏着一条能用的“成功”曲线?