做过强化学习项目的朋友,十有八九都被“稀疏奖励”这个问题折腾过。智能体在仿真器里跑了几十万步,几乎所有回合的回报都是零,损失函数涨涨跌跌但策略就是学不动,到最后只能靠人为设计密集奖励函数硬撑——那是真的费头发。我入行接手的第一个机械臂抓取任务就撞上了这堵墙,后来真正帮我把墙凿开一个大洞的,是一个叫 Hindsight Experience Replay(HER,事后经验回放)的思路。很多资料把它讲得很玄,上来就甩公式,其实核心就一句话:当一条轨迹没有达成既定目标时,别急着扔掉,把轨迹里实际到达的状态“篡改”成虚拟目标,再让算法从这个失败样本里学到东西。就靠着这个“事后诸葛”式的技巧,我硬是在不改环境、不加奖励函数的条件下,把稀疏奖励任务的训练效率拉高了不止一个量级。这篇文章我不谈高深数学,只把 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, k=4): 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策略能保证重标出来的目标在时间上是“顺理成章”的——新目标的达成点都在当前时刻之后,相当于强行把一条失败轨迹从某个中途点开始重写成一个成功故事。
如果任务本身特别难、成功率长期接近 0,future策略也会面临“没有足够的成功示范可学”的窘境。这时候我会加一层混搭:一部分重标目标用future,一部分用原始目标(即不重标)。别小看这条,很多人把重标比例设成 100%,反而容易因为完全丢掉原始目标信号,导致策略漂移,跑起来怪怪的。
3.3 超参数与网络结构上的几个关键设置
HER 不是一个“装了就能用”的黑盒组件,有几个配套设置直接决定它能不能发挥威力。
第一,奖励函数的选择。HER 要求目标状态和实际状态之间的距离可以计算,最常用的是基于距离阈值:$reward = 1$ 当 $dist(s, g) < \epsilon$,否则为 $0$,或者用负数距离 $-dist$。用阈值稀疏奖励配合 HER,往往比用连续距离奖励稳定性更好,因为阈值奖励给出来的学习信号足够干净。我一开始用的是连续负距离,训练曲线抖得厉害,换成阈值稀疏奖励后马上稳定下来。
第二,网络输入结构。因为策略和价值函数的输入是 $(s, g)$ 拼接起来,目标 $g$ 的编码方式和状态 $s$ 要保持一致。比如状态里用的是关节角度,目标里也应该是关节角度;如果用笛卡尔坐标,两边就统一用笛卡尔坐标。如果混用,网络很难学到这个映射关系。有一次我图省事,状态传的是关节角,目标传的是末端坐标,训练了五万步都学不起来,排查半天才发现是这个低级错误。
第三,经验回放池大小和采样比例。HER 会把同一批轨迹以多个目标存进 buffer,数据量膨胀了 k 倍(通常 k=4),如果 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 + final) |
4.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,性价比极高。