
“hindsight”这个词字面上是“后见之明”放到不同领域意思完全不一样。心理学里说的是“事后诸葛”那种偏差工程圈里Mozilla还给日志分析工具起过这名但在强化学习这个圈子里一提到hindsight大家条件反射想到的就是那个赫赫有名的算法——Hindsight Experience Replay也就是HER。这个项目标题就一个词信息量却非常大。HER解决的是强化学习里最磨人的稀疏奖励问题智能体在环境里狂奔上千步奖励始终是0它到底该怎么学传统Q-learning的做法基本是“看运气”运气不好几百万步都转不动。HER换了个思路目标A没达成那我就把这整段轨迹伪装成朝着目标B努力的成果而目标B恰好是轨迹里实际到达过的某个状态。这样一来原本没有任何学习价值的失败轨迹就变成了能提供有效梯度的成功经验。如果你正在做机械臂抓取、导航规划、机器人控制这类项目或者你手里的任务奖励函数根本设计不出来那这篇文章就是写给你的。下文会从原理讲到代码实现再附上我实际过程中踩过的坑和排查思路尽量让你少走弯路。1. 内容整体设计与思路拆解1.1 稀疏奖励问题RL最经典的拦路虎先把标准强化学习的逻辑捋一遍。智能体每一步执行一个动作环境返回一个奖励信号智能体根据历史累计回报去更新策略。拿CartPole这种经典任务举例每个时间步只要杆子不倒就加1分奖励是稠密的智能体每一步都能快速判断动作好坏学起来自然很快。但真实世界的任务远没有这么温柔。机械臂抓取一个螺丝只有机械爪完全闭合、物体被抓起来的那一瞬间奖励才变成1之前所有动作反馈全是0。想象一下智能体随机地把机械臂挥来挥去从这个状态空间里找到“抓取成功”这个区域概率可能低到十万分之一甚至更低。轨迹里每一步都是0奖励没有梯度没有信号相当于一个学生在毫无提示的情况下做一道完全没头绪的题做对了也不知道为什么对做错了也不知道为什么错。从概率角度看这个问题更直观。假设环境状态空间的维度是d目标达成区域占整体状态空间的体积比例是p那么纯随机策略单次尝试的成功率大概就是p这个数量级。在机械臂任务里p通常小到可以忽略这意味着平均要跑几十万次尝试才可能碰到一次成功。更尴尬的是就算运气好撞上一次成功了靠孤零零一条轨迹也学不出什么泛化能力下次换个初始位置照样抓不起来。稀疏奖励问题本质上就是样本效率和探索效率的双输。不是算法不聪明是信息源本身就没有信息。1.2 HER的核心思想换个角度解读失败HER的想法其实带着一点“事后诸葛亮”的味道既然我最终没有到达目标A但我这一路走过来确实经过了若干状态那我为什么不把这条路重新解读成“克服重重困难抵达目标B”的成功示范呢拿机器人开抽屉举例。设定的目标是“把手移动到抽屉把手的位置并拉开”智能体连续尝试了100步最后手的位置和目标差了10厘米这条轨迹对原目标来说一败涂地。但在采集轨迹的时候HER会记录下每个时间步实际到达的状态然后把轨迹最后的位置或者中途某个状态当作新的“既定目标”重新计算奖励手真的到了这个位置那么对于“到达这个位置”的目标而言这条轨迹就是一次满分的成功示范。这么做直接的效果是原本毫无价值的失败轨迹被重新变成了带着“完整状态-动作-目标”三元组的成功经验。智能体从中学到的是一个更本质的规律——当目标状态恰好等于我这条轨迹的终点时我刚刚执行的那一串动作确实能达成目标。这比硬生生等待一个稀有的成功信号要高效得多。从课程学习curriculum learning的角度看HER也很有意思。它相当于自动给学习过程编排了一个从易到难的热身序列先让智能体学会如何到达那些容易到达的状态再逐步扩展目标覆盖范围到更远的区域。随着重标记的目标越来越接近真实分布智能体对“如何达成一个任意给定目标”的理解会越来越完整最终泛化到原本那个棘手的真实目标上。1.3 为什么必须配合off-policy算法使用很多人问过我一个问题HER是不是一个独立的算法能不能直接替换掉PPO答案是不能。HER本质上不是一个完整的强化学习算法它更像一个经验池改造模块必须寄生在off-policy算法比如DQN、DDPG、TD3、SAC之上才能发挥作用。原因在于HER重标记出来的经验与策略参数是解耦的。想象一下你采集了一条轨迹把终点状态重标成一个新目标那这条经验里记录的动作、奖励、下一个状态和目标状态全部是可以复用的。它不依赖于“当前策略是什么”只依赖于“这条轨迹确实发生过了”。所以它可以被反复从回放缓冲区里采样出来用于训练。而像PPO这类on-policy算法经验用完就丢每一轮都用最新的策略去采样重标记带来的信息增益根本没有机会被充分消化。关键差异在于HER改善的是经验池的内容质量而不是直接修改策略梯度的计算公式。它通过提高单位样本的信息密度让off-policy算法在奖励极其稀疏的情况下也能学到梯度方向但前提是这个算法本身要有回放缓冲区。理解了这一点你就明白为什么OpenAI那篇论文里所有实验都是DDPG配HER而不是PPO配HER。2. 核心细节解析与实操要点2.1 四种目标重标记策略的取舍OpenAI那篇提出HER的文章里明确规定了四种从轨迹中选取新目标的方式分别是final、future、episode和random。我用过很长一段时间大概讲讲每种的实际表现。final最简单粗暴直接把轨迹最后一步的状态当作新目标。要求再低一点的任务用final也能看到效果比如目标是到达某个区域轨迹末尾离目标区不远那重标记出来的目标就和真实目标接近学习曲线还算好看。但它有个问题轨迹中途经历了多次转折末尾只是最后停的地方很可能是个随机位置信息量偏弱。future策略是我最推荐的也是在绝大多数任务中效果最好的。它的做法是从当前时间步之后的某个时刻随机挑一个后续状态当作当前时间步的新目标。这样保留了因果顺序智能体确实在接下来的轨迹中到达了这个状态并且这个状态之后没有发生破坏性的变化。实测下来future策略在Fetch系列任务中比final的收敛速度快了不少成功率也能稳定提升一个档次。episode策略是从整条轨迹里任意挑选一个状态实现上是允许从历史状态中选这样带来的问题是可能选到早已路过的状态目标“时效性”被稀释。random策略几乎是随机从经验池里挑一个状态当目标基本等于没用我试过几次都没什么提升。我的最终建议是默认选择futurek值取4如果任务状态空间维度特别高或者轨迹特别长可以适当调大k但别超过8否则经验池会被大量的“远目标”稀释。2.2 关键超参数k值、重放比例与目标维度以下是我在实际调参中总结出的几个最影响HER效果的因素挨个说一下。k值指的是每条原始经验额外生成的重标记经验条数。k值越大重标记经验在经验池中的占比越高稀疏奖励轨迹被“纠正”的密度就越大训练初期更容易找到梯度。但k值过大也有副作用额外生成的目标往往是轨迹中途或末尾的状态距离初始状态非常远对智能体来说属于“极端困难目标”如果数量占比过高反而会让训练变得不稳定。我见过有人把k调到16结果训练中期出现明显的震荡。replay比例指的是原始经验与重标记经验被采样的占比。原文建议是1比1也就是采样时原始经验和重标记经验各占一半。实际调参时这个比例对性能影响非常显著。如果重标记经验占比过高模型会对“如何达成随机目标”过度拟合真实目标反而学不好。我通常的做法是先按1比1起步然后观察成功率曲线如果真实目标成功率增长缓慢尝试把重标记占比降到0.5比1、0.3比1如果反而下降就提高重标记占比。目标维度的设计和HER的成败完全绑定。HER要求目标必须是状态空间中的一个可观测子集比如机械臂末端的三维坐标而不是一个离散的任务ID。如果你把一个包含位置、速度、姿态的完整高维状态当作目标那重标记出来的经验几乎没法学习因为即使再聪明的重标记算法也无法从一条轨迹中推断出所有维度上都严格匹配的目标。尽量把目标限制在任务真正关心的维度上比如只关心位置不关心速度这样重标记出来的经验天然就具有更多的有效样本。2.3 奖励函数设计的隐藏细节HER能用前提是你要有一个度量目标达成程度的奖励函数。绝大多数情况下这个函数是距离函数新目标g是轨迹经过的某个状态奖励给的是当前实际状态与g之间的距离差或者距离小于某个阈值的二值奖励。这里有个细节必须注意阈值膨胀会毁掉整个任务。假设你设置了一个比较宽松的阈值比如机械臂末端距离目标5厘米就算成功那智能体很快就学会“差不多就行了”它不会去追求精确到达目标状态。而在HER的框架下目标状态是轨迹中的真实状态如果阈值太宽重标记出来的经验里“目标达成”根本没达成错误信号就会被一遍遍放大。我给出一份经验值参考二值奖励的阈值最好是目标空间正常尺度的大约5%到10%。比如目标是末端位置在1立方米的范围内那阈值设在5到10厘米比较合适。如果你用距离作为连续奖励就不存在阈值问题但需要额外设计一个奖励缩放系数避免Q值范围过大导致训练不稳定。另外还建议设计一个辅助的“成功判定函数”它只在真实目标上生效用于记录成功率指标不参与奖励计算。这个判定函数的作用是让你在训练日志中看清模型对真实目标的真实掌握程度而不是被重标记经验带偏。3. 实操过程与核心环节实现3.1 环境选型与基线对比为什么选Fetch系列HER的经典验证环境是OpenAI Gym里的Fetch系列常见几个任务包括FetchReach、FetchPush、FetchSlide和FetchPickAndPlace。我建议不要一开始就跑这些背后那个巨大的仿真库可以先在一个轻量级环境里把代码流程跑通再上机器人仿真。Fetch系列的核心交互结构是目标是一个三维位置坐标奖励稀疏只有在目标位置与物体位置的距离小于阈值时才给0否则给-1。状态空间里包含物体当前位置、目标位置、手指位置、相对距离等字段。这套设计天然就是为HER准备的因为目标状态可观测、低维度、有明确的距离度量。实操中我的对比基线建议这样定先跑一个纯DDPG拿来当基准然后跑DDPG加HER最后跑DDPG加HER再加一些优化技巧比如优先经验回放、目标网络更新比率调整。对比的意义在于你能明确看到HER带来的提升幅度。我跑下来的经验是纯DDPG在FetchReach这种简单任务上勉强能学会但FetchPush这种任务基本学不动加了HER之后FetchPush能达到90%以上成功率差距非常明显。3.2 核心代码实现在DDPG中集成HER下面给出一段我常用的HER经验池集成代码使用PyTorch和Gym接口。注意这里省略了完整DDPG实现重点展示HER重标记和采样逻辑。import numpy as np import torch from collections import deque class HERBuffer: def __init__(self, capacity, k4, strategyfuture): self.capacity capacity self.k k self.strategy strategy self.buffer deque(maxlencapacity) def add_episode(self, episode, achieved_goals): # episode: list of dicts, each contains {state, action, reward, next_state, goal} # achieved_goals: list of achieved goal vectors for each time step for i, transition in enumerate(episode): # 原始目标经验直接入池 self.buffer.append(( transition[state], transition[action], transition[reward], transition[next_state], transition[goal] )) # 对每条经验做目标重标记 for i, transition in enumerate(episode): for _ in range(self.k): if self.strategy future: # 从当前步之后的步中随机选一个时间步的achieved_goal future_idx np.random.randint(i, len(episode)) new_goal achieved_goals[future_idx] elif self.strategy final: new_goal achieved_goals[-1] elif self.strategy episode: new_goal achieved_goals[np.random.randint(0, len(episode))] else: # random: no-op于此略 continue new_reward compute_reward(new_goal, achieved_goals[i], threshold0.05) self.buffer.append(( transition[state], transition[action], new_reward, transition[next_state], new_goal )) def sample(self, batch_size, replay_ratio1.0): # replay_ratio表示原始经验占比这里简化处理为原始:重标记 1:1 batch np.random.choice(len(self.buffer), sizebatch_size, replaceFalse) states, actions, rewards, next_states, goals [], [], [], [], [] for idx in batch: s, a, r, ns, g self.buffer[idx] states.append(s) actions.append(a) rewards.append(r) next_states.append(ns) goals.append(g) return (torch.tensor(np.array(states), dtypetorch.float32), torch.tensor(np.array(actions), dtypetorch.float32), torch.tensor(np.array(rewards), dtypetorch.float32), torch.tensor(np.array(next_states), dtypetorch.float32), torch.tensor(np.array(goals), dtypetorch.float32)) def compute_reward(goal, achieved_goal, threshold): # 二值稀疏奖励距离小于阈值算成功 dist np.linalg.norm(goal - achieved_goal) return 0.0 if dist threshold else -1.0这段代码里有几个细节值得重点说。第一不要对整个episode一次性集中重标记那会给经验池带来严重的样本相关性正确做法是以“回合结束”为粒度将整段经验及其重标记版本一起入池但采样时仍然用均匀随机采样不对同一回合的经验做特殊分组因为经验池足够大之后相关性会被稀释。第二compute_reward里的threshold是真正决定学习难度的参数不能太大也不能太小我通常先试0.05再根据任务尺度调整。第三observation里要包含goal信息重标记只修改goal字段不要改动state、action这些实际交互数据。数据流上有个容易踩的坑你必须在每个时间步都记录“当前实际到达的目标位置”achieved goal而不能只记录最终结果。原因很简单重标记的目标是从轨迹中任意一个时间步的状态里挑出来的这意味着每一步的achieved goal都得提前保存好。很多人的代码里只存了最后的achieved goal结果future策略没法实现只能用final效果直接掉一截。3.3 训练日志怎么读成功率曲线的三个阶段很多同学训练完只看finally的成功率忽略中间过程这会导致你无法判断算法是否真的在工作。我看了大量训练曲线后发现HER加持下的训练过程一般会走三个阶段。第一个阶段叫“漫无目的期”。训练刚开始的几万步成功率曲线几乎是一条水平直线在0附近微弱波动。这个阶段很正常因为经验池里还没有足够多的成功轨迹即使重标记了智能体也在疯狂试探。不要着急持续训练。第二个阶段叫“快速上升期”。在某一个拐点之后成功率突然开始飙升增长率可能一天比一天高这表示智能体已经意识到“达成某些目标”是有规律可循的策略网络开始输出有意义的动作。这个阶段通常能持续到70%左右是训练最让人兴奋的时期。第三个阶段叫“平台期”。成功率升到某个平台后就不再明显增长偶尔还会掉几个百分点。平台期的出现通常意味着剩余的目标难度过高或者策略已经收敛到局部最优。此时可以尝试调整replay比例、降低探索噪声、或者延长训练步数但不要指望会有爆发式提升。读曲线时有一条重要的经验如果训练跑到中期成功率还纹丝不动先别急着调超参先确认经验池里是否有足够多的重标记成功经验。你可以打印出经验池里“原始经验中reward不为0”的比例通常这个比例非常低小于1%这才是正常状态。如果这个比例过高说明环境本身其实没那么稀疏那你根本不需要HER直接用标准DDPG就能好。4. 常见问题与排查技巧实录4.1 目标状态分布失衡HER失效的常见原因HER最怕的一种情况是目标状态分布严重失衡。举个例子某个任务中智能体的轨迹全部集中在一个很小的区域里那么重标记出来的新目标也全是从这个小区域里选的经验池里的成功经验高度同质化模型泛化不出来。我在机械臂抓取中遇到过这问题。目标位置被限制在一个非常窄的工作空间里智能体很快就能让末端到达那个区域但一旦初始位置变远成功率立刻暴跌。排查思路很简单打印经验池中重标记目标的三维坐标分布如果发现它们挤成一个小团说明探索严重受限。这时需要加大探索噪声、提高随机初始状态的多样性或者干脆调整状态空间的边界。4.2 特殊状态转移的陷阱终止、越界与不合法状态HER的重标记逻辑默认环境会持续跑完整个回合然后从轨迹里挑一个状态当目标。但很多真实环境里存在终止条件和越界状态机器人掉下悬崖、机械臂撞到限位、物体掉出桌面这些情况下轨迹的“终点”往往是一个非法或极端状态。把这样的状态重标记成目标后果非常麻烦。举个例子物体已经滑落桌面轨迹末端的目标位置在桌面之外你把它标记成“成功目标”智能体学到的是“手伸到桌子外是成功”完全违背任务意图。实际项目中不能盲信重标记必须额外写一个合法性过滤器新的目标状态必须满足环境约束在限位内、在有效区域内才允许入池。我习惯的做法是在add_episode里加一个判断比如新目标位置的坐标是否超出工作空间的合法范围超出就跳过不重标记。这个过滤器的代码不超过十行却是避免“花两个星期学出来一个有毒策略”的关键。4.3 调参实战经验三个我踩过的坑与结论第一个坑k值贪多。一开始觉得重标记目标越多越好直接把k拉到16结果训练速度反而变慢最后还出现严重震荡。原因前面说过重标记目标太多、目标距离太远模型根本拟合不过来。k值建议在4到8之间先用4看平台期表现再增加。第二个坑奖励阈值设得太大。当时为了任务能快速看到成功把阈值放得很宽结果智能体学出了“走个大概方向就算成功”的坏毛病后面把阈值收窄成功率反而大幅下降因为策略已经无法精确控制末端。正确做法是直接设一个较小的阈值让模型从训练第一天就适应高精度要求虽然前期成功率涨得慢但后期更稳。第三个坑忽略achieved goal的维度对齐。在对接自定义环境时我一度把achieved goal设置成3维位置但状态空间里的目标字段是4维多了一个姿态角导致compute_reward里np.linalg.norm报错或者算出来的距离完全不对。排查了半天才发现问题。这个很容易在接新环境时犯一定要从第一行数据就开始验证数组维度是否对齐。4.4 一个被低估的细节探索噪声的初始值前面提到的都是经验池和奖励设计层面的问题最后补充一个容易被低估的细节——探索噪声的初始值。DDPG这类算法在训练开始阶段会用OUNoise或高斯噪声来增加探索力度。HER的作用是让稀疏奖励下的失败轨迹变得可用但探索本身依然是产生多样性轨迹的前提。如果噪声太小轨迹全是一条直线重标记出来的目标也全在一条线上效果自然不好。实际做法是把探索噪声的初始幅度设置得比平时大一点让智能体在早期能“乱跑”起来等成功率进入快速上升期之后再把噪声幅度逐步降下来。我个人的经验是噪声初始幅度比默认值大0.5到1倍收敛速度会有肉眼可见的提升。写在最后说了这么多最后还是想强调一遍我自己的体会HER并不神秘它更像是给RL工程师提供了一副“失败滤镜”让我们可以用另一种姿态审视那些没有奖励回报的轨迹。我以前遇到稀疏奖励任务的第一反应是拼命调奖励函数后来发现重标记的思路往往能带来更根本的改善。如果你决定在项目里尝试HER我的建议是先用开源环境把代码跑通再迁移到自己的任务上。迁移的时候多花点时间核对目标空间维度、合法状态边界、奖励阈值这三个地方这三个细节比我前面提到的所有超参都更容易导致项目翻车。对了最后再分享一个小技巧训练中途记得定期把经验池里的重标记目标分布可视化出来画成散点图。你能直观看到智能体探索了哪些区域、哪些目标从没被真正学过这在调试时比看十行训练日志都有用。