☰
HER算法实战:用“事后经验回放”破解稀疏奖励困境
2026/10/1 16:43:04 网站建设 项目流程

如果你接触过强化学习,对“稀疏奖励”这四个字一定不陌生。hindsight这个词,日常语境下是“事后诸葛亮”,可在强化学习圈子里,它往往指代 Hindsight Experience Replay(事后经验回放,简称 HER)——一个专门解决“奖励稀疏到根本学不动”这类问题的经典算法。没有 HER 之前,我在机械臂抓取任务里跑随机策略,跑了几百万步成功率还是 0;把 HER 接进回放流程之后,同样一套环境,成功率能推到 80% 以上。这篇内容不打算从论文抄概念,而是从“为什么要换目标”“代码怎么写”“参数怎么调”“踩了哪些坑”几个角度,把这个算法的来龙去脉讲明白。适合正在做机器人控制、操作类任务,或者刚被稀疏奖励折腾到怀疑人生的同学阅读。

1. 为什么需要“事后诸葛亮”:稀疏奖励到底难在哪

1.1 先用一个生活类比理解 HER 的直觉

打篮球的时候,我想投一个三分球,结果手一滑,球传到了队友手里。这时候我不会傻到把这个动作定性为“投三分失败”,而是会告诉自己:刚才其实是想传球,而且传得很准。于是这个动作在“传球”这个目标下变成了一次成功经验,我就能从中总结出“怎么传球更稳”的技巧。

HER 干的差不多就是这件事。强化学习里,一个 episode 结束时如果没达到原始目标,绝大部分算法会认为整条轨迹毫无价值:奖励全是 0,梯度信号也是 0,策略根本动不了。HER 换了个视角,它把这条轨迹实际到达的那个状态,重新定义成“事后目标”(hindsight goal),再用这个新目标去重新计算奖励。这样一来,原本看起来毫无信息量的探索轨迹,就变成了一条“成功到达某状态”的经验,可以拿去训练了。

这个直觉说起来简单,但背后的假设很关键:任务必须是可以定义“目标状态”的,也就是 goal-conditioned 的强化学习。如果任务的奖励只依赖一个固定终点,没有可替换的目标变量,HER 就无从下手。这也是为什么它常用于机械臂抓取、推箱子、导航这类任务,而不是通用的游戏 AI。

1.2 稀疏奖励的“冷启动”问题

在稀疏奖励环境里,奖励函数通常长这样:只有 agent 到达目标 g 时才给 +1,其他时刻全是 0。问题就出在这个“只有”上。

假设状态空间很大,随机策略到达目标的概率是 p,那么为了看到一次正奖励,平均需要 1/p 次尝试。如果任务稍有难度,p 可能小到 10^-6 甚至更低。更麻烦的是,策略梯度算法在没有正奖励信号的时候,更新方向和随机噪声没什么区别,相当于在原地随机游走。你看着 reward 曲线一动不动,实际上算法可能连“向目标靠近一步”这个最基本的行为都没学会。

我当时用 DDPG 跑一个简单的二指机械臂推球任务,初始阶段完全靠动作噪声在乱撞。几万步下来,成功率始终是 0,因为智能体从头到尾没拿到过一个正奖励,价值函数对任何状态都输出 0,策略自然也不知道哪个动作更好。这不是参数没调好的问题,而是奖励信号本身缺失导致的“冷启动”困境。

1.3 奖励塑形为什么治标不治本

面对稀疏奖励,很多人第一反应是加奖励塑形。比如“离目标越近奖励越高”“分阶段给中间奖励”。老实说,这是最直接的办法,我早期也这么干过,但用久了明显感觉到几个硬伤。

第一,奖励塑形依赖人工设计,每个新任务都要重新写一套,而且容易出错。距离函数选欧氏距离还是曼哈顿距离,要不要加速度惩罚,这些细节都会影响最终策略。第二,塑形后的奖励很容易被钻空子。智能体有时候并不会真正学会“达成目标”,而是学会“停留在离目标最近的位置”,因为那里奖励最高。这就是所谓的奖励黑客,表面上 reward 很好看,实际任务成功率一塌糊涂。第三,它把“稀疏”问题变成了“怎么设计密集奖励”的问题,并没有触及核心。

HER 的思路则是不引入任何人工先验,完全依靠“目标状态是否可达”这个客观信息。智能体到达不了原定目标,但总能到达某个状态,那就把这个状态当作目标来学。从数据中自动生成学习信号,这是它比塑形优雅的地方。

2. 核心细节解析:HER 到底改变了什么

2.1 事后目标替换:把失败样本“洗白”成成功样本

先看 HER 的操作层面。普通经验回放里,一条经验是一个五元组 (s_t, a_t, r_t, s_{t+1}),其中 r_t 是智能体在原始目标 g 下拿到的奖励。HER 在把这条经验存入回放池之前,会额外做一步:从这条轨迹里挑一个状态 s_after,把它当作事后目标 g',然后重新计算奖励 r_t' = R(s_t, a_t, g')。

关键在于,如果 g' 取的是未来某个时刻的状态,那么 s_{t+1} 很有可能和 g' 非常接近。对于“到达目标则 +1”的奖励函数来说,r_t' 大概率是正奖励。也就是说,一条在原始目标下毫无价值的失败轨迹,在事后目标下变成了一条“成功到达某状态”的有效样本。

你可以把回放池想象成一个训练集,HER 相当于在训练集里注入了大量“这个动作会导致那个状态”的正样本。价值函数会从这些样本里学到“接近目标状态的动作是有价值的”,策略就慢慢不再是随机游走。注意,这里的“洗白”不是说算法被骗了,而是它确实学到了一个真实规律:某些状态序列之间存在因果联系,而这些联系在原始目标下被掩盖了。

2.2 四种目标构造策略:final、episode、future、random

具体从哪挑出 s_after 作为事后目标,论文和后续实现里常见四种策略,这里一个个说。

  • final:直接用 episode 的最终状态作为事后目标。最简单,只有一个候选,适合 task 比较短、最终状态足够有代表性的场景。
  • episode:从整条轨迹的所有状态里随机抽一个。覆盖范围广,但可能抽到太早的状态,导致目标分布和“当前序列确实朝向它发展”这一点脱节。
  • future:从当前时刻之后的状态里随机抽一个。这个策略保证“轨迹确实是朝那个目标前进的”,因果关系最强,实践中最常用。
  • random:从经验回放里随便挑一个状态当目标。完全随机,噪声大,一般不会单独用。

我自己的习惯是 future 占大头,比如 80% 的未来目标 + 20% 的 final 目标。为什么不全用 future?因为 future 目标都来自当前轨迹后半段,目标分布会偏向“容易到达的状态”,长期训练可能会让策略忽略较难的目标。掺一点 final 可以保留一些不同难度的样本。

2.3 为什么 HER 必须配 off-policy 算法

这里有一个容易被新手忽略的关键点:HER 不是修改采样方式,而是在回放时改写了“目标变量”和“奖励变量”。改写之后,这条经验对应的策略分布已经变了。想要在这种数据上更新策略,算法必须能脱离当前策略、直接利用历史数据学习。这正好是 off-policy 算法的看家本领,所以 DQN、DDPG、SAC、TD3 都能和 HER 配合得很好。

反过来说,PPO 这类 on-policy 算法用 HER 就很别扭。PPO 更新时要求数据来自当前策略,并且依赖轨迹的连续性计算 advantage。HER 一改写目标,同一段轨迹在不同目标下的奖励会不同,GAE 的优势估计就会乱掉。实际效果往往是训练过程极不稳定,甚至比朴素策略梯度还差。所以别看到“经验回放”四个字就把 HER 塞给任意算法,先搞清楚你用的是 on-policy 还是 off-policy。

3. 实操过程:从环境到训练一条龙

3.1 环境选型:用 Fetch 系列可以省一半力气

想亲手复现 HER,最省事的做法是用 OpenAI Fetch 系列环境,比如 FetchReach、FetchPush、FetchPickAndPlace。这些环境天生就是 goal-conditioned,观察空间是字典类型,包含三个字段:observation(机器人自身的状态)、achieved_goal(当前实际达到的目标状态)、desired_goal(期望目标)。环境内部还提供了 compute_reward 函数,可以在任何状态-目标组合下计算奖励。

如果你用的是自己的环境,而不是现成的 Fetch,那第一步就是把它包装成同样的结构。核心就一点:环境必须能把“当前状态”拆成 observation 和 achieved_goal 两部分,还要能根据任意 desired_goal 计算奖励。如果环境做不到这两条,HER 根本没地方接。我当时为了一套工业分拣环境,花了一整天改观测定义,把像素坐标、夹爪位姿、目标点分开输出,才让 HER 顺利跑起来。

3.2 用 Stable-Baselines3 搭一个最小可跑示例

实际写代码时,我推荐直接用 Stable-Baselines3(SB3)里内置的 HerReplayBuffer,少造轮子。下面这个例子是 SAC + HER 跑 FetchReach 的配置。

from stable_baselines3 import SAC from stable_baselines3.her import HerReplayBuffer from stable_baselines3.common.env_util import make_vec_env import gymnasium as gym env = make_vec_env("FetchReach-v2", n_envs=1) model = SAC( "MultiInputPolicy", env, replay_buffer_class=HerReplayBuffer, replay_buffer_kwargs=dict( n_sampled_goal=4, goal_selection_strategy="future", online_sampling=True, max_episode_steps=200, ), learning_starts=1000, batch_size=256, tau=0.05, gamma=0.95, verbose=1, ) model.learn(total_timesteps=200_000) model.save("her_fetchreach")

跑完以后自己写一个成功率评估函数更靠谱。因为 goal-conditioned 环境下只看 reward 很容易被误导,真正关心的是“多个随机目标下,智能体能成功几次”。

import numpy as np def evaluate_success(model, vec_env, n_episodes=20): success_count = 0 for _ in range(n_episodes): obs, _ = vec_env.reset() done = False while not done: action, _ = model.predict(obs, deterministic=True) obs, reward, terminated, truncated, info = vec_env.step(action) done = terminated or truncated if terminated and "is_success" in info[0]: success_count += int(info[0]["is_success"]) break return success_count / n_episodes print(evaluate_success(model, env, 50))

这套组合在 FetchReach 上一般十几万步就能看到明显效果,成功率能到 80% 以上。FetchPush 难一些,需要百万步量级。FetchPickAndPlace 更吃时间,两百万步打底。

3.3 参数到底是拍脑袋还是算出来的

HER 有几个参数直接决定训练质量和资源消耗,别拿到手就默认值跑到底。

  • n_sampled_goal:每条原始经验额外生成多少个事后目标。这个值会直接放大回放池的容量,每存一条经验实际占用 1 + n_sampled_goal 条样本的空间。经验上取 4 到 8 比较好,太大会导致内存溢出,太小则事后目标样本不够。
  • goal_selection_strategy:目标选择策略,我建议从 future 起步,训练稳定后再考虑混合。
  • max_episode_steps:SB3 的 HerReplayBuffer 需要你明确传入 episode 最大步数,没有这个值它没法从轨迹中定位“未来状态”。忘记传会直接报错或者采不到有效目标。
  • gamma:如果 episode 不算长,0.95 就够了。设成 0.99 会让值函数收敛变慢,对稀疏奖励场景其实没必要。
  • batch_size:我习惯用 256 以上,因为 HER 数据多样性高,小 batch 更新容易抖动。

还有一个经常被忽略的点:探索噪声。SAC 本身就带一定随机性,但如果你用 DDPG 配 HER,一定要给动作加噪声,否则前期的成功样本太少,HER 再厉害也救不回来。

3.4 训练曲线怎么看

在 HER 场景下,我建议把成功率曲线当作主指标,reward 曲线只做参考。原因很简单,HER 的回放池里掺杂了大量“改写目标后的奖励”,平均 reward 并不能真实反映策略在原始目标上的表现。

我见过很多次这种情况:训练刚起步时 reward 不涨反降,因为事后目标样本刚开始大量填充回放池,相当于人为制造了很多“成功”。但如果这时候去看 rollout 成功率,会发现它在稳步上升。等到策略逐渐学会向目标靠近,成功率会突然跳升,从 10% 到 80% 往往就是几万步之间的事。这种“跳变”是稀疏奖励任务里很典型的特征。

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

4.1 回放池内存爆炸:n_sampled_goal 的账不能不算

HER 最容易被低估的副作用就是内存消耗。n_sampled_goal 设为 4,意味着存 10 万条原始经验,实际要存 50 万条样本。如果每条状态是 281 维的向量,float32 存储,一个样本就要 1124 字节,50 万条就是 562MB。如果任务复杂、状态维度更高,破 GB 是分分钟的事。

我踩过一次坑,在 FetchPickAndPlace 上想靠增大 buffer 提升性能,直接把内存吃满了。后来换成减少 n_sampled_goal、适当缩短 max_episode_steps,并用更紧凑的向量化存储,才算稳住。如果你用的是自定义环境,先算清楚单条样本的字节数,再定 buffer 上限。

4.2 为什么我用 PPO + HER 跑不动

不少同学看到 HER 效果好,第一反应是在自己熟悉的 PPO 流程里加一个 HER 模块。跑出来效果很差,然后怀疑参数没调好。实际上问题出在算法性质上。

PPO 依赖重要采样修正,要求当前更新的数据确实由当前策略产生。HER 改写目标后,数据里的“目标”已经不是采样的原始目标了,重要采样权重就没法正确计算。再加上 PPO 用 GAE 估计优势,GAE 基于整条轨迹的回报一致性,HER 会让同一轨迹在不同目标下产生不同奖励,优势估计直接失真。所以 HER 在 on-policy 框架里不是参数问题,是结构问题。老老实实用 SAC、DDPG、TD3 这类 off-policy 算法就行。

4.3 训练没效果,先别急着调参,检查这三件事

如果 HER 跑了一阵子,成功率纹丝不动,我建议按顺序排查三件事。

第一,环境是否正确返回了 achieved_goal。很多自定义环境为了省事,把 achieved_goal 写成一个固定值,HER 一替换目标就会发现所有“事后目标”都一样,学习信号完全失效。打印几个 achieved_goal 出来,确认它们会随时间变化。

第二,compute_reward 是否真的根据 desired_goal 计算,而不是写死“只要到达 A 点就成功”。HER 的核心能力是评估任意目标,如果 reward 函数只认固定目标,替换后的样本全是误导。

第三,初始探索是否充分。HER 需要数据里有足够多“途中有变化”的轨迹。如果动作噪声太小,每个 episode 基本原地踏步,事后目标全是初始状态,等于什么都没学到。检查 rollout 里 achieved_goal 的方差,方差太小就加大动作噪声或调高探索参数。

4.4 成功率上去了,但泛化差

HER 训练出来的策略有时候在测试集上表现不错,但换一批新目标就垮掉。这个问题通常出在目标分布的“过拟合”上。

因为 future 策略偏爱从当前轨迹后半段采样目标,这些目标往往集中在容易到达的状态附近。随着训练推进,回放池里的目标分布会越来越“偏科”,策略就只擅长那些简单目标。想缓解的话,我建议在目标选择策略里保留一部分 random 或 episode 采样的样本,让目标覆盖更广。更彻底的办法是训练时对目标采样空间做随机化,让 desired_goal 本身均匀覆盖整个可达区域。HER 不是万能的,它擅长把“可达”变成“可学”,但“要学哪些可达目标”仍然需要你来约束。

5. 延伸思考:HER 的边界在哪里

5.1 只有当“目标可达”时 HER 才有效

HER 的根本假设是:从任意状态出发,总能到达某个状态,并且这个状态可以当作目标来学习。一旦这个假设不成立,HER 就会失效。

举个例子,一个任务要求依次按下三个开关才能开门,门开后才算成功。这种情况下,只按开关 1 得到的“事后目标”对最终任务没有任何帮助,HER 会往回放池里塞大量“只按了一个开关就成功”的无意义样本。更极端的情况是目标状态孤立且不可达,HER 就会学到原地不动这种投机行为,因为“保持原状”确实可以被当成一个事后目标。所以 HER 比较适合连续控制、机器人操作这类“状态空间连续、目标分布自然连续”的任务,而不是离散逻辑型任务。

5.2 HER 和课程学习、分层强化学习的组合思路

HER 不是孤立使用的,它和其他技术叠起来效果更好。比如用自动课程学习(如 HGG,Hindsight Goal Generation)来挑选更有价值的事后目标,而不是完全随机选;或者把 HER 作为下层控制器,上层用课程策略决定当前该训练哪个目标,这就是分层思路的雏形。

我最近在尝试的做法是:先用 HER 在仿真里训练一个“目标到达”的底层策略,然后冻结底层,在它上面学一个高层任务规划器。这个架构在分拣任务上效果不错,底层不需要频繁重训,高层只需要负责目标选择。这算是一个比较自然的扩展方向,如果大家感兴趣,我后续可以专门写一篇分层 HER 的实战笔记。

6. 我踩过的坑和一些私货

6.1 一次失败的目标定义经历

有一回做真实分拣项目,我把目标定义成机械臂末端的完整位姿向量,包括位置和姿态。结果 HER 训练了很久,成功率一直上不去。后来发现,姿态部分对任务并不重要,而且姿态空间太复杂,随机探索很难覆盖到实际可达的子空间。我把目标改成“抓取点的三维坐标加上夹爪张开距离”之后,训练曲线立刻正常了。

这个经历让我养成了一个习惯:目标定义一定要和任务核心强相关,能少一个维度就少一个维度。目标空间维度越低,HER 的事后目标越容易覆盖,成功率上线就越快。不要贪图“信息完整”而把无关维度塞进目标里。

6.2 一个调参救活项目的案例

另一个印象比较深的项目,是帮朋友调试一个类似 FetchPush 的推箱子任务。他跑了两百万步,成功率只有不到 5%。

我先看了回放池,发现 future 采样比例太高,目标几乎都是轨迹末端的局部状态,导致策略只会推箱子到一个固定角落。我把目标选择策略从纯 future 改成 future 0.6 + episode 0.2 + final 0.2,最后一次性把 n_sampled_goal 从 8 降到 4,因为他的机器内存已经到瓶颈。改完后再跑五十万步,成功率到了 60% 以上。

所以如果你遇到 HER 训练效果平平,先别怀疑算法本身,把目标选择策略、目标空间维度、内存约束这三个点逐一审一遍,往往比盲目堆训练步数更有效。

HER 这套“事后诸葛亮”的思路,说到底是一种很优雅的数据增强方式。它没有引入复杂的网络结构,没有改变目标函数,只是换了一个角度看经验数据,就能把稀疏奖励的冷启动问题化解大半。真心建议正在搞机器人控制的同学,动手试一次 HER,你会发现原来跑不动的任务,其实只是缺了一双“事后”的眼睛。

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

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

立即咨询