做强化学习的都知道稀疏奖励问题是最让人头疼的事情之一。我最早接触 hindsight 这个概念是在读 OpenAI 那篇 Hindsight Experience ReplayHER论文的时候当时第一反应是“这个操作也太取巧了”但认真想明白之后又觉得“确实就该这么做”。这个算法出来已经有几年了但现在回头看它依然是解决稀疏奖励问题最简单、最实用、落地成本最低的思路之一尤其适合机器人操作这类目标明确的连续控制任务。这篇文章就用实战的视角把 HER 从原理到实现完整拆一遍重点说清楚它到底解决了什么问题、代码是怎么写的、以及我在实际跑实验中踩过哪些坑。1. 什么是Hindsight一句话讲清楚它在解决什么问题1.1 稀疏奖励让强化学习“无从学起”的死局强化学习的核心逻辑是智能体通过与环境交互、获取奖励信号来学习策略。如果奖励信号本身非常稀少比如机器人只有在把方块正好放进目标位置时才能得到 1其他时候全是 0那智能体就会面临一个非常尴尬的局面它完全不知道自己距离成功还有多远因为所有“失败”的尝试收获的反馈都是一模一样的 0。这个问题在学术界有个专门的名字叫“奖励稀疏”sparse reward。很多真实场景都有这个特点比如机械臂抓取、桌面推箱子、机器人导航这类任务往往目标非常明确但奖励信号只有成功的那一刻才出现。在这种情况下标准强化学习算法的学习效率会急剧下降因为智能体只能靠瞎蒙碰运气才能拿到一次非零奖励然后才能从这个稀有的经验里慢慢回溯学习。如果状态空间稍微大一点这个“碰运气”的过程可能需要几百万甚至上千万次尝试在真实机器人上根本不可接受。我之前用 DQN 跑过一个简单的推箱子任务环境只有二维平面、四个动作理论上不难但因为没有给中间奖励模型跑了足足两百多万步还是学不会。后来我把奖励改成“越靠近目标奖励越高”的稠密奖励形式几万步就收敛了。所以问题的根源很清楚奖励信号太稀疏梯度信号就消失学习就没有方向。1.2 一个经典的“捡瓶子”场景为了把问题说具体我们用一个经典场景来贯穿全文就是机械臂抓取任务。假设我们有一个七自由度的机械臂面前桌子上有个瓶子每次训练开始时瓶子被随机放在桌上的某个位置同时随机给定一个目标位置。机械臂需要把瓶子推到或者抓到目标位置才算成功。这个任务如果用稀疏奖励来定义那就是当瓶子和目标的距离小于某个阈值比如 5 厘米时奖励为 1否则奖励为 0。从直觉上你就能感受到机械臂一开始连怎么动都不知道全靠随机探索它几乎不可能一上来就刚好把瓶子推到 5 厘米以内的位置。所以训练初期智能体收到的几乎全是全零奖励没有任何有效梯度模型就只能原地打转。很多刚入门的朋友会问“为什么不直接加一个中间奖励比如让奖励和距离成反比”这确实是一种办法叫奖励塑形reward shaping但它有一个隐患你设计的中间奖励可能会让智能体找到一个“偷懒”的策略比如绕过目标、原地抖动或者把瓶子推得更远反而拿到更高分数。奖励塑形做得好不好非常依赖设计者的先验知识而且不同任务要单独设计不够通用。HER 这条路线的核心诉求就是在不手动设计中间奖励的前提下让智能体从稀疏奖励里学会一个可用的策略。1.3 HER的核心思想学不会的目标就换一个能学会的HER 的思路用一个生活化的类比来解释再合适不过就像一个学生考试目标是想考全班第一这是原始目标但他第一次只考了第二十名。如果只看目标这次考试是完全失败的。但聪明的人不会把这次失败当作毫无价值的经验而是会想“虽然我没考到第一但我现在的能力水平大概就在第二十名左右那我先以‘考进前二十’为目标复盘一下看看哪个科目拖了后腿。”也就是说他会把这次“失败”的结果当作一个已经达成的次级目标从中提取有价值的学习信号。HER 做的事情本质上就是这样在智能体尝试完成目标 g 却失败后我们不把这个 episode 扔掉而是把它的实际终点状态当作另一个目标 g 重新放回经验池。因为智能体“实际到达”了 g所以这个 episode 在 g 这个目标下是“成功”的奖励是 1梯度就是有效的。虽然这个经验不是朝着原始目标 g 的方向但它能让智能体学到“从当前状态到 g 需要采取什么动作”这个知识累积起来就会让智能体逐渐学会操作物体的基本动力学最终一步步逼近原始目标。这个想法看起来简单到有点“作弊”但它背后的直觉非常深刻失败的经验里其实藏着成功的线索关键在于换一个角度去看待它。在强化学习里这个“换角度”就是目标重标记goal relabeling也就是 HER 的核心机制。后面我会详细拆解它的实现细节。2. HER的原理拆解从后见之明到事后经验回放2.1 标准经验回放与它的局限先回顾一下标准经验回放Experience Replay的基本流程。强化学习训练过程中智能体跟环境交互产生一条条经验格式一般是四元组 (状态 s, 动作 a, 奖励 r, 下一状态 s)。我们把每条经验存进一个固定大小的缓存里训练时从中随机采样一个小批量来更新网络。这个机制的好处是打散了样本之间的时间相关性让数据更接近独立同分布这对神经网络的稳定性非常重要。但在目标条件化强化学习Goal-Conditioned RL中经验还要额外带上一个目标 g格式变成 (当前状态 s, 目标 g, 动作 a, 奖励 r, 下一状态 s)。这时你会发现问题如果目标 g 只来自训练开始时环境随机分配的那个真实目标那么绝大多数经验里的奖励 r 都是 0经验池里“有效学习信号”的比例极低采样效率自然就上不去。本质问题在于传统经验回放把“失败”的经验一律视为无价值数据直接丢弃或让它稀释在大量无效数据里。HER 做的事情就是打破这个偏见失败经验本身也可以是宝贵的学习材料。2.2 目标重标记HER的灵魂操作HER 的具体操作可以拆成三步智能体在环境里从初始状态出发带着目标 g 执行策略直到 episode 结束期间记录整条轨迹状态、动作、奖励轨迹结束后遍历这条轨迹上的每一个时间步对每个步的经验把它的原始目标 g 替换成一个“事后目标 g”这个 g 通常是这条轨迹最终到达的状态 s_T或者也可以选轨迹中某个未来的状态根据替换后的目标重新计算奖励然后将“重标记后”的经验重新存进经验池之后的训练更新就和普通经验回放没有区别了。第二步里有一个关键操作是计算新奖励。因为我们设定的奖励函数通常是“到达目标则 1否则 0”所以重标记后新的奖励可以直接判定为 1因为轨迹的最终状态 s_T 与 g 本来就是同一个东西或者至少是同一个物体在不同时刻的位置距离必然小于阈值。这就是 HER“凭空”创造稀疏训练信号的核心手段。需要注意的是这里有一个微妙的点我们用轨迹终点作为事后目标那么沿轨迹越早的状态距离这个目标的距离就越大这反而提供了一个天然的“学习梯度”——早期状态离目标远晚期状态离目标近智能体能从中学习到“朝这个方向的运动会导致状态逐步接近某个目标”这正是策略学习需要的信号。2.3 数学表述和Hindsight损失严格点说HER 并不改变强化学习的数学目标。它只是改变了训练数据的分布。原本我们要优化的目标函数是J E_{(s,g,a,r,s) ~ D} [Q(s,g,a) 的某种回归误差]其中 D 是从经验池里采样出来的经验分布。HER 做的是把一部分样本的 g 换成了 g并重新计算奖励然后照常计算损失。由于 g 是根据事后状态生成的它天然满足奖励非零条件所以损失函数的监督信号密度大大提高了。用公式来表达重标记后的奖励r(s_t, g, a_t, s_{t1}) 1_{||f(s_{t1}) - g|| ε}这里 f 是从状态中提取物体位置信息的函数ε 是成功阈值。因为我们选择 g f(s_T) 且 t1 T 时距离为零所以最后一步奖励一定是 1而更早的步则视情况而定可能为 0 也可能为 1如果策略已经在中间某步就到达了终点则之后的步都有奖励。在实现中我们还面临一个选择每个 episode 里到底重标记多少条经验OpenAI 论文里提出了四个选项分别是最终状态final、未来状态future取未来一个随机时间步的状态、episode 内的随机状态episode、以及完整轨迹的随机状态random。他们在实验里发现future 策略表现最好因为 final 策略只重标记到最终状态目标多样性有限而 future 策略为每个经验步提供了一个不同的、且满足时序一致性的目标数据多样性更高学习效率也更好。2.4 为什么HER有效从奖励密度看本质要理解 HER 为什么有效可以把训练过程想象成在教学一个小孩投篮。你每次让小孩随便把球扔出去然后要求他必须投中篮筐才给奖励那这个小孩根本学不会因为大部分尝试连筐都碰不到。但如果你换一种玩法不管他球扔到哪里你就说“这次的目标就是那个位置”然后根据距离篮筐的远近给他一些反馈那他很快就能掌握“扔出去的方向和力度与落点之间的关系”。HER 在数学上的效果等价于在经验池中大幅提高了非零奖励样本的占比。在原始经验里可能只有 0.1% 的样本有非零奖励经过重标记后这个比例可以提高到接近 100%对重标记部分而言这就直接解决了梯度消失的问题让 Q 函数和策略网络都能获得足够的更新方向。另外还有一点容易被忽略HER 其实是在“自己给自己出题”。原始目标只有一个但重标记后的目标可以有很多个相当于变相扩大了训练数据的覆盖范围提升了目标空间的探索多样性。这对泛化能力很有帮助模型见到过更多不同的“目标-轨迹”组合后面遇到新目标时就不会那么手足无措。3. 实操过程在真实模型中实现HER3.1 环境选型用Gym的Fetch系列做实验理论聊完了下面进入实操环节。要用 HER 做实验最经典的环境就是 OpenAI Gym 里的 Fetch 系列任务比如 FetchPush、FetchPickAndPlace、FetchReach、FetchSlide。这些环境的共同特点是七自由度机械臂、随机初始物体位置、随机目标位置、稀疏奖励成功给 0 的负奖励失败给 -1用 Gym 惯用的“越低越好”表达方式。FetchReach 是最简单的版本只有机械臂末端需要到达目标点没有物体操作。FetchPush 需要推动一个方块到目标位置FetchPickAndPlace 需要抓取方块再放到目标位置FetchSlide 则需要击打方块滑动到目标难度递增。我自己一般建议初学者先跑 FetchReach 验证代码流程再去跑 FetchPickAndPlace 体验一下真正的稀疏奖励难度。安装环境的方式很简单用 pip 安装 gym 和 mujoco_py再根据官方仓库安装 gymnasium-robotics或者老版本用 gym 的 robotics 套件。需要提醒的是老版本 gym 和新版本接口差异很大网上很多老教程用的是 gym 0.20 以下的版本如果你用的是新环境 API会遇到 terminated/truncated 的问题。建议直接用较新的 gymnasium-robotics文档和代码都更清晰。3.2 算法骨架DDPGHER的基础搭配HER 不是一个独立的强化学习算法它是一个经验重放模块需要挂在一个基础的强化学习算法上使用。最常见的搭配是 DDPGDeep Deterministic Policy Gradient因为 Fetch 系列环境都是连续动作空间DDPG 处理起来比较自然。当然也可以用 TD3、SAC甚至 PPO 也能配 HER 用但通常 off-policy 算法配合经验回放更自然效果也更好。整体框架分四块Actor 网络、Critic 网络、经验池和 HER 重标记逻辑。Actor 负责根据当前状态和目标输出动作Critic 负责评估“在当前状态和目标下采取某动作的价值”。HER 则常驻经验池负责在轨迹结束后生成重标记数据。我对这套框架的建议是不要在代码结构上把 HER 写死在 DDPG 里而是把 HER 作为一个独立的数据处理模块这样后续切换算法或者调整重标记策略会方便很多。我自己的项目里经验池提供一个 add_episode 方法每次 episode 结束后统一在里面做 HER 重标记其他算法模块完全不需要感知 HER 的存在。3.3 关键代码解读这里给出一段简化版的 HER 经验池核心逻辑使用 Python 伪代码风格可以直接套进你自己的项目里。import numpy as np from collections import deque class HERReplayBuffer: def __init__(self, capacity, relabel_strategyfuture, k4): self.buffer deque(maxlencapacity) self.relabel_strategy relabel_strategy self.k k # 每条原始经验重标记生成的新经验条数 def add_episode(self, episode): # episode 是一个字典包含键 # obs: list of (goal_obs, achieved_goal) # actions: list # next_obs: list # rewards: list # dones: list T len(episode[actions]) # 普通经验直接入池 for t in range(T): self.buffer.append({ obs: episode[obs][t], act: episode[actions][t], rew: episode[rewards][t], next_obs: episode[next_obs][t], done: episode[dones][t] }) # HER 重标记 achieved_goals [obs_dict[achieved_goal] for obs_dict in episode[obs]] for t in range(T): # 根据策略选择事后目标 if self.relabel_strategy final: new_goal achieved_goals[-1] elif self.relabel_strategy future: # 从 t 之后的时间步里随机选一个 future_indices np.arange(t, T) if len(future_indices) 0: future_idx np.random.choice(future_indices) new_goal achieved_goals[future_idx] else: new_goal achieved_goals[-1] elif self.relabel_strategy episode: new_goal np.random.choice(achieved_goals) else: raise ValueError(Unknown relabel strategy) # 替换目标并重新计算奖励 # 这里注意obs 和 next_obs 需要替换其中的 goal 部分 new_obs replace_goal(episode[obs][t], new_goal) new_next_obs replace_goal(episode[next_obs][t], new_goal) # 计算新奖励这个函数根据你的环境距离阈值来写 new_rew compute_reward(new_next_obs[achieved_goal], new_goal) # 新经验入池k4 表示每条原始经验生成 4 条重标记经验 for _ in range(self.k): self.buffer.append({ obs: new_obs, act: episode[actions][t], rew: new_rew, next_obs: new_next_obs, done: episode[dones][t] })这段代码里有几个细节值得展开。第一个是 replace_goal 函数在 Fetch 环境里观测是一个字典包含 observation机械臂自身状态和 achieved_goal物体实际位置。重标记核心思路是保持状态不变只把目标 key 从原始 goal 换成 new_goal。第二个细节是 compute_reward 的重新计算。在 Gym Fetch 里原始奖励函数是距离阈值判断我们重标记后需要重新走一遍这个判断。其实由于 new_goal 就是某个 achieved_goal这个奖励大概率是 0成功或者 -1失败取决于阈值定义不用太担心精度问题。第三个细节是 k 这个超参数。每条原始经验生成 k 条重标记经验这个 k 直接影响经验池数据比例。OpenAI 论文里用的 k4实际用下来也还可以。如果你显存或内存压力大可以降到 2效果差距不会特别明显但训练速度会快一些。3.4 超参数设置一栏表在我跑过的实验里下面这套超参数的稳定性和效果都比较均衡可以作为起始点参考超参数推荐值备注HER 重标记策略future多种策略里泛化性最好每条经验重标记次数 k4显存/内存紧张可降为 2经验池容量10^6 条越大越好但注意内存占用Actor 学习率1e-3调小可能更稳但收敛慢Critic 学习率1e-3两边保持一致省心目标网络更新系数 τ0.05相对大一点贴近实际常用值探索噪声 σ0.2高斯噪声配合 OU 噪声也可以但效果差距不大Batch size256显存允许可以更大比如 512折扣因子 γ0.98Fetch 任务一般不需要太大每 episode 最大步数50Fetch 环境默认配置训练总步数10^6 量级FetchPickAndPlace 通常 50-80 万步内能看出效果这套参数不是固定答案。我在不同环境、不同随机种子下跑过几次波动主要在收敛快慢上但最终都能稳定到一个可行策略。这里需要特别强调随机种子的重要性HER 虽然算法本身比较稳定但 DDPG 这类 off-policy 算法对种子还是敏感的建议每次实验固定三个以上种子取平均曲线来评估不然很容易被单次实验的方差误导。4. 常见问题与排查技巧实录4.1 模型不收敛先查这四处跑了很久都不收敛可能是哪里出了问题我用 HER 这一年多遇到过的问题集中在四个地方**第一处是观测里目标没替换对。**这是最隐蔽的 bug。HER 重标记的核心就是替换目标如果你在重标记时忘了同步替换 next_obs 的目标或者替换时用了错误的索引训练过程会表现得时好时坏看起来像模型在“抽风”但代码不报错。我排查这种问题的方法很笨但很有效在训练前期把经验池里随机抽几条重标记经验手动打印它的目标、下一状态和计算出的奖励核对奖励是否跟目标距离一致。**第二处是奖励函数阈值跟环境不匹配。**Fetch 环境的成功阈值是 0.05 米也就是物体和目标位置的距离小于 5 厘米才算成功。如果你自己实现了 compute_reward用了 0.1 甚至更大的阈值那奖励密度会偏乐观模型学到的策略精度达不到真实要求反之阈值太小例如 0.01那重标记产生的成功信号也变少了收敛速度会明显下降。跟环境默认保持一致就好。**第三处是噪声过大导致策略震荡。**我刚开始用 DDPG 时为了让探索更充分把高斯噪声标准差设成了 0.5结果训练曲线震荡得像过山车策略一直无法稳定逼近目标。后来发现 0.2 足够用了而且 HER 重标记已经人为增加了大量“成功”样本探索需求没有想象中那么大。如果噪声太大会让模型误以为动作空间里有很多无效动作从而影响 Q 值的收敛。**第四处是网络容量不足。**Fetch 类任务的观测维度不高大约 25 维左右其中包含物体位置、机械臂关节角等但如果你把目标向量也拼进去维度还是可控的。如果你的任务状态空间很大比如像素输入那普通的 MLP 就不够了要换成卷积结构或者先加一个 encoder。不要指望 HER 能凭空解决表征问题它解决的是奖励密度问题不是感知问题。4.2 关于噪声、目标网络更新和回放比例的细节经验池里原始经验和重标记经验的比例很重要。我刚跑 HER 时犯过一个典型错误重标记经验的比例过高比如 k8导致经验池里几乎都是重标记数据原始目标的经验被稀释得没剩几条。结果模型学会了“把物体推到某个随机位置”的能力却学不会“把物体推到指定位置”的泛化能力表现在测试时成功率很低。后来把 k 调回 4并保证采样时原始经验和重标记经验各占大约一半问题就解决了。目标网络更新频率也是一个容易被忽视的细节。DDPG 里 Critic 有目标网络用来稳定 Q 值计算的。如果你 τ 设得太大比如 0.1目标网络更新过快相当于每步都在追着当前网络跑稳定性就差设得太小比如 0.001学习速度又太慢。0.05 是我试过多次后比较舒服的平衡点。探索噪声在 HER 里的角色也有点微妙。理论上既然有重标记样本提供学习信号探索噪声好像就该小一点但实际跑下来我发现训练前期噪声还是要保持住一些因为重标记样本虽然能教智能体“从状态 A 到状态 B 该怎么做”但前提是智能体得先有足够多样的状态 B 可选。如果噪声太小轨迹终点覆盖的状态空间就太集中重标记样本的多样性也就差了。我一般在训练前 30% 阶段用 0.2 噪声后面逐步衰减到 0.05 甚至 0.01效果会比全程固定好一些。4.3 在复杂场景中HER的改动HER 也不是万能的。如果你的任务目标不是“到达某个状态”而是“完成某个不可逆的操作”比如拧开瓶盖、组装零件HER 的重标记逻辑就需要仔细考虑。因为这类任务不管你最后做到什么程度都没法简单地说“这次的目标就是当前状态”操作的过程和结果之间的因果关系非常复杂。这种情况下单纯依赖状态距离来判断目标是否达成就不够用了可能需要引入辅助的状态描述或逆模型来辅助重标记。另一个常见变体是分层目标。比如在导航任务里目标可以是“到达某个房间”而不是“到达某个精确坐标”HER 的重标记粒度就要做调整。有些研究者会把 HER 和 HRL分层强化学习结合起来先是高级策略选一个子目标低级策略用 HER 来学习如何逼近这个子目标这样效果会好很多。还有一种情况是目标空间和状态空间不一致。比如目标是“物体的最终位置”但状态里包含机械臂关节角等无关信息。重标记时一定要明确目标只与物体位置有关替换目标时只改物体位置部分其他状态保持原样。我在做抓取任务时因为把机械臂的关节角也当成目标的一部分来更新导致重标记目标完全失真模型久久不收敛。都是血泪教训。5. 一点个人经验与后续思路最后分享一点我在实际使用中的体会。HER 之所以吸引我是因为它用一种极其朴素的方式破解了一个表面复杂的问题不设计额外奖励函数、不修改环境、不增加额外学习阶段只需要在你已有的经验池里做一次“目标重标记”就让原本学不动的稀疏奖励任务变得可学。这种“换个角度看数据”的思路在深度学习时代尤其值得借鉴很多时候我们缺的不是更复杂的模型而是更好的数据组织方式。如果你正在做机器人操作、导航或者任意一个带明确目标状态的连续控制任务HER 应该进入你的首选方案清单。我的建议是不要在仿真里纠结太久先在简单环境里跑通代码再逐渐加大难度。附带提一句在仿真环境里验证过的 HER 策略迁移到真实机器人上通常还需要额外的 domain adaptation 工作但它至少能让你的仿真训练阶段省下大量时间。做实验时多固定几个种子、多记录几条曲线你会对 HER 的真实表现有更准确的判断。