ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

稀疏奖励怎么办?详解HER事后经验回放原理与实现

2026/10/3 21:30:57 拓冰建站 浏览量
稀疏奖励怎么办?详解HER事后经验回放原理与实现 hindsight这个词很有意思直译过来是“事后聪明”、“事后诸葛亮”。在强化学习里这个词代表一种非常反直觉的训练思路既然Agent没能到达预设的目标那就换个角度把“它实际到达的那个状态”临时当作目标再让模型从这个所谓的“失败”经历中去学习。这其实就是Hindsight Experience Replay事后经验回放简称HER的核心思路一篇来自OpenAI在NIPS 2017上的经典工作。这篇文章我会从稀疏奖励问题讲起把HER的原理、实现、调参和踩坑完整过一遍适合那些已经在用DQN、DDPG、SAC等算法但发现环境奖励太稀疏、模型死活学不动的朋友。读完你就能自己动手把HER接进现有代码里大概率能解决一批RL落地时的冷启动难题。1. 为什么需要“后见之明”稀疏奖励下的学习困境1.1 奖励稀疏到底有多难强化学习的本质是试错。Agent和环境交互做对了给正奖励做错了给负奖励靠这些反馈信号不断调整策略。但有很多任务的奖励是稀疏的只有完成了整条轨迹才给1分中间过程全是0。机械臂抓取物体、机器人推箱子、迷宫导航甚至一些棋类博弈的中间局面都属于这种类型。为什么稀疏奖励会让常规RL算法很难受这里要理解一个细节神经网络更新靠的是梯度而梯度是由价值函数估计出来的。如果一个轨迹从头到尾都是0奖励那么价值函数在这里的梯度信号几乎是平的网络不知道该往哪个方向调整。我打个比方这就像一个人蒙着眼在迷宫里找出口走了半天没有听到任何反馈他只能继续乱走。如果迷宫特别大概率上能撞到出口的机会微乎其微。这个概率甚至不是线性下降而是随状态空间维度增大指数下降比如一个n位二进制翻转问题每步随机翻一位目标全为1的概率是2的负n次方n稍微大一点随机策略就能永远碰不到正奖励。1.2 随机探索经常在做无用功有人可能会说那用一些探索策略比如加噪声、加熵正则不行吗实际上效果也非常有限。噪声只是让动作在原有策略附近抖动但如果策略本来就没有任何偏向一个高维连续动作空间里随机抖动能恰好到达目标状态的概率依然趋近于零。我自己做过一个测试在FetchReach这样的机械臂环境中如果只用DDPG加随机高斯噪声去探索初始阶段几乎99%的episode都不会给任何正奖励。这意味着什么呢经验池里存放的全是“无信号”的经验Critic学出来的是一个近乎常数函数Actor再从这种价值估计里去更新走一步算一步最终的策略基本就是原地打转。那种感觉就是算法跑得挺欢loss也降了但一看真实成功率纹丝不动。这种场景下人为设计中间奖励又很容易翻车。你要么不知道怎么设计要么设计出的导向奖励被Agent钻空子。比如给“靠近目标一点就给奖励”Agent可能学会在原地抖动刷距离奖励而不是真正完成任务。于是就需要一种方法不需要人工设计额外的奖励又能在稀疏奖励中挖掘出学习信号HER就是在这样一种诉求下被提了出来。1.3 后见之明的核心思路为什么有效HER的思路非常朴素一条轨迹虽然没有达到最终目标但在轨迹过程中Agent确实到达了一系列具体状态。如果把这些状态临时当作目标那么这条轨迹就是一条“成功轨迹”。举个例子机械臂原本想抓杯子但没抓到最后停在杯子旁边10厘米处。从“抓杯子”这个目标看它失败了但从“移动到杯子旁边”这个目标看它成功了。下次再碰到类似状态它至少知道怎么运动过去这就是积累。于是HER的做法是把每条经验都复制一份原始目标保留一份另外再生成一份新的经验把目标换成这个轨迹实际到达的某个状态。这样经验池里就多出了一堆“成功经验”价值函数就有了梯度。虽然这些成功经验不是最终任务意义上的成功但它们是“可达成性”意义上的成功能够教给Agent一个基础技能子库。最终任务的目标往往是在这些可达成技能的组合之上产生的。它的本质可以说是一种目标层面的数据增强。2. HER的核心原理与目标重标注策略2.1 多目标与条件化策略既然要对目标做重标注那么Agent的策略和价值函数就必须是依赖目标的也就是goal-conditioned。比如在机械臂任务里状态s是机械臂的关节角、末端位置这些信息目标g是期望到达的位置策略输出就是动作决策时把当前状态和目标拼接在一起作为输入。这一点非常重要因为HER整个方法建立在一句话上策略本来就应该学会“怎么到达各种各样的目标”。只有策略接受目标作为输入才能将重标注后的目标喂给它。如果策略是简单地从状态到动作的映射没有任何条件那HER就无从谈起。所以在实现时如果环境本身是单目标场景比如只需要控制机械臂到一个固定点你需要先显式地构造一个目标空间把环境改造成多目标场景最简单的做法就是固定目标后仍然为目标变量预留一个输入维度。在多目标设定下我们回顾一条原始经验transition它一般表示为这样的五元组当前状态、当前动作、奖励、下一状态、目标。在训练时无论是Actor还是Critic都要把目标作为条件s和g一起输入网络。而在HER中一条transition会被重放两次一次用原始目标另一次用重标注的新目标。两次计算出的奖励是不同的因为距离目标的状态不一样。2.2 四种目标选择策略final、future、episode、random重标注最关键的问题就是新目标从哪里来论文里给出了几种选择方式我一个个说明白。最朴素的叫做final策略指把一条episode的末尾状态作为新目标。如果Agent整条轨迹从初始状态游荡到了某个终态我们假设这条轨迹的目的就是到达这个终态。这种做法对无状态解耦的任务效果很好好处是目标就是真实可达的永远不会出现“虚构目标”坏处是如果轨迹很长前半段的状态离终态很遥远重放出的奖励可能极大梯度也会比较剧烈。如果一条episode很长更常用的是future策略。它会在当前时间步t之后随机挑一个未来状态s_{tk}作为新目标。为什么要选未来状态而不是过去状态因为从物理意义上讲t之后状态是Agent接下来会经历的之间可能有因果联系t时刻的动作对未来的状态有一定影响。用这种方式重标注能构造出时间上更紧凑、学习起来更高效的样本。论文里还专门比较过这两种策略实验结论是future通常比final更稳收敛更快特别是在拥有较长episode的任务中。还有两种策略一种叫episode也就是从当前这条episode里随机均匀采一个状态作为目标另一种叫random即从整个重放池中随机采样历史状态作为目标。这两种效果在很多任务中都明显不如前两种。原因也不难理解episode策略虽然保证目标是可达的但可能跳得非常远很多中间状态和目标之间的关联性不够强random策略就更随机了可能采到一个和当前状态完全无关的目标那样产生出的“成功经验”其实没有太多指导意义。2.3 重标注为什么能产生正向梯度我们要知道神经网络的训练是统计性的数据里只要存在正信号网络就会沿着“增加那些正信号对应动作概率”的方向更新。在稀疏奖励环境中原始数据全是稀疏信号偶尔一个正奖励会淹没在大量无信号样本中。HER通过重标注把原本零奖励或负奖励的样本改造成正奖励样本这相当于人为制造了大量可供学习的正样本。有人会担心这些“伪造的目标”会不会让策略学到错误的东西不会因为重标注的新目标确实是被Agent到达过的状态。机械臂确实到达了某个位置这是事实所以这个经验是真实的“到达成功”经验只是目标不是我们最终关心的那个。从数学上看HER只改变目标变量不改变状态和动作之间的真实转移关系因此不会引入虚假动力学。它解决了什么问题用一句话概括就是让Agent在稀疏奖励下也能获得足够密集的成功监督信号。这种信号比人工设计奖励更干净因为它不会诱导Agent去钻漏洞。人工奖励可能被Agent用一些取巧方式刷高而HER生成的正样本代表了真实到达目标的能力所以策略学到的是真功夫。3. 从零实现HER改造经验池与训练循环3.1 经验池数据结构需要怎么改HER首先是一个经验处理层面的技巧需要配合一个通用的off-policy RL算法使用比如DDPG、SAC、TD3等都可以。原因是HER依赖经验重放而on-policy算法比如PPO每次更新时会丢弃旧经验HER发挥的余地就有限了。如果你是基于DDPG或SAC的代码来改造第一步要扩充经验池的存储内容。原来的transition只存状态、动作、奖励、下一状态、done这五个字段现在还需要额外存一个字段这个transition对应的目标g。经验池里每条数据的结构建议做成这样# 一个transition需要包含的字段 transition { obs: obs, # 当前状态 s action: action, # 动作 a reward: reward, # 对应目标 g 的即时奖励 r next_obs: next_obs, # 转移后的状态 s goal: goal, # 本条经验原始目标 g done: done # 对应目标 g 是否达到终止条件 }这里要注意一个容易忽略的细节done标志也是和goal绑定的。同一个(s, a, s)如果一个目标是“够到杯子”那没够到就是doneFalse如果重标注的目标是当前位置那这个下一步状态就是终点done应该置为True。这个标志会在TD误差计算和终止价值估计时被用到如果算错了价值函数的收敛方向会出问题。3.2 目标重标注的核心逻辑训练中每个episode结束后我们要把这整段轨迹保存下来然后用不同的新目标重新生成一批额外样本再写入经验池。核心的重标注函数可以这样实现def relabel_episode(episode, compute_reward, strategyfuture, k4): # episode: list of transitions每条包含 obs, action, next_obs, goal, done # compute_reward: 函数输入 next_obs 和 goal返回奖励值 relabeled_transitions [] T len(episode) for t in range(T): trans episode[t] # 选择新目标 g if strategy final: new_goal episode[-1][next_obs] elif strategy future: # 从 t1 到 T-1 中随机采样一个时间点 future_idx np.random.randint(t 1, T) # 注意边界处理 new_goal episode[future_idx][next_obs] elif strategy episode: future_idx np.random.randint(0, T) new_goal episode[future_idx][next_obs] elif strategy random: # 从全局 buffer 随机采一个状态实际实现中需要额外维护状态列表 new_goal sample_random_state_from_buffer() # 用新目标重新计算奖励和 done new_reward compute_reward(trans[next_obs], new_goal) new_done check_success(trans[next_obs], new_goal) # 构造一条新的 transition状态、动作、转移不变只有 goal/reward/done 变了 new_trans { obs: trans[obs], action: trans[action], reward: new_reward, next_obs: trans[next_obs], goal: new_goal, done: new_done, } relabeled_transitions.append(new_trans) return relabeled_transitions这段代码里我故意没处理future策略中t等于T-1时的边界问题实际工程实现时需要判断一下如果t已经到最后一个时间步可以直接放弃这个时刻的重标注或者退化为final策略。这个细节不处理跑起来不会报错但你会发现最后一步重标注出来的新目标就是自身奖励恒为1价值信号会产生轻微偏差。另一个值得关注的工程细节是random策略需要维护一个全局状态列表。最好在每次eval的时候顺便把到达过的状态记录下来或者从经验池中去重采样。为了省内存可以只保存状态的子集比如每10步采一个状态存入列表。这样random策略的采样效率会高很多。3.3 与DDPG、SAC等算法结合的配置要点HER本身不改变策略梯度的计算公式它只影响数据分布所以在接入时不需要改actor和critic的主更新逻辑。但有几个地方必须注意。网络输入结构要改。原来DDPG的Critic输入是(s, a)现在要变成(s, g, a)Actor输入是(s, g)。也就是说网络内需要把s和g在特征层进行拼接。如果s和g的维度一样你也可以直接把两者对位拼接成一个2倍维度的向量但更通用的做法是分别编码后再拼接。奖励函数必须是可重计算的。HER要求你能够在给定s和任意目标g时立即算出奖励值。所以环境实现里奖励函数需要独立成模块不能只返回一个标量就完事。比如机械臂任务中奖励函数经常是r -||p_current - p_goal||_2这个公式要写成一个可调用的函数供重标注时使用。额外的重标注比例也需要控制。经验池中一条原始transition通常对应一条重标注过的transition也就是说每条经验被重放两次。如果你希望HER强度更大可以一条transition生成多个额外transition比如future策略中为每个t采样2个不同的未来目标。但我不建议盲目增加数量因为经验池中同一状态的样本比例过大会造成过拟合。实践里的合理区间是1:1到1:3。训练循环的整体框架我贴一下方便新手照葫芦画瓢for episode in range(total_episodes): obs env.reset() episode_buffer [] goal env.get_goal() while not done: action actor.get_action(obs, goal, noiseexploration_noise) next_obs, reward, done, info env.step(action) episode_buffer.append({ obs: obs, action: action, reward: reward, next_obs: next_obs, goal: goal, done: done }) obs next_obs # 原始经验写进 buffer for trans in episode_buffer: replay_buffer.add(trans) # 重标注经验写进 buffer relabeled relabel_episode(episode_buffer, compute_reward, strategyfuture, k4) for trans in relabeled: replay_buffer.add(trans) # 常规 off-policy 更新 for _ in range(update_steps): batch replay_buffer.sample(batch_size) critic_loss, actor_loss update(batch)这个流程看起来和普通DDPG没多大区别核心差异就是多了一个relabel环节和一个goal字段。整个算法本身不复杂复杂的是环境适配和调参。4. 踩坑实录HER训练中常见问题与排查技巧4.1 目标分布选择对收敛的影响我先说结论大多数任务里future策略是默认优先选的final策略在episode较短时效果也不错episode和random策略在实际工程中我基本不会用。这里有个具体案例我之前在某个机械臂摆放任务上做过对比实验。用final策略时前期因为每个episode终点都离起始点不太远重标注后的奖励信号比较平滑收敛速度还行但到后期发现策略始终无法完成长距离搬运动作原因是final策略重标注出的目标普遍集中在中期状态附近长距离目标的样本比例不足。换用future策略之后每个时间步都能和后续某个状态产生配对相当于在任意一段子轨迹上都构造了目标信号最终效果明显提升。如果你发现任务一直不收敛首先检查目标采样策略是否合理不要一上来就怀疑算法。可以写个小脚本统计一下经验池中重标注出来的奖励值分布。如果奖励值大部分都集中在0附近说明新目标设计得离当前状态太远学习信号还是太稀疏。这时候可以把future策略中的k值调小一点让新目标尽量出现在未来3到5步内梯度信号会更密集。4.2 reward定义的一致性问题这是最隐蔽的坑。有些人在原始环境中为了引导学习加了shaping reward比如r 距离的正函数但在HER重标注时继续沿用这个shaping reward结果可能完全相反。举个例子如果奖励函数包含“离目标越近奖励越高”的成分那么把目标重标注为某个远处状态后Agent在接近这个远处状态的路途中可能先经过一个更近的点从而获得一个更高的瞬时奖励。多次训练后策略会被这些虚假的高奖励吸引最终行为严重偏航。我的实践原则是一定要让compute_reward函数保持简单最好用二值稀疏奖励比如到达目标阈值范围内给1否则给0或者用一个单调的距离函数但保证目标是唯一的极值点。HER的价值就在于它可以自己生成成功样本不需要靠shaping reward来引导这时候shaping reward反而可能污染重标注数据。如果你非要保留shaping reward建议设置一个标准重标注时只使用稀疏奖励分量而原始样本中可以使用shaping奖励两者不能混用在同一套目标重计算逻辑里。4.3 done标志和终止条件不匹配很多RL代码里done的定义是“如果当前步达到了目标就返回True”。但注意有些环境里目标达成后episode仍然会继续运行一段时间比如机械臂触碰到物体后还要保持一定时间才算成功或者环境本身有最大步数限制。在HER重标注之后done标志必须重新计算。有些实现偷懒直接把原始transition里的done复制过来用结果就会导致训练信号混乱。比如一条原始transition因为到达最终目标而doneTrue重标注成另一个目标后明明没到达新目标done却依然为TrueCritic会学到“随便做一个动作就能获得终止态的高价值”这会让critic的价值估计彻底偏差。我的做法是在重标注函数里同时计算new_done和new_reward而且算done时用的判定函数必须和reward一致。例如reward是“距离小于阈值就给1”那么done也应该是“距离小于阈值就为True”。两者严格绑定绝不分开处理。4.4 常见问题速查表现象可能原因排查方向Loss一直不降目标重标注比例太低或新目标距离过远检查经验池中正负样本比例调整future策略k值收敛后成功率很差done标志错误或shaping reward污染检查重标注的done计算逻辑简化reward函数训练过程剧烈震荡future策略采样到t1处的自身状态处理边界条件禁止当前状态作为自己的未来目标策略总在原地抖动随机探索噪声过大正样本被淹没降低exploration noise增大batch sizeBuffer溢出或OOMHER让经验池样本量翻倍状态拼接存储减少每episode重标注样本数降低buffer容量5. 实际操作中的心得体会与适用边界5.1 HER在哪些任务上效果好、哪些任务会失效HER不是万能药它特别适合这类任务目标可以用一个完整状态或者一段状态向量表示并且从任意状态出发都能通过一段有限步的轨迹到达另一个状态。机械臂抓取、物体推动、导航寻路这类具有几何连续性的控制任务HER都能发挥出很强作用。相反如果任务是纯粹的逻辑推理或者非目标导向型任务比如下棋、玩扑克牌这些场景中不存在明确可重标注的目标状态HER就没有用武之地。另外还有一个重要前提目标空间必须和状态空间存在关联。重标注的新目标必须是一个真实可观测的状态不能是抽象概念。比如“赢下比赛”这个目标是boolean值它不是一个可以被重标注的状态。强行套HER只会浪费时间。对于这类任务替代思路往往是课程学习或层级强化学习比如先训练一个低级控制器去完成简单的子目标再用高级控制器调度子目标。HER和课程学习理念上有些相通但机制不同课程学习是人为设计难度阶梯HER是从失败经验中自动构造成功经验。两者可以搭配使用但不应混淆。5.2 HER与先进方法的叠加经验HER有一个很好的特性它和很多主流改进是正交的。我经常在同一个项目里同时使用HER、SAC、自动奖励缩放、以及经验池优先级采样。SAC负责提升探索稳定性和熵正则HER负责补充稀疏奖励下的正样本这两个配合得很好。如果你用TD3也一样能叠TD3的target policy smoothing机制和HER完全兼容不需要改动重标注逻辑。但是要注意叠加时不能无脑组合。经验池优先级采样需要额外处理HER的重标注权重。我用过Proximal Prioritized Experience也就是先按TD误差排序再叠加上一个采样概率。你会发现经过HER改造后的样本TD误差通常比较小因为它们提供的是确定性较强的成功信号。如果优先级系数设置得过高模型会反复重放那些“特殊成功样本”而忽略普通样本训练方差反而变大。我建议把优先级的指数参数从默认的0.6降低到0.4左右用HER时效果更稳。另一个值得考虑的叠加是goal augmentation也就是在重放时额外把“人类演示中的目标”混进重放池这种做法在一些需要高精准度抓取的任务上提升明显因为纯粹依靠self-supervised目标可能会缺少一些“难以自己尝试出来的精致目标”。如果你手头有少量演示数据强烈建议尝试一下这个方向。5.3 HER代码落地时的几点工程建议如果你要从头实现HER建议先用一个简单环境验证整个流程是否跑通。我推荐用OpenAI Gym里经典的bit-flip环境作为验证环境简单且直观。在这个环境里状态是一个n维二进制向量动作是翻转其中一位目标是一个全1向量。这类环境只有到达目标才给奖励非常适合快速验证HER的有效性。我当时的实验结果是n15的bit-flip不用HER时随机策略几乎不可能学到任何行为加HER后大约数千个episode就能稳定到达目标。一旦确认流程正确再迁移到复杂的环境中。迁移时第一步先输出经验池中每个transition的reward分布确保正样本占比不低于20%否则学习会很慢。第二步是记录训练过程中的真实任务成功率注意这里的成功率是“以原始目标评估”而不是以重标注目标评估。很多人只看loss下降就觉得万事大吉结果最后发现loss很低但成功率是0这是因为重标注目标太简单导致Critic和Actor在“简单目标”上过拟合了。我在实际使用中发现HER最大的价值不是让模型直接解决最终任务而是帮模型先学会“如何到达一般状态”。这个能力是很多下游任务的共同基础。建议读者在做机械臂、移动机器人导航任务时把HER作为默认的数据增强手段接入而不是等模型学不动了再亡羊补牢。它实现成本不高却能给训练过程带来质变是当前goal-conditioned RL落地时性价比最高的技巧之一。