资讯动态

事后经验回放HER:破解稀疏奖励的强化学习实战指南

发布时间:2026/10/3 4:47:10 来源:尧图企业网站定制
hindsight这个词我最早是在认知心理学的书里读到的hindsight bias中文一般译作“后见之明偏差”或者“事后聪明”。意思是人在知道结果之后会不自觉地认定“我早就知道事情会变成这样”。后来我转入强化学习工程又在OpenAI的一篇论文里跟它重逢Hindsight Experience Replay事后经验回放。同一个英文单词在两个领域里的命运截然相反心理学把它当成一种需要警惕的认知偏差算法工程师却把“事后聪明”变成了一种专门破解稀疏奖励问题的方法。这篇博文就以这个词为主线把HER算法的原理、代码实现、调参踩坑一条龙讲清楚最后回到我们日常的工程复盘里聊聊人的“事后聪明”是怎么悄悄影响判断的。不管你是刚接触强化学习的研究生还是已经在跑RL项目的算法工程师都能从这里面拿到可以直接用的东西。先说明白整篇文章不搬运教材全部按我自己从读到论文到复现再到自己项目里落地的理解来讲。1. 稀疏奖励困境为什么强化学习项目卡在第一步1.1 从一次失败的机械臂抓取说起先想象一个典型的场景你训练一个七自由度机械臂去抓桌子上的杯子。策略网络每走一步你就检查一下“杯子当前位置”和“你希望它到达的位置”是不是同一个点是就给1不是就给0。50步之内如果还没达成整个回合结束重置。听起来很直白对吧但真正跑起来你会发现训练了几万步之后策略可能退化成了“原地不动”——因为至少不动不会被额外惩罚而且随机探索偶尔还能混到一点噪声奖励。不是网络笨是奖励函数给的信息太少了。整条轨迹里几乎全是0没有任何中间信号告诉它“你往左偏了”“你距离目标近了0.01米”。在深度强化学习里这类环境被称为稀疏奖励环境。问题是现实世界的大部分任务天然就是稀疏奖励的自动驾驶安全到达目的地、机器人把零件装配到位、游戏里打开一扇隐藏的门这些任务在成功之前你就是拿不到正反馈。1.2 稀疏奖励和密集奖励的本质区别密集奖励的意思是每一步都能拿到一个与目标距离相关的数值比如“当前状态与目标状态的欧氏距离的负数”距离越近奖励越大。有了这种连续信号策略相当于被一个人在旁边不停唠叨近了近了远了远了往左一点。强化学习算法在这种环境里表现得非常稳定因为每个transition都携带梯度信息。稀疏奖励则完全不同大部分transition的奖励都是0或者-1只有极少数“成功”的样本才有正奖励。如果用常规的off-policy算法去训练经验回放池里存了上百万条transition正样本的比例可能低到万分之一。Q函数根本学不出“哪些动作导致成功”因为样本不均衡到几乎没有正例。这时候很多人的第一反应是改奖励函数把稀疏变成密集。但这条路的代价比想象中大得多。1.3 手工设计奖励函数的三个致命伤第一伤是偏心。你手工设计的距离度量未必是任务真正的目标。比如机械臂抓取如果你用末端位置和杯子位置的欧氏距离做奖励策略完全可能学到“把手伸到杯子旁边但就是不握紧”因为握紧这个动作在距离度量里没有体现出价值。第二伤是脆弱。奖励函数的系数稍微改一点策略行为就变。为了把系数调到合理范围你可能要跑十几组实验每一组都是几百万步的采样时间成本高得让人崩溃。更麻烦的是换一个环境就得重新调一遍完全没有迁移性。第三伤是作弊。策略会像审计师一样盯住奖励函数的漏洞。只要某个状态能刷出高奖励它就去钻哪怕这个状态和真实目标风马牛不相及。说到底手工奖励函数是一种人为注入先验知识先验错了后面整个算法都跟着歪。所以工程师们一直想要一种“我不用精心设计奖励函数也能在稀疏奖励下学会任务”的通用方法。HER就是在这样背景下被提出的。2. Hindsight的核心思想把失败重写为成功2.1 一个关键的画面每个episode里都藏着黄金HER的想法说来简单到不像一篇NeurIPS论文。我们重新审视一个失败的episode原始目标g策略执行了一段轨迹τ {s0, a0, s1, a1, ..., sT}最终状态sT不等于g所以没拿到正奖励。但请打住sT本身是不是一个合法的状态当然是。既然agent在tT的时刻真的到达了sT那我们干脆换一种叙事方式把目标从g改成g sT然后重新计算奖励。这时候最后一步的奖励很可能就变成了0假设0代表成功。从那一刻起这段原本“失败”的轨迹凭空多出了一批“成功”样本。换句话说HER做的事就是强行让每个轨迹都至少“成功”一次。你本来要去A点没去成最后停在了B点。没关系我把目标改写成B点你成功了。这批改写后的transition被存进经验回放池策略就能从中学到“当目标恰好是B时刚才那串动作模式是对的”。虽然这个“目标契合度”是事后构造出来的但它确实隐含了真实的因果信息——因为那串动作真的把状态推向了B。2.2 HER算法的完整流程下面用伪代码把HER嵌进标准off-policy算法的流程里方便直接对照理解。输入: off-policy强化学习算法(如DDPG/SAC), 目标重标注策略M, 额外目标数K 循环每个episode: 1. 采样原始目标g执行策略π收集轨迹τ [(s0,a0,r0,s1), ..., (sT-1,aT-1,rT-1,sT)] 2. 把原始transition存入replay buffer R 3. 对轨迹中的每个时间步t: # 额外目标重标注 for i in 1..K: g 根据策略M从当前轨迹或buffer中采样一个新目标 r compute_reward(s_{t1}, g) # 用新目标重新计算奖励 把 (s_t, a_t, r, s_{t1}, g) 存入R 4. 从R中采样mini-batch更新actor与critic这四步里面第3步是所有魔法发生的地方。原文中那一步看似简单但有几个工程细节直接影响成败我放在后面的实现章节仔细说。2.3 用“数学考试”类比这个算法我第一次看的时候总觉得它在“造假”。后来想通了它特别像一个数学考试考砸了的学生拿到卷子以后没有只看分数而是把错题翻出来自言自语说“虽然我没做出来题号1但我好歹写出了题号1要用到的辅助线如果当时目标是证明这个辅助线我已经会了。”他把错题重新命名为“我做过什么”然后从里面提炼经验。当然这里面有个微妙之处每次重写目标之后策略被训练成“在给定目标g时模仿那一段行为”但如果g与真实目标分布差距很大会不会把策略带偏HER论文里专门研究了这点结论是只要重标注的目标分布与真实目标分布合理重叠它最终会加速而不是拖慢学习。因为大多数任务的目标空间本身就是连续可达的agent四处乱撞所到过的状态基本都能被其他成功轨迹覆盖到。3. 工程落地HER的代码实现与参数选择3.1 四种目标重标注策略对比HER的具体性能高度依赖“怎么从轨迹里提取额外目标”论文给出了四种策略这里直接整理成表格。策略名目标来源特点final轨迹最终状态sT最简单只在episode结束时取一个目标样本利用率最低但稳定future从当前时间步t之后的任意状态中随机采样因果性最强论文实验中的默认王牌大多数任务上表现最好episode从当前episode的所有状态中随机采样比future更宽松但可能采样到“当前及之前”的状态因果性稍弱random从整个replay buffer中随机采样探索覆盖面最广但噪音大很多目标与轨迹动作毫无因果关联我自己的经验是绝大多数情况下“future”是最省心的选择。原因也很直白你取的是未来某个时间步的状态而那个状态确实是由当前策略动作一步步推过去的因果链条是真实的。用“random”策略时要小心它等于把大量无关目标硬塞给当前轨迹Q函数会被一股脑灌进来的目标分布搞糊涂。3.2 基于stable-baselines3的快速复现现在最方便的方式已经不是从头手写HER而是用stable-baselines3后面简称sb3里现成的HerReplayBuffer组件。它和SAC、TD3、DDPG这些off-policy算法都能配合。我直接给一个能跑的骨架例子import gymnasium as gym import gymnasium_robotics from stable_baselines3 import SAC # 不同sb3版本的HerReplayBuffer导入路径略有差异 try: from stable_baselines3.common.buffers import HerReplayBuffer except ImportError: from stable_baselines3.her import HerReplayBuffer # 注册gymnasium-robotics环境 gym.register_envs(gymnasium_robotics) # Fetch系列的GoalEnvobs是dict类型 env gym.make(FetchPickAndPlace-v4, max_episode_steps50) model SAC( policyMultiInputPolicy, envenv, replay_buffer_classHerReplayBuffer, replay_buffer_kwargsdict( n_sampled_goal4, # 每个transition额外生成4个目标 goal_selection_strategyfuture, online_samplingTrue, ), batch_size256, gamma0.98, tau0.05, learning_rate1e-3, buffer_size1_000_000, verbose1, ) model.learn(total_timesteps500_000) model.save(her_sac_fetch_pick)这里有几个关键参数单独拿出来说。n_sampled_goal每个transition写入buffer时额外重标注的目标数。默认我习惯设4。太小的话相当于每段轨迹只“作弊”出4个成功目标学习信号密度不足太大会让buffer里重写样本占绝对主导原始目标信息反而被稀释同时内存和训练时间线性翻倍。goal_selection_strategyfuture前面表里说了这是综合效果最好的方案。online_samplingTrue表示每个新transition进来时就立刻做一次重标注采样而不是等采集完一整条episode再批量处理。实际工程里前者能更快产生有效样本。buffer_size1_000_000HER大幅度扩充了有效样本量buffer太小容易过早淘汰早期探索样本导致策略还没来得及从中学到东西就忘了那些“成功瞬间”。3.3 哪些任务适合HER判断标准比代码更重要代码写出来不难难的是判断“你的问题是不是HER的菜”。根据我自己踩坑总结能发挥HER威力的任务需要满足以下三个条件。第一任务必须是多目标的goal-conditioned。HER的核心操作是“改目标”如果目标空间只有固定一个点比如“汽车从A开到B”而且初始状态也固定那所有轨迹经过重标注之后目标都是一样的等于没做。HER适合的是那种“目标本身是可采样的状态”——机械臂抓取物体、导航到任意点位、操控物体到不同位置这些全是天然多目标的。第二环境必须是off-policy算法能用的。HER需要把重写后的transition放进replay buffer再从buffer里采样更新这是off-policy算法的标准特性。PPO这种on-policy算法没法直接享受HER的红利它的数据用完就丢重写后的样本根本无处安放。第三目标空间最好是低维连续或离散的。HER里的目标是采样出来的状态如果目标空间是100维的图像或点云随机采样两个目标之间的差距会非常小成功信号还是难以出现。这种情况一般需要先接一个表示学习模块把状态压缩成低维向量再做HER。4. 调参与踩坑实录从零跑到可用4.1 一组真实的实验效果对照当时我拿到HER后的第一个复现实验是在FetchPush系列上做的。这些任务来自OpenAI的机器人环境本身是稀疏奖励的只有机械臂把物体推到目标位置才给reward 0否则每步-1。用纯DDPG跑FetchPush跑了50万步成功率曲线几乎躺平常常在0到0.05之间震荡。换成DDPGHER之后同样的采样步数成功率能冲到0.8上下。FetchPickAndPlace我自己的复现里大概在0.7到0.8之间最难的是FetchSlide要推动一个滑块穿过一段摩擦区域HER加SAC也只能到0.5左右。不同随机种子的波动很明显所以对比基线的时候一定要固定seed多跑几个种子取中位数不要只跑一次就下结论。那为什么HER对“推开”有效对“滑动”就变得费力因为Slide任务的目标状态和中间状态之间隔着一条长而光滑的动力学曲线策略需要精确控制力的大小才能让滑块停在目标附近。HER能提供成功样本但这些成功样本在发力不当的情况下会很快被策略丢弃奖励分布依然稀疏。这提示我们HER不是万能药它解决的是“奖励信号稀疏下的样本利用问题”解决不了“策略表达力不足或动作控制精度不够”的问题。4.2 我踩过的坑坑一直接把环境当成普通Env用。HER要求环境是GoalEnv观测空间必须是包含observation、achieved_goal、desired_goal三个键的Dict空间。很多人报错ValueError: cannot call int() on dict基本就是环境没配成GoalEnv。gymnasium-robotics里注册的Fetch系列默认就是GoalEnv但如果你是自己写的环境要把这一步做扎实。坑二K值太小还赖算法不work。我一开始图省事n_sampled_goal设成1跑了20万步成功率纹丝不动。后来把K提到4甚至8很快就看到曲线抬头。原因是K1时每100个真实transition里只有1个重写成功样本Q函数起跑速度太慢。坑三奖励重标定时把阈值用死了。Fetch环境里compute_reward内部有一个距离阈值默认是0.05。如果你自己写环境注意这个阈值不要设得太大比如0.5那样随便走一步就算成功重写出来的样本全是噪音策略学到的是“原地晃一下就是胜利”。我建议阈值先设成0.05到0.1之间先确认单一奖励尺度下能学出来再考虑放大。坑四忘了探索噪声。HER虽然样本利用率高但依然需要策略在初期做足够的探索才能碰撞出“未来状态”供重标注用。DDPG/SAC里如果噪声scale设成0策略早早陷入局部HER采集到的成功目标都集中在一小块区域后面怎么学都学不动。我一般会让探索噪声在训练前30%的地段时间保持一个相对高一点的值再逐步衰减。4.3 常见问题速查表现象可能原因解决办法训练初期成功率全为0且不动探索噪声太小或目标重标定K过低提高噪声scalen_sampled_goal4曲线中期震荡明显replay buffer太小早期样本被过早冲掉增大buffer_size或降低batch_size策略学到一半开始退化部分重写目标与当前策略能力不匹配换goal_selection_strategy为future并调低K内存占用爆炸每个transition被复制了K1份减小K或buffer_size或改用episodic采样使用PPO时HER完全无效on-policy算法无法利用重写后的历史样本换成DDPG、TD3、SAC等off-policy算法5. 从算法回到人hindsight bias如何影响我们的判断5.1 事故复盘里的“我早知道”讲完算法里的hindsight我想反过来聊点更贴近日常的。心理学里的hindsight bias同样是一个事后判断的故事但它不是在帮助学习而是在扭曲记忆。1975年心理学家Fischhoff做过一个经典实验。他给参与者读一个历史事件的材料要求他们在不知道结果前预测事件结果的概率。过一段时间后再问他们“你当时预测的是多少”结果参与者回想起来都说自己当时给的预测概率更高。也就是说知道了结果之后人会不自觉地把自己过去的判断“修正”得更接近真相。这个偏差在工程复盘里非常危险系统出了事故每个人开完会都能头头是道说“我早就觉得那个模块有问题”“早说过缓存要过期”。但翻聊天记录和决策文档大部分人当时根本没有提出明确的预警即使提了也只是众多猜测中的一个。事后聪明的本质是结果信息覆盖了事前的不确定性让大脑误以为自己一直站在正确的一边。5.2 对抗后见之明偏差的两个实操方法我个人的经验是光靠“意识到偏差”很难克服它必须用工具做对抗。方法一启动前做“premortem”。所谓premortem就是在项目开始时假设项目一年后已经失败然后写下5条可能致因封存起来。等复盘时再打开一条一条对照。这个做法和HER其实有一点神似先预设一个未来结果再往回推原因。区别在于premortem生成的是假设可以提前设计防范措施而hindsight bias生成的是结论通常只用来甩锅。方法二决策现场写“预期笔记”。当你做一个关键技术选型时当场写下来“我选X方案预期收益是什么风险点是什么我觉得它成功率大约多少”。过一两个月再翻开你就能诚实面对当初预测的准确性。我试过一段时间后最直观的感受是自己对不确定性的敏感度提高了开会时更愿意说“我不知道”而不是“我早就知道”。5.3 把失败当作可用的数据而不是可耻的污点把HER和hindsight bias放在一起看会发现一件特别有意思的事。算法里的HER把失败轨迹重构成成功样本它不否认“失败”本身发生过只是换个目标让数据里多出可学习的信息。而人类的hindsight bias在做的是反方向的事情它企图抹掉“事前不确定”这一事实制造一种“我本来就知道”的幻觉。我从HER中学到的思维方式是失败本身不是需要被掩盖的东西它是不带标签的数据。问题只在于你有没有能力换一个视角从里面抽取哪怕一小段接近正确的东西。这种思考方式对我们做模型评估、写技术复盘、做项目规划都成立。6. 最后再分享一点我的使用习惯如果你要在一个新任务里用HER我建议先过一遍自检清单第一任务是不是多目标目标状态能不能被观测并且连续可采样第二用的算法是不是off-policy有没有replay buffer第三训练初期有没有足够的探索噪声保证轨迹能散到不同状态。这三个问题都通过跑HER基本不会让你失望。我自己近两年做机器人操作相关实验已经把HER当成稀疏奖励任务的默认底座之一大部分时间它都能把成功率从“零基础”拉到一个能看的水平。而在日常工作里我也养成了一个配套习惯每次项目立项或者技术选型之前先写一条“预期笔记”把不确定性直接记录在案。这样等复盘的时候知道自己当时什么知道、什么不知道反而比硬撑“我早知道”更能接近真相。事后聪明这件事本身不可怕可怕的是只在事后才装聪明。真正的hindsight是像HER那样把已经发生的结果重新用来设计下一次尝试而不是用来证明自己有多高明。希望这篇从算法到认知的梳理能让你下次再看到hindsight这个词时多一层自己的判断。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑