当“后见之明”成为一种算法:用Hindsight Experience Replay破解稀疏奖励难题
做强化学习的朋友一定对“稀疏奖励”不陌生。环境里转了半天,reward永远是0,模型根本不知道自己在干嘛,训练曲线死气沉沉,别说收敛,连一丁点提升的迹象都看不到。我最早被这个问题卡住是在一个机械臂抓取任务上,目标位置的奖励阈值设得比较高,10万步跑完,成功率照样是0,当时整个人都快麻了。
后来换了个思路,把“事后诸葛”这件人类特别擅长的事教给算法——既然这次没到达目标,那不如换个角度想:如果我把实际到达的位置当作目标,刚刚这整条轨迹是不是就是一次成功的示范?这就是本篇要聊的Hindsight Experience Replay(HER,后见经验回放)。这个思路最早出自OpenAI的论文Hindsight Experience Replay,它解决的不是“怎么让策略更强”,而是“怎么在几乎拿不到奖励信号的环境里,让学习依然能发生”。
这篇文章我会从原理到代码、再到调参踩坑,把HER怎么用、为什么有效、哪些场景千万别硬用,一次说清楚。适合正在做机器人控制、导航、推荐策略这类稀疏奖励任务的读者,也适合想拓宽强化学习视野的朋友。
1. 项目概述与核心思路拆解
1.1 HER到底在做什么
先给一个最直观的理解。假设一个机械臂要抓取一个物体,物体位置是目标g,策略输出动作a,环境反馈状态s和奖励r。如果机械臂没有碰到物体,r就是0。问题来了:在几百步的轨迹里,绝大多数transition的reward都是0,价值网络学不到梯度方向,策略自然出不来。
HER的核心视角特别简单:回顾这段episode,虽然没拿到目标g的奖励,但机械臂在这一路确实碰到过很多其他位置s_t。如果我把“碰到s_t”这件事当作目标,那这条轨迹其实通向了一个“成功状态”。换句话说,每次失败的经验里其实藏着大量成功的数据,只是它们对应的目标被写死了,没有被挖掘出来。
所以HER的做法是:在episode结束后,除了用原始目标g存储经验,还额外生成若干条“替代目标”的经验。最常用的替代目标就是这条轨迹上随机选取的某个实际状态s_t。这样做的结果,是把一堆稀疏奖励为0的失败轨迹,转换成了奖励为1的成功轨迹,训练信号一下子密了起来。
1.2 为什么传统方法搞不定稀疏奖励
很多人在接触HER之前,习惯性地用Reward Shaping给环境手工设计密集奖励,比如机械臂离目标越近奖励越大。这确实能解决一部分问题,但坑也很深:第一,奖励函数的设计非常依赖任务先验,换个环境就要重新调;第二,不合规的奖励函数经常导致策略学到投机取巧的捷径,比如绕开障碍反而拿到更高奖励;第三,在一些真实场景里,根本没有办法定义“越近越好”这类度量。
HER的聪明之处在于,它不需要任何额外的手工奖励设计,完全从数据本身挖掘学习信号。它不修改MDP,只是重新标记(relabel)已有经验的目标,从而让replay buffer里产生更多正向奖励信号。这一点让它和Reward Shaping、Curiosity-driven等方法在本质上区分开来:它做的是“数据增强”,不是“激励重塑”。
1.3 HER适合解决什么问题
从我的实践来看,HER最适合的是具备以下三个特征的任务:
- 目标明确且可度量:必须能够明确判断“这个状态算不算达成了目标”,而且目标最好能用状态的一部分或者一个向量表示。
- 动作-状态空间连续:因为HER的relabel机制需要状态和目标之间存在合理映射,连续控制任务里这种映射天然存在。
- 单目标即可分解的多目标训练:HER本质上是在训练一个Universal Value Function,能够对任意目标输出Q值或策略,所以多目标连续任务尤其顺手。
比如机械臂抓取、机器人推动物体、迷宫导航、连续控制中的到达类任务,都是典型场景。而不适合的场景包括:离散动作空间的大型棋类游戏、稀疏奖励极其极端且状态空间高度离散的任务,这些场景里relabel出来的目标可能毫无意义,反而污染训练数据。
2. 核心原理与目标重标记机制详解
2.1 从策略梯度到战后重放的数学直觉
先回顾一下Off-Policy算法中最重要的概念:经验回放。DDPG、TD3、SAC这些算法都依赖一个replay buffer存储(s, a, r, s', done)四元组,训练时随机采样小批量更新Q函数和策略。
但在稀疏奖励环境下,replay buffer里大部分样本都是r=0,Q函数的梯度信号主要来自极少数的成功样本。就算成功样本存在,数量也太少,没法支撑稳定更新。HER做的事情是在存储阶段就把“伪成功”数据造出来:同一段轨迹,除了存原始目标版本,再造K份新目标版本的数据。对于每一份新数据,把原目标g替换成轨迹上的某个状态s_t,奖励重新计算为r' = reward_func(s_{t+1}, g', done)。这样replay buffer里就有了大量r'=1的样本,Q函数就能从“怎样才能到达这个目标”中学到东西。
这里要特别注意,HER不是改变策略的目标分布,而是在训练分布上做文章。原始目标g仍然会被保留一定比例,保证策略不会遗忘真实任务。通常保留比例和新增比例之间有个权衡,一般设为原始经验:新增经验 ≈ 1:4或者1:8。
2.2 目标重标记的四种常见策略
OpenAI论文里提出了四种替代目标的选择方式,我逐一说说它们的特点:
- final:只用轨迹最后一个状态作为新目标。最简单粗暴,但信息量不高,因为最后一个状态往往离目标很远(比如机械臂把物体推飞了),生成的数据可能不是“成功导向”的。
- future:从当前时刻t之后的状态里随机选一个作为新目标。论文推荐默认方案,也是绝大多数实现里的默认值。因为未来状态和当前状态有时间关联,得到的“成功”经验更可靠。
- episode:从整个episode里随机选一个状态作为目标。随机性更大,探索性更强,但更容易产生“假成功”。
- random:从所有已知状态里随机选一个目标。一般不推荐单独用,通常在特定状态表征场景下才考虑。
论文实验表明,future策略在多数连续控制任务上表现最好。我自己的经验也是这样,future采样的比例设到0.8左右(80%的新目标来自future),剩余20%用episode随机,这样既保证了成功率数据的质量,又留了一部分探索性。
2.3 为什么HER能提升样本效率
从信息论角度看,传统方法在稀疏奖励下,每个成功样本携带的信息量巨大但极其稀缺。HER通过对失败轨迹的重标记,把原本“失败的轨迹”转化为“另一种目标下的成功轨迹”,等价于在不增加环境交互次数的情况下,把正样本的比例大幅提升。
这意味着,在同样的10万步采样预算下,HER能让Q函数更新时遇到的正样本数量提高一个数量级。Q函数的收敛速度加快,策略自然就能更快找到有效区域。这也是为什么很多复现实验里,HER+PPO或者HER+DDPG的样本效率和最终成功率都远高于纯PPO或DDPG的原因。严格来说HER是经验处理技巧,它和任何Off-Policy算法都能配合,不需要修改算法主体结构。
3. 环境配置与代码实现全流程
3.1 实验环境准备
我推荐用Python 3.8以上的环境,深度学习框架选PyTorch即可。为了快速上手,可以先在OpenAI Gym的FetchReach-v1和FetchPush-v1这类环境上做实验,它们本身就是HER论文里的标准测试环境。不过这类环境已经合并到gymnasium-robotics里,需要单独安装。
pip install gymnasium-robotics pip install torch tensorboard如果不想折腾仿真环境,也可以自定义一个简单的1D到达任务来验证HER机制。我就常用一个极简环境:智能体从0点出发,目标是到达位置g(比如5.0),到达位置在0.1范围内即算成功,奖励为0/1稀疏型。任务虽小,但足以看出HER和普通DDPG的差距。
3.2 核心代码模块拆解
先看环境接口部分,HER通常要求环境能额外返回achieved_goal。以定制1D环境为例:
import numpy as np import gymnasium as gym from gymnasium import spaces class ReachGoalEnv(gym.Env): def __init__(self, goal=5.0): super().__init__() self.action_space = spaces.Box(low=-0.5, high=0.5, shape=(1,), dtype=np.float32) self.observation_space = spaces.Dict({ 'observation': spaces.Box(low=-10, high=10, shape=(1,), dtype=np.float32), 'achieved_goal': spaces.Box(low=-10, high=10, shape=(1,), dtype=np.float32), 'desired_goal': spaces.Box(low=-10, high=10, shape=(1,), dtype=np.float32), }) self.goal = goal self.state = None def reset(self, seed=None, options=None): self.state = np.zeros(1, dtype=np.float32) return self._get_obs(), {} def step(self, action): self.state = np.clip(self.state + action, -10, 10) achieved = self.state.copy() success = float(np.abs(achieved[0] - self.goal) < 0.1) reward = 0.0 if success == 0 else 1.0 terminated = bool(success) truncated = False return self._get_obs(), reward, terminated, truncated, {} def _get_obs(self): return { 'observation': self.state.copy(), 'achieved_goal': self.state.copy(), 'desired_goal': np.array([self.goal], dtype=np.float32), } def compute_reward(self, achieved_goal, desired_goal, info): dist = np.abs(achieved_goal - desired_goal) return (dist < 0.1).astype(np.float32) - 1.0注意这里compute_reward返回的是0/1穿越阈值的形式,实际上为了数值稳定,很多时候我们会返回-0.0或-1.0的形式,具体看算法实现要求。OpenAI Baselines的HER实现中,compute_reward通常返回-1和0的稀疏形式,目的是配合强化学习中的生存奖励逻辑,训练时也有一定作用。
3.3 HER回放逻辑的实现要点
这一段是HER的核心,即如何把新目标注入replay buffer。实现上我通常直接在存储transition时做判断:每次episode结束后,遍历每一步t,存储原始数据,同时以一定概率生成新目标并存储新数据。
简化示意伪代码如下:
def store_episode_with_her(episode, replay_buffer, her_ratio=0.8): # episode内每个transition的格式: obs, action, reward, obs_next, done obs_list, act_list, rew_list, obs_next_list, done_list = episode horizon = len(obs_list) for t in range(horizon): # 原始经验 replay_buffer.add(obs_list[t], act_list[t], rew_list[t], obs_next_list[t], done_list[t], goal=obs_list[t]['desired_goal']) # HER重标记 if np.random.random() < her_ratio: # 从t之后的future状态里选替代目标 future_t = np.random.randint(t, horizon) new_goal = obs_list[future_t]['achieved_goal'].copy() new_reward = compute_reward(obs_next_list[t]['achieved_goal'], new_goal, {}) replay_buffer.add(obs_list[t], act_list[t], new_reward, obs_next_list[t], done_list[t], goal=new_goal)这里的关键点:
- 新目标只替换desired_goal,其他字段保持不变;
- 新奖励必须用新的目标重新计算,不能沿用原奖励;
- done标志一般不需要修改,因为即使新目标是可达的,轨迹是否终止仍然取决于原始环境逻辑,除非你希望重新定义终止条件。
3.4 完整的训练脚本框架
接下来把DDPG+HER组合起来。DDPG是确定性策略梯度算法,配合HER比较经典。核心训练循环大致如下:
# 初始化环境、回放缓冲区、Actor、Critic、目标网络等 env = ReachGoalEnv() buffer = HerReplayBuffer(capacity=100000) for episode in range(1000): obs, _ = env.reset() episode_buffer = [] for t in range(max_steps): action = actor.select_action(obs) obs_next, reward, terminated, truncated, _ = env.step(action) episode_buffer.append((obs, action, reward, obs_next, terminated)) obs = obs_next if terminated or truncated: break buffer.store_episode(episode_buffer) if buffer.size() > batch_size: for _ in range(updates_per_step): batch = buffer.sample(batch_size) # 更新Critic和Actor # 更新目标网络注意updates_per_step一般设为40或者100,因为HER生成的数据量很大,策略更新次数相应也要提高。在FetchPush这类任务上,我用类似的流程配合TD3,差不多80万步就能看到成功率明显上升,而纯DDPG基本在200万步还是一无所获。
4. 常见问题与调试经验实录
4.1 为什么用了HER还是练不出来
这个问题我见过很多次,基本可以分成三类原因:
第一类,目标表征不合理。HER要求目标必须能用状态向量的一部分表达。如果你的环境里目标是一个离散ID,或者是一个高维复杂结构,relabel后的“成功”定义就会失真。解决办法是把目标重新编码成连续向量,比如用传感器读数、物体坐标等。
第二类,奖励计算逻辑和HER不一致。很多人改环境时只改了state,忘了同步修改compute_reward函数。比如原始奖励是距离的负数,但compute_reward里却拿新目标计算稀疏0/1奖励,导致奖励体系前后矛盾。调试时先把奖励函数单独拿出来测一遍,确定新旧目标下都能正确返回。
第三类,HER的比例和频率设置不当。如果her_ratio设得太低(比如0.2),正样本仍然不足;设得太高(比如0.99)又会丢失原始目标的经验,导致策略过分偏向“任意目标都能到达”,反而忘了真实目标。我的经验是0.8左右比较稳。
4.2 HER和Off-Policy算法的兼容性细节
HER本身和On-Policy算法(比如PPO)也能结合,但效果没那么好。原因在于On-Policy算法每次更新用的都是当前策略采样的数据,不能充分利用replay buffer中重标记的历史数据。虽然可以通过引入experience replay让PPO变成“准Off-Policy”,但这样会破坏PPO的重要性采样比约束,稳定性下降。
所以我的建议是:优先选择DDPG、TD3、SAC这类Off-Policy算法来搭配HER。SAC本身是最大熵框架,抗探索性更好,和HER配合往往比DDPG更稳。考虑到TD3对超参数不敏感,如果时间紧张,直接上TD3+HER组合。
4.3 训练过程中的排查技巧
用TensorBoard盯几个关键指标:
- replay buffer中成功率样本占比:如果始终低于1%,说明HER的改造没有正确生效或者环境设计有问题。
- Q值的分布:训练初期Q值应该逐步上升,如果Q值一直不增长且reward都是0,大概率是HER没有正确存储新数据。
- 策略的“假装成功”行为:HER可能会让策略学到“快速移动到某个任意位置”这种捷径,因为relabel后很多目标都很容易达成。这时候可以在评估阶段只用原始目标做测试,不要用relabel后的目标评估。
另外建议训练过程中定期保存模型,并持续输出一段时间内成功率的滑动平均。防止训练到一半过拟合到relabel目标上,导致真实目标成功率不升反降。
4.4 和其他稀疏奖励方案的对比实操心得
除了HER,我还试过Curiosity-driven、状态空间覆盖最大化等方法。对比下来,HER的最核心优势是不需要额外模型。Curiosity方法要额外训练一个前向动力学模型来算内在奖励,计算量大,而且内在奖励和稀疏任务之间经常存在尺度不匹配。状态覆盖方法又容易让策略变成“观光客”,到处乱跑但不完成任务。
HER的短板也很明显:如果环境状态非常复杂,比如多模态传感器输入,目标重标记的空间会变得非常庞大,反而增加了Q函数拟合难度。这时候可以考虑给HER加一个前置的表示学习模块,先把状态压缩到低维空间,再在低维空间里进行目标重标记。这个思路其实就是把HER和表示学习结合,后续有不少论文在探索,我也在自己的项目里试过,能明显改善效果。
5. 项目扩展与进阶应用思考
5.1 从单一任务到多任务目标迁移
HER天然支持多任务:因为每个transition都带有一个desired_goal向量,训练出来的策略其实是一个Universal Policy,输入状态和目标,输出动作。这意味着训练完成后,你可以直接喂入新的目标g',让策略尝试完成没见过的任务。这种泛化能力在机器人领域尤其珍贵,因为真实场景里目标位置千变万化,不可能每个都重新训练一次。
我在机械臂抓取实验中试过这个思路:先用HER训练一个目标在区域A的策略,训练完成后把目标换成区域B、C、D,成功率虽然有所下降,但仍然远高于从零训练。这说明HER策略学到了“到达目标”这个通用技能,而不是死记硬背某个坐标点。
5.2 结合课程学习进一步提升上限
另一个很自然的扩展是课程学习(Curriculum Learning)。HER虽然能自动从失败中学习,但在目标特别远、状态空间特别大的任务里,直接使用HER可能还是收敛太慢。这时候可以把目标难度分级,先让智能体学习到达近距离目标,然后逐渐增加目标距离。HER在每一级别的课程里都能稳定提供正样本来,课程切换也会更平滑。
我在一个导航任务里试过这种组合:难度分三级,第一级目标距离2米,第二级5米,第三级10米,每个级别都使用HER。最后的效果比直接训练10米目标节省了大概40%的样本量,而且稳定性更高。如果有时间,强烈建议尝试课程+HER的组合。
5.3 落地部署时的注意事项
把HER训练出的策略部署到真实机器人之前,一定要先做仿真到现实的迁移检查。因为HER高度依赖目标重标记的正确性,而仿真环境和真实环境之间难免存在动力学误差。建议先在仿真中加入随机扰动(Domain Randomization),再在真实环境中用小范围目标测试策略的泛化能力。
另外,实际部署时还需要为策略加一个安全防护层,比如当策略连续输出异常动作时切换到人工控制。这不是HER本身的问题,但任何从强化学习训练得到的策略,在真实场景落地时都需要过这一关。
6. 实操总结与个人体会
做完整套HER项目之后,我个人最大的体会是:在强化学习里,数据比算法更容易被忽略,而HER恰恰是一个让“废物数据”重新发挥价值的方法。它不需要你设计精妙的奖励函数,不需要你调厚重的网络结构,只需要你对经验存储和回放机制做一个小小的改动,就能让原本学不动的任务产生质的飞跃。
我踩过最大的坑就是一开始不理解为什么HER在FetchReach里效果这么好,但换到另一个环境就完全失效。后来才反应过来,是因为那个环境的目标本身不在状态空间里,导致relabel出来的目标是随机噪声。所以,始终记住HER的适用前提:目标必须可以从状态中提取,并且这个提取是可靠的。
最后再分享一个小技巧:如果你只是想让某个稀疏奖励任务快速出效果,先把目标目标和状态的定义打印出来,亲眼看看relabel后的经验到底是什么样的。很多时候问题就出在这一步——你以为是环境问题,其实是你给算法喂的数据根子上就歪了。把这一步排查干净,HER基本就能发挥出七八成的功力。