☰
事后经验回放HER:破解强化学习稀疏奖励难题
2026/10/1 6:16:37 网站建设 项目流程

第一次看到 Hindsight Experience Replay 这个标题时,我首先想到的是心理学里“事后聪明”的味道。事情发生之后再回头看,总觉得结果早就注定了,这种后见之明在生活里经常让我们高估自己的判断力。可放到强化学习里,hindsight 这个词被玩出了完全不同的花样,它变成了一种非常实用的训练技巧:把没成功的轨迹,改造成能教会智能体的成功经验。这个思路来自 Andrychowicz 等人在 2017 年提出的 Hindsight Experience Replay,中文社区一般叫“事后经验回放”,也有人直接叫 HER。这套方法直到今天仍是解决稀疏奖励问题最通用、最有效的思路之一。这篇内容我会从原理拆解、四种重标策略对比、可抄作业的代码实现、参数调优和踩坑记录几个维度完整展开,适合刚接触深度强化学习、想在机器人控制或目标达成类任务里把训练跑起来的朋友。

1. 这个标题到底在说什么:HER 的出发点和适用面

1.1 稀疏奖励问题为什么让人抓狂

做强化学习的同学,大概率都经历过这样的场景:环境给的奖励不是每一步都有,而是非 0 即 1——达成目标给 1,没达成就是 0。机械臂推一个门把手,门没到指定角度之前,每个动作都拿不到任何反馈;无人机飞到目标点上方三米以内才给奖励,差半米也什么都没有。这种奖励函数在真实任务里非常常见,因为真实世界很少会告诉你“你离目标还差多少”,只会告诉你“成功或失败”。

问题在于,稀疏奖励下策略网络得到的监督信号几乎等于零。智能体输出动作,环境返回 0,网络不知道哪个动作有效,哪个动作在方向上是对的。靠随机探索碰运气撞到目标,在高维连续动作空间里概率低到接近不可能。你可以想象成在完全黑暗的房间里找一枚掉在地上的针,手里只有一个蜂鸣器,蜂鸣器只在针被踩到时响一下。大多数时候你甚至会怀疑这房间里到底有没有针。

很多团队这时候会下意识去做奖励塑形(reward shaping),给环境加一堆中间奖励。但人工设计中间奖励很容易出问题,设计坏了会诱导智能体投机取巧,比如为了获取“靠近目标”的奖励而原地绕圈,反而学不到真正需要的策略。HER 不修改奖励函数,不添加任何中间奖励,它只是换了一个视角去看已有的失败轨迹,让失败本身也能产生学习信号。这个视角就是 hindsight。

1.2 把失败轨迹改写成“已经做到的经验”

HER 的核心机制可以浓缩成一句话:目标没达成,那就换一个已经达成的目标,把这段轨迹当作成功样例放回经验池。举个例子,机械臂接到的指令是把方块推到位置 A,但一整个回合跑完,方块被推到了位置 B,位置 A 没达成,原始奖励是 0。HER 会在这一回合结束之后,额外生成一些“虚拟经验”,把这些经验里的目标改成位置 B,并且把奖励重算成 1。

对智能体来说,位置 B 确实是这条轨迹最终到达的状态,所以“从起点把方块推到 B”这件事在这段轨迹里是成立的。虽然这不是人类给它的原始目标,但“如何把一个目标状态达成”这种能力是可以跨目标迁移的。一个能学会把方块推到 B 的策略,稍加泛化,也就有了把方块推到 A 的可能性。换句话说,好好利用没完成的任务,把失败结果当成教学材料,这就叫“事后聪明”。

这种思路从直觉上很好理解。我自己刚开始学控制的时候,经常在仿真环境里调一个机械臂抓取任务,跑了上百万步失败率依然奇高。后来把轨迹回放改成 HER 式的目标重标之后,训练曲线开始有了反应。道理很简单:原本那些完全浪费掉的数据,现在每一段都能给网络提供至少一个“成功方向”的信号,哪怕这个成功方向只是次优的,也比全是零奖励强太多。

1.3 什么样的任务适合用 HER

不是所有强化学习问题都能套 HER。HER 的前提是任务具备明显的目标条件结构:智能体的任务可以被描述成“从当前状态出发,达到某个目标状态”。典型场景包括机械臂推方块、开门、到达指定位置、控制机器人走到某个坐标,甚至包括一些多关节运动控制任务。目标可以是位置坐标、物体状态、关节角度,甚至是图片特征向量,只要你能定义一个“目标达成与否”的判定函数就行。

如果任务没有清晰的目标状态,比如玩围棋、打斗地主这种胜负只在一局结束才揭晓的对抗型任务,HER 就没什么发挥空间,因为“换一个目标”本身很难定义。如果任务已经有非常稠密、合理的人工塑形奖励,HER 带来的边际收益也不会太明显。它最发光发热的地方,还是稀疏奖励加目标条件这个组合。另外,HER 天然适合 off-policy 算法,像 DDPG、TD3、SAC 都是常见搭配;on-policy 的 PPO 要接 HER 会比较别扭,因为重标后的经验不能直接拿来算当前策略下的重要性采样。

2. 核心原理拆解:四种目标重标策略怎么选

2.1 经验回放里的数据流组织方式

要理解 HER,先要搞清楚它在经验回放里的位置。常规 off-policy 训练中,每条经验是一个五元组:状态、动作、奖励、下一状态、完成标志。HER 的思路是在这个五元组之外,把目标也作为组成部分写进经验里。于是经验变成了六元组:当前状态、动作、奖励、下一状态、目标、下一状态是否达到目标。

“目标”不是一个独立变量,它要和状态一起被策略网络看到。机械臂任务里,网络输入通常是当前关节位置和方块位置拼接在一起,目标位置也拼进来,网络才能知道“我现在在哪、目标在哪、我怎么动作”。HER 重标的时候,本质上是把这条经验中的目标字段替换成另一个新目标,然后重新计算奖励和是否达成标志,再把这个新经验推回经验池。

这个设计有个很关键的点:原始经验也保留,重标经验是额外叠加进去的。经验池里既有原始目标下的失败经验,也有重标目标下的成功经验。两种经验混合在一起,既保留了真实任务的原始分布,又增加了可供学习的成功样本。比例控制得好,策略既不会偏离原始任务,又能在目标达成方面获得足够多的正反馈。

2.2 final、future、episode、random 四种策略

HER 论文里提出了四种重标策略,区别在于“新目标从哪里来”。我在实验里把四种策略都跑过一轮,各自的特征差异非常大,值得仔细比较一下。

策略新目标来源主要特征我的实操感受
final该轨迹最终到达的状态最简单,每个轨迹固定生成一个虚拟成功经验实现成本最低,但目标多样性不足,样本利用率偏低
future该轨迹当前时刻之后随机选的 k 个未来状态目标多样性和可达性平衡得最好默认首选,绝大多数场景我都会优先试它
episode整个轨迹中随机抽一个状态目标分布更广,挑战性更高噪音偏大,适合目标空间特别丰富的场景
random从环境中随机采样目标目标几乎不可达,接近噪声一般只用来做对照组,实际很少用它

four种策略的“目标来源”差异直接决定了重标经验的质量。final 策略最简单,一个轨迹只造一个成功样本,数量有限;episode 策略虽然目标多样,但可能会抽到当前状态之前的状态,导致“从当前状态回到过去状态”这种时间上不自洽的目标,训练噪声会变大;random 策略完全是碰运气,目标基本不可达,对学习没有什么帮助。future 策略之所以最好,是因为它抽的是当前时刻之后的未来状态,这些状态是智能体通过真实动作链走过的状态,既可达又和当前状态相关,天然构成了一个合理的循序渐进目标序列。实际操作中我一般取 k=4,也就是每条轨迹在每个时间步额外生成 4 条重标经验,这个值在论文里也是常用默认值。

2.3 如何根据任务特点调整重标策略

future 策略虽然默认很好,但遇到不同任务还是得微调。如果目标空间非常狭小,比如只在一条直线轨道上移动,future 抽到的目标可能高度相似,多样性不足,这时候可以把 episode 策略混着用,或者适当增加 k 的数值。如果任务的状态空间维度很高,目标本身是高维向量,未来状态之间差异可能非常细微,重标目标相近,学习效率会下降,这时可以考虑先对状态做降维或特征编码,再在特征空间里做目标重标。

还有一个很容易被忽视的问题:future 策略抽样时,必须限定在“当前时刻之后”的状态集合里。如果时间 t 时刻抽了一个 t 之前的状态作为目标,看似也是轨迹中的真实状态,但智能体已经过了那个点,物理过程不可逆,等于逼迫网络学习一个时间上不自洽的映射,训练会变得很抖。实现时千万别图省事从全轨迹范围随机抽。

3. 实操过程与核心环节实现

3.1 轨迹收集与缓冲区改造

HER 的落地实现并不复杂,但有一些细节会影响训练成败。先讲轨迹收集。常规 rollout 时,每个 episode 结束都会得到一整段 transition 列表。HER 要求我们保存这条列表中的所有状态,尤其是每个时间步的“实际达到状态”,也就是 achieved state。这个 achieved state 可以简单理解成“这个时刻,方块实际在哪里”。它不是网络输出,而是从环境状态里直接读取的信息。

缓冲区里最好分开存原始状态和目标状态,不要采集时就把目标拼接到观测里。理由很简单:目标重标会频繁改动目标字段,如果观测里已经混入了目标,每次重标都要重新拼接整个观测向量,不仅麻烦还容易出错。更好的做法是缓冲区只存原始状态,训练采样之后再把目标和状态拼接起来,送给策略网络和 Q 网络。

伪代码里,轨迹收集大概是这样一个结构:

for episode in range(total_episodes): episode_transitions = [] obs = env.reset() done = False while not done: # 动作来自当前策略,加上随机探索噪声 action = policy.select_action(obs) + exploration_noise() next_obs, reward, done, info = env.step(action) achieved = extract_achieved_state(next_obs) episode_transitions.append({ "obs": obs, "action": action, "reward": reward, "next_obs": next_obs, "achieved": achieved, "original_goal": info["goal"] }) obs = next_obs # 一个 episode 结束后,开始生成重标经验

3.2 核心目标重标与奖励重算的实现

重标逻辑可以单独写一个函数。输入是一条轨迹和当前时间步的下标,输出是若干条新经验。要实现的关键点有三个:抽新目标、重算奖励、拼接新的目标条件观测。

def calculate_her_sample(episode, t, k=4, strategy="future"): T = len(episode) if strategy == "final": new_goals = [episode[-1]["achieved"]] elif strategy == "future": # 只从 t 之后的状态里抽 candidates = range(t + 1, T) selected_ids = np.random.choice(candidates, size=k, replace=False) new_goals = [episode[i]["achieved"] for i in selected_ids] elif strategy == "episode": selected_ids = np.random.choice(T, size=k, replace=False) new_goals = [episode[i]["achieved"] for i in selected_ids] else: new_goals = [env.sample_random_goal() for _ in range(k)] her_samples = [] for g in new_goals: achieved = episode[t]["next_obs"] if use_next_obs else episode[t]["achieved"] # 判定是否达成目标,thresh 是距离阈值,由任务定义 new_reward = 1.0 if distance(achieved, g) < thresh else 0.0 her_samples.append({ "obs": episode[t]["obs"], "action": episode[t]["action"], "reward": new_reward, "next_obs": episode[t]["next_obs"], "goal": g, }) return her_samples

这个函数写出来后,训练主循环里要做的事情就很朴素了。每个 episode 收集完毕,依次遍历每个时间步,把原始经验推进缓冲区,再把 HER 生成的重标经验也推进缓冲区。注意,HER 重标的奖励有时不一定是 0 或 1,如果你设计了稀疏加密集混合的奖励,也可以保留原来的密集奖励项。但大多数论文实验里,HER 主要搭配二值奖励使用,效果已经很好了。

3.3 与 SAC/TD3 算法接入的完整流程

HER 本身不规定你用什么 actor-critic 算法,它只负责“喂数据”。我拿 TD3 举个例子。TD3 更新时,需要从缓冲区采样一批四元组:观测、动作、奖励、下一观测。接入 HER 后,唯一变化是采样得到的观测要包含目标,也就是先把原始状态和重标目标拼接起来,再送给 Critic 和 Actor。

训练循环大致可以组织成下面这个形态:

for episode in range(total_episodes): # 1. 收集一条完整轨迹 episode_transitions = collect_episode() # 2. 逐时间步生成 HER 样本并推入缓冲区 for t in range(len(episode_transitions)): replay_buffer.push(episode_transitions[t]) her_samples = calculate_her_sample(episode_transitions, t) for sample in her_samples: replay_buffer.push(sample) # 3. 从缓冲区采样并更新 TD3 / SAC for _ in range(update_steps): batch = replay_buffer.sample(batch_size) obs_with_goal = concat(batch.obs, batch.goal) next_obs_with_goal = concat(batch.next_obs, batch.goal) # 后面的 Critic / Actor 更新照常 td3_update(obs_with_goal, batch.action, batch.reward, next_obs_with_goal, batch.done)

我特别提醒一下这个拼接时机:必须在采样之后再做。如果你提前把目标拼进去,缓冲区里存的每一条都得重写,一旦 HER 重标多个目标,内存会快速膨胀。分开存储、用时拼接是空间更省、代码更稳的做法。另外,在计算目标条件下的下一观测时,用 next_obs 和同一个新目标拼接,不要用旧的下一状态去配新目标,否则 Critic 算出来的 Q 值会错位。

4. 参数调节与训练排障

4.1 关键超参数和参考取值范围

HER 有自己的一套超参数需要单独调,最常见的几个是 k、重标轮数、目标判定阈值和探索噪声幅度。

  • k:每条轨迹每个时间步生成的重标目标数。默认取 4,目标空间小或训练时间紧张可以先从 4 起步;目标空间特别大时可以提高到 8,但注意经验总量会变成原来的 k 倍,内存开销同步增加。
  • 重标轮数:有些实现把 HER 作为额外 epoch 来跑,一条轨迹会被反复重标多次。论文里常用的设置是每轮轨迹额外做 8 个训练 epoch,和 k 搭配起来效果最好。
  • 目标判定阈值:我见过太多人忽略这个。如果阈值设得太大,几乎每个重标目标都会被判定为成功,奖励全是 1,策略学不到目标的精确距离;阈值设得太小,即使 HER 重标后成功比例依然很低。更合理的做法是依据环境本身的到达精度要求来定,比如机械臂末端定位任务,一般用末端误差小于 3 到 5 厘米作为成功标准。
  • 探索噪声:HER 训练前期需要足够大的探索噪声来获取多样化的未来状态,噪声太小会让未来目标集合太窄。训练后期再把噪声逐步调低,让策略收敛到确定动作。

经验池大小也要注意。原始轨迹经过 HER 放大 k 倍后,真实经验占比会被稀释。如果池子开得特别大,大量旧数据和新数据混在一起,训练前期会感觉进展很慢。我通常在仿真任务里设经验池容量为 100 万条左右,每一条轨迹推入原始经验同时推入 4 倍重标经验,这样就能让有效样本的比例保持在一个健康的水平。

4.2 亲手踩过的坑:训练曲线持续不动是怎么回事

我第一次把 HER 接到自定义机械臂抓取环境时,跑了两百多万步,成功率曲线几乎贴在零轴。当时第一反应是检查代码逻辑,结果发现重标逻辑没问题,原始奖励是 0,重标奖励也正确算成了 1,训练却在原地打转。后来逐个排查,发现是探索噪声设置不合理。噪声太小,机械臂每次都在原地小幅抖动,轨迹里的未来状态高度相似,HER 重标出来的“成功经验”全是同一堆状态,信息量严重不足;噪声太大,机械臂又像喝醉了一样乱飞,好不容易进入目标区域也很快冲出去,轨迹质量极差。最后把噪声方差调成介于两者之间的值,训练曲线才开始有起色。

另一个很常见的坑是目标没有归一化。机械臂任务里,位置坐标通常是米制单位,范围大概在 0 到 1 之间,但关节角度可能是弧度制,范围从 -3 到 3。这两类数值拼在一起送给网络时,量纲不一致会让梯度被大数值特征带偏。我后来把所有状态和目标都做了归一化,或者转成相对坐、相对于目标点的偏移量,训练一下平稳了很多。

还有一次遇到的诡异现象是,训练前期损失一直在下降,后期突然爆掉。查了半天发现是目标判定阈值设得太宽松,重标经验里大量“成功了但实际离目标很远”的数据,网络被这些虚假成功经验洗脑,Q 值开始高估。把阈值收紧后,训练重新回到了正常轨道。

4.3 常见问题速查表

现象可能原因排查思路
成功率长期为 0探索不足或奖励阈值过严调高探索噪声,先看智能体是否产生多样化轨迹,再把目标阈值放宽一点测试
动作一直在原地抖动探索噪声过大或策略网络学习率偏高降低噪声方差,调低学习率,观察轨迹状态差异
奖励上升后反复回落目标归一化没做好,或后期没有降探索对状态和目标做归一化;训练后期逐步削减噪声
Q 值估计持续偏高重标反复把稀疏奖励变成 1,导致 bootstrap 滚雪球降低学习率,增加 Critic 更新次数,或改用 TD3 的 clipped double Q
缓冲区内存溢出k 设置过大,或重标轮数太多降低 k,适当限制经验池容量
未来目标采样越界future 策略里选到了轨迹末尾的索引抽样时确保下标范围是 t+1 到 T-2,T 是轨迹长度

5. 从单目标到多目标,以及它的更多玩法

5.1 多目标统一策略怎么合并信息

HER 天然适合做多目标统一策略。比如一个机械臂环境里,有时要求抓红色方块,有时要求抓蓝色方块,目标不同但底层技能一致。常规做法是给每个目标各练一个策略,模型数量爆炸,维护成本高。HER 的做法是把目标直接编码进观测,让一个策略学会条件化控制——拿到红色方块目标就抓红色,拿到蓝色方块目标就抓蓝色。

训练时,每个 episode 从目标分布里随机采样一个目标,策略在这个目标下执行;轨迹结束后,HER 不仅用原目标重放,还把实际状态重标成另外几个新目标再放回缓冲区。这样,一个策略实际上见过“从任意状态达成任意相似目标”的成功轨迹,泛化能力会明显强于单目标训练。这里再提醒一下,目标分布要尽量均匀采样,别只盯着某几个固定目标反复刷,否则网络对这几类目标的过拟合会很严重。

5.2 在真实机器人上使用 HER 的注意点

仿真里 HER 跑得风生水起,搬到真实机器人上就要多留几个心眼。真实场景里最麻烦的问题是目标状态的测量偏差。比如用动捕系统读取机械臂末端位置,读取误差可能会有几毫米到几厘米的噪声,HER 重标时把这个噪声目标当成“成功目标”,相当于把模糊的成功标准喂进了网络。所以在真实机器人上,目标判定阈值通常要比仿真里更大一点,给测量误差留出余量。

真实机器人还有安全限制。仿真里可以让机械臂随机乱动,真实环境不行。对策是先在一个简单的仿真模拟器里用 HER 学到一个大致可用的策略,再迁移到真实机器人上做微调,真机阶段只允许策略偏离较小的探索噪声。很多团队还会先用手动遥操作采集一批示教轨迹,把成功样本先灌入缓冲区,HER 再基于这些数据做目标重标扩展,效率会更高。

5.3 几个值得看一眼的改进方向

HER 之后还有不少后续改进,我简单提几个方向。一个是 “Noisy HER”,它在重标目标上额外加一点噪声扰动,让虚拟成功目标不完全等于轨迹实际到达状态,这样能让策略学到的目标分布更宽,不容易只记住“到达过哪里”而忽略“如何到达更远的地方”。另一个是与课程学习结合,先让智能体在容易达成的重标目标上学会基础能力,再逐渐增加原始目标和重标目标之间的距离,相当于把 HER 变成了一个自动课程生成器。还有一个方向是基于模型的 HER,用动力学模型预测未来状态,再在这些预测状态上做重标,可以省掉大量真实采样成本。

这些改进各有适用条件,如果你只是想把 HER 跑通,先别急着上各种变体。把基础版 HER 调明白,再根据任务反馈决定要不要加噪声或课程。很多情况下基础版已经能把成功率从接近零拉到可用的水平。

6. 写在最后:一点个人经验

在我自己做机器人控制项目的这段经历里,HER 最打动我的地方不是它有多巧妙,而是它真正尊重了“失败”的价值。常规强化学习把失败数据当垃圾丢掉,HER 却能从垃圾堆里翻出可用的教材。这种思路放到项目里,也提醒我多去复盘那些没跑通的过程,而不是只盯着成功指标看。如果你正准备在自己的稀疏奖励环境里上 HER,我给你的建议很简单:先实现一个 future 策略、k 取 4、把观测和目标分开存、拼接放到采样之后,然后跑通一版再谈调参。成功率不会一夜之间暴涨,但你会看到训练曲线从死水一潭逐渐泛起涟漪,那一步跨过去之后,很多问题都会豁然开朗。

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

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

立即咨询