☰
事后经验回放(HER):让强化学习从失败中学习,破解稀疏奖励难题
2026/10/3 14:36:12 网站建设 项目流程

hindsight,中文通常翻译成“后见之明”。搞机器学习的人大概都有这种体会:每次事后复盘项目,失败的逻辑都清清楚楚,可当时就是看不穿。这个现象,在强化学习里被人工智能复刻得很彻底——一个 agent 在稀疏奖励任务里横冲直撞几万步,始终拿不到正反馈,不是因为它“笨”,而是因为“不知道该往哪走”这件事本身就会让学习信号直接归零。直到 OpenAI 团队提出 Hindsight Experience Replay(事后经验回放,以下简称 HER),大家才真正意识到:与其让 agent 硬扛失败,不如教它把每一次没达成的目标“改写”成一个已经达成的目标,从失败里榨取学习价值。

下面我会把 HER 的原理、代码、调参经验和实际踩坑一次性讲清楚。如果你在做机器人抓取、长期决策、推荐系统这类稀疏奖励场景,这篇应该能帮你省下不少试错时间,也顺带聊聊“事后视角”这个思维方式本身能迁移到哪些地方。

1. 先搞清楚:AI为什么学不会“后见之明”

1.1 稀疏奖励:一场永远不给过程分的考试

想象一下,一场考试不公布答案、也不给步骤分。你交卷之后系统只回一句话:对了还是错了。如果整张卷子没有一字不差命中标准答案,那不管写得多接近,都算 0 分。这就是典型的稀疏奖励问题。

真实场景里,机械臂抓取就是活生生的例子。机械臂的输出是几十维连续动作(关节力矩或者末端速度),每走一步只能拿到一个 0 奖励,直到它精准地把物体抓起来,才会收到一个 +1。中间这个过程,可能是几百上千步,每一步单独看都“无法区分好坏”。很多人在这种任务上第一次跑强化学习时会特别沮丧:策略网络的 loss 不降、reward 曲线纹丝不动,仿佛代码写错了,但检查一万遍就是没有 bug。这不是实现问题,是算法面对稀疏奖励时的天然困境——大量样本里没有梯度可用。

之所以难,是因为强化学习的更新逻辑本质是“奖励加权下的微分”。如果所有转移都带 0 奖励,那策略梯度里所有动作的概率更新项全都无效,相当于一个人在完全没有反馈的黑屋子里乱走,走一百步和走一步没有任何区别。这也是很多 RL 项目从仿真环境搬到真实场景时最痛苦的一点:仿真里可以给稠密奖励,真机上往往只有“成功/失败”两个信号,奖励一下子稀疏了几个数量级。

1.2 随机探索的致命局限:概率小到无法接受

碰到稀疏奖励,新手第一反应往往是“加大随机探索”。我最初做机械臂位姿估计任务时也是这么想的,但把状态空间大致一算就冷静了:哪怕只是机械臂末端在一个 1m 立方体空间里移动,目标区域占全空间不到万分之一,动作又是连续变量,每一步随机动作撞对目标的概率差不多是百万分之一量级。也就是说,期望需要探索一百万步才能踩到一次正反馈。真机跑一百步需要十几秒,这个探索预算谁扛得住?

更要命的是,就算运气好撞到了目标区域,只拿了一个 +1,这个样本也只覆盖了极窄的状态-动作组合。从这一条成功轨迹里,agent 能学到的信息远远不够支撑它泛化到其他起点。随机探索不是没用,而是面对高维连续动作空间时,它的效率低到像拿显微镜找蚊子,能找到,但你等不起。

1.3 “事后诸葛”的切入点:把失败改写成目标

HER 的视角非常反直觉:agent 不需要真的完成“原定目标”才能学到东西。它只要实际到达了某个状态,我们就可以把“原定目标”替换成“实际到达的状态”,然后重新计算奖励,把这条轨迹当作一次成功演示存进记忆。

举例:机械臂本来要抓绿色方块,结果歪打正着抓到了蓝色方块。这条轨迹在“抓绿块”任务里确实是失败,但如果你把任务临时改成“抓蓝块”,那么这条轨迹就是教科书级别的成功演示。agent 因此学会的是“抓到蓝色方块”这个子技能,等下次遇到“抓蓝色方块”的目标时,它不再是零经验的新手,而是有底子的老手。

有个关键前提要明确:HER 必须配合经验回放(replay buffer)来用,属于 off-policy 范畴。DDPG、DQN、SAC 这类会用历史样本反复学习的算法都合适;PPO 这类 on-policy 算法每次采样用完就丢,没有“事后重写历史”的载体,没法直接用 HER。这解释了为什么 OpenAI 原版实现是 DDPG+HER,而不是 PPO+HER,也是很多人在网上看各种复现时最容易忽略的一个前置条件。

2. HER 核心机制拆解:一条经验如何被“篡改”

2.1 重标注的数据流:从五元组到六元组

正常情况下,一条强化学习经验是 (s, a, r, s', done),HER 之后变成 (s, a, r', s', done, g'),其中 g' 是被替换后的新目标,r' 是根据新目标重新计算的奖励。这个 r' 不是随便填的,也不等于原来的 r,而是调用环境的 reward 函数,用 s' 对 g' 重新计算。

我最早实现时犯过一个低级错误:直接把原来的 r 复制给了重标注样本。结果就是训练曲线一路乱飞,agent 时而感觉“成功”时而感觉“失败”,连目标是谁都搞不清楚。后来才明白,HER 重标注的核心是“目标变了,奖励必须跟着做一次完整重算”。哪怕新目标和实际到达状态非常接近,也不该偷懒,因为环境和目标之间的关系可能不是简单的欧氏距离,可能带有阈值判定或多目标组合的复杂逻辑。

2.2 三种目标重标注策略对比

HER 里怎么挑新目标,直接决定数据质量。论文里提供了三种策略:

策略做法特点
final把 episode 结束时最终到达的状态作为新目标实现最简单,但一个 episode 只能构造一个额外目标,信息密度偏低
future从当前时间步之后的轨迹里随机挑一个状态作为新目标OpenAI 默认推荐,兼顾“目标可达性”和“数据丰富度”
episode从整个轨迹的任意时刻采样状态作为新目标数据量最大,但可能选取到过于遥远的目标导致学习噪音偏大

我做 FetchSlide 实验时对比过:final 能让成功率爬到 40% 左右,换成 future 后能到 70% 以上,episode 反而又掉回到 50% 上下。原因也好理解:future 策略选出的新目标,和当前动作执行后的实际状态之间保持着一种“轨迹因果一致性”——目标是未来某一步真真切切到达过的位置,而不是凭空捏造的远处点。这相当于在时间上做了一个软约束,让 agent 学习的不是天文数字级的目标映射,而是“一步一步走过去”的动作序列。

2.3 为什么能加速收敛:自动课程学习的视角

HER 最妙的地方在于,它无形中把训练分布从“困难任务主导”变成了“渐进任务主导”。因为在新生成的经验里,目标大多是“已经到达过的状态”,这些状态往往比原定目标更近、更简单。于是 agent 先学会一堆“伸手就能碰到”的子技能,再逐步逼近真正困难的目标。整个过程很像课程学习(curriculum learning),但不是手工设计课程,而是自动从轨迹里生成难度递进的样本。

也正是因为这一点,HER 对目标的定义方式有要求:目标必须可以从状态里显式恢复出来。比如 FetchReach 环境里,目标就是一个三维坐标点,和机械臂末端位置同构,所以实现起来非常自然。如果一个场景的目标是隐式的(比如“把桌子摆得漂亮”),或者奖励函数是黑盒不可逆推,HER 就没有用武之地。

2.4 使用 HER 的三个硬性前提

说几个我在实际项目中总结出的前提条件,不满足就别硬套:

  • 奖励函数必须可重新计算:拿到新目标 g' 和状态 s',环境要能立刻给出 r',否则重标注只是自欺欺人。
  • 状态里必须包含 achieved goal 信息:很多环境默认不给你“实际到达的目标”,只有完整观测。你需要自己从状态里提取出来,才能作为新目标。
  • 目标空间要和状态有语义关联:如果 goal 是离散编号、和底层状态毫无关联,HER 学习到的映射就是空中楼阁。

这三点每一条看着简单,但真到企业级项目里,往往因为第一步就不满足而劝退不少人。

3. 手把手实现 HER:环境选型、核心代码与参数配置

3.1 环境与算法选型建议

想最快上手,我推荐从 OpenAI Gym 的 Fetch 系列开始,尤其是 FetchReach-v1、FetchPush-v1、FetchSlide-v1。这几个环境天然提供 observation、achieved_goal、desired_goal 三个部分,等于官方已经把“从状态中提取目标”的脏活帮你干好了,你只需要专注写 HER 逻辑。

底层算法优先选 DDPG 或 SAC,因为它们在连续控制里成熟稳定,且都是标准的 off-policy 架构。OpenAI Baselines 原生实现是 DDPG+HER,如果你嫌 DDPG 调参烦,SAC+HER 在社区里也有大量成功案例。我自己通常用 SAC,因为它的 entropy 自适应机制能少调一个温度参数,训练前期更稳。

网络结构就一句话:把 obs 和 goal 拼起来作为输入。SAC 的 Q 网络输入维度等于 obs_dim + goal_dim 即可,policy 网络一般不接 goal,只用 obs 输出动作。但有些实现也把 goal 拼进 policy,效果差不多,主要是细节习惯问题。

3.2 HER 核心代码:别再抄错版本

HER 的实现核心其实很短,我把最关键的采样逻辑贴出来。

import numpy as np def her_sample(transitions, achieved_goals, k=4): """ transitions: list of (s, a, r, s_next, done, original_goal) achieved_goals: 每一步实际到达的 goal,长度与 transitions 一致 """ her_data = [] n = len(transitions) for i, (s, a, r, s_next, done, g) in enumerate(transitions): # 原始经验一定保留 her_data.append((s, a, r, s_next, done, g)) # 从未来时刻中采样 k 个状态作为候选新目标 if i + 1 < n: future_indices = np.random.choice( np.arange(i + 1, n), size=k, replace=True ) for idx in future_indices: g_prime = achieved_goals[idx] r_prime = reward_fn(s_next, g_prime, None) # 重算奖励 her_data.append( (s, a, r_prime, s_next, done, g_prime) ) return her_data

这段代码只需要在每轮 episode 结束后调用一次,把返回的 her_data 全部塞进 replay buffer 就行。有几个细节值得强调:

  • future_indices用的是np.arange(i+1, n),这是我踩过坑的地方。有人为了简单会用全局随机,结果把“未来时刻”换成了“任意时刻”,破坏了轨迹因果一致性,收敛速度明显变慢。
  • replace=True允许重复采样同一个目标,在轨迹较短时能稳定提供足量数据,不用刻意去重。
  • reward_fn必须是你环境的真实奖励函数,不要用自己拍脑袋写的距离函数凑合。尤其在 Push/Slide 这类带阈值判定的任务里,随手写的奖励和实际物理约束对不上,学习出来全是幻觉。

3.3 四个关键超参数怎么设

  • K 值:每条原始经验额外生成的重标注经验数量。OpenAI 论文用 k=4,后续很多社区复现都沿用这个值。K 太小,重标注数据不足;K 太大,buffer 里重标注样本占比过高,agent 会过于乐观地认为“所有动作都接近成功”,反而损害真实任务成功率。FetchPush 这类任务 K 从 2 到 8 我都试过,4 附近最稳。
  • HER 比例:通常指包含新经验后,每轮往 buffer 里塞多少重标注样本。Baselines 默认是 100%,也就是每条原始经验额外生成 k 条。如果内存吃紧,可以降到 50%,但曲线会稍微慢一点。
  • Buffer 大小:因为重标注让数据规模膨胀到原来的 k+1 倍,buffer 建议比原生算法大 2~4 倍,给 agent 足够的“历史错题”复习空间。
  • 目标拼接维度:如果 obs 是 25 维、goal 是 3 维,critic 输入就是 28 维。有些实现把这个拼接放在网络内部,有些放在环境封装层,效果没差别,只是别漏拼就行。

3.4 怎么判断 HER 真的在起作用

训练时要看的不是 reward 曲线,而是任务成功率(success rate)。HER 生效的几个信号:

  • 训练早期 buffer 里“已达成目标”比例显著上升,这代表重标注机制确实在往 buffer 里灌优质数据。
  • 成功率曲线不再是贴地平台期,而是先慢后快地往上爬,形状像倒置的指数衰减。
  • 在稀疏奖励下,reward 均值的涨落会比较大,但 success rate 的趋势更稳定,也更可信。

如果跑了十几个 episode,success rate 还是铆死在 0,先别怀疑代码,先检查重标注后的奖励是不是真的按新目标重算了。这个问题排除了再考虑调 K 和网络宽度。

4. 实操中常见的问题与排查速查表

4.1 重标注奖励算错:症状是损失爆炸

我实际遇到最多的坑就是重标注奖励没重算。有些复现代码为了省事,直接从原始 transition 里复制 r,导致同样 (s, a) 在 buffer 里同时有“正奖励”和“负奖励”,Q 网络几乎无法收敛,loss 在几个 epoch 内直接爆到几百。排查方法很粗暴:打印 buffer 里同一条 s 对应的不同奖励分布,如果同一条样本分别出现过 0 和 +1,那基本就是奖励重算环节写错了。

4.2 future 策略下目标采样范围收窄

当 episode 长度很短、或者轨迹后半段状态高度重复时,future 策略能采到的新目标非常有限,数据多样性下降。我遇到过机械臂轨迹 30 步里最后 20 步末端停在同一个位置反复震荡的情况,future 采样的目标几乎全一样,等于重标注退化成单点重复。解法是混着用 final 策略:把 future 和 final 按 8:2 混合进重标注流程,既能维持因果一致性,又不会因为目标单一而丢失信息。

4.3 HER 和 Reward Shaping 怎么取舍

很多项目里奖励塑形(reward shaping)和 HER 不是二选一,而是互补。我的经验是:能用 HER 就不用手写复杂奖励,因为手写 reward 极容易诱导 agent 钻空子。比如我曾在机械臂任务里为了加速训练加了“越靠近目标奖励越高”的稠密距离项,结果 agent 学会了一个“围着目标画圈”的局部最优,真实任务成功率反而下降。

更合理的配方是:HER 作为主要学习信号来源,外加一个极稀疏的成功奖励(只有成功才 +1),尽可能不引入额外稠密 reward。如果必须加距离项,一定要配合专家轨迹或仿真验证,防止 agent 在模拟器里学会“作弊动作”。

4.4 常见问题速查表

症状可能原因处理建议
训练早期 loss 爆炸重标注奖励未按新目标重算检查 reward_fn 是否被正确调用
success rate 一直为 0目标采样范围太小或 K 过小提高 K,混合 final + future 策略
训练后期过拟合buffer 过大且重标注比例过高调低 HER 比例,缩小 buffer
真机表现与仿真差异大坐标系或单位不一致先在仿真里验证 goal 编码,再做坐标变换
与 PPO 集成失败PPO 是 on-policy,无 replay buffer换 DDPG/SAC,或绕开 HER

这张表是我在多个任务里反复踩出来的,可以直接当排查手册用。

5. 更进一步:hindsight 思维还能迁移到哪里

5.1 从算法到项目复盘:同样的“事后改写”套路

有意思的是,HER 背后的思想放到工程管理上同样好用。很多项目复盘之所以流于形式,是因为大家死死盯着“原定目标没达成”这件事不放。如果换一种问法:这次实际走出了哪条路、学到了哪些能力、这套能力能支撑什么新的目标——就完全对应着 HER 里“用实际到达状态替换原目标”的动作。

举个我自己的例子。有一回我设计推荐系统离线实验,原定指标是点击率提升 2%,最后只提升了 0.8%,看似失败。但我把“目标”替换成“验证了冷启动冷门物品的曝光分配逻辑”,这个实际达成的方向反而成了下一步模型的骨架。事后看,这 0.8% 并不是失败,而是把目标定错了。HER 教会我的就是:目标本身不是神圣不可改的,经验信号比目标重要得多。

5.2 可以继续尝试的扩展方向

HER 的变体很多,比如把重标注目标输入到逆动力学模型、用 HER 生成对抗样本、在模仿学习里用“从失败轨迹看成功子目标”做数据增强。我个人接下来想试的方向是把它和扩散决策模型结合,看看能不能在稀疏奖励的长期规划任务上跑出更漂亮的效果。如果你已经在做类似尝试,欢迎来讨论,这类领域还很新,需要更多一手经验。

5.3 最后分享一点个人体验

从我第一次在 FetchReach 上跑通 HER,到现在在更复杂的仿真推箱任务上调参,最大的感受是:HER 不是万能药,但它提供了一种特别好的“失败观”——失败不是没有信息,只是信息用错了形式。写这篇内容也是想把这个思路分享给正在酝酿稀疏奖励项目的人。先跑 Fetch 环境,把重标注、奖励重算、K 值这几个点全打通,再上真机,不然在真机上发现问题,来回调试的成本会让你怀疑人生。

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

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

立即咨询