☰
HER算法:用“后见之明”破解稀疏奖励难题——强化学习样本效率提升之道
2026/9/30 9:58:00 网站建设 项目流程

1. 为什么一个叫“后见之明”的算法,成了稀疏奖励的救星

先说个我自己的例子。去年我在训练一个机械臂模型,任务是让它把方块推到桌面上的指定位置。跑了几十万步,reward纹丝不动,模型没有任何要学习的迹象。痛定思痛,把奖励函数重新设计成基于距离的稠密奖励,才勉强有了反馈。后来回头看,真正的问题不是奖励函数不够精细,而是环境本身就处于“大部分时间都拿不到任何奖励”的状态——这其实是强化学习里最常遇到的坑,专业上叫稀疏奖励问题(Sparse Reward Problem)。

当时我如果早一点知道今天要聊的这个算法,至少能少走一个多月的弯路。它就是由OpenAI在2018年提出的Hindsight Experience Replay,缩写是HER,论文标题就叫《Hindsight Experience Replay》。名字里的“Hindsight”直译过来是“后见之明”,讲的正是人类最熟悉的一种心理现象:事后复盘总是容易的,但我们能不能把这种“事后聪明”教给机器?

一句话概括HER的核心思想:当一个回合失败了没关系,我们把它重新解释成“成功”——只是目标变了而已。比如机械臂没把方块推到目标点,但它其实把方块推到了另一个位置。换个角度看,它并没有失败,它只是成功完成了一个“推到另一个位置”的任务。只要把那个实际到达的位置当作目标,这个轨迹就不再是零奖励的失败样本,而是充满正奖励的成功样本。

这篇文章不打算停留在概念层面。我会带你把HER的原理拆开揉碎,从目标重标记、轨迹采样、网络结构一直聊到参数怎么调,代码怎么写,以及我在实战里踩过的那些坑。适合正在入门强化学习、被稀疏奖励折磨的读者;也适合已经跑通DQN/DDPG、想进一步提升样本效率的朋友。

2. 先把“后见之明”这件事想明白

2.1 人类视角的“事后聪明”和机器有什么不同

先做一个思想实验。你让一个小孩去投篮,他的目标是“把球投进篮筐”。前面二十次都投丢了,球落在了不同位置。如果只看结果,他的每次尝试都失败了。但下一次轮到他投的时候,他其实学会了什么呢?他学会了“往那个方向用力一点球会飞得更高”“往左边偏一点会落在更左边”——这些是基于之前每一次投丢动作的经验调整。换句话说,虽然目标没完成,但每一次失败都提供了有效的经验信息。

强化学习的情况很类似。在稀疏奖励环境下,一个回合结束只返回一个信号,比如“是否到达目标点”,到达给+1,否则给0。这样的reward信号量太少了,模型很难从中学到任何梯度信息。你可能会想:那我加上每一步的距离作为reward不就行了?但实际工程里,奖励函数设计本身就是一门玄学,设计得不好会诱导模型钻空子,比如机械臂发现“把方块扔到地上”能获得更大的距离收益,于是学会了摔东西而不是推方块。

HER的思路则完全不同:它不修改奖励函数的定义,而是修改“这个回合追了什么目标”。既然球落到了地上某个位置,那我们就新定义一个目标“让球落在这个位置”,然后回头看这个回合里每一步动作,它们都是朝这个目标前进的有效动作,每一步都能给予正向的reward。这样一来,原本完全不可学习的稀疏奖励问题,被转换成了稠密且有效的经验数据。

2.2 为什么普通回放缓冲区学不到东西

强化学习里的经验回放(Experience Replay)是把智能体与环境互动的transition(状态、动作、奖励、下一状态)存进一个缓冲区,然后随机抽样去更新Q网络或策略网络。这种做法的好处是打乱了样本之间的时间相关性,让网络更新更稳定。

但这里存在一个结构性问题:如果样本里的奖励基本都是0,那缓冲区里存的全是无效样本。无论怎么抽样、采样多少次,模型都得不到任何“做得好”的信号。我们说强化学习是试错学习,但试错的前提是至少偶尔能试成功一次,从而获得正样本。

HER的贡献,就在于它从“存量样本”上做文章:不增加新的交互,而是把缓冲区里已经存在的那些“失败轨迹”,通过重新分配目标,把它们变成正样本。样本数没变,但有效信号的数量成倍增长。这就相当于把一个班级里所有考了60分以下的学生,都按“55分是目标”的标准重新评价,发现孩子们“完成度”其实不低。这当然有自欺欺人的味道,但对训练网络来说,这种重新标注恰恰提供了渐进式的学习信号。

2.3 HER的三句话总结

  • 每个回合跑的轨迹,除了按“原目标”存储,还要额外按“实际到达状态”重新存储一份
  • 存储时,把“实际到达状态”作为新的目标,重新计算reward
  • 采样训练时,新目标和原始目标混合使用,让模型既学“怎么达成指定目标”,又学“怎么达成眼前这个已达成目标”

有了这个框架,下面就可以进到具体实现层面了。

3. 核心原理拆解:目标重标记到底做了什么

3.1 你需要一个能“带目标”的transition结构

普通强化学习里的一个transition长这样:

(s, a, r, s')

到了目标条件强化学习(Goal-Conditioned RL)里,transition会多一个目标变量:

(s, g, a, r, s')

这里的g就是当前回合要完成的目标。整个回合的目标是固定的,每一帧都会带着这个目标去决策。网络学到的策略也是一个带条件的策略:π(s, g),意思是“在这个状态下,朝着这个目标,我该做什么动作”。

HER的改动就在这个g上。当智能体执行完一个回合,我们手里有一条完整的轨迹:

[(s_0, g_original, a_0, r_0, s_1), (s_1, g_original, a_1, r_1, s_2), ..., (s_T, g_original, a_T, r_T, s_{T+1})]

注意,这个g_original是预设的初始目标。由于稀疏奖励,大部分时刻的r都是0。

然后HER会做一件事:在轨迹中挑一个实际状态s_t,把它当作新的目标 g' = s_t,再用g'代替g_original,为这条轨迹重新生成一份transition序列。因为每一步的状态都是朝着s_t前进的,所以每一步的reward会变成非零值(通常距离越近reward越大,或到达时给正奖励)。

3.2future策略:怎么选“新目标”最科学

目标重标记听起来简单,但有一个选择:选哪个状态作为新目标?论文里比较了四种策略,分别是:

  • final:选轨迹的最终状态作为新目标
  • future:选当前transition之后的某个随机状态
  • episode:选当前回合中随机某个状态
  • random:从整个回放缓冲区里随机选一个状态作为目标

很多人直觉上会觉得final最合理,毕竟轨迹最终状态就是实际到达的状态。但实验结果表明,future策略效果最好。

原因也不难理解。final把整条轨迹都重新标记为“到达最终状态”,这话没错,但容易产生大量“长期目标已完成”的样本,另外无法回答“如果目标的可行域很大,最终状态并不代表整条轨迹都朝它前进”的问题。future在当前时间步t往后随机选一个状态当作目标,这样在时刻t,目标确实在将来的某个位置,说明从t向后的这段轨迹确实是在朝目标前进;而t之前的那些transition虽然也被存储,但因为目标状态是“未来的”,它们仍然是一条合理逼近目标的轨迹。这种策略巧妙地把“目标不可达”转换成“目标在进度上更远的地方”,配合时序逻辑,学习信号最自然。

我在实际实现中基本只用future策略。

3.3 奖励函数的一致性必须保证

HER涉及到一个容易出错但被很多人忽略的细节:重新标记目标后,奖励函数形式不能变。如果原始奖励函数是:

r(s, a, g) = -[f(s') == g]

即“下一状态是否符合目标,符合为0,否则为-1”,那么重新标记后也要用同一个函数去计算reward。如果原奖励函数是稠密的负距离函数:

r(s, a, g) = -|f(s') - g|

重新标记后也用它重算。好处是,无论目标怎么换,网络看到的reward分布是统一的,不会出现“同一状态、同一动作、两个目标、reward数值范围完全不同”的矛盾信号。

我遇到过一种错误做法:为了偷懒,重新标记后不重算reward,直接沿用原来那步的r。这会导致新目标下的样本与奖励值完全错位,训练出来的策略会非常怪异。记住,HER的核心是重新计算reward,绝不是简单复制一份数据。

3.4 HER在DDPG里的位置

HER本身不依赖某种特定的RL算法,它是一个“数据层面”的改造方法。理论上,它可以叠加在任意off-policy算法上,比如DQN、DDPG、TD3、SAC。但最经典的搭配还是DDPG+HER,因为DDPG天然适合连续控制问题,而HER最初就是为了解决连续动作空间的机器人操作任务设计的。

在DDPG框架里,HER只改动了一个地方:采集完一个回合的轨迹后,除了按原来的目标存储一遍,再额外按重新标记的目标存储一遍。存储完成后,后续的采样训练流程完全不变。也就是说,HER可以像一个插件一样嵌入你已有的代码框架,改造成本很低。

4. 从零实现HER:核心代码怎么写

4.1 环境的选择与目标怎么抽象

先选一个合适的验证环境。OpenAI Gym里有个经典环境叫FetchReach-v1,任务是控制机械臂移动到某个目标点。目标空间是三维坐标。这个环境是稀疏奖励的,只有机械臂末端接近目标位置时给+1,否则给0。用这个环境验证HER再合适不过。

环境返回的观测分两部分:observation是机械臂相关的状态,desired_goal是目标位置。当回合结束时,检查实际末端位置与目标位置的距离是否小于阈值,来决定是否给正奖励。

我自己复现实验时,没有直接用Gym的版本,而是自己写了一个简化的2D点目标环境:一个点在平面上移动,目标是到达某个坐标。这一步的抽象意义在于,把“目标”和“状态”的类型统一成向量,方便直接演示HER的逻辑。

4.2 transition的存储与重新标记

先定义一个buffer里存储的数据结构。注意,我把每个transition按“原始目标”和“重标记目标”各存一份,这是最简单的做法。更省显存的做法是先只存原始transition,采样时再随机替换目标,但那个思路会带来代码复杂度提升。

上代码。先看核心的HER回放缓冲区:

class HERReplayBuffer: def __init__(self, capacity, k=4, strategy='future'): self.capacity = capacity self.k = k # 每个transition额外重新标记的目标数量 self.strategy = strategy self.buffer = collections.deque(maxlen=capacity) def store_episode(self, episode_transitions, info): """ 参数说明: episode_transitions: 一个列表,每个元素是 (s, a, r, s_next, g_original) info: 回合额外信息,如实际到达的goal集合 """ for transition in episode_transitions: s, a, r, s_next, g_original = transition # 先按原始目标存储一份 self.buffer.append((s, a, r, s_next, g_original)) # 对每一条transition,额外生成k条重标记样本 T = len(episode_transitions) for t in range(T): s, a, r, s_next, g_original = episode_transitions[t] # 按future策略:从t之后的时刻里随机取k个状态作为新目标 future_states = [episode_transitions[i][3] for i in range(t+1, T)] if len(future_states) < self.k: # 如果剩余状态不够k个,就允许重复采样 chosen = np.random.choice(future_states, size=self.k, replace=True) else: chosen = np.random.choice(future_states, size=self.k, replace=False) for new_goal in chosen: # 以新目标重新计算reward,这里用简单的稀疏奖励 new_reward = 0.0 if np.linalg.norm(s_next[:2] - new_goal[:2]) < 0.05 else -1.0 self.buffer.append((s, a, new_reward, s_next, new_goal))

注意代码里的两个细节:第一,new_reward必须用new_goal重新算,不能沿用原来的r;第二,future_states是从t+1开始取,保证新目标在未来的方向上。

4.3 训练循环的完整接入

有了HER回放缓冲区后,训练主循环和普通DDPG差别不大。伪代码如下:

for epoch in range(total_epochs): episode_transitions = [] obs = env.reset() episode_reward = 0 goal = obs['desired_goal'] for t in range(max_steps): action = policy.get_action(obs['observation'], goal) next_obs, reward, done, info = env.step(action) episode_transitions.append( (obs['observation'], action, reward, next_obs['observation'], goal) ) obs = next_obs if done: break # 关键:HER利用整个回合的轨迹做重新标记 her_buffer.store_episode(episode_transitions, info) # 从缓冲区里随机采样mini-batch,更新网络 for _ in range(n_updates): batch = her_buffer.sample(batch_size) # 对batch里每个样本,用一致的reward和goal更新Q网络和策略网络 update_policy_and_q(batch)

实现上最容易忽略的一步:在store_episode阶段,episode_transitions中已经包含了原始reward。如果你用的是稀疏奖励环境,reward大部分是-1(或0),但原始目标下这些样本照存不误。HER的妙处在于,虽然原始样本大部分是负奖励,但重标记后的样本会带来大量高价值正样本,两者混合后,Q网络才学得到区分优劣。

4.4 参数怎么选:k值、R_mix和网络结构

HER论文里有一个超参数k,控制每个transition额外重放多少个目标。论文实测表明,k=4在多数场景下效果比较好。更大的k意味着更大的重放样本容量,但也会拉长训练时间;更小的k省时间但样本多样性不足。

还有一个常被忽略的超参叫“重放比例”(replay ratio)。实际训练时,每次梯度更新采样的batch里,最好混合原始目标样本和HER重标记样本。我在实践中的做法是50%原始目标样本、50%HER样本。如果HER样本比例太高,模型会对“目标的统计分布”产生偏移,比如更擅长从“当前状态出发完成随机一个状态”而不是“完成设定目标”。这个比例没有标准答案,需要根据任务微调。

网络结构方面,DDPG+HER通常用两层全连接网络,每层256或512个神经元,激活函数用ReLU。目标空间维度如果很大,比如图像目标,则需要加卷积层或使用视觉编码器。我自己在机器人仿真任务里,直接用512+512的结构,效果已经很稳定。

5. 实操效果对比:HER到底能快多少

5.1 Bit Flipping实验:一个教科书级别的验证

为了直观理解HER的威力,我用一个极简环境做测试。这个环境叫Bit Flipping:有一个长度为20的比特串,目标串是随机的另一个比特串,每个动作是翻转某一位。只有当整串比特全部匹配时,奖励才为+1。这个环境最大的特点是:随机策略几乎不可能碰到正奖励。因为成功概率是2的20次方分之一,约等于一百万分之一。

在这个环境里,普通DDPG完全无法学习,训练曲线是一条水平线,因为经验缓冲区里连一个正样本都没有。而加入HER之后,大约几千个回合就能达到接近100%的成功率。原因很直接:HER把一条随机翻转的轨迹重新标记为“目标就是最终翻转后的比特串”,于是这条轨迹的每一步都接近final goal,每一步都会收到奖励。模型可以不断地从这些虚拟成功样本中学习。

我把训练成功率画出来对比过,第一阶段(前2000回合)普通DDPG成功率永远挂零,HER则已经爬升到60%以上。这个对比是我见过的最直观的“HER有效”的证据。

5.2 FetchReach环境:连续控制的表现

在FetchReach-v1环境里,目标是一个三维点,机械臂要移动末端去触摸它。这个任务其实已经不能算完全稀疏了,因为环境还有个observation里包含物体位置信息。但我把奖励替换成严格稀疏的:只有当距离小于5cm时才给+1。这种情况下,普通DDPG成功率在30万步内几乎没有突破;DDPG+HER大约在10万步时可以达到约80%的成功率。

另一个值得一提的现象是:HER训练出来的策略对目标点的泛化性更强。因为重放缓冲区里的目标来自“实际采样到的状态”,这些状态的分布天然覆盖了机械臂可达空间。而普通目标条件策略如果只在一个固定目标点上训练,换一个目标点可能就失灵了。HER相当于隐式地做了数据增强,丰富了目标的采样分布。

5.3 交替采样对训练稳定性的影响

我在实验中发现一个有意思的现象:如果每次更新100%从HER重标记样本里抽样,早期训练会非常快,但后期会出现Q值过高估计的问题。因为那些“虚拟成功样本”的target reward都是接近满分的,Q网络会倾向于认为所有状态下都能快速成功,从而过度乐观。混入一部分原始稀疏样本后,Q值能回归到合理区间。这解释了为什么我一直强调原始样本和重标记样本需要混合使用。

实操中,我还会给HER样本设一个小的优先级权重。比如让HER样本的采样概率略低于原始样本(0.4 vs 0.6),或者反过来,根据任务决定。这不是论文里的标准做法,但在我测试的多个任务里,适当的样本比例调控确实能提升最终成功率。

6. 常见问题与排查技巧实录

6.1 训练很久仍然无法成功,最先该查哪里

如果DDPG+HER跑了几十万步成功率还是零,我的排查顺序是:

  1. 先检查重新标记后的reward是否真的是正的。打印几个HER样本出来核验:新目标是否确实与s_next距离接近。很多情况下,这里出的问题在于目标空间和状态空间的单位不一致,比如目标是四元数,状态是欧拉角,但直接拿二者做距离计算,导致reward全是负的。
  2. 检查future策略取的是s_next还是s。有的实现会把当前状态s_t误当作新目标,这样模型学到的是“原地不动”,训练当然无效。
  3. 查看采样比例。如果HER样本比例过低,效果就不明显;如果过高,Q值过估计。我一般先从9:1(原始:HER)开始试,如果学习速度太慢再逐步调高HER比例。

6.2 为什么有时HER会导致策略“躺平”

有一种典型故障:策略学会了“不管目标是什么,都往一个固定的平均位置跑”。这本质上是因为重放缓冲区里很多目标状态是从轨迹中采样的,而这些目标的分布偏向于轨迹走向的中间态。如果目标空间很大而轨迹覆盖不足,模型可能找到一条“中间路线”来最小化平均距离误差。

我遇到这种情况后,采取的修正措施是:把一部分HER目标改为final策略,仅取回合最终状态作为新目标,这样可以强制模型训练数据覆盖到真正的可达空间边界。或者把原始目标的采样比例提高一点,让模型更关注“达成预设目标”而不是“去过的地方”。但需要注意,这仍然是一个任务层面的调节问题,没有一个固定配方能通吃所有环境。

6.3 代码层面的常见bug清单

写HER代码时,最常见的几个bug:

  • 重标记后没有重新计算reward,导致reward与goal不匹配
  • 存储transition时只存了(s, a, r, s_next),丢掉了原始目标g
  • 使用final策略时,把最终状态当成所有时间步的目标,导致早中期transition的目标是“将来才会到达的状态”,这些transition在t时刻其实是无效的
  • 没有注意轨迹长度不一致,future策略在短轨迹上采样越界
  • 目标空间是归一化的,但状态空间没有归一化,导致距离计算失效

这些bug都不会导致程序崩溃,但会让训练结果看起来像“玄学”。我建议在写完HER后,写一个单元测试:手动构造一条简单轨迹,手动计算预期的新目标和新reward,与代码输出比较。

6.4 什么时候不应该用HER

HER不是万能的。它适合问题结构满足“目标可以在状态空间中直接定义”的情况,比如机械臂抓取、导航、机器人操作。但如果目标的定义依赖隐藏信息、或者目标本身不可从观测中推断,HER就很难奏效。比如一个对话任务,目标是“用户满意”,这个目标无法直接从状态空间里取值,HER就无法重标记。

另外,HER改善的是样本效率,不改变算法本身的探索能力。如果动作空间极大而奖励信号又极端稀疏,HER能提供渐进信号,但初始随机探索如果真的完全到不了任何有意义的区域,那它的帮助也会有限。在这种情况下,可以考虑结合随机化目标采样(如Go-Explore)做更激进的探索。

7. 从HER延伸到更广的“后见之明”思维

7.1 目标重采样在其他算法里的影子

HER之后,很多工作吸收了它的思想。比如RIG(Relabeling and Implicit Goals)、GCBC(Goal-conditioned BC)里都能看到类似“利用事后状态作为目标”的思路。在多任务强化学习里,用行为状态自动生成目标,是一种绕过人类设计目标的通用策略。它的本质是:用智能体自己的行为轨迹来定义任务伪目标,从而消除对人工目标设计的依赖。

这个思路在工程落地里有个很重要的作用:降低了任务设计的门槛。传统IRL(逆向强化学习)需要专家轨迹来学奖励函数,HER却直接从失败轨迹中“萃取”目标。对一个工业应用场景来说,与其让工程师花两周时间定义reward逼近函数,不如设置一个可达性检测函数,然后把HER作为默认的数据增强手段加上去。

7.2 事后复盘思维对整个AI项目的启发

说实话,HER给我的最大启发不在算法本身,而在“如何重新认识失败数据”。很多AI项目都会积累大量失败的任务日志,在传统的监督学习里,这些数据如果不能标注正确答案,几乎就是垃圾。但HER告诉我们:换个目标定义,失败数据就能变成另一种任务的训练样本。这是一种数据利用观的转变——从“只使用成功数据”变成“让失败数据也说话”。

这个思路迁移到业务里也很有效。比如推荐系统里,用户点了这个商品但没点击那个商品,站在“预测点击率”的任务里,没点击就是负样本;但站在“理解用户偏好相似性”的任务里,没点击的商品和点击过的商品之间也存在结构关系,完全可以换个预测目标再学一次。

7.3 落地时值得注意的工程事项

最后聊几个工程化部署HER时的经验:

  • 优先把HER封装在数据采集和存储层,不要侵入到网络结构代码里,这样算法可以灵活切换(从DDPG换到TD3只需改一个配置项)
  • 对每个episode,先完整存原始轨迹;训练时再统一做目标重标记,比存入缓冲区前做重标记更省内存
  • 目标空间的归一化非常重要。我在实验里踩过坑:位置目标直接用原始坐标,导致距离范围从0.01到10差异巨大,reward一直在饱和区或截断区震荡,学习极慢。归一化到[-1,1]后一切正常。
  • 如果环境有并行采集,注意线程安全的缓冲区写入。多进程采集加上无锁队列容易在重标记时出现数据错乱。我习惯在主进程统一做HER重标记,子进程只负责把原始transition发送回来。

8. 写在最后:一点真实体会

从第一次在论文里读到HER到亲手复现、调参、部署,我最大的感受是:“后见之明”看起来是一个心理学概念,实际上它是一类工程方法论。我们总是希望模型一次就能学到正确行为,但现实是大多数时候模型都在犯错。与其硬碰硬地跟稀疏奖励较劲,不如大方承认“这个回合失败了”,然后换个角度把它重新定义成成功经验。

如果你现在正被稀疏奖励折磨得头大,我建议你先别急着用复杂的好奇心探索机制或者手调稠密奖励函数,先动手加一个HER看看。我赌你会在半天之内看到训练曲线的明显变化。当然,HER不是银弹,它会引入新的超参数和调试成本,但当你亲眼看到那条从零开始爬升的成功率曲线时,一切折腾都是值得的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询