HER算法解析:用事后经验重放攻克稀疏奖励难题
发布时间:2026/10/1 17:38:53来源:尧图网络
Hindsight 这个词中文直译是“后见之明”说白了就是我们常说的“回头看”。做开发这些年我越来越觉得“回头看”是个被低估的能力。代码写多了会形成惯势踩坑踩多了也会有路径依赖真正能带来突破的反而常常是事后复盘时那个“早知道当时换个目标就好了”的念头。而在强化学习里面这个念头被做成了一个正经算法名字就叫 Hindsight Experience Replay也就是 HER。这篇博文想聊的就是 HER 到底解决什么问题、核心机制怎么拆解、放到自己项目里怎么落地以及我在实际操作中踩过的那些坑。如果你是做机器人控制、目标条件强化学习或者正被稀疏奖励折腾得焦头烂额这篇内容应该能给你一些非常具体的参考。1. Hindsight 是什么一个关于“事后重标”的故事1.1 稀疏奖励为什么是绕不过去的坎先站在一个实际问题面前。假如你让一个机械臂去抓取桌面上的杯子动作空间是连续的四维或七维状态空间动辄几十维。绝大多数尝试里机械臂连杯子的边都没碰到你该给它什么奖励如果给 0那一整条轨迹里所有转移没有任何学习信号策略梯度或者 Q 值的更新都等于在做布朗运动。如果给一个基于距离的稠密奖励比如末端执行器和目标点之间距离的负数那你又要调距离函数的尺度、权重、以及“距离很近但方向不对”的惩罚逻辑。稀疏奖励的真正代价不是“没奖励”而是奖励完全无法覆盖绝大多数状态。对于强化学习来说没有信号的地方就是一片死区。传统解法里奖励塑形是最直接的思路但你等于把对任务的先验理解全部塞进 reward function。这种手工设计不但耗时而且很容易被 agent 钻空子比如只优化距离但不关心抓取姿态。课程学习是另一条路让 agent 从简单目标逐步过渡到难目标但需要人为划分难度等级换一个场景就得重做一套课表。HER 的出现让我觉得很有启发的点在于它把“探索”和“理解目标”解耦了。它不要求环境给你一个完善的奖励函数也不要求人为设计课程而是从已经跑出来的失败轨迹里自己挖掘出有意义的成功信号。本质上它利用了“后见之明”这个人类身上很常见的能力哪怕这次没抓到杯子但我在某个瞬间其实已经把机械臂推到了一个特定位置那我把这个位置当作目标重新学习一遍是不是就有信号了1.2 从“奖励塑形”到“事后重标”的思维切换为了搞清楚 HER 为什么有效得先理解它和常规做法在思维层面的差别。奖励塑形是“事前的”它试图在轨迹发生之前就把每个状态的优劣定义好。HER 是“事后的”它不在优美地设计 reward而在轨迹发生之后把已有的转移数据重新赋予新的目标让原本看起来失败的转移变成“针对另一个目标成功的转移”。这样做的意义非常实际一个是把目标条件化建模另一个是自动生成伪成功样本。在目标条件强化学习Goal-Conditioned RL里策略是带输入参数的即 π(a|s, g)。你关心的是策略能不能在给定不同目标 g 的情况下完成对应行为。HER 做的就是把那些“原本朝着目标 g 失败的轨迹”重新标注成“朝着另外一个后来实际达到过的状态 g′”的成功轨迹。千万要注意HER 不是改环境的奖励它改的是训练时重放缓冲区里的数据。这意味着它和环境的判定逻辑完全解耦环境可以继续用自己的稀疏奖励去评估成功率而训练数据里却充满了密集的有效反馈。我常用一个比喻它像一个复盘教练不改变比赛规则只看录像时告诉你“你刚才这个跑位虽然没接到球但如果把球传向另一个空位就能接住”。这种重新解释数据的能力让学习效率提升了一个量级。1.3 它适合用在什么场景我总结下来HER 特别适合三类场景。第一类是真机或仿真机器人操控比如抓取、推箱子、插销这类任务天然有明确目标状态和稀疏的成败判定。第二类是那些目标空间和状态空间几乎一致的任务这样“把某个状态当作新目标”才自然合理。第三类是已经有 off-policy 算法的项目比如 DDPG、SAC、TD3因为 HER 本质是数据重放技巧需要往现成的经验回放机制里插入数据重标注环节。如果你只是想跑一个静态分类或者网格世界搜索HER 并不是对症的药。它的价值在于目标条件化的连续控制任务而不是普适的强化学习加速器。搞清楚这个适用范围比学会代码实现更重要。2. 核心机制拆解HER 的四个关键设计点2.1 经验元组的重构谁被替换了普通 off-policy 强化学习存储的是五元组 (s, a, r, s′, done) 状态、动作、奖励、下一状态、结束标志。HER 在训练时会把它改写成一个六元组视角的样本 (s, a, r′, s′, g′) 其中 g′ 是一个重新设定的虚拟目标r′ 是按照这个新目标重新计算的奖励。这里有个非常容易误解的细节我们不是把整条 episode 重新标成一个新目标而是逐条 transition 地重标。为什么要这么做因为如果只把整条轨迹的目标换成最后状态那么中间很多动作并不是最优的但至少在目标条件化框架里这些动作可以让 agent 学会“先往某个方向移动”的行为。逐条重标的粒度更细能够提供大量稳定的梯度信号。举个例子。一个轨迹是机械臂从初始位置出发尝试去抓杯子但最后一刻杯子滑落了。原始目标“杯子在 A 点”失败了。HER 会从这条轨迹的中间状态里选择一个状态比如机械臂在 t10 时刻到过的位置坐标把它当作虚拟目标 g′。然后对每一步重新计算奖励如果当前状态离 g′ 越来越近奖励就是正向的。于是这条本来失败的轨迹变成了“成功接近 g′”的优质教学数据。2.2 目标重标策略final、future、episode 和 random虚拟目标不能随便挑挑得不好会引入噪声甚至误导训练。目前主流的四种策略我整理成一张表供你对照策略重标目标来源特点适用情况final轨迹最后一步的状态最简单提供整条轨迹一致的虚拟目标但样本多样性差任务轨迹短、状态空间连续future当前时间步之后随机一个状态最常用兼顾多样性和因果合理性大多数连续控制任务episode整条轨迹任意一步的状态多样性最强但可能选到和当前动作无关的状态探索性较强、任务不敏感random从经验池中随机取目标简单但容易造出无意义组合目标空间极大且随机探索友好我实际用得最多的是 future 策略。原因是 “future” 这个词的定义保证了虚拟目标是在当前时刻之后某个时间步达到过的状态。从因果上看既然后续真的到达了那个状态那么前面若干步的动作行为是有指导意义的。final 虽然更简单但整条轨迹只用一个虚拟目标样本多样性不足训练后期容易过拟合到少数几条轨迹。episode 策略多样性最强但会选出一些和当前动作毫无关系的状态需要更大容量去消化。random 除非有其他考量否则我一直不太推荐因为随机目标一旦落在不与轨迹空间重合的区域就等于凭空制造了不可解样本。2.3 稀疏奖励的替换从距离到布尔值重标目标确定之后下一步是重新计算奖励。最常用的两类是布尔型和距离型。布尔型奖励在很多公开代码里长这样如果当前状态和虚拟目标之间的误差小于某个阈值奖励为 0否则为 -1。有时候也反过来成功给 1失败给 0。关键在于绝对不能沿用原始环境的大稀疏奖励否则重标的意义就没了。在连续控制问题里我倾向于把布尔型替换成带距离度量的奖励比如说reward -np.linalg.norm(achieved_goal - desired_goal, axis-1)这样的好处是每一步都有一个连续的负距离信号即使当前离虚拟目标很远梯度也指向“缩小距离”的方向。需要注意的是距离函数的选择要和状态空间的尺度匹配。如果状态空间里位置坐标是米角度是弧度直接把它们拼成一个向量求范数可能导致角度的微小变化被位置的大尺度淹没。我踩过这个坑后面会在问题章节展开说。2.4 为什么必须搭配 off-policy 算法HER 从模型设计上就和 off-policy 算法绑定。原因在于它的核心动作是“修改经验池里面的历史数据”再随机采样去训练。这要求算法本身允许从历史经验中反复采样也就是需要经验回放机制。像是 DDPG、TD3、SAC、DQN 这些 off-policy 方法天然适合。而 policy gradient 类的 on-policy 方法比如 PPO、TRPO每一轮数据用完就要丢弃HER 就无从施展了。实操中我会优先选 DDPG 或 SAC 作为底层算法。DDPG 简单直接和 HER 组合很经典SAC 则额外引入熵正则化探索性更强。如果你用的是 TD3也可以无脑套 HER因为 TD3 本身就是 DDPG 的稳定版逻辑上完全兼容。值得提醒的是如果是 off-policy 算法但目标空间和状态空间不一致需要保证重标的目标能够合法映射回状态空间否则会出现“训练时用一个状态测试时拿不到这个状态”的窘境。3. 实操落地从环境准备到超参设定3.1 第一步准备一个适合 HER 任务的目标条件环境刚开始跑 HER 的时候很多人会直奔 OpenAI Gym 里的 Fetch 系列环境。Fetch 系列确实是把 HER 发扬光大的基准但它们对环境依赖较多跑一次训练也要不少时间新手很容易在环境安装上卡住。我的建议是先用一个自定义的简单环境验证核心流程确认 HER 或适用于你的场景后再切换到重负载环境。一个简单的目标条件环境可以设计成二维平面上有一个小球动作是水平与垂直方向上的位移增量目标是到达某个坐标点。状态是当前位置坐标目标是目标坐标稀疏奖励定义为“到达阈值 0.1 以内给 0否则 -1”。这个环境小而清晰非常适合观察 HER 是否生效。下面是一个精简的环境定义片段class SimplePointEnv: def __init__(self): self.state np.zeros(2) self.goal np.zeros(2) self.threshold 0.1 def reset(self): self.state np.random.uniform(-1, 1, size2) self.goal np.random.uniform(-1, 1, size2) return self.state, {achieved_goal: self.state.copy(), desired_goal: self.goal} def step(self, action): self.state self.state np.clip(action, -0.1, 0.1) reward 0.0 if np.linalg.norm(self.state - self.goal) self.threshold else -1.0 done False info {is_success: reward 0.0} return np.concatenate([self.state, self.goal]), reward, done, info注意这里返回的 state 我把当前状态和目标拼在了一起这样便于对接传统 off-policy 算法。HER 重标时会直接操作 env_info 里的 achieved_goal 和 desired_goal而不是操作拼接后的 state这是一个非常重要的约定很多人在自定义环境时把这块搞混导致重标后样本完全乱掉。3.2 第二步实现 future 目标重标逻辑以最常用的 future 策略为例重标发生在一条完整 episode 结束后。我们需要保存这条轨迹里的状态、动作和 achieved_goal 序列。重标的代码大概长这样def relabel_episode(episode, k4): new_transitions [] T len(episode[actions]) for t in range(T): for _ in range(k): future_t np.random.randint(t, T) new_goal episode[achieved_goal][future_t] new_reward compute_reward(episode[achieved_goal][t], new_goal) new_transitions.append(( episode[observations][t], episode[actions][t], new_reward, episode[observations][t 1], new_goal, False )) return new_transitions这个片段有两个细节值得强调。第一个是np.random.randint(t, T)它保证只从当前时刻之后采样符合 future 策略的定义。第二个是重标后的 done 标志我统一设成了 False因为虚拟目标并不是环境的真正终止条件如果置成 True算法会误以为后续没有状态Q 值的 bootstrapping 就会出错。如果你用的是 OpenAI Baseline 里 deepq 或 ddpg 的代码风格通常做法是维护一个临时 buffer把原始轨迹和重标后的轨迹同时塞进去。原始轨迹的奖励还是按原目标算重标轨迹则按新目标算。两者在缓冲区里共存训练时统一被采样。3.3 第三步训练主循环如何衔接经验池训练主循环的逻辑其实不复杂。每跑完一个 episode我们先不做任何训练把轨迹暂存。等 episode 结束把原始轨迹写入经验池再从这条轨迹生成 K 条重标轨迹也写入经验池。整体结构可以抽象成下面这组伪代码for epoch in range(epochs): episode [] obs env.reset() done False while not done: action policy.select_action(obs) next_obs, reward, done, info env.step(action) episode.append((obs, action, reward, next_obs, done, info)) obs next_obs replay_buffer.add_episode(episode) # 原始经验 replay_buffer.add_her_samples(episode, k) # 重标经验 for step in range(total_steps_per_epoch): batch replay_buffer.sample(batch_size) algorithm.update(batch)经验池的容量值得你刻意调大。因为 HER 会额外生成 K 倍的重标样本如果容量太小旧数据很快被新数据冲掉会导致重标经验来不及被充分学习。我常用的配置是原始容量乘上1K例如本身打算用 50 万条经验那 HER 场景下我会开到至少 200 万条。3.4 超参数设定K 取多大最合理K 是每个原始 transition 额外生成的重标样本数这是 HER 最核心的超参数。论文里的标准设定是 K4我的实际经验是 K4 在多数连续控制任务上就是“甜点区间”。如果 K 取 1虚拟样本太少稀疏奖励问题不能有效缓解。如果 K 直接上到 20缓冲区内重标样本严重过剩模型反复学习那些“容易完成”的伪目标反而降低了真实目标的拟合能力。这里有个逻辑可以帮你理解K 的本质是在真实数据和伪数据之间取一个平衡。K4 意味着每个真实转移伴随 4 个伪目标转移模型每看一条真正轨迹就要看 4 条“后见之明”轨迹。这个比例既给了足够的恢复信号又不至于让模型忽略真实目标。训练步数建议排在 100 到 200 epoch 左右观察收敛趋势。如果你用的是 Fetch 之类复杂环境需要耐心一点前 50 个 epoch 里 reward 曲线很可能是平的因为 agent 还没构造出有效的策略。我自己见过不少人在前 10 个 epoch 看到奖励没变化就跑去调参实际上 HER 的收益通常要在探索积累一定量之后才显现。4. 常见问题与排查技巧实录4.1 奖励尺度不匹配导致训练发散这是我最常遇到的问题。如果你自定义环境时状态由多个物理量拼接而成比如位置、速度、姿态直接用它们的欧氏距离作为奖励尺度大的分量会主导梯度尺度小的分量几乎学不到。举个例子位置范围在 [-1,1]角度范围在 [-π, π]角度稍一变距离变化可能很大机械臂就会优先学“转动关节”而不是“接近目标”。一个有效的排查方法是把训练前期的虚拟目标奖励打出来看数值分布。如果幅度突然从 0.1 跳到 5说明尺度不协调。解决办法有三个一是分别计算物理量各自的距离再加权求和二是对状态做归一化三是在 reward 外面加一个适当的缩放系数。我惯用的是第二种先把观测和目标的每一维都缩放到同一量级再去算距离。4.2 虚拟目标选得“太容易”或“太难”HER 说白了是靠重标样本引导学习但如果虚拟目标分布不当也会适得其反。太容易的情况是虚拟目标就在当前状态附近距离很近奖励几乎是 0梯度非常小模型学不到什么。太难的情况是选择的虚拟目标来自一条极长的轨迹末端远超当前步可达范围模型要预测的回报跨度太大Q 值估计极不稳定。future 策略天然规避了一部分问题因为它从当前之后的状态里采样目标总是“未来能到达的”。但如果你把 K 调得很大或者 episode 长度过长future 采样出来的虚拟目标也可能距离太远。我处理这类问题的方法是在重标时加一个距离过滤只选择与当前状态距离在合理范围内的未来状态作为虚拟目标超出范围的丢弃。这个过滤项不会显著增加代码复杂度却能明显稳定训练曲线。4.3 成功判定与奖励函数不一致很多任务通过 info 里的 is_success 判断是否成功但训练时用的 reward 可能是距离型。这两个概念一旦不一致就会出现评估已经成功但奖励还在负值的情况或者奖励显示接近目标但 is_success 始终为 False。测试时我们看 success 率训练时 Q 函数优化的是 reward两者跑偏会让曲线看起来毫无进展。举个例子位置误差阈值设成 0.1但距离奖励用的是原始欧氏距离数值模型优化到 0.05 时已经可以成功但 reward 还在 -0.05它还会继续优化反之如果模型停在 0.12reward 相对较好却因为没有进入成功阈值导致 success 率一直上不来。这个问题的标准化解法很简单要不把成功判定当作奖励雷同的规则要不奖励和 success 使用同一个阈值和同一种度量。4.4 训练前期的伪成功率假象HER 让人高兴得太早的假象也发生过。因为训练数据里充斥着重标后的伪成功样本模型在缓冲区上表现出很高的“成功率”但如果换到真实环境这个成功率往往对不上。原因很好理解重标样本的目标是后来才选定的并不代表 agent 在一开始就主动朝着它走。模型只是学会了“朝某个已到达过的状态修正”而真实环境里没有这种事后修正机会。因此我强烈建议你把评估和训练彻底分开。训练时用 HER评估时绝不加载重标数据必须让 agent 在真实验证环境里跑完整 episode直接记录 is_success。另外评估环境里不能开启重标逻辑否则测出来的指标就是自欺欺人。这个坑很隐蔽因为你肉眼看着训练 loss 在降可能会误以为一切正常。5. HER 的扩展方向与场景思考5.1 抓取、推动与移动机器人HER 最直接的应用是在机械臂抓取与操控任务里。无论你用的是 Fetch、Franka 还是 UR 机械臂目标通常是六自由度的抓手位姿或者物体的位置。在这些任务里稀疏奖励问题极其严重因为机械臂必须完成一整套接近、对齐、闭合抓手的流程才算成功中途任何一步偏差都会导致失败。HER 的价值在于把一次失败轨迹拆解成多个“接近某个位姿”的成功片段让策略逐步掌握整个运动链。移动机器人导航也可以用到 HER。目标点是二维坐标轨迹中车辆虽然没到达最终目的地但途经了很多中间位置。把这些途经点当作虚拟目标机器人就能学到“先移动到中间点”的子策略最终逐步逼近终点。这个思路和分层策略不同它不需要预先定义子目标一切都从数据里自动涌现。5.2 与 DDPG、SAC、TD3 的组合配置如果你用 DDPG HER我建议把探索噪声保持在一个适中的水平。噪声太小探索不足重标样本的多样性不够噪声太大轨迹自己都偏离方向虚拟目标也会变得杂乱。我习惯用 OU 噪声但把 std 设得比普通任务略小因为 HER 已经在数据层面增加了探索。换成 SAC 的话熵系数初始值建议比常规任务略高一点。SAC 本身鼓励探索配合 HER 可以快速覆盖更多状态。TD3 和 HER 的组合也比较顺滑因为 TD3 的 target smoothing 可以抑制 Q 值的高估正好对冲 HER 重标样本可能带来的 Q 值偏差。5.3 对稀疏奖励问题的方法论启发如果你从 HER 抽离出来看会发现它带来的启发其实很朴素失败数据里往往藏着可被重新解读的信号。传统强化学习把 rewards 当成客观事实但 HER 告诉我们在目标条件化场景下目标可以是在事后被调整的。这种信息重用的思想可以迁移到很多领域比如模仿学习里给示范数据重新定义意图或者逆强化学习里从多条轨迹推断潜在目标。做项目时遇到稀疏奖励问题我不建议第一时间就上 HER而是先梳理清楚你的状态、目标、动作三者之间的关系。只有当目标状态可以被表达成状态空间里的点HER 才有用武之地。如果你的目标是一个抽象概念比如“完成任务”或者“让用户满意”那 HER 帮不上忙因为无法定义“把哪个状态当作目标”。我个人实际操作下来的体会是HER 之所以有效不完全是因为它增加了数据而是因为它改变了模型对“成功”的定义方式。训练一套从失败中提取有效信息的算法远比单纯调大经验池有意义。把虚拟目标的重标当作数据增强来理解你可能觉得它平平无奇但真正在你的机器人任务上跑起来看到原本纹丝不动的 success 率开始爬升时那种“原来是这么回事”的感觉还挺值得体验的。最后再分享一个小技巧如果你在复杂环境里跑 HER 发现训练不稳别急着调网络结构先重新检查一下你的 achieved_goal 是从哪里取的。很多人的问题出在 info 里传的是当前观测而不是真实验证后的目标状态这一步错了HER 再强也白搭。
网站建设高端定制企业官网