☰
HER算法拆解:用事后经验重放破解稀疏奖励难题
2026/9/28 7:19:02 网站建设 项目流程

这几年的强化学习项目里,我有一半以上的时间都花在同一个问题上:机械臂明明在动,但奖励曲线纹丝不动,永远是一条直线。拿推箱子来说,目标是把木块推到指定位置,机械臂推了几百个episode,一次都没有推到过目标附近,于是每一次都收到0奖励,价值函数和策略都得不到任何梯度暗示。很多团队遇到这种情况就开始堆reward shaping,把距离差拆成中间奖励,但这样做既费劲又容易破坏原始任务的定义。直到我认真啃了一遍Hindsight Experience Replay(HER)的论文,又在自己的环境里复现和调优了两个月,才真正明白什么叫"换个角度看失败"。Hindsight这个词本身就是"后见之明",在强化学习里它代表一套完全不同的思路:不修改奖励,而是修改目标。这篇博文就把HER的完整原理、实现拆解、复现时踩过的坑,以及它到底适合用在哪些任务上,全部摊开来讲,给正在被稀疏奖励折磨的算法工程师和研究生一个可以直接落地的参考。

1. 为什么"事后视角"是稀疏奖励问题的突破口

1.1 稀疏奖励为什么难:一个推积木的例子

先回到最基本的设定。假设机械臂的观测是关节角度和末端位置,目标是木块的目标位置。每一步环境会检查"当前木块位置与目标位置的距离是否小于某个阈值",如果小于就返回0奖励,否则返回-0(在很多环境里是0)。如果你直接拿DDPG去训,初始策略几乎不可能偶然碰到那个阈值,所以一个完整episode的累计奖励就是0。模型能学到的只有"怎样做都一样",策略更新方向完全由初始Q函数的噪声决定,于是训练很快进入死循环。

这时候常见的做法是引入中间密集奖励,比如用负的距离作为每一步的惩罚。这个办法确实能打破零奖励的僵局,但它有一个隐蔽的问题:你引入的中间奖励本质上是在篡改原任务的奖励信号,策略可能学会去把距离缩小,但不再执行"把木块推到目标点"这一精确动作。更糟的是,很多真实机器人系统根本没法实时计算目标位置的距离,或者距离传感器有噪声,强行做reward shaping会引入新的不稳定因素。换句话说,真正稀缺的不是奖励值,而是"有用经验"。我们需要的是在现有失败数据中挖掘出学习信号,而不是重新设计一个奖励函数。

1.2 事后反思的直觉:把"没做到"变成"做到了"

HER的核心直觉听起来简单到不像算法:既然这轮episode没能把木块推到目标G,那木块实际上被推到了位置A,我们为什么不把A当作目标,重新计算这一轮轨迹的奖励呢?这样一来,同样是"从起点把木块推到A"这段动作序列,在原始目标G下是失败示例,在事后目标A下就成了不折不扣的成功示例。模型就有了一个正奖励的样本可以学习。

你可能会觉得这有点"自我安慰"。训练还需要分清楚,后续真正要解决的目标是G而不是A,会不会把模型带偏?答案是不会,因为HER是一种目标条件化的学习机制。我们不是简单地改变奖励值,而是把目标本身作为输入的一部分喂给策略和价值函数。策略需要学习的是:在给定任意目标g的情况下,如何采取动作。当目标被重新标记为A时,模型看到的是"目标=当前位置A,当前状态=马上到达A的状态,动作=推",它学到了"在目标为A时,这组动作结果是好的"。普通目标G只是众多可能目标中的一种,模型依然会去尝试接近G。

1.3 "重标目标"不是自欺欺人:目标条件化才是关键

为什么HER的重标目标策略能work,而直接改奖励会出问题?因为HER没有改变底层环境的动力学,也没有改变奖励函数的定义,它只是在一个episode执行完之后,额外构造了一些不同目标的查询。原奖励函数仍然保持一致性:在目标G下,只有真正到达G附近才给奖励;在目标A下,只要到达A附近就给奖励。价值函数学到的是"任意状态对于任意目标的期望回报",这比"单个固定目标的期望回报"更容易泛化,也更容易学习。因为很多不同目标对应的最优动作路径是共享的,比如"往右推"这种原始动作模式,在目标位于右侧时有效;模型可以从大量被重标的目标中学到这种共享模式,最终迁移到原始目标上。

这个视角也解释了为什么HER必须搭配目标条件化的策略,而不是普通策略。普通策略输入只有观测,无法区分当前是在追求G还是在追求A;目标条件化的策略输入是观测加目标,所以同一个状态在不同目标下可以产生不同动作。换句话说,HER不是在奖励上动手脚,而是在"任务空间"里做数据增强。这种增强非常干净,不会破坏环境本身的物理规律。

2. HER算法完整拆解:从Episodic Replay到四种目标选择策略

2.1 目标条件化MDP与HER的样本重构

在算法层面,HER把一个多目标强化学习问题定义为目标条件化MDP,记为 (S, A, T, R_g, γ)。其中S是状态空间,A是动作空间,T是环境转移,R_g是依赖于目标的奖励函数。策略输入是(s, g)。每个episode采样时有一个预先设定好的目标g,但在episode结束后,HER会额外生成一批"事后目标"。

具体的重构过程可以这样描述:假设一个episode有L步,轨迹是(s_0, a_0, r_0, s_1, ..., s_L)。我们从轨迹中对每一步都取出原始transition (s_t, a_t, r_t, s_{t+1}, g)。这个transition继续保持原样存入经验池,因为原始目标的信息不能丢。然后,HER会从这条轨迹中智能地选择g'作为新目标,重新计算奖励 r' = R_g'(s_{t+1}),生成新的transition (s_t, a_t, r', s_{t+1}, g')也存入经验池。这里的奖励计算必须使用完全相同的环境奖励函数,唯一的区别是传入的目标从g变成了g'。这就是"事后"的直观含义:当episode跑完,你才回头选择一个本回合真实达到过的状态作为"虚拟目标"。

下面是我常用的HER采样伪代码,方便你直接理解,实现时可以按这个框架套到自己的环境里:

def generate_her_transitions(episode, k=4, strategy='future', reward_fn): her_transitions = [] # episode 里记录每一步的 obs, action, next_obs # 同时记录所有状态中与 goal 同维度的部分,作为候选目标 all_goals = [transition['state_for_goal'] for transition in episode] for i, trans in enumerate(episode): obs = trans['obs'] action = trans['action'] next_obs = trans['next_obs'] original_goal = trans['goal'] # 保留原始目标的一条 transition her_transitions.append({ 'obs': obs, 'action': action, 'reward': reward_fn(next_obs, original_goal), 'next_obs': next_obs, 'goal': original_goal }) # 额外生成 k 条事后目标 transition for _ in range(k): if strategy == 'final': new_goal = all_goals[-1] elif strategy == 'future': future_idx = random.randint(i, len(episode) - 1) new_goal = all_goals[future_idx] elif strategy == 'episode': new_goal = random.choice(all_goals) else: # random new_goal = random.choice(all_goals) new_reward = reward_fn(next_obs, new_goal) her_transitions.append({ 'obs': obs, 'action': action, 'reward': new_reward, 'next_obs': next_obs, 'goal': new_goal }) return her_transitions

有一点要特别提醒,这里的state_for_goal是指从状态里抽出的那一部分与目标向量同维度的量。比如Fetch环境的目标是三维坐标,状态里对应的就是物体末端的三维坐标。你不能把整个观测向量都塞进goal,那样目标维度太大会导致训练极其不稳定。

2.2 四种目标选择策略的采样逻辑与选型对比

论文里给出了四种事后目标的选择方式,它们的差别在于如何从当前episode的状态序列里挑选g'。我用一个表格整理,方便逐条对比:

策略采样范围优点局限适用场景
final只选episode最后一个状态计算最简单,目标集中信息量小,方差高简单目标、短episode
random从episode所有状态中随机选覆盖面广,目标多样性好可能出现同一状态的前后矛盾目标分布复杂时兜底
episode从人类经验中随机选任意状态比random稍稳定时间信息被忽略与random差别不大
future从当前时间步之后的状态中选保持因果关系,目标通常可达需多一个索引计算论文推荐配置,一般任务首选

最常用的就是future,而且论文默认k=4,意思是每一条原始transition额外生成4条future目标transition。选future而不是random,是因为future保证新目标是在当前时间步之后真实出现过的状态,这样transition中的动作与目标之间不会有明显的时间错位。如果从过去的状态里选目标,会出现"目标在t-1时刻已被实现,t时刻动作还在往那边推"这种奇怪样本,影响学习效率。

很多人会把k设置得很大,比如k=16或k=32,直觉是数据越多越好。但实际测试下来,k太大会让经验池里事后样本占绝对主导,模型把所有目标都当成易达成的目标,反而减少了对原始目标的关注,导致原始目标进度变慢。我在自己的实验里通常先在k=4下跑通,再根据原始目标成功率的变化缓慢调高,而不是一开始就堆数量。

2.3 与Off-Policy算法结合的完整训练流程

HER不是一个完整的强化学习算法,它只是一个经验增广模块,必须耦合到某个off-policy算法里,最典型的是DDPG、DDPG+HER、TD3或SAC。为什么必须是off-policy?因为HER要求从replay buffer里多次采样重标定的transition反复学习,而on-policy算法像PPO每次采样完就丢,无法利用事后经验。

我常用的训练流程是这样的:先用一个探索策略(比如DDPG的噪声策略)和环境交互,采集完整的episode;episode结束后使用HER生成重标定transition并写入replay buffer;然后从buffer里均匀采样一个batch,更新Actor和Critic。关键一步是网络输入:Critic的输入是concat(obs, goal),Actor的输入同样是concat(obs, goal)。如果在某个任务里目标和观测维度差别太大,可以考虑各自过一层编码再融合,但大多数Fetch类环境直接concat就够用。

还有一个实现细节值得注意:重标定transition和原始transition的奖励计算必须用同一个reward_fn,不能因为目标是事后设的就加大或减小阈值。我在复现时把奖励函数封装成独立的模块,定义为reward_fn(next_state, goal) -> float,这样原始样本和事后样本共用同一套代码,避免后续调参时出现两套口径。

3. 复现HER时我踩过的坑:目标一致性、奖励口径与超参数

3.1 坑一:Batch内目标不一致导致Q值炸掉

这个坑我印象极深。第一次实现HER时,我只是简单地把所有transition堆进一个大的回放池,训练时随机均匀采样一个batch,然后直接做标准的Q-learning更新。结果跑了没两千步,Critic的loss直接爆炸,Q值变成巨大负数。排查了很久才发现,问题出在采样batch里的transition目标是完全混杂的。Q-learning更新目标需要计算r + γ * Q(s_next, argmax_a Q(s_next, a, g)),其中的g必须和当前transition的g完全一致。如果同一个batch里有不同目标g1、g2、g3,Critic会对不同目标计算同一个输出,梯度方向互相冲突,严重时直接让网络发散。

解决办法有两种。第一种是采样时做目标分组,保证一个mini-batch里的transition全部来自同一个目标(或少数几个目标),这样Q目标的计算不会互相干扰。第二种是采用"multi-goal"方式,在batch里为每个样本单独计算Q值,也就是Q(s, a, g)函数里,每个样本的g都进网络,但要确保对于每个样本的目标,下一状态价值也是同目标。最稳妥的做法是像OpenAI的实现一样,batch采样时先随机选一批episode,再从每个episode里取多条transition并保持它们的目标一致。如果你的环境是单目标环境,只需要重标定目标,这个问题相对轻;如果一上来就做多目标并发训练,务必按目标分组。

3.2 坑二:HER目标与原目标使用不同奖励口径

这个坑比较隐蔽,通常出现在你为了加速收敛,把原目标的奖励阈值设得很小,比如距离小于0.1给奖励;而为了增加HER样本成功率,把事后目标的阈值设大,比如距离小于0.5就给奖励。表面上看,更多HER样本有正奖励,训练更快;但实测两三万步后,原始目标成功率几乎不变,甚至出现"模型疯狂接近但始终不成功"的现象。

原因很简单:模型从奖励口径松的HER目标中学到了"只要靠近到0.5就算成功"的宽松策略,但这个策略在严格的原始目标下并不成立。Critic的目标条件化只是表象,它会在宽松目标下把Q值估计得很高,进而引导Actor去追求"接近"而非"精确到达"。这一点我后来专门验证过:把HER目标和原始目标统一成完全相同的阈值函数,比如都用distance < 0.05,训练曲线立刻变得健康。所以,HER的奖励函数必须是环境本身的奖励函数,不支持任何形式的"放宽"。

3.3 坑三:目标维度与观测维度归一化不匹配

在Fetch环境中,观测是机械臂的关节角度、末端位置和物体位置,数值范围大体在[-1,1]或[0,0.5];而目标位置是三维坐标,范围在[0,5]以上。如果直接把obs和goal拼在一起送给网络,等于让高数值的goal主导了网络的前几层权重,损失函数会被目标维度的误差主导,导致动作预测偏向目标而忽略状态信息。

我的解决办法是:把obs和goal分别做标准化。obs使用RunningMeanStd,goal使用环境的min-max或固定的归一化参数。这里有一个很多人忽略的细节,HER重标定后的goal总是从episode的状态里取出来的,它的分布和原始goal的分布可能不同。如果只按原始goal的分布做归一化,HER样本的goal在归一化后可能会超过范围,影响训练。更好的做法是统计所有进入buffer的goal(包括重标定目标)的min和max来归一化。我在实验里对不同时刻的buffer分布做了对比,发现重标定目标的范围往往更集中,用统一范围归一化会更稳定。

3.4 坑四:k值、采样窗口与Replay Buffer大小怎么定

关于k值,论文推荐4,但具体任务可以调整。我自己的经验是:任务越稀疏、原始目标越难达成,k值越应该偏大;但k值增大会让正样本比例过高,负样本变得稀疏,模型会变得过度乐观。一个比较稳的调法是先固定k=4,看原始目标成功率是否增长;如果增长慢,把k升到8,同时把Replay Buffer容量加大到原来的两倍,因为更多的训练样本需要更大的缓冲来避免重复抽样。

采样窗口也很关键。future策略中,在时间步t要选择一个未来状态作为目标,如果窗口太长,选了很远的未来状态,会导致"当前动作和那个未来状态"之间的因果关系很弱。比如机械臂在t时刻正在向左动,而future窗口选到了5秒后它回到右侧的状态,这两者没有直接因果。论文默认是在整个剩余时间轴上均匀采样,但实际复现时我发现限制在t + 20步以内(具体episode长度的1/4到1/3)往往训练更稳定。另外,Replay Buffer条数不能只按transition数算,因为重标定会让数据量膨胀到原来的(k+1)倍,buffer长度设置要留出足够的余量。

4. 在Fetch系列环境上的效果:从一味乱撞到稳定收敛

4.1 Fetch环境介绍与为什么它是标准测试场

OpenAI Gym的Fetch系列是HER论文里的标准测试环境,包括FetchReach(末端到达)、FetchPush(推箱子)、FetchPickAndPlace(抓取并放置)、FetchSlide(滑动物体)。这些环境都是7自由度机械臂任务,目标是以三维坐标表示,奖励为稀疏的0/1:当前状态与目标的欧氏距离小于0.05就返回0(成功),否则返回-0(其实是0)。这一整套环境的好处是:目标可以被轻松修改,状态里包含了物体末端坐标,正好满足HER需要的"事后可以重设目标"的条件。

因为原始环境是稀疏奖励的,所以它是检验HER效果的最佳基准。普通DDPG在FetchReach上都很难收敛,更不用说FetchPush。这很符合真实情况:真实机器人任务很难在随机探索中偶然成功,所以稀疏奖励问题是绕不开的坎。

4.2 对照实验设计:DDPG vs DDPG+HER

我复现时跑了三组对照:纯DDPG、DDPG+HER(k=4,future)、DDPG+HER(k=8,future)。所有算法共享相同的网络结构(两层256单元MLP)、相同的探索噪声和相同的训练步数。在FetchReach上,纯DDPG大概需要500个episode才能达到90%以上的成功率,而DDPG+HER只用100个episode左右就能到同样的水平。到了FetchPush,纯DDPG训练20000个episode成功率仍不到5%,几乎是在乱撞;DDPG+HER在2000个episode左右就能看到明显的成功率上升,最终稳定在80%以上。

我还记录了平均Q值的变化。纯DDPG的Q值一直徘徊在-0.1左右,因为没有正奖励样本可学;HER版本在很早期就出现了大量正Q值,因为重标定让正样本数量激增。这个曲线对比很直观,也让我真正相信"增加正样本"不是玄学,而是有统计基础的。

环境算法达到80%成功率所需episode最终成功率
FetchReachDDPG约35085%
FetchReachDDPG+HER约8098%
FetchPushDDPG未达到(2万episode内<5%)3%
FetchPushDDPG+HER约180082%

这个表是我在自己机器上跑出的典型结果,细节会因为随机种子有波动,但趋势非常稳定。HER带来的提升在越稀疏、越困难的任务上越明显,因为原始探索几乎给不出任何信号,全靠事后目标来补充学习信号。

4.3 数据效率提升的背后原理:一次经验,多次学习

为什么HER的数据效率能高出这么多?最直接的原因是一条原始轨迹会被重标定成多条不同目标下的transition,相当于把一次交互数据在多个任务上复用。第二个原因是目标条件化让价值函数学到的是整个目标空间上的映射,而不是某个固定目标上的映射。当模型在多个目标上都有经验后,它对于"如何让目标距离变小"这种通用规律有了更强的归纳。

更底层的解释是从失败样本中提取"反向成功样本"。想象一个episode从初始状态出发,机械臂推了一下木块,木块从位置P0变成P1,但P1离目标G很远。原始目标下这是一条负样本;但如果我们把目标设成P1,那么"用这个动作把木块从P0推到P1"就是一条正样本。换个说法,模型从一条失败轨迹里学到了一个微型成功的子目标。大量的微型成功合在一起,就能逐步搭建出通往原始目标的路径。这也是HER为什么在真实机器人领域被大量使用的原因——它不需要人工设计子目标,自动从数据中产生可学习的子目标。

5. HER的适用范围与它的"自我欺骗"风险

5.1 三类最适合HER的问题特征

不是所有强化学习任务都能套HER。经过多轮实践,我总结出三类典型特征,同时满足时HER收益最大。

第一,目标必须是可观测且可重设的。你必须能在episode结束后,把某个状态的一部分设为新目标,并且新目标在任务语义上是合法的。例如机械臂抓取中,物体位置可以作为目标;但如果是游戏里"击败Boss"这种二值目标,就没有自然的连续状态可以当作新目标。

第二,环境动力学不能因为目标改变而改变。HER的妙处在于它重设了目标,但环境的状态转移只取决于动作,不取决于目标。如果目标本身会改变物理参数,比如机器人需要走到某个温度区域,而目标区域的温度影响运动摩擦,那重设目标后就无法对应实际动力学了。这类任务不太适合直接套HER。

第三,奖励函数必须能通过新目标重新计算。也就是说奖励只能依赖于(state, goal)的函数,并且这个函数必须清晰可计算。如果奖励依赖于隐藏的全局目标或对手行为,事后重设目标就没有意义。

5.2 从HER到GoalGAN:事后思想的后继者

HER证明了"把到达过的状态作为目标"的有效性,但也暴露了一个问题:重标定目标总是从当前policy能达到的状态中选,这会让学习偏向于已经容易达到的状态。如果原始目标在探索空间中处于遥远区域,模型可能会一直停留在容易达到的目标附近,难以推进到困难区域。

后来有很多工作尝试解决这个问题。比如GoalGAN,用生成对抗网络生成新的目标,让生成的目标尽量落在当前策略的"边界"上;还有使用能量函数、课程学习等方法动态调整目标难度。这些都可以看作是事后思想的一种延展:先定义目标分布,再从经验中按难度采样目标,让策略一直在"近期可达但又不完全可达"的目标上进步。如果你研究HER已经有一段时间,想往上深入,可以从这条线切入。

5.3 会不会自我欺骗:当目标总被重标,模型还有追求吗?

最后聊一个我一直在琢磨的问题。HER把失败样本包装成成功样本,训练出的模型会不会越来越倾向于"认为当前状态就是目标",从而放弃原始目标?理论上目标条件化策略是可以区分目标的,所以它不会无条件放弃;但在实践中我确实观察到一种微妙现象:随着训练推进,模型对困难目标(原始目标)的Q值估计往往比对简单目标更保守,导致Actor更偏好简单目标。如果评估时只统计原始目标的成功率,这种偏好不会影响指标;但如果模型被部署到原始目标必须被精细完成的环境里,它可能会产生"为了避免失败,试图降低目标难度"的内隐行为——尽管算法上不存在这个意识,但目标条件化网络会把高Q值的简单目标区域作为吸引子。

我的应对办法是:在HER之外,保留一小部分纯原始目标样本,并定期用原始目标成功率作为早停和模型选择的指标。也就是说,HER只管扩充训练数据,但最终评估和部署必须回到原始目标上。另外,在任务难度跨度特别大的时候,我建议给原始目标样本一个更高的经验池采样权重,让模型不会完全被简单目标包围。

说实话,HER不是万灵药,但它是我见过的对付稀疏奖励问题最优雅的方案之一。它把一个看似哲学的问题——事后视角能不能用于实时学习——变成了一个极其具体的工程实现。如果你正在自己的环境里被稀疏奖励逼得想放弃,不妨先检查一下你的任务是否具备可重定目标的条件;如果具备,跑一个HER baseline,大概率比你现在手调的reward shaping要省心得多。我自己的经验是,第一次真正看到FetchPush成功率从0爬到80%的那个下午,比看完十篇论文都更有说服力。希望这篇拆解也能帮你少走一点弯路。

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

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

立即咨询