事后经验回放(HER):把失败轨迹变成稀疏奖励下的学习信号
发布时间:2026/10/2 9:01:40来源:尧图网络
干我们这行的谁没被 hindsight 这个词噎过。生活里它常被当贬义词指“事后诸葛亮”式的马后炮但在强化学习圈子里它恰恰是稀疏奖励任务从“学不动”变“学得动”的关键一招。尤其是 Hindsight Experience ReplayHER出现之后“失败轨迹”不再是一堆没用的零奖励数据而能被重新标成有效经验继续训练。这篇内容我想把 hindsight 拆成两层来讲一层是强化学习里的 HER 算法另一层是它背后那套“把失败终点当学习目标”的复盘方法论。适合两类人看一类是想在稀疏奖励环境里跑通 RL 任务的算法工程师另一类是整天做项目复盘、但总觉得复盘没复出东西来的团队负责人。读完你至少能明白一件事失败本身不产生价值从失败轨迹里重新提取目标才产生价值。1. 从“事后诸葛亮”到“事后经验”hindsight 的两副面孔1.1 大众语境里的后见之明为什么招人烦先说实话日常对话里“hindsight”这个词很少有人喜欢。项目失败了有人跳出来说“我早就知道会这样”这确实不讨喜因为这种表态既没有提供新的信息也不承担任何决策责任纯粹是在用时间差制造优越感。真正的 hindsight 不应该停留在“我早就知道”而应该是“我现在知道并且下次能用上”。这两种后见之明的差别本质上是主动和被动的差别。被动型复盘只关注“结果偏离预期”然后止步于情绪判断主动型复盘会把结果当作一个客观发生过的状态想办法把状态倒推回决策链条里找出哪些中间节点本来可以改变哪些节点即便重来一遍也大概率会这样。后者才是工程意义上的后见之明。在技术语境里我把 hindsight 当成“事后状态重解释”的能力。它不要求你当时就预测对但要求你在事后能从已知终点出发反向重构出一条可学习、可复用的路径。这一点说出来很简单做起来却很难因为大多数人复盘时都盯住了“原本想达成的目标”而不是“实际到达的状态”。1.2 强化学习里为什么非要“事后经验”强化学习里的难度大家也都知道最难受的还不是环境复杂而是奖励太稀疏。机械臂推一个球球刚好落进目标区域给 1其他情况一律给 0。在这种设定下一条完整的 episode 跑完如果球没进目标区整条轨迹的奖励就是一大串 0。算法面对这一串 0完全没有梯度信号不知道该往哪个方向优化。这就像一个人蒙着眼在迷宫里找出口走了一百步没撞到墙但也没找到路他根本不知道自己哪一步走对了。稀疏奖励问题在 goal-conditioned 任务里尤其致命。goal-conditioned 的意思是智能体的目标不是一个固定任务而是一个可以不断切换的“目标状态”比如“把球推到坐标 (0.5, 0.2)”。环境动力学不会因为目标变化而变化球还是那套物理规律但每个 episode 开始时你告诉它的目标不同。正是这个“目标不影响动力学”的特性给 hindsight 留出了操作空间既然换目标不会改变环境反馈那我不如直接换掉目标本身。HER 的核心操作因此非常反直觉一条轨迹没达成原目标原本应该给 0 奖励但如果我们把目标改成轨迹实际到达的终点状态那么同一条轨迹从“失败案例”瞬间变成一个“成功案例”。新的目标下轨迹前半段的奖励可能还是 0但越靠近终点奖励越接近 1学习信号就出来了。这一招解决的不是环境探索问题而是“经验利用”问题——同样的探索数据通过重新标注信息量翻了几倍。2. 核心技术拆解HER 是怎么把失败变教材的2.1 核心思想把失败终点改成学习目标HER 全称 Hindsight Experience Replay中文可以叫“事后经验回放”。它建立在 off-policy 强化学习框架之上核心流程一句话就能说清每跑完一条 episode保留原始经验同时额外生成几条“事后视角”的经验做法是把原目标 g 替换成这条轨迹到达过的某个未来状态 g再按新目标重新计算每一步的奖励。为什么有效可以这样体会。假设你让机器人把红色方块推到左上角它推偏了方块停在了右下角。原目标下所有奖励是 0没有任何反馈。但如果你把目标临时改成“推到右下角”那么这条轨迹最后一段恰好就是成功轨迹你等于免费获得了一条带有正奖励的演示。只要环境动力学与目标无关这条轨迹对“学习如何把方块推到右下角”这个子问题就是完全有效的数据。用考试来类比你做错了一道计算题但如果把题目里的最终答案临时替换成你的错误结果你会发现你其实在做一道“新题”时完整写出了解题过程这个过程本身是可复用的。问题是你不能总靠这种替换糊弄考试但在 RL 里我们需要的是“解题过程”而不是“答案本身”所以这个替换就是合理的。2.2 机械臂推球一个能秒懂的例子拿 OpenAI 早期论文里常用的机械臂推球环境来说。状态是关节角度、末端位置和球的位置目标是球最终停在目标位置 g。每条 episode 开局随机给一个 g机械臂推球如果最终球离 g 小于某个阈值奖励 1否则 0。实际运行时会发现在机械臂完全没有经验的前期球基本乱跑一百条 episode 里可能一条成功都没有。普通 DQN 或者 DDPG 在这种数据下只会学到“怎么都无所谓”因为所有样本的价值目标都收敛到 0。但 HER 在每条 episode 结束后会从轨迹未来状态里随机采样 k4 个位置比如“球在第 12 步经过的位置”“球在第 30 步的位置”等等把这些位置当作新目标重新计算各时间步的奖励。于是同一条乱推的轨迹被拆分出四五个“看似有人指挥”的成功子轨迹。更妙的是这些重标注后的轨迹里奖励变化是有渐进趋势的。因为在轨迹中球是从初始位置慢慢移动到终点 g 的对“终点 g”而言每一步的球位置都在接近它。如果你把奖励做成连续函数比如负距离那你得到的是一条从负分缓升到 0 分的曲线比纯二值奖励的样本友好得多。即使奖励还是二值的轨迹最后几步离 g 很近也更容易命中阈值变成 1。所以 HER 不只是增加正样本还增加了“从远到近”的渐变型正样本序列。2.3 HER 的完整流程与伪代码把 HER 放进标准 off-policy 循环里整个流程大概是策略采样一条 episode原始数据入回放池然后对同一段轨迹做多次重标注生成额外样本入池训练时从池中随机采样 batch更新 Q 网络或 actor-critic 网络。为了避免轨迹被“原生目标”遗忘原始经验必须保留不能全用重标注样本。伪代码可以写成这样方便理解import numpy as np def relabel_episode(episode, k4, rngNone): episode 包含状态序列 obs、动作序列 acts、原目标 goal if rng is None: rng np.random.default_rng() N len(episode[obs]) new_experiences [] for t in range(N): # 候选目标当前时刻之后的未来状态包括最终状态 future_states episode[obs][t 1:] if len(future_states) 0: continue # 随机抽取 k 个未来状态作为事后目标 selected rng.choice( len(future_states), sizemin(k, len(future_states)), replaceFalse ) for idx in selected: new_goal future_states[idx] # 用新目标重新计算奖励done 也要按新目标判定 new_reward compute_reward(episode[obs][t], episode[acts][t], new_goal) new_done is_goal_achieved(episode[obs][t 1], new_goal) new_experiences.append({ obs: episode[obs][t], act: episode[acts][t], reward: new_reward, next_obs: episode[obs][t 1], goal: new_goal, done: new_done, }) return new_experiences # 每完成一条 episode 后调用 for traj in collect_episodes(policy, env): replay_buffer.add_original(traj) relabeled relabel_episode(traj, k4) replay_buffer.add_many(relabeled)注意这里的done跟 RL 里的“回合终止”不完全一样。重标注目标选在第 30 步的位置第 30 步是终点但 episode 可能在 50 步才真正结束所以done需要单独定义成“目标达成”而不是“episode 结束”。这个细节很多人第一次写会弄错后面排查问题时会再提。2.4 三个关键参数与设置思路HER 有两个核心超参数和一个必须理解的前提条件。第一个核心参数是k即每条原始轨迹生成几条重标注轨迹。原论文常用 k4再大不一定更好因为重标注轨迹之间存在相关性多了会导致缓冲区内经验分布偏斜。第二个是目标采样策略常用三种final只选最终状态、future从轨迹当前时刻之后随机选、random从整条轨迹随机选。实际效果上 future 通常最稳因为它既保留了时间上的因果性又兼顾了样本多样性。第三个真正容易踩坑的是目标空间维度。HER 把目标当作状态的一部分去训练目标维度太高采样效率会急剧下降。比如目标是一个 2048 维的图像向量那“未来状态采样”几乎不可能采到有语义关联的目标HER 就退化成了普通 replay。所以 HER 在实践中更适合目标可表示为低维状态向量的场景比如坐标、姿态角、速度等。如果目标必须是高维图像通常需要先接一个编码器把状态压缩成低维特征再采样。还有一个前提条件必须记住环境动力学不能依赖于目标。只有满足“目标只是条件不影响状态转移”的假设重标注轨迹才能被认为是有效的。比如“把开关拨到左边”这类需要和目标产生物理交互的任务换目标之后轨迹的物理过程其实变了HER 就不能直接套用。3. 实操过程与核心环节实现3.1 环境与依赖准备我这边为了写这个复现流程选了一套最省事的组合OpenAI Gym 的 FetchReach 类环境 stable-baselines3。这些都属于公开的 RL 库安装直接用 pip 就行我这里不多展开。性能上不追求极致重点是验证 HER 是否真的能让稀疏奖励任务在几千步内就开始出现正奖励。安装之后先把环境跑通确认观测空间和目标空间的结构。FetchReach 这类环境里goal 通常是三维坐标observation 包含机械臂状态和物体位置。要特别注意目标不是动作的一部分它是以额外输入形式塞给策略网络的。在 stable-baselines3 里这对应 MultiInputPolicy观测侧是 dict包含 observation 和 desired_goal 两个 key。还有一个环境细节值得多说一句这类环境默认带有 compute_reward 方法也就是你可以传入状态、动作和 goal立刻得到奖励。HER 的重标注完全依赖这个函数所以如果你要自定义环境一定要把 compute_reward 写正确并且保证它只依赖状态、动作、目标不依赖任何内部计时器。否则重标注出来的奖励和新目标对不上训练会不稳定。3.2 基于 stable-baselines3 的复现配置SR 上最直接的写法是直接用 stable-baselines3 的 HerReplayBuffer。配置代码非常短from stable_baselines3 import SAC from stable_baselines3.her import HerReplayBuffer model SAC( MultiInputPolicy, envenv, replay_buffer_classHerReplayBuffer, replay_buffer_kwargsdict( n_sampled_goal4, # 相当于论文里的 k goal_selection_strategyfuture, # 推荐用 future ), batch_size256, learning_starts1000, # 先随机探索一千步再开始学习 buffer_sizeint(1e6), tau0.05, gamma0.98, verbose1, ) model.learn(total_timesteps200_000)这里选 SAC 而不是 DDPG是因为 SAC 在连续控制任务里更稳调参压力小。HER 本身不绑定特定算法只要算法是 off-policy 就行。on-policy 算法比如 PPO 理论上也能用重标注后的数据但它要求数据来自当前策略重标注不改变行为策略却会改变价值估计实际上混着用很容易出错所以实战里基本都是 DDPG、SAC、DQN 这类搭配。learning_starts1000也很关键。HER 的价值在于利用已经产生的轨迹如果训练一开始就更新网络缓冲区里样本太少重标注也没意义。先让策略随机跑一跑积累一批“失败轨迹”再开始学训练曲线会更稳。3.3 训练效果怎么判断不要只看总奖励曲线HER 作用下的曲线经常在一个平台上横很久然后突然提升。更好的判断方式是观察回放缓冲区里的“有效样本比例”随机从 buffer 里抽 1000 条样本统计其中奖励非 0 的比例。如果比例在稳定上升说明重标注真的在起作用如果长期为 0说明目标采样策略或者奖励函数写错了。我一般会额外打印一个指标每个 episode 里重标注后的样本中第一次出现正奖励的时间步分布。你会发现随着训练推进出现正奖励的时间步会越来越早这说明策略不是在靠运气撞目标而是在学习“如何从更早的位置逼近目标”。这个趋势一旦出现基本可以确认 HER 没有白装。如果训练结束测试时要评估“对原始目标的表现”注意测试阶段不要做任何重标注让智能体真正执行原目标。HER 只是训练技巧不是推理技巧。测试表现能到多少取决于策略是否真的学会了对不同目标都有良好泛化这也是 HER 的终极目标——让同一个策略能应对“任意目标”而不是只会处理训练时见过的目标。3.4 我踩过的坑与补救过程第一坑奖励始终全零。我当时自定义了一个环境重标注函数写好了但 buffer 里抽出来的样本奖励全是 0。查了一下午发现 compute_reward 里判断目标达成的阈值设到了 1e-6而环境本身状态噪声就有 1e-3 量级导致任何目标都“不可能达成”。这不是 HER 的问题是奖励函数设计的问题。后来我把阈值放宽到 0.05relabel 后的正样本立刻出现。第二坑K 太大导致训练震荡。我一开始图省事把 k 设成 8想着样本越多越好。结果 buffer 里重标注样本占了九成原始目标的经验被严重稀释策略对原始目标的记忆变得越来越差。后来把 k 降到 4并确保原始轨迹全部保留问题才缓解。重标注经验不是免费的午餐它本质上是在“目标条件分布”上做重采样过度采样会改变策略要解决的分布。第三坑目标维度和未来窗口不匹配。另一个任务里目标是一个 32 维向量HER 的 future 采样基本采不到语义相关的目标训练曲线一直在原地。后来我把目标换成关键位置的 3 维坐标采样窗口也从“整条轨迹的未来状态”改成“只看轨迹后半段”效果才出来。这提醒我HER 的目标空间设计直接影响采样质量目标越简单、越连续重标注越有效。4. 把 hindsight 从算法搬到工作流通用复盘方法4.1 为什么工程师也需要“事后重标注”算法上的 HER 给我最大启发不是它的公式而是它的前提我们不缺失败轨迹缺的是对失败轨迹的重新解释。项目复盘也一样。绝大多数团队做完一个项目手头有的是完整的记录、聊天记录、代码提交记录、事故报告这些就是“原始轨迹”。但复盘会上大家讨论的是什么是“我们原本要达成的目标为什么没达成”。这个角度没有任何增量信息因为它没有提供新的目标函数。真正有用的复盘应该像 HER 一样先把“原本目标”放到一边认真回答几个问题这个项目实际到达的是哪个状态哪些决策在这个状态下其实是对的换成一个更合理的目标这段经历能不能变成可用经验这些问题听起来很绕但本质上就是在做经验重标注。你只是在把“失败案例”改写成“在另一种目标定义下的成功案例”然后把它存入团队的回放池也就是流程文件、总结文档、规范清单。4.2 五分钟复盘模板把失败经验“重标注”我实际用下来一个不花太多时间但足够有效的复盘模板只需要四栏对应 HER 的四个环节复盘栏目要回答的问题对应的 HER 概念目标重述我们原本设定的目标是什么原目标 g实际终点项目最终真实到达的状态是什么事后目标 g奖励重标按实际终点回看哪些动作和决策真正有效relabel 奖励样本入池这一条经验是否值得写进下次的检查清单或规范replay buffer每栏写不超过三行控制在五分钟内。第一栏和第二栏的差距决定了复盘有没有信息量。差距越大说明不确定性和运气成分越高这时候不要急着追责而要承认这个项目里存在大量“换目标即成功”的偶然路径这些偶然路径值得记录。第三栏是核心很多人在这里会发现导致项目失败的关键决策在另一个目标定义下反而是最优决策。这不是为失败开脱而是在承认决策质量不能只看单次结果还要看它对不同目标的泛化能力。第四栏要求所有人把提炼出的经验写进可执行的文档而不是散落在会议纪要里。4.3 复盘的两个常见误区第一个误区是把复盘做成追责会。追责会问的是“你是谁”复盘问的是“换一个目标设定这件事还成立吗”。一旦讨论走向个人责任信息就会被防御心理过滤重标注就失去意义。我在的实际经验是复盘会上坚决不出现“当时为什么不……”这种句式问题全部改成“在当时的输入下哪些信号是被我们忽略的”。第二个误区是“如果当时做了 X 就好了”。这种句式表面上是反思实际上是在用“更好的结果”作为目标对旧轨迹做单点修正而不是重标注。它不会产生可复用的经验因为“如果当时”只对那一条路径有效。正确的问法应该是“在同样的情境下下次我要用什么样的判断原则”。从单个决策上调到判断原则才真正把经验从一条轨迹泛化到一类场景这和 HER 中“换一个目标”的思路是一致的。5. 常见问题与快速排查清单5.1 训练不收敛的快速排查表我把训练 HER 时最容易遇到的问题整理成了表格按优先级排序。如果你在跑类似项目可以直接对照。症状可能原因快速排查动作训练很久奖励仍然全 0compute_reward 阈值过严或目标空间定义错误随机抽 1000 条 buffer 样本打印奖励分布确认 relabel 后存在非 0 奖励测试时智能体不执行原目标乱跑重标注样本占比过高原目标经验被稀释统计 buffer 里原始轨迹和重标注轨迹比例控制 k4并保留全部原始样本连续控制动作发散、震荡目标维度过高或学习率过大降低目标维度到低维状态向量试试把学习率降到 3e-4 以下k 降到 2训练曲线前期正常后期突然崩溃缓冲区目标分布漂移可能采样策略被训练过程污染检查是否在训练中修改了目标生成方式确认采样使用固定随机种子策略正奖励有了但策略就是不提升奖励尺度不合适二值奖励梯度太稀疏改成基于距离的连续奖励比如-dist并适当缩放其中第二条是我见过的最隐蔽问题。eset HER 确实会增加大量有效样本但如果重标注样本比例远大于原始样本policy 会越来越倾向于“服务重标注目标”而对用户真正关心的原始目标反应迟钝。要记住重标注样本是辅助原始样本是主线两者的主旨是互补不是替代。5.2 三个容易忽略的实现细节第一个细节done标志要区分“目标达成”和“回合终止”。重标注目标往往选在轨迹中间状态这时该条经验不算回合终止否则会造成目标状态和终止状态混淆价值估计偏差很大。实现时建议单独算一个goal_achieved布尔值不要直接沿用done。第二个细节未来目标采样时是否包含当前时刻本身。通常选t1到T之间的状态不包含t时刻。如果你把当前状态也加进去会出现“目标就在脚下”的天然正奖励样本导致策略学会偷懒什么都不做就直接拿奖励。第三个细节目标归一化。如果目标坐标范围很大比如从 0 到 100而状态范围在 0 到 1HER 采样出来的目标分布会很乱Q 网络也很难拟合。把目标空间做归一化或者至少保证目标的分布和状态分布尺度一致这是很多人忽略但影响很大的一点。5.3 最后的实操心得我自己的切身体会是第一次把 HER 跑通的时候最让我震撼的不是“失败轨迹也能学习”这个结论而是它让“目标”从一个固定指令变成了一种可以事后修正的视角。这个思维一直被我带进日常项目复盘里效果比任何 checklist 都持久。跑通 HER 时一个小技巧很管用训练早期就定期保存那些“失败轨迹”的切片并可视化看球的位置变化轨迹。你很快会发现策略虽然没达成目标但已经在尝试向多个方向探索重标注之后这些探索方向全变成了有效梯度。每次看到这个画面我对“探索数据不规则但绝不浪费”这句话就多一层理解。如果你也正在被奖励稀疏问题折磨先把注意力从网络结构上移开认真检查你的回放池里有多少条经验可以被重新标成学习信号。答案往往比你想的多得多。
网站建设高端定制企业官网