
做过强化学习项目的朋友十有八九都被“稀疏奖励”这个问题折腾过。智能体在仿真器里跑了几十万步几乎所有回合的回报都是零损失函数涨涨跌跌但策略就是学不动到最后只能靠人为设计密集奖励函数硬撑——那是真的费头发。我入行接手的第一个机械臂抓取任务就撞上了这堵墙后来真正帮我把墙凿开一个大洞的是一个叫 Hindsight Experience ReplayHER事后经验回放的思路。很多资料把它讲得很玄上来就甩公式其实核心就一句话当一条轨迹没有达成既定目标时别急着扔掉把轨迹里实际到达的状态“篡改”成虚拟目标再让算法从这个失败样本里学到东西。就靠着这个“事后诸葛”式的技巧我硬是在不改环境、不加奖励函数的条件下把稀疏奖励任务的训练效率拉高了不止一个量级。这篇文章我不谈高深数学只把 HER 的动机、原理、实现细节和踩坑记录一次讲透希望能给同样被稀疏奖励折磨的人一点实际的帮助。1. hindsight 到底解决了什么问题1.1 稀疏奖励强化学习落地路上绕不开的坎先说说我一直很头疼的问题。做机器人控制类的强化学习任务时环境往往只有“成功”和“失败”两种结果。比如机械臂推箱子推动一次、推错方向、推到一半掉下去最后的奖励都是 0只有推进目标区域才算成功给一个正向奖励。这在数学上没有任何问题但在实际训练中就是灾难——因为在绝大多数回合里智能体收到的奖励信号全部是零它根本不知道“自己刚才那些动作到底是好是坏”。这个现象叫稀疏奖励Sparse Reward。奖励信号密度太低策略梯度估计不出来有意义的趋势探索又纯靠随机碰撞相当于让一个人蒙着眼在一间空屋子里找一枚硬币每走一步都不给任何提示。大多数情况下几百个回合探索下来全是失败价值函数Critic学到的几乎全是“什么动作都不值钱”策略自然动弹不得。传统做法里工程师们最常见的应对方案是设计密集奖励函数给中间状态加分比如“离目标越近奖励越高”。听起来合理但实际非常坑。你拆过机器人任务的奖励函数就知道光平衡“鼓励前进”和“防止抖振”这两条就足够调一两周。而且一旦环境有变化比如机械臂换了型号或者目标物尺寸变了奖励函数经常要跟着推翻重来可复用性极差。1.2 为什么“学不到东西”的本质原因是数据利用率太低再往深挖一层稀疏奖励之所以难搞根本原因不是学习算法不够聪明而是大量有价值的数据被浪费了。你想一个回合里机械臂可能把箱子推到了目标点的正左边——虽然没“成功”但这个状态离成功已经很近。如果把这一段轨迹称作“失败的”直接扔掉那算法就永远学不到“接近目标区域的动作是好动作”这个经验。换句话说失败轨迹里藏着黄金只是当时没人告诉算法这些状态是有价值的。hindsight 这个思路厉害就厉害在它从哲学层面解决了一个困扰奖励信号是环境给的但是在训练时谁规定这条轨迹只能服务于它最初的那个目标如果目标设定的机制是可以重写的那我们完全可以把失败轨迹变成“成功轨迹”来用。这个转变看似小小一步却直接改写了样本利用率的下限。我第一次在代码里实现这套逻辑时说实话心里是打鼓的——总觉得“篡改目标”像是在作弊。但等你亲手把训练曲线跑出来看到成功率蹭蹭往上涨就会彻底明白这不是作弊这是把实验数据里本来就有价值的那部分重新盘活。2. 核心思路用“事后诸葛亮”打破白学一整局的魔咒2.1 把所有任务都看成目标达成问题HER 的具体操作先要从“目标条件化强化学习”Goal-Conditioned RL这个框架说起。通常的 RL 任务里状态 $s$ 和环境给奖励 $r$学的是一个从状态到动作的映射。而目标条件化任务不一样我们会额外指定一个目标 $g$奖励函数是带目标的即 $r_t R(s_t, a_t, g)$策略也要写成 $\pi(a|s, g)$——它要学会的是“把这个初始状态 $s_0$ 想办法弄到目标 $g$ 附近”。这种表述方式乍一看像是增加了复杂度但它带来一个极大的优势同一个回合可以同时为多个目标服务。想象一个机械臂抓取回合机械臂伸出手抓起红色方块放到蓝色区域西南角。如果这一回合原始目标是“抓红色方块放到蓝色区域”那它确实失败了。但如果把目标临时改成“抓红色方块放到蓝色区域西南角”那这一回合就是一次完美的成功示范——中间每一步的 reward 全都变成正常的、非零的、可学习的信号。HER 的关键操作就两步先把已经到达的状态领域里一般叫 achieved goal记作 $s_g$取出来然后把它当作新的目标重新给这一整条轨迹注入奖励信号。既然我们用事后视角知道了轨迹实际停在哪个状态我们就可以反推“也许停在这个状态也是个值得学习的目标”然后把整个回合当作一次达成这个新目标的成功的经验。2.2 用生活类比理解重标注Relabeling为了把这个逻辑讲透我经常用一个很生活化的类比。你家小孩练投篮投了半天一个都没进。传统教练的办法是骂一顿说“你下次好好投”——这就是没反馈抠门一点的教练会给“投得够不够接近篮筐”打分——这就是密集奖励函数。HER 不是这两种它更像一个聪明的教练把每次投偏的球落地位置都圈出来然后对这个小朋友说“你刚才那一球目标其实可以是篮板左上角你看你准确地打中了红点区域”——于是“打中红点”这个有用的肌肉记忆就被强化下来下次再投的时候小朋友至少知道方向往哪边使劲。这个类比特别贴切因为 HER 并不是让智能体直接学会达成某个新目标而是让它在失败中积累泛化的策略能力。一条轨迹虽然达不到“红色方块放到蓝色区域”但它展示了“红色方块放到西南角”这个过程需要哪些动作技巧这些技巧恰恰是将来真正达成目标时所需要的。所以 HER 的本质是数据重标注relabeling——不改环境、不改奖励只改训练时使用的目标。2.3 为什么必须配 off-policy 算法使用这里有个容易忽视的细节。HER 能成立隐含前提是你有“经验回放池”Replay Buffer并且你从中采样时可以随机地、离线地替这些旧经验换一个新的目标重新算一遍 reward 再丢给网络学。因此HER 只适用于 off-policy 的算法比如 DDPG、SAC、TD3 这些基于经验回放的算法如果你用的是 PPO 这类 on-policy 算法每一条轨迹学了就丢、没法回放重标HER 就基本使不上劲。我第一次踩的坑就在这里看到论文里写 HER 效果惊艳赶紧接上我惯用的 PPO跑了一个晚上曲线纹丝不动差点以为论文是造假的。后来一查才发现问题出在算法类型上。所以千万别想当然HER 和 off-policy 是一对绑定组合。3. HER 的具体实现与关键策略3.1 算法主流程伪代码与结构拆解下面给出一个精简版的 HER 伪代码用 python 风格写方便大家对着看# 1. 初始化策略 pi、价值网络 Q、经验池 replay_buffer for episode in range(max_episodes): # 采样一个原始目标 g g sample_goal() # 记录回合的原始状态序列和动作序列 trajectory_states, trajectory_actions [], [] state env.reset() for t in range(max_steps): action pi(state, g) # 策略在当前状态和目标下给出动作 next_state, reward, done env.step(action, g) # 原始经验状态、动作、reward 和原目标 replay_buffer.store(state, action, reward, next_state, g) trajectory_states.append(state) state next_state if done: break achieved_goal achieved_from_trajectory(trajectory_states[-1]) # 轨迹终点状态 # 2. 重新标注为这条轨迹生成 4 个新目标 for new_goal in sample_new_goals(achieved_goal, trajectory_states, k4): for state, action, next_state in trajectory_states: new_reward reward_function(state, action, next_state, new_goal) replay_buffer.store(state, action, new_reward, next_state, new_goal) # 3. 从 replay_buffer 中采样经验做梯度更新如 SAC / DDPG 的正常更新这里面最核心的采样函数sample_new_goals可以有很多策略不同的策略对最终效果影响非常大。我在下一个小节详细展开。伪代码里注意一个关键点重标轨迹时每一条原始轨迹的 reward 要用新的目标重新算一遍而不是把原来的 reward 原样塞进 buffer。这是很多人容易写成 bug 的地方。以前我在一个社区帖子里看到有人直接把原来奖励用的done标志也原样存了导致重标后明明“成功”了但done还是 False训练效果一塌糊涂。重标之后一定要重算 reward同时重标后的回合不要错误地延用旧的终止标志。3.2 四种目标回放策略怎么选最稳OpenAI 那篇 HER 论文里很贴心地对比了四种从轨迹里选新目标的策略我把它们整理成一张表方便对比策略名称做法特点适用场景final只取轨迹最后一个状态作为新目标最简单方差最小任务成功率很低、目标空间小的时候random从整条轨迹里随机抽 k 个状态作为新目标覆盖更广但可能选到无关紧要的早期状态探索初期、目标空间大时future从轨迹的“当前时间步之后”随机抽 k 个状态作为新目标最均衡样本效率最高论文推荐大部分机器人控制任务episode把轨迹里所有状态都作为新目标目标太多容易引入噪声数据量充裕、目标维度低时我自己在实际项目里绝大多数情况下用future策略k 取 4效果最稳。为什么不总是用final因为如果机械臂绕了不少弯路最后才停在目标附近只拿终点一个状态当目标前面绕弯路的那些过渡状态反而学不到有效引导信号。而future策略能保证重标出来的目标在时间上是“顺理成章”的——新目标的达成点都在当前时刻之后相当于强行把一条失败轨迹从某个中途点开始重写成一个成功故事。如果任务本身特别难、成功率长期接近 0future策略也会面临“没有足够的成功示范可学”的窘境。这时候我会加一层混搭一部分重标目标用future一部分用原始目标即不重标。别小看这条很多人把重标比例设成 100%反而容易因为完全丢掉原始目标信号导致策略漂移跑起来怪怪的。3.3 超参数与网络结构上的几个关键设置HER 不是一个“装了就能用”的黑盒组件有几个配套设置直接决定它能不能发挥威力。第一奖励函数的选择。HER 要求目标状态和实际状态之间的距离可以计算最常用的是基于距离阈值$reward 1$ 当 $dist(s, g) \epsilon$否则为 $0$或者用负数距离 $-dist$。用阈值稀疏奖励配合 HER往往比用连续距离奖励稳定性更好因为阈值奖励给出来的学习信号足够干净。我一开始用的是连续负距离训练曲线抖得厉害换成阈值稀疏奖励后马上稳定下来。第二网络输入结构。因为策略和价值函数的输入是 $(s, g)$ 拼接起来目标 $g$ 的编码方式和状态 $s$ 要保持一致。比如状态里用的是关节角度目标里也应该是关节角度如果用笛卡尔坐标两边就统一用笛卡尔坐标。如果混用网络很难学到这个映射关系。有一次我图省事状态传的是关节角目标传的是末端坐标训练了五万步都学不起来排查半天才发现是这个低级错误。第三经验回放池大小和采样比例。HER 会把同一批轨迹以多个目标存进 buffer数据量膨胀了 k 倍通常 k4如果 buffer 太小重标目标就容易冲刷掉原始经验。我一般会把 buffer 容量设置成至少 100 万个状态-动作样本采样时固定原始目标和重标目标的比例比如 1:2这样既保证学到原始任务又能享受重标目标的引导信号。这个比例在不同任务上可能要微调但 1:2 是一个不错的起点。4. 实操中的问题排查与工程经验4.1 为什么我的 HER 没效果先查这五件事HER 并不是所有场景都灵。如果按标准装好以后效果不佳我建议按下面的顺序排查而不是盲目调参算法是不是 off-policy这是个前置项PPO/TRPO 等 on-policy 算法直接用 HER 是失效的先检查自己的训练循环是否从 replay buffer 采样更新。目标重算了吗重标轨迹后 reward 必须重新按新目标计算同时确认done标志是否也要跟着更新。漏掉这两个里任何一个训练基本就是空转。目标表征是否合理目标向量维度太高比如直接用整张图像像素当目标会让距离计算失去意义建议先用低维状态向量目标维度太低又体现不出中间引导要根据任务选择合适粒度。成功率是不是极端低如果智能体探索了几千回合到达的状态始终是同一个局部区域那 HER 重标出的目标也长一个样等于没引入多样性。这时候问题可能出在探索策略本身比如噪声系数太小、或者初始状态分布太窄。重标比例是不是过高上面说过100% 重标容易丢原始目标。把比例调到 0.5~0.8 之间再试试很多“没效果”其实只是信号失衡。4.2 常见问题速查表下面这张表是我长期跑实验总结出来的建议直接收藏现象可能原因解决方案训练曲线长期贴零on-policy 算法接 HER换成 DDPG/SAC/TD3 等 off-policy 算法前期学得动、后期又退化buffer 太小重标目标冲刷原始经验扩大 buffer 到百万级降低重标比例成功率波动剧烈奖励函数使用了连续距离改阈值稀疏奖励$\epsilon$ 设为任务精度的 1.5 倍左右目标看似学了很多但任务成功率不高目标空间与动作空间不匹配检查状态和目标表征是否一致确认距离度量的度量方式是否对应机械臂探索时总是朝同方向动噪声系数太小探索不足调高高斯噪声或 ε-greedy 的 ε或引入动作空间扰动策略用了 future 策略反而变差轨迹太短或目标时间跨度太大减小 k 值或试 mixed 策略future final4.3 一些容易被忽略的细节有几个小细节论文里往往一笔带过但在工程实现里能气死人。一个是重标时机。经验池里积累的数据风格在训练中不断变化如果重标目标全部是在采集轨迹那一刻算好存入 buffer后面策略更新时 buffer 里的目标分布可能已经和当前价值函数不太匹配。所以我在工程里更喜欢“存储原始目标采样时再实时重算”——每次从 buffer 里采到一个 batch就以一定概率重新选目标再算 reward。这样重标逻辑和数据存储解耦灵活度更高实测训练曲线也更平滑。另一个是目标状态与观测状态之间的时间对齐。轨迹里每个时刻的状态要对应到同一个时刻的观测特征。有些基准环境在返回state时带默认噪声或者用自定义 wrapper 改变了状态维度都可能导致重算的目标和状态对不上。如果你发现 HER 的性能浮动特别大先认真检查环境返回的state、achieved_goal、desired_goal三个量到底是什么维数最好在正式训练前打印几个样例人工核对。还有个比较进阶但很实用的经验HER 在任务早期非常需要乐观探索。我常在训练初始阶段把动作噪声调高一点等到成功率越过某个阈值比如 20%再降回去。原因很简单HER 的重标效果依赖到达状态的多样性而探索初期正是多样性增长最快的窗口错过这个窗口后面怎么补都补不回来。5. 从 HER 出发的扩展思考与个人体会5.1 一个思路带来的连锁反应HER 对我的影响不只是解决了一个机械臂抓取任务更重要的是一种思维方式的转变——在数据充足而标签缺失的情况下重新审视自己手里的数据。很多时候我们抱怨奖励信号太稀疏、监督信息太少其实是被“环境给定的奖励”框住了想象力。环境说这一回合失败但环境没有禁止我们把目标换成实际到达的那个状态。这个思路后来被我迁移到好几个项目里在模仿学习中用“事后对齐”处理少量专家轨迹在分层 RL 中用子目标重标缓解中上层策略的稀疏奖励甚至在做离线 RL 数据清洗时也能借鉴重标思想给低质量数据重新贴“伪标签”来扩充有效样本。这并不是说 HER 万能。它的边界也很清楚比如在目标再现性差、动作对状态影响极其微弱的场景比如有些稀疏回报的对话任务重标出来的伪目标对学生策略没有帮助反而会引入误导。但在机器人控制、导航、操作类任务中HER 确实是我目前见过最优雅、最不折腾的样本效率技巧之一。5.2 给新人的一些私房建议如果你正准备在自己项目里尝试 HER我的建议是第一步,不要一上来就搞复杂环境先在用 Gym 自带的 FetchReach 或手写一个二维平面点到达任务上跑通整个流程把重标、buffer、reward 重算这些环节的数据都 print 出来看一遍确认每一步的数据形状和数值符合预期。第二步把几种重标策略都跑几百个 episode画在同一张图里对比你会对 final、future 等策略的行为差异有非常直观的感觉。第三步再迁移到自己的任务环境这时你已经有了稳定的“套路”排错效率会高很多。另外我也想提一句HER 这类方法在工程落地上真的很有价值因为它是少数能直接嵌入现有 off-policy 训练框架、不用动环境代码的技巧。很多时候老板只给你一个性能很差的基线不给你改环境奖励的权限HER 就是那种能从数据侧把水搅活的黑科技。对我个人来说这是我在实际项目中用过最值回票价的一个 trick性价比极高。