我从"hindsight"这个词切入,聊一个在强化学习里非常经典的思路。做RL的工程师和研究者应该都听过Hindsight Experience Replay(事后经验重放,简称HER),这个思路最早由OpenAI在2017年提出,核心就一句大白话:训练时别再盯着"没达成目标"的失败轨迹唉声叹气,把这条轨迹本身当成一次成功,换个目标重新学一遍。
这篇文章会从它解决的问题、算法原理、工程实现到训练调试踩坑,完整拆解一遍。如果你正在做机器人控制、目标导向的连续决策任务,或者手头环境给的奖励极度稀疏、模型怎么训都不动,这篇文章应该能帮你省下大量试错时间。
1. 项目概述:从"马后炮"中挖出训练信号的强化学习思路
1.1 它到底解决什么问题
强化学习有一个老大难问题:稀疏奖励(sparse reward)。很多真实任务不是把密密麻麻的奖励函数写好的游戏,而是"做对了才有奖励,做不对就是零"。比如机械臂抓取,可能只有物体真正被抓起来才给一个奖励,其余几千步探索全是零。问题在于,当奖励信号几乎全是零时,策略梯度法和Q-learning都很难获得有效的学习信号——你的算法根本不知道往哪个方向挪动哪怕一点点才是正确的。
我见过很多刚接触RL的人在这种环境下训练,曲线一排一排全是平的,Q值永远在零点附近抖动,成功率一整个下午都是0。这不是你的代码写错了,而是算法的假设根本不适用于这种任务:策略更新依赖"奖励值的差异"来产生梯度,如果所有探索都拿到一模一样的零分,策略根本不知道该往哪边走。
这个问题最典型的场景包括:机械臂抓取摆放、机器人导航到目标点、游戏里必须集齐道具才能通关、甚至一些推荐系统中"用户是否点击"这种离散结果反馈。它们有一个共同特点——目标明确、结果明确,但过程里没有任何中间奖励注入,随机探索的成功率又极低。
HER就是冲着这个场景来的。它的论证很直接:一次"失败"的轨迹里,其实藏着一条"成功"的子轨迹——虽然没达到你指定的目标,但它确实在很短的时间内到达了某个可达状态,这种时间上的因果链是有价值的。
1.2 一句话理解核心思想
用一句话概括HER的思路:把一条没达成原定目标的轨迹,重标定成一条"目标是轨迹末端实际状态"的成功轨迹,存进经验回放池里让模型继续学。
举个例子,你让机械臂把积木从位置A推到位置B,结果它一把推歪,积木停在了位置C。正常回放里这是失败数据,奖励为0,毫无信息量。HER会把它改写成:目标被替换成"把积木推到C"(也就是轨迹实际到达的位置),然后这条轨迹在这个新目标下就是一条满分轨迹。模型从中能学到一个通用的映射关系:给定某个目标状态,前面这一串动作序列是有效的。
这种变换之所以有效,是因为目标条件策略(goal-conditioned policy)本身就希望学到"状态到目标的泛化能力"。你真正想要的不是只把积木推到B,而是一个能推任何位置的策略,而每条失败轨迹提供的"意外成功"恰好丰富了策略的目标覆盖范围。
2. 原理深挖:目标重标注为什么有效
2.1 从马尔可夫决策过程看目标条件策略
先把模型建立起来。在目标条件下的强化学习中,马尔可夫决策过程被扩展为包含目标G的元组:(S, A, G, R, T, γ)。策略不再是只基于状态s输出动作,而是基于状态s和目标g的联合观测输出动作a = π(s, g)。
奖励函数R(s, g, a, s')通常写成指示函数的形式:如果新状态s'满足目标g的判定条件,奖励为1,否则为0。在连续控制中,这个条件往往是用距离阈值判定的,比如机械臂手指与目标物体之间的距离小于某个阈值就算成功。
在这种设定下,你可以发现一个关键性质:一条轨迹对不同目标而言,其"好坏"是完全不同的。同一串动作,在"目标=轨迹末端实际状态"这个视角下就是一次完美执行,因为策略刚好把它从初始状态引导到了目标状态;而在"目标=原始目标"视角下则是一无是处的垃圾数据。
HER换个目标,本质就是换了评价视角,把那条轨迹里真正有价值的信息(到达某个状态的因果链条)提取出来。这跟人学习很像:投篮没进,但球比上一次飞得更高更靠近篮筐,你会记住这个手感,不断逼近目标。
2.2 重标注本质上是修改奖励函数
我们从形式上看HER做了什么。假设一次episode采样出的轨迹是τ = (s₀, g, a₀, r₀, s₁, g, a₁, r₁, ..., sₜ, g, aₜ, rₜ),原始奖励全是0,因为从未达到过g。HER构造一个新的轨迹τ' = (s₀, g', a₀, r₀', s₁, g', a₁, r₁', ..., sₜ, g', aₜ, rₜ'),其中g' = sₜ(轨迹最终状态),rₜ'标定为1或按距离定义密度,中间步骤同样按"是否在那一时刻达成了g'"计算。
这样,经验池中多了一批"正样本"。虽然它们在原任务视角下是失败数据,但在重标定的任务视角下是成功数据。Q函数通过拟合这些样本,会学会"当目标接近当前可达状态时,这条轨迹的动作序列有高的累计回报",从而形成一条可优化的梯度路径。
这比手工加中间奖励(reward shaping)要优雅得多:手工奖励需要设计者深刻理解任务,还容易引入局部最优,诱导策略走捷径。HER完全不需要额外的领域知识,它只是调整了数据的投喂方式。
2.3 为什么不是简单伪造数据
很多第一次接触HER的人会有一个疑问:拿失败轨迹重新贴个目标,这不就是造假吗?模型学到的不是一个"作弊"的策略吗?
这个质疑需要正面回应。关键在于,重标定后的样本并不涉及伪造动作和状态,它只是重新定义"这次尝试想达到的目标"。轨迹中的观察、动作、状态转移全部真实发生过,唯一改变的是我们问模型的问题:从"你能到达B吗?"改成"你确实能到达C吧?"——后者是有事实依据的,因为轨迹末端状态就是C。
更准确地说,HER是把一个失败样本中的"部分经验"转化成了另一个目标条件下的"完整经验"。模型从中学会的是动作序列与状态变化之间的动力学关系,这种关系具有跨目标的迁移性。训练完成后,策略不只在重标定的那些目标上工作,它学会了整个目标空间内的映射:从任意状态出发,向任意目标逼近的能力。
这也是为什么HER在Fetch系列机器人环境上效果那么显著:训练后期策略展现出的泛化能力,往往不只是记住几个目标点,而是能够在连续目标空间中插值出新目标。
2.4 与HER配套的采样策略(final/future/episode/random)
在实践应用中,HER还需要策略指定用哪种方式从失败轨迹中选择重标定的目标。原论文给出了四种:
| 采样策略 | 重标定目标选取方式 | 特点 |
|---|---|---|
| final | 固定使用轨迹最终状态 | 最简单,计算量小,但目标多样性不足 |
| future | 从轨迹中当前步之后的随机未来状态里抽样 | 目标更均匀,训练效果通常更好 |
| episode | 从当前轨迹中随机均匀抽取任意状态 | 引入过多样性,目标可能太远 |
| random | 从整个经验池中随机抽取其他轨迹的状态 | 偏差大,很少用 |
我实际用下来的体会是:final和future各有利弊。final实现最透明、最简单,适合快速验证HER流程是否跑通;一旦确认能学到东西,建议换成future,并设置随机采样的时间步在当前时间步之后,这样重标定的目标在时间上更"可到达",避免用未来信息构造过去不存在的目标。
另外还有个重要的超参数k,表示每条真实轨迹会被重标定多少次。也就是说,一条失败轨迹会被拆成k条不同目标下的轨迹都存进buffer,每个transition都要复制k份。论文里推荐k=4,但实际需要根据任务复杂度和buffer大小调整。k太大会让重标定样本占比过高,冲淡原始目标的学习信号;k太小又可能在buffer里几乎没有重标定样本,失去效果。
3. 工程实现:关键代码与参数选型的完整记录
3.1 一个最小可运行的HER流程伪代码
HER并不是一个独立的RL算法,它更像一个数据增强层,挂在任何离线策略算法外面。下面是核心训练循环的结构:
# 伪代码:HER + DDPG 风格的主循环 for epoch in range(epochs): for episode in range(n_episodes): # 每个batch跑若干episode episode_data = [] # 保存完整轨迹 obs = env.reset() goal = env.get_goal() # 初始目标 for t in range(max_steps): action = actor(obs, goal) + noise next_obs, reward, done, info = env.step(action) episode_data.append((obs, action, reward, next_obs, goal, done)) obs = next_obs if done: break # HER重标定:对轨迹内每个transition补写k个新目标样本 her_transitions = hindsight_relabel(episode_data, her_strategy="future", k=4) replay_buffer.add(episode_data) # 原始轨迹 replay_buffer.add(her_transitions) # 重标定轨迹 # 正常训练:从buffer里采样,更新critic和actor update_actor_critic(replay_buffer.sample(batch_size))关键点在于hindsight_relabel这个函数。它是一个纯数据处理函数,不涉及任何梯度计算。对于轨迹中的每个时刻t,如果选择future策略,它会在t之后的时间步中均匀随机抽一个未来状态作为新目标,然后把该transition的goal字段替换掉,奖励也按新目标重新计算。一个时长50步的episode,在有k=4的重标定情况下,经验池会增加4倍数据量。
3.2 代码级别的重标定实现要点
真正动手写HER时,有几个细节容易出错:
奖励函数重新计算不能只看末端。重标定时我们对轨迹里每个transition都重新计算reward,判断标准是"该时刻状态下是否已经达成了新目标"。比如用欧氏距离作为目标判定,那么某一步的状态离重标定目标距离低于阈值,该步奖励就是1。这要求环境的状态里必须能提取出achieved_goal(即当前实际达成的目标状态),否则无法计算距离。绝大多数gym的GoalEnv已经为你实现了这个接口,包括observation字典中的desired_goal和achieved_goal两个字段,直接用即可。
数据结构要完整,不能只改一个观测。有些实现可以偷懒只把目标替换后加进buffer,但如果你的轨迹同时存了observation、achieved_goal和reward,务必全部同步替换。若只改了reward而没改目标字段,critic拟合时输入的目标与奖励对应关系就错乱了,模型会学到完全没意义的东西。
另外一个很容易踩的小坑是使用future策略时,重标定目标要选"当前时刻之后"的状态,而不是整个轨迹里任意的状态。理由很直观:如果从t=1时刻的状态里抽一个t=50时的状态做目标,策略面对的状态目标距离可能过大,Q值近似会变得不准确。原论文里的实现是从当前时间步之后的区间采样,这一步别省。
3.3 算法主干选型建议:Off-policy是硬前提
选HER之前要明确一个前提:它只适用于off-policy的算法,也就是使用经验回放池的算法。DDPG、TD3、SAC、DQN这些都可以;PPO、A2C这类on-policy算法直接套HER会出问题。
原因在于重标定样本是事后生成的,必须有一个大缓冲区存储并反复采样,才能被充分利用。On-policy算法采集完数据就更新然后丢弃,重标定的样本只被看到一次,不仅浪费,还会因为目标分布偏移引发训练不稳。
我在实际项目中用的组合是HER+SAC,效果比HER+DDPG更稳。SAC的熵项天然提供了探索能力,配合HER的目标重标定,在机械臂推积木这类任务上通常能在几千个episode内达到较高的成功率。如果环境动作空间比较小、单步开销高,DDPG也是不错的选择,训练速度更快,只是对超参数更敏感,需要耐心调学习率和噪声规模。
还有一个需要注意的是网络结构设计。HER的目标空间一般和状态空间维度相似,最简单的方式是把state和goal拼起来作为输入,但这样会丢失一些结构信息。更好的做法是分别编码,再用相乘或相加的方式融合特征。我的经验是中等规模的全连接网络(两到三层,每层256到512个神经元)在大多数任务上已经足够,不要一味加宽加深,否则小样本场景下容易过拟合。
4. 训练实战:从环境搭建到收敛的全流程
4.1 实验环境与benchmark选择
验证HER效果,我首选的是OpenAI Gym里的机器人目标达成类任务。最常用的是这三个:
- FetchReach:机械臂末端需要到达目标点,最简单,适合验证跑通。
- FetchPush:机械臂把物体推到目标位置,中等难度,是HER论文里的标准演示任务。
- FetchPickAndPlace:需要先抓取物体再放到目标位置,难度更高,能检验算法在长时间序列下的表现。
这些环境有个方便的设定:每个step返回的observation是一个字典,包含observation、achieved_goal、desired_goal三部分,非常适合直接用来做goal relabeling。HER的官方实现也是在基于这类环境验证的,很多开源库(如stable-baselines3以及各种RL框架的her实现)都会绑定这些环境跑基准测试。
你可以用简洁的一行命令启动一个环境做快速测试:
import gym env = gym.make("FetchReach-v1") obs = env.reset() print(obs.keys()) # dict_keys(['observation', 'achieved_goal', 'desired_goal'])如果从零搭环境,注意一点:环境一定要能提供achieved_goal,这是实现HER的前提条件。如果你的自定义环境没有这个输出,就得自己在环境外部维护"当前实际目标状态",否则无法计算目标距离和重标定奖励。
4.2 关键超参数参考
我给出了一份经过多轮实验验证的配置模板,直接照抄大概率能跑通FetchReach或FetchPush:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 学习率 | 1e-3 | actor和critic共用此值,SAC可适度降低 |
| 折扣因子γ | 0.95 | 目标达成任务通常步数少,γ不需要太高 |
| 最大episode长度 | 50步 | Fetch类环境默认值 |
| replay buffer容量 | 1e6 | HER数据量翻倍,buffer必须够大 |
| HER采样策略 | future | 效果稳定且实现不复杂 |
| k值 | 4 | 每条轨迹重标定次数,测试范围可2~8 |
| 每epoch episode数 | 16~64 | 影响数据累积速度,配合buffer大小调整 |
| batch size | 256 | 常用值,不要太小 |
| 网络结构 | 三层MLP,每层256 | 注意state和goal先分别编码再融合 |
训练时如果发现成功率迟迟不上升,可以先从两个方向调整:一是增大k或换成future策略,让重标定样本更丰富;二是检查探索噪声或SAC熵系数是否过小,导致策略过于保守、探索范围不足。很多时候问题不在HER本身,而是探索不够。
4.3 训练监控:不只是看平均回报
提到监控指标,我强烈建议不要只看平均回报这一条曲线。在稀疏奖励环境下,平均回报在很长一段时间里可能都是零或接近零,很容易让人误判训练无效。我通常会同时监测三个指标:
第一是滚动平均成功率,指最近N个episode中达到目标条件的比例。这是终极指标,但它非常滞后,在训练初期几乎不变。第二是critic对重标定样本的Q值,如果Q值能随着训练逐渐上升,说明模型确实从重标定数据中学到了"目标可达性"的表征——此时距离策略改善就不远了。第三是actor对随机目标的输出动作多样性,用于判断是否陷入模式崩溃,如果输出动作方差过小,说明策略分布坍缩,需要调整探索机制。
我见过很多次这样的过程:Q值缓慢上升的时候成功率纹丝不动,坚持几百个episode后成功率突然从一个平台跳到另一个平台。这正是稀疏奖励训练中的典型特征——策略在某个临界点突然学会了跨过目标阈值,随后的成功率曲线会呈现台阶式上升,而不是平滑上升。
这提醒我们一点:在HER训练中,耐心很重要。每一个episode看起来都在原地打转,但重标定样本里的信息正在一点一点改造模型的表征空间,一旦量变引发质变,效果会很惊人。
5. 踩坑实录:稀疏奖励训练中的高频问题与排查方法
5.1 重标定后的样本比例失衡
我在最初实现HER时犯过一个错误,为了让模型尽快学到东西,我把k值调到了16,结果训练进度反而变慢了。原因在于重标定样本占经验池比例过高,Q函数过度拟合"接近目标"的样本,而真正原始目标相关的样本被淹没,导致原始任务的Q值估计失效。
经验准则是:重标定样本与真实样本的比例建议控制在1:1到3:1之间。一个衡量方法是在每次采样时统计当前batch中重标定样本的占比,如果长期超过80%,就考虑降低k值或使用final策略减少数据多样性。
5.2 目标与状态输入的拼接方式
另一个隐藏很深的坑在于网络如何处理goal和state。很多人直接把两个向量拼接成一个长输入喂进MLP,这在目标空间和状态空间差异较大时,效果往往不如分开编码再融合的结构。
推荐的做法是:用两层MLP把state和goal分别映射到相同维度(比如128维),然后做逐元素相加或相乘,再输入后续层。这样做能让网络显式建模"状态与目标的差"这一关键信息。如果你发现训练曲线平台期过低,可以考虑换用这种结构,常常会有意外提升。
5.3 与优先经验回放(PER)的配合问题
如果你在HER基础上叠加优先经验回放(Prioritized Experience Replay),要格外小心。PER根据TD-error给样本赋予采样权重,而重标定样本的TD-error通常天然偏低——因为它们对应的目标是轨迹实际可达的状态,critic拟合得很好。
这会导致PER倾向优先采样原始失败样本,重标定样本的采样概率越来越低,最终HER形同虚设。解决思路有两个:一是对重标定样本和真实样本分组管理,各用各的优先级队列,以固定比例混合采样;二是在优先级更新时对重标定样本做权重衰减,防止它们被完全边缘化。这个问题在论文和标准实现里很少被讨论,但做工程化时非常常见。
5.4 多步回报与HER的兼容性
还有一个更精细的问题,就是多步回报与重标定的冲突。如果你在TD3或SAC里使用了n-step return,需要确保目标替换后,n步的奖励序列也要按照新目标重新计算,不能只改最后一个transition的奖励。
否则critic在计算多步累积回报时,会混入与新目标不一致的历史奖励,导致奖励信号自相矛盾,训练甚至可能出现发散。如果实在懒得逐项重算,我建议在HER训练时把n-step设置为1,保持最简单的一致性,等策略有明显进步后再考虑引入多步回报。
5.5 轨迹长度对重标定的影响
有时你会发现,同一个任务,环境允许最大步数从10改成50后,HER的效果反而变差了。原因是步数变长后,轨迹里早期状态与后期状态的差距变得非常大,future策略从后期采样的目标对早期来说太过遥远,产生大量"离谱"的重标定样本。
此时可以试试改进策略:只在距离当前步不超过一定时间窗口的未来区间内采样目标。原论文没有强制要求这个窗口,但我实测下来,限制未来采样范围在几步之内,训练稳定性会明显提高。
6. 后续扩展与个人体会
HER这个思路对我个人影响很大,不只是因为它解决了一类具体问题,更因为它提供了一种看待失败数据的方式。很多RL训练中的"垃圾数据",其实只是目标定错了而已。一个失败的episode里包含的动力学信息,可能比精心构造的环境奖励函数有价值得多。
我也遇到过一些有意思的引申方向。比如在模仿学习中,专家示范往往覆盖率很低,我们也可以用HER的思路,把任意一条失败轨迹看作一种隐性示范,重标定目标后再去做行为克隆;再比如离线强化学习里,如何从固定数据集中提取更多目标信息,同样可以参考HER的做法。这些方向在最近的一些工作里都有影子,说明这个思想的生命力还在延续。
如果让我给一个最直接的建议:先去FetchPush环境里自己跑一遍HER,然后把k值分别改成1、4、8做一次对比实验,亲眼看看成功率曲线的差异是怎么变化的。这种体会比读十篇论文都深刻。
训练中的成功率和稳定性不完全取决于算法创新,能耐心观察曲线、分辨何时该加探索、何时该调目标重标定参数,才是把RL算法真正用在项目里的核心能力。希望这篇文章能让你在稀疏奖励的坑里少走几步弯路。