HER强化学习目标重标记:破解稀疏奖励难题的实用指南
发布时间:2026/10/1 12:28:08来源:尧图网络
1. 先搞清楚 hindsight 到底在解决什么问题先别急着看代码我需要先讲清楚为什么会有 hindsight 这个东西。如果你是刚接触强化学习不久的读者大概率遇到过这种场景写了个 DDPG 或者 PPO在 Gym 的拿物体、推箱子这类任务上训练了几百万步reward 曲线纹丝不动偶尔冒个尖又掉下去最后你怀疑人生地检查网络初始化、reward scale、learning rate结果全都正常。问题出在哪十有八九出在稀疏奖励上——智能体整条轨迹一个正向奖励都没拿到梯度不知道该往哪个方向走自然学了个寂寞。hindsight 这个词翻译过来是“后见之明”它对应的论文是 2017 年发表在 NeurIPS 的Hindsight Experience Replay作者团队是当时谷歌 DeepMind 的第一作者是 Marcin Andrychowicz。这篇论文的核心表述其实特别朴素既然智能体在一个 episode 里没能达成预期目标那我们就换个思路把“它实际达到的状态”当作这次经历的目标让这段失败的轨迹在重放时变成一条“成功的经验”。这就像一个人投篮没投进但球擦着篮筐飞过去落到了篮板后面——你回头复盘的时候不能说这次投篮白费了至少你知道了在“把球打到篮板那个位置”这件事上你的发力方式是可行的。把这个逻辑迁移到强化学习里面就是 today 的 HER。这项技术适用的范围很明确稀疏奖励、多目标、目标达成类的任务。换句话讲如果你做的任务是用机器人手臂抓取物体、走迷宫到达终点、控制机械夹爪把方块摆到特定位置并且你发现当前奖励函数几乎是全零HER 大概率是让你逃离泥潭的最有效手段之一。当然它更适合配合 off-policy 算法用比如 DDPG、TD3、SAC因为在 on-policy 算法里对经验做二次标记重组逻辑上会有些别扭。下面我们把整个思路拆开从原理到实现再到我实际复现时踩过的坑一条条过一遍。2. HER 核心机制目标重标记到底做了什么HER 的全文核心只有一件事改变经验回放时“目标”的取值让失败的轨迹也能对策略更新产生正向作用。这句话看过去容易实现起来牵扯的细节却不少。2.1 一个成功的回放片段长什么样在普通 off-policy 强化学习里我们需要维护一个经验池里面存的每条 transition 大概长这样(s_t, a_t, r_t, s_{t1}, done, g)这里的 g 就是这条轨迹对应的目标。拿“把物体推到指定位置”这个任务来说g 是一个三维坐标比如 [0.3, 0.2, 0.1]表示我们希望把正方体推到空间里的这个点。普通回放时我们直接从池子里采样这些 transition 更新网络。问题来了当目标位置离起始位置很远、或者随机初始化导致大部分轨迹都完不成任务时绝大部分样本的 reward 都是 0网络更新时所有样本都指向一个结论——“这些动作都没用”。实际训练中梯度方向被这种无效信号占满策略输出的动作就变成了一团噪声。这里涉及到一个概念上的基础状态和目标在构造时的数学关系。比如 FetchReach 这种环境state 是机械臂关节角度和末端位置goal 是末端目标坐标我们通常会把 goal 拼接进观测向量或者把 state 和 goal 分开输入网络。HER 的做法就是把回放时的 g 直接替换成 trajectory 的某个真实状态。这种做法不改变网络的输入输出结构只改变 buffer 里存的内容语义所以它能适配各种 off-policy 算法。2.2 事后重标记的本质把失败变成成功这句话听上去像魔法实际实现逻辑其实很简单。假设一个 episode 有 T 个时间步原始设定的目标 g 是终点 A但我们整条轨迹最后停在状态 B。如果没有 HER这 T 个 transition 全都以 g A 为条件去更新奖励基本全是 -1 或 0。有了 HER我们额外生成 T 条新 transition把这些 transition 的 goal 改成 B并且把 B 对应的奖励重新计算一遍——注意这里的 done 也要做相应调整如果轨迹结束状态恰好等于 B那这条“伪造”的样本就是一个成功的样本。用 reward 函数来理解更直观假设 reward 就是“当前状态和目标之间的欧氏距离是否小于某个阈值”如果是则奖励 0否则奖励 -1。原始轨迹从 A 到 B每一步 reward 都是 -1。但如果我们把目标改成 B轨迹最后一步的状态和目标的距离是 0所以最后一步的 reward 应该改成 0前面的步骤可以维持 -1因为那些时刻确实还没到达 B。于是这一整条重标记过的轨迹就变成了一条“越走越接近目标 B”的学习信号。对网络来说相当于额外告诉它“如果你把自己的目标理解为 B那么这套动作序列是能在最后完成任务的”。反复用这样的经验训练策略网络对不同目标的动作映射会逐渐丰满起来。有一个容易忽略的细节重标记后的 transition 只追加进 replay buffer原始以 g A 为目标的 transition 也保留。这样 buffer 里既有“困难目标”的样本也有“事后发现可达目标”的样本网络既能区分难易又能从易样本中稳步提升。2.3 四种采样策略怎么选论文里给出了四种从 episode 中挑选“新目标”的策略它们分别叫 final、future、episode、random。我先用最通俗的方式解释一下四者的区别。final把 episode 最后一步的状态作为重标记目标。这个最简单计算量最小但牺牲了中间过程的信息。future从当前时间步往后随机选一个未来时间步的状态作为目标。这是论文里最推荐的策略。它会让每个 transition 都对应一个“接下来能达到”的状态学习信号更密集。episode从整个 episode 里随机选一个状态作为目标不管它是过去还是未来。random直接从环境中采一个随机状态当目标几乎没什么意义很少用。实际效果排序future 通常 final episode random。我用 FetchPush 这类任务测过final 虽然稳但学习速度偏慢future 从整个轨迹中提取目标让每个时间步都有了梯度方向所以如果你没有特别理由默认选 future 就行。这里补充一个细节future 策略里通常要配一个超参数 k表示从当前步往后选取未来状态的数量论文默认是 4也就是每个 transition 会额外生成 4 条重标记样本。这个值不是越大越好太大了 buffer 里的样本分布会严重偏向“近目标”原始目标样本比例被稀释反而伤害最终策略的泛化能力我后面会详细展开。3. 动手实现 HER关键代码与配置细节理论讲完接下来是动手环节。我不会给你贴一份完整项目的全部代码因为环境不同、算法不同粘贴起来没有意义。但我会把 HER 独立出来的几个关键函数和逻辑写清楚你完全可以按这个思路嵌入到自己的代码里。3.1 从环境到算法状态、目标、动作的定义在写代码之前第一步要确认你的环境是不是适合 HER。适用条件有三个任务必须可以被形式化为“达成某个目标”也就是说存在一个可计算的目标空间。goal 和 observation 的维度通常要能对齐或者至少能在计算距离时互相映射。必须有一个类似compute_reward(achieved_goal, desired_goal, info)的函数这个函数接受“实际达到的状态”和“期望目标”输出奖励值。OpenAI Gym 的 Fetch 系列环境都天然具备这个接口你只需要在环境初始化时把reward_type设为sparse就能拿到 HER 最适配的稀疏奖励形式。在实现层面我推荐你把自己的经验数据结构设计成这样{ obs: 当前观测, act: 当前动作, rew: 原始奖励, obs_next: 下一观测, done: 是否结束, goal: 原始目标, achieved_goal: 当前实际达到的状态, achieved_goal_next: 下一时刻实际达到的状态 }注意achieved_goal这个字段平时可能用不上但 HER 的实现必须靠它来做目标重标记。有些新手在自定义环境的时候没有保留这个字段结果后面想加 HER 只能重新采数据白白浪费时间。3.2 replay buffer 与 relabel 流程的代码骨架HER 和普通经验回放的差异主要体现在 buffer 的写入和采样阶段。整个流程这么定义每个 episode 结束时把原始轨迹存进去然后调用一次重标记函数额外生成若干条 transition 存进同一个 buffer。采样阶段不需要任何特殊操作因为重标记后的样本和普通样本已经混在同一池子里。我这里给一个浓缩版的目标重标记函数逻辑是通用的import numpy as np def her_relabel(episode_transitions, k4, strategyfuture): 输入一个 episode 的 transition 列表 每个 transition 是 dict包含 obs, act, rew, obs_next, done, goal, achieved_goal, achieved_goal_next 输出额外的 transition 列表重标记后的样本 new_transitions [] episode_len len(episode_transitions) for t, trans in enumerate(episode_transitions): # 根据策略选择新目标的时间片索引 if strategy future: candidates np.arange(t 1, episode_len) if len(candidates) 0: continue # 随机选 k 个未来时间片如果不够 k 个就全选 sample_count min(k, len(candidates)) future_idx np.random.choice(candidates, sizesample_count, replaceFalse) elif strategy final: future_idx [episode_len - 1] # 固定选最后一步 else: future_idx [np.random.randint(0, episode_len)] # episode / random 随便先这样处理 for f_idx in future_idx: new_goal episode_transitions[f_idx][achieved_goal] # 重新计算奖励和 done new_rew compute_reward(new_goal, trans[goal]) # 这里只是示例不要照抄 # 其实正确的做法是调环境的 compute_reward(achieved_goal..., desired_goalnew_goal, info...) # 因为不同环境奖励结构不同建议直接从环境拿 reward 函数 new_done (f_idx episode_len - 1) # 如果选的是最后一步done 为 True new_transitions.append({ obs: trans[obs], act: trans[act], rew: new_rew, obs_next: trans[obs_next], done: new_done, goal: new_goal, achieved_goal: trans[achieved_goal], achieved_goal_next: trans[achieved_goal_next] }) return new_transitions上面的代码里我把重标记的骨架逻辑写出来了但实际使用时有个地方需要特别注意compute_reward不能自己瞎写必须调用环境提供的奖励函数否则你重标记出来的 reward 和真实环境分布不一致网络学到的是个“假目标”。拿 OpenAI 的 gym 环境举例你可以把env.env.compute_reward这个函数直接传进来它接收achieved_goal、desired_goal和info三个参数。在自定义环境时你有两种处理办法一是继承 gym 环境类并实现这个接口二是直接在环境类里写一个独立方法跑训练时从外部传进来。我个人更推荐前者因为这样能保证后续接入 RL 框架时代码风格一致。还有一下因为重标记改变了 done 标记回放时如果用 done 来截断 TD 计算要注意重标记样本里 done 的含义。曾经我在这里踩坑直接把done一股脑存成 False结果 TD3 算法里 target 一直往后看导致目标值过估计训练出来的 Q 值飘得离谱。记住重标记后如果新目标等于状态转移后的状态且轨迹已结束done 应该是 True否则设为 False。3.3 网络结构与超参数我踩过的几个大坑HER 本身不是算法是技巧所以网络结构一般跟随你的 baseline 算法走。以 DDPG 为例Actor 和 Critic 都是四层 MLP中间层 256 或 512 个神经元激活函数用 ReLU。但有两个细节影响巨大。第一个是输入拼接。观测和目标的拼接方式你可以直接把 concat(obs, goal) 作为网络输入也可以像某些实现那样让网络两个头分别编码再合并。实测下来在 Fetch 系列环境上直接拼接已经够用第一层 256 维就有足够容量把两个信息融合。关键点是 obs 和 goal 如果量纲不一致要在这个地方一起归一化。比如观测里机械臂关节角度范围是 [-2, 2]而 goal 的坐标范围是 [0, 1]不归一化的话网络前几层梯度会被大数值特征带跑偏训练初期 loss 下降得很好但实际策略几乎是乱动。我刚开始复现时就犯过这个错损失函数降到趋近于零让智能体跑环境一看它全程在原地乱抖。第二个是 replay buffer 的容量和采样的 mini-batch 中新旧样本比例。HER 会大量生成重标记样本如果 buffer 容量太小比如只有 1e5你会发现重标记样本占比越来越高原始目标样本逐渐被挤出去策略最后被驯化成“只擅长完成事后目标”。反过来也不行原始目标样本太少网络对真正的任务目标反而失去拟合能力。实践经验是 buffer 容量设 1e6 以上重标记样本和原始样本的比例用 1:1这样相对安全。如果你嫌 1e6 内存压力大至少保证 buffer 大小是单 episode 长度的 50 倍以上。超参数方面我直接给一份我在 FetchReach 上验证过比较稳的组合actor 学习率 1e-3critic 学习率 1e-3gamma 0.98polyak 系数 0.95batch size 256每步都更新actor 每隔两步更新一次。这里的 gamma 和标准 RL 环境不完全一样Fetch 系列环境的轨迹长度一般是 50 步左右gamma 设太高会导致信用分配范围过大小轨迹任务反而不敏感0.98 是我试下来稳定性最好的折中值。当然这不是铁律不同任务还是要重新调。4. HER 在不同任务上的表现与适用范围有朋友问过我HER 是不是一个万能药加了它啥任务都能学。我不想骗你真的不是。它的适用场景集中在“多目标 稀疏奖励 可判断是否成功”的任务。更精确地讲任务的目标空间必须是连续或者足够密集的这样重标记出来的目标才有意义。如果你的目标空间只有两个离散值 “开门”和“不开门”那重标记的效果就非常有限因为你无法从失败轨迹里提取出一个“新目标”来优化轨迹。4.1 适合与不适合的场景先说适合的场景机械臂抓取和推箱子这类任务的目标是三维坐标或物体位姿状态空间和目标空间都是连续的HER 基本是标配。导航类任务机器人从起点移动到目标点如果终点位置在 episode 开始时随机采样HER 能明显加速学习。多物体操作比如把特定颜色的方块放进特定颜色的槽位目标本身包含物体颜色和位置信息这时建议把目标表示成一个向量而不是单一坐标——否则信息量不够重标记出来的目标缺乏区分度。不适合的场景有三类。第一类是竞技对抗类对手是另一个智能体目标不是静止的重标记会引入大量伪目标。第二类是目标空间高稀疏且离散的任务比如走迷宫目标点有限且路径稀缺重标记很难生成有效样本。第三类是任务本身已经有特别稠密的奖励函数——比如每一步都有与目标距离成比例的奖励——这时候 HER 带来的边际收益很小反而增加了内存占用和计算开销。我曾经为了省事把一个稠密奖励任务硬套 HER训练速度变慢最终策略水平没有明显提升纯属画蛇添足。4.2 和其他技巧的组合使用HER 在生产环境里很少单独用通常和下面的技巧搭配。第一个是目标采样时的分布调整。如果环境中目标 g 的采样范围很大但训练初期智能体只能完成非常靠近初始位置的目标你可以让重标记的目标偏向“稍微有一点难度”的状态。实际操作是对重标记的目标加上一个范围限制比如只选择距离轨迹起始状态不超过某个阈值的未来状态。这种干预能避免 buffer 里堆积大量太容易或者太难的目标训练波动会小很多。第二个是curriculum learning。你可以把 HER 和课程学习相结合做法是初始时只从很近的目标开始训练随着成功率提升逐渐放松目标采样范围。HER 里面有个天然优势重标记过程本身就隐式地完成了“从易到难”的任务演化。因为早期的 trajectory 只能达到初始位置附近重标记目标自然集中在近处当策略进步后trajectory 覆盖范围变广重标记的目标也会跟着变远。第三个是辅助奖励塑形。虽然 HER 天生是为稀疏奖励设计的但直接把环境设成 100% 稀疏有时会让前期学习过慢。你可以用compute_reward里加一个小的距离惩罚项但要注意别让惩罚项主导梯度更新。我习惯把稀疏 reward 阈值设为 0.05距离小于 0.05 才算成功然后额外加一个 -0.1 * distance 的 term 作为稠密辅助效果比纯稀疏和纯稠密都好。这种做法在业界其实很常见你让环境奖励本身保留“稀疏判定”的语义但训练信号里掺一点连续的信息双向都沾光。第四个是与数据增强组合。针对视觉输入的任务比如从相机图像里学习目标达成你可以配合随机裁剪、颜色抖动等图像增强技巧。HER 的重标记发生在目标空间而数据增强发生在输入观测空间两者互不干扰。反过来讲如果目标空间本身做了增强比如对目标坐标添加噪声要非常小心处理不好会把“真实目标”和“伪造目标”的边界弄模糊。5. 常见问题与排查技巧实录到这里代码思路和调参方向都讲完了。下面我挑几个自己实际跑实验时遇到的、以及帮别人排查时见过的典型问题一条条列出来。这些都是那种“看论文永远看不到”的细节经验。5.1 训练不收敛Q 值疯狂震荡很多人在第一次跑通 HER 时遇到的第一个问题就是训练初期 agent 的成功率一栏还是 0但 Q 值已经崩到负数几千。这个问题的根源通常是 reward 的幅度没约束好。你想想如果环境返回稀疏奖励是 -1 和 0那么 Q 值的理论范围只在 [-50, 0] 之间一条轨迹最多 50 步。如果 Q 值出现几百上千的数值十有八九是归一化的问题——比如 obs 和 goal 的量纲差异太大导致网络输出被特征值推高。处理办法是最开始就做 input normalization或者 stateless 地做 min-max 缩放。另一个可能是学习率太大TD3 这类算法本身对学习率比较敏感降到 3e-4 或者 1e-4 试一下多数情况下能稳住。5.2 目标维度过大导致重标记失效如果你把整个 observation 都当作 goal 的一部分重标记的维度会非常大然后你又会发现 checkpoint 成功率总是无法突破某个瓶颈。比如目标向量是 20 维但实际对任务起决定作用的只有 3 维位置信息HER 的奖励信号就会被其他 17 维的冗余信息稀释。判断方法很简单看一眼成功状态的 achieved_goal 是否有很多维度基本不变或者噪声很大如果是就把冗余维度去掉只保留真正和 reward 计算相关的维度。这也是为什么建议你设计目标表示时尽量做到“能够由状态向量直接映射得到”最好内部就是同一个向量的一部分这样后续排查也方便。5.3 重标记样本导致的分布偏移HER 会产生大量“事后成功”的样本这会导致一种典型的数据分布偏移replay buffer 里到处都是成功率很高的样本网络学出来的 Q 值过于乐观真实 rollout 时效果却拉胯。这个问题在 buffer 容量小或k设太大时尤其明显。我建议你把k从 4 降到 2或者把 buffer 里的重标记样本总量限制在一个比例内。实操里最简单的办法是每次 episode 结束时原始轨迹是 N 条重标记最多也生成 N 条重标记和原始样本的占比就不会超过 1:1。顺带说一个更细的规则有些实现会把重标记的优先级设得比原始样本更高这会让训练更快但也更容易震荡。如果你用了优先级采样建议把重标记样本的初始优先级乘以 0.8稍微压低一点。5.4 和 DDPG / SAC 结合时要注意什么SAC 和 HER 的搭配并不像 DDPG 那样天然顺畅原因是 SAC 的熵温度系数在训练中会自动调节而重标记目标会改变样本分布导致温度系数在训练初期迅速调低策略变保守探索性下降。一个可行的做法是固定温度系数一段时间比如前 2 万步不自动更新或者给温度系数设置一个较大的下界。DDPG 没有这个问题但 DDPG 本身对超参数敏感HER 不会雪上加霜不过你需要关注超参数调优的顺序先把 DDPG 在稠密奖励版本上跑通再换成稀疏奖励 HER否则你分不清问题出在算法还是出在 HER 的配置上。5.5 关于 debug如何判断 HER 起作用了我在调试 HER 时有一个习惯盯着 rollout 里智能体末端执行器和最终目标之间的距离分布看而不是只看 reward 曲线。如果你发现距离分布的中位数在下降说明策略确实在学到东西哪怕 success rate 还是 0。这不是玄学HER 的效果本质上是让策略在目标空间里的覆盖范围逐步扩大覆盖范围扩大在前期的表现就是“离目标更近”而非“到达目标”。等你看到 success rate 开始往上跳的时候整个训练其实已经进入后期了前期的所有学习都被距离分布给“沉默地记录”下来。另外一个实用的 debug 手段是同时跑两套实验一套开 HER一套关掉其他完全一致。如果两边曲线完全一样说明你的 HER 配置根本没生效多半是重标记后的样本并没有真正写进 replay buffer或者 buffer 的采样权重没把重标记样本算进去。这个听起来很蠢但我确实见过有人写完 HER 代码忘记在 episode 结束后调用重标记函数整个训练跑的其实还是普通的 DDPG。6. 写在最后的经验之谈回头看一下HER 这个技巧在强化学习里的地位有点像编译器优化它不改变你算法的语义只是让每个样本发挥更大的价值。我个人的体会是如果你正在做任何目标达成类的连续控制任务而且在稀疏奖励下迟迟卡壳先别急着加各种花里胡哨的奖励塑形停下来试一下 HER大概率能让你少熬几个通宵。最后再分享一个我自己用下来的小技巧在记录训练日志时除了 success rate把“重标记样本占总采样比例”这个指标也存一份。如果这个比例在训练中段开始异常升高说明 buffer 里的经验多样性不够了要么调低k要么重新增大探索噪声。这个小指标救过我很多次不然你可能只会一脸懵地看着 success rate 涨到某个位置再也不动根本不知道是因为目标分布收窄了。希望这篇拆解能给你节省一些试错成本。
网站建设高端定制企业官网