
生活中我们都有过这种时刻一道题怎么想都想不出来等老师讲完才恍然大悟后悔自己为什么没换个角度。这种“事后聪明”英文叫 hindsight在强化学习里也有对应的技术而且用途大得惊人——它叫 Hindsight Experience ReplayHER事后经验回放。我第一次接触 HER 是在做机械臂抓取仿真的时候。当时用普通 DQN 和 DDPG 训练环境里只有“抓到了”才给一次奖励其余时间全是 -1训练了上百万步成功率纹丝不动。后来在 OpenAI 的论文里看到 HER 这个方法抱着试试看的心态改了一下经验回放逻辑结果训练曲线肉眼可见地往上走。这篇文章我就打算把这个“事后聪明”的原理、实现和踩坑经验一次讲清楚给正在跟稀疏奖励搏斗的同行一点参考。全文围绕 HER 的核心思想、重标记策略、实操复现、常见坑位和扩展应用展开适合做过一点深度强化学习、但被稀疏奖励折磨过的工程师和研究生。即便你是刚入门 RL 的新人只要会基本的 Python 和 DQN这文章也能让你把 HER 用起来。1. 核心问题稀疏奖励为什么劝退一大波强化学习算法1.1 先从“难学的环境”说起强化学习里最头疼的不是动作空间大也不是状态维度高而是奖励信号太稀薄。比如机械臂要从桌上抓一个杯子传统做法是每一步都算一下机械臂末端和杯子的距离距离缩小就给奖励这是密集奖励。但很多真实任务根本给不出这种连续反馈只能在“任务完成”时给你一个 1其他时候都是 0 或者 -1。这就是稀疏奖励环境。在这个设定下强化学习算法就像是闭着眼睛在一个超大迷宫里找出口唯一知道的信息是“没到出口”。如果出口在迷宫深处的某个角落随机探索撞到出口的概率低到可以忽略那么算法收到的几乎所有样本都是负奖励整个策略网络得不到任何“往哪个方向走更好”的梯度信号训练自然卡死。对比维度密集奖励稀疏奖励反馈频率每步都有信号仅任务完成时训练难度较低极高现实适配性需要人工设计更接近真实场景对算法要求常规 off-policy 即可需要 HER 等技巧我见过不少项目跑在稀疏奖励上loss 曲线看起来在下降但其实策略一点没进步因为 -1 的梯度把所有动作都往“平均值”方向推根本区分不出哪个动作更优。很多人误以为是网络不收敛把学习率调了又调其实问题出在奖励信号本身。1.2 失败的轨迹真的没有价值吗传统经验回放的做法是把探索过程中产生的状态、动作、奖励存在缓冲区然后随机采样来更新网络。但如果所有轨迹的奖励都是 -1那么无论怎么采样网络都只能学到“这些动作都不行”学不到“这些动作让状态变得更接近目标了”。也就是说失败轨迹被当成了废料直接扔掉或者当作负样本草草处理。HER 的出发点特别朴素既然失败轨迹里也包含状态变化的过程那我能不能把这条轨迹的目标改一改让它变成一条“成功轨迹”举个例子机械臂本来想抓杯子结果没抓到但它在第 50 步把杯子推到了某个位置。从原始目标看这是失败但如果把目标改成“把杯子推到那个位置”那么这条轨迹就是成功的因为机械臂确实做到了。这种“改目标”的操作学术上叫目标重标记goal relabeling它能把大量原本无用的失败轨迹转化成带正奖励的经验为网络提供有效的学习信号。你不需要让智能体每次都成功只需要让它能从失败中看到“哪些状态是可达的”然后一步步扩展可达区域。1.3 HER 的提出背景与影响力HER 来自 2017 年发表的论文《Hindsight Experience Replay》作者是 OpenAI 等机构的研究者。论文发布后很快成为稀疏奖励任务尤其是机器人操作类任务的标准配置后面的 DDPGHER、SACHER 等组合几乎成了抓取、推动类任务的默认基线。可以说HER 把“从失败中学习”这件事从口头禅变成了可落地的算法组件。它为什么影响这么大因为它在不改变任何神经网络结构、不增加额外监督信号的前提下只靠修改经验回放的采样逻辑就能显著提升稀疏奖励下的训练效率。对于工程实践来说这种“零成本改造”最容易被接受你可以把 HER 无缝接入已有的 DQN、DDPG、SAC 实现里不需要重新设计网络也不需要调复杂的奖励塑造。2. 算法原理拆解事后经验回放是怎么运作的2.1 多目标马尔可夫决策过程要理解 HER先得理解它适用的环境框架。HER 面向的是多目标goal-conditioned任务也就是说每个回合都有一个额外输入——目标。状态不只是当前的环境状态 s而是 (s, g) 的组合其中 g 是这一回合希望达到的目标。以机械臂抓取为例s 是机械臂各关节角度、末端位置、物体位置等信息g 是杯子被抓取的目标位置。奖励函数可以写成只有当 s 中的物体位置与目标 g 的距离小于某个阈值时奖励为 0否则为 -1。这种“到目标不扣分没到目标扣一分”的设定会让智能体倾向于尽快达到目标因为每一步 -1 的累积是个无底洞。HER 真正利用的是目标 g 的可编辑性。既然 g 是环境外在给定的输入不是环境状态的一部分那么我们在回放经验时就可以用“事后”的状态来重写这个 g把原本失败的经验变成成功经验。这一点是 HER 的理论基础——核心假设是智能体应该能从“已经达到的状态”中学习哪怕这个状态不是最初想要的。2.2 目标重标记的执行逻辑HER 的一整套流程可以拆成四个步骤每一步都很简洁但组合起来效果惊人让智能体以目标 g 去探索一个回合记录下完整轨迹包括每一步的状态、动作、奖励、下一状态。轨迹结束后不管有没有达成目标随机从这条轨迹中选取若干个后续状态作为“虚拟目标”g。用 g 重写这条轨迹的每一条经验。重写时动作 a 保持不变——这是关键因为动作是智能体真实执行过的。把原始轨迹和重标记后的轨迹一起存入经验回放缓冲区供后续网络更新使用。重标记后的奖励怎么算因为轨迹中的某个状态 s_t 后来确实到达了 s_{t1}而虚拟目标 g 正好取的是后面某一步的状态那么从 s_t 走到 s_{t1}距离 g 一定是变近的甚至直接达到。于是这条经验就从负奖励变成 0 奖励网络就能学到“在靠近某个状态时这个动作是有效的”这一正向信息。我在实现时遇到过一个小坑重标记时不能改变经验的“状态”部分只能改变“目标”部分。因为状态 s_t 是环境真实反馈你改了它就不是真实经验了而目标 g 是外部条件改掉它不影响物理真实性。一旦混淆这两者训练出来的策略会非常奇怪因为网络学的是“在假状态下执行动作”对真实环境毫无意义。2.3 四种重标记策略final、future、episode、random论文里给出了四种选取虚拟目标 g 的策略它们的区别在于“从轨迹的哪个时间点取目标状态”。final 策略直接把整条轨迹最后一个状态作为 g。这个策略最直观“最终走到了哪里就以哪里为目标”。实现最简单但有个问题如果轨迹特别长前面的状态离最终状态很远重标记后的目标跨度太大学习效率不高。future 策略对轨迹中的每个时间点 t从 t 之后的某个时间点随机取一个状态作为该时刻的虚拟目标。这个策略保持了目标与当前状态的时序关系避免了“目标在身后”的尴尬论文实测效果最好也是我主要用的策略。episode 策略随机从整条轨迹中取一个状态作为所有时刻的虚拟目标不关心时间前后。实现更简单但可能选到一个“出现在当前动作之前”的目标逻辑上不太合理适合无时间依赖的简单任务。random 策略从训练过的所有历史状态中随机选一个作为虚拟目标而不是局限于当前轨迹。这个策略能带来更多样化的目标但目标与当前轨迹无关导致重标记后的“从 s_t 到目标”不一定是真实可达的训练方差较大。策略目标来源时间一致性适用场景实测效果final整条轨迹终点弱短轨迹、简单任务中future当前时刻之后强通常都适用最佳episode整条轨迹随机点弱无时间依赖任务中低random全球历史状态无探索性任务低我个人的经验是future 策略在所有机器人操作类任务里几乎没有败绩优先选它。final 适合那种轨迹很短、很快就结束的任务比如一步到位的位姿调整。random 我不太推荐可能只在多任务混合训练时有点用常规场景下反而会稀释目标的信息密度。2.4 HER 怎么嵌入 DQN/DDPG/SACHER 不是一个独立训练算法它是对经验回放机制的改造所以它只能配合 off-policy离策略算法使用。理由很简单on-policy 算法要求样本来自当前策略而 HER 会反复重写并复用过去的轨迹这违背了 on-policy 的假设。实际中 HER 最常搭配 off-policy 价值类算法比如 DQN、DDPG、SAC、TD3。以 DDPG 为例HER 嵌入后的训练流程大概是这样的每回合让 Actor 网络以当前目标 g 执行动作收集轨迹。回合结束时把轨迹送入 HER 模块做目标重标记原始轨迹和新轨迹都存入 replay buffer。每次网络更新时从 buffer 里随机采样一个小批量把 (s, g, a, r, s, g) 喂给 Q 网络和 Actor 网络。Q 网络的目标值计算要考虑目标维度即 Q(s, g, a)而不是普通的 Q(s, a)。这里有个很容易被忽略的地方重标记后同一批样本里可能存在“原始目标”和“虚拟目标”两种数据Q 网络的输入维度必须包含目标向量。如果你的网络原来只吃状态向量接入 HER 后要把目标拼接到状态后面。我在第一次实现时忘了改网络输入层结果训练时直接维度报错改好之后一切正常。SAC 那边类似只不过损失函数里多了熵项。HER 与 SAC 的兼容性也相当好苏黎世那边很多机器人实验都用 SACHER 作为默认基线说明这套组合的稳定性已经被广泛验证过了。3. 实操环节复现 HER 的核心流程3.1 环境选型与搭建想亲手跑通 HER最推荐的入门环境是 OpenAI Gym 里的 Fetch 系列和 Bit Flipping。FetchReach 是最简单的一个机械臂只需要把末端移动到指定位置目标维度是三维坐标FetchPush 和 FetchSlide 稍难涉及推动物体到目标位置需要机械臂学会间接控制Bit Flipping 则是一个纯离散的逻辑任务常被用来验证 HER 本身的有效性因为它完全没有任何物理引擎开销。我在实际复现时选了 FetchPush 作为主环境因为它的状态空间和动作空间适中物理仿真速度快一份训练跑下来大约几十分钟适合迭代实验。环境里每个回合都会随机生成一个目标位置机械臂初始位姿固定物体位置随机奖励按照“物体最终位置是否在目标附近”计算。安装方面只需要装好 gym 和 mujoco或它的开源替代再装一个稳定版的 PyTorch 或 TensorFlow 就够了。如果你不想碰物理引擎Bit Flipping 环境只需要 NumPy也能把 HER 的核心逻辑跑通。我的建议是先用 Bit Flipping 验证算法正确性再上 Fetch 环境看真实物理表现。3.2 关键超参数与选择逻辑HER 真正需要仔细调的超参数不多但每个都直接影响效果。第一个是目标重标记的比例 k论文里默认每条原始轨迹额外生成 4 条重标记轨迹。k 太小正向样本不够多k 太大缓冲区里全是“事后目标”原始目标的样本被稀释可能导致策略过度偏向可达目标而忽略最初任务。第二个是 replay buffer 的大小。HER 依赖大规模缓冲区分担重标记样本的多样性一般建议至少保存最近 10 万步的经验。Fetch 环境每回合 50 步假设你跑了 1 万回合那就是 50 万条经验buffer 至少要能放下这些。我以前偷懒只设 5 万结果训练到后期很多早期的“好轨迹”被挤出缓冲区成功率明显下降。第三个是 Q 网络的目标网络更新频率。DDPG 里 target network 通常用软更新也即以较小比例逐步靠近原网络更新系数一般设 0.005。HER 改造后Q 网络要同时拟合原始目标和重标记目标两种数据对值函数稳定性要求更高我会把软更新系数调低到 0.001 左右配合稍低的学习率训练会更稳。超参数建议值影响重标记比例 k4越大正向样本越多过大则原目标被稀释Replay buffer 大小≥10万步过小会丢失历史好轨迹目标网络软更新系数0.001~0.005越小越稳收敛稍慢Actor 学习率1e-4 ~ 1e-3过大会导致策略震荡每回合最大步数50取决于环境默认设置3.3 从零实现 HERDDPG 的关键代码下面我给出一个简化但完整的 HERDDPG 训练循环伪代码重点展示目标重标记和采样过程神经网络部分只写接口方便你替换成自己的实现。# 伪代码HER DDPG 训练循环简化 import numpy as np from collections import deque class HERBuffer: def __init__(self, capacity100000, k4, strategyfuture): self.buffer deque(maxlencapacity) self.k k self.strategy strategy def store_episode(self, episode, goal): # episode: list of (s, a, r, s_next) # goal: 本回合原始目标 for t, (s, a, r, s_next) in enumerate(episode): self.buffer.append((s, goal, a, r, s_next, goal)) # 目标重标记 if self.strategy future: for t in range(len(episode)): # 随机挑选一个 t 之后的状态作为虚拟目标 future_idx np.random.randint(t, len(episode)) g_prime episode[future_idx][3] # 用 s_next 状态替换目标 s, a, _, s_next episode[t] r_prime 0 # 因为 g 是可达状态奖励设为 0 self.buffer.append((s, g_prime, a, r_prime, s_next, g_prime)) def sample(self, batch_size): idx np.random.choice(len(self.buffer), batch_size, replaceFalse) samples [self.buffer[i] for i in idx] # 拆包成各自矩阵并返回 def train_ddpg_with_her(): env make_fetch_push_env() her HERBuffer() actor, critic, target_actor, target_critic build_networks() for episode_idx in range(total_episodes): goal env.sample_goal() obs env.reset() # 包含状态和目标信息 episode [] for step in range(max_steps): action actor.get_action(obs, noiseTrue) next_obs, reward, done, info env.step(action) episode.append((obs, action, reward, next_obs)) obs next_obs if done: break her.store_episode(episode, goal) # 从 buffer 采样更新 critic 和 actor batch her.sample(batch_size256) update_critic(batch, target_critic) update_actor(batch, target_critic) soft_update(target_actor, actor, tau0.001) soft_update(target_critic, critic, tau0.001) if episode_idx % 100 0: print(fEpisode {episode_idx}, success rate: {evaluate(actor)})这段代码把核心逻辑压缩得很紧凑但每一步都有对应注释。有一个地方你必须要理解清楚重标记时我取了 episode 里某个时间点 t 之后的 s_next 作为虚拟目标并且把奖励直接设为 0不再计算距离。这是 HER 的一个优化因为“这个状态是本回合真实到达过的”所以重写后至少在当前时刻智能体确实达到了目标。虽然严格来说从 s_t 到 s_next 只是一步的转移目标并不一定在动作执行后马上达成但在实际操作中这种近似非常有效。3.4 训练效果观察与指标判读训练中最直观的指标是“回合成功率”每个回合结束时计算当前物体位置与目标位置的距离是否小于阈值统计成功回合占比。没有 HER 的 DDPGFetchPush 跑到 50 万步成功率基本在 1% 以下偶尔还能跌到 0因为探索全靠运气。加了 HER 之后大约 20 万步时成功率就能突破 50%50 万步左右稳定在 90% 以上。成功率曲线不是平滑上升的会有一段较长“平台期”前期几乎没动静然后突然快速上涨。这个平台期是正常现象因为算法正在从海量重标记样本中慢慢学到“哪些状态是可达的”这一内部模型一旦内在模型建立了策略就会出现跳变式提升。很多新手看到平台期就以为训练失败了提前中断错过了后面的上升。另一个值得关注的是 Q 值曲线。HER 训练下 Q 值一般不会像普通 DDPG 那样一路跌到负数而是会稳定在一个接近 0 的负值区间这是因为重标记样本让网络认识到“很多状态其实是可成功的”Q 值自然不会太悲观。如果 Q 值出现剧烈振荡通常是你缓冲区过小或者学习率过高需要往回调。4. 常见问题与排查技巧实录4.1 重标记后的目标太简单或太难HER 重标记的目标是轨迹中真实到达过的状态理论上都是“可达”的但不同策略选择的目标难度差异很大。如果只用 final 策略有些轨迹的终点和目标原点差距巨大重标记后的目标是整条轨迹的终点网络需要学习一个跨度很大的映射关系难度高。反过来如果只用 episode 策略可能随机选到特别接近当前状态的目标学起来太轻松导致网络对稍微远一点的目标没有泛化能力。我在实践中发现future 策略之所以最好用是因为它对每个状态 t 都选择了“从 t1 到终点”之间的目标目标离当前状态既不会太近也不会太远刚好构成一个合理的学习梯度。如果你的任务时间跨度很长可以适当混合 future 和 final让一部分样本保持难任务避免策略收敛到“只会做简单任务”。4.2 目标值函数不稳定HER 引入虚拟目标后Q 网络要同时估计不同目标的 Q 值这比单目标任务更容易出现值函数高估或振荡。我遇到最典型的现象是训练中期 Q 值突然冲到 0 附近然后成功率反而下降。排查后发现是目标网络软更新系数太大Q 值追赶太快导致策略被错误的高估信号带偏。解决办法有三个方向一是降低软更新系数到 0.001给目标网络更强的惯性二是降低 Q 网络学习率让值函数平滑更新三是增大 batch size256 甚至 512 都比 128 稳定。这三个手段本质上都是在“降低每次更新的步伐”让值函数逐步逼近而不是剧烈跳跃。4.3 探索不足导致的死区HER 能挽救失败的轨迹但前提是探索过程必须产生一定数量的“近似成功”轨迹。如果随机策略太弱每回合机械臂都乱甩停在原地附近那么重标记后得到的目标也都在原地附近算法始终学不到远离初始位置的目标。这就是死区问题HER 单独无法解决需要外部增强探索。我的做法是给动作加上更大的探索噪声或者在前 10% 回合里完全不使用策略网络只执行随机动作让机器人充分尝试各种路径。也有人用课程学习从容易的目标开始逐步过渡到难目标。反正要记住HER 是放大器不是无中生有的引擎你得先让智能体有东西可以“事后反思”。4.4 计算开销与控制HER 每回合要额外生成 k 倍重标记样本存储和采样开销增加了不少。FetchPush 环境一条轨迹 50 步k4 时一回合会写入 250 条经验训练 50 万步就是 1.25 亿次写入操作内存占用和采样时间都会上涨。实践中的折中方案是不用每一条原始经验都重标记可以只在采样时按一定比例混合重标记样本。另一种做法是降低 k 到 2效果虽然差一些但训练速度能快 30% 左右。我通常先用 k4 跑通确认效果后如果训练太慢再尝试 k2 比较。总之 HER 的开销是线性增长的提前评估一下你的机器扛不扛得住。4.5 快速排查速查表现象可能原因排查方向成功率长期为 0探索不足重标记目标全在初始附近增加随机探索引入课程目标Q 值剧烈振荡学习率或目标更新系数过大降低学习率、调低 tau、增大 batch成功率先升后跌缓冲区太小早期好轨迹被丢弃增大 replay buffer 容量训练速度过慢重标记样本太多减小 k或降低重标记比例重标记后维度报错网络输入没拼接目标向量检查输入层维度状态目标这张表是我在多个项目里踩坑后整理的基本覆盖了 80% 的 HER 训练异常情况。如果你遇到表里没有的问题建议先把所有超参数减半、buffer 加大一倍、网络加深一层这三个方向总能缓解大部分不稳定因素。5. HER 的扩展与行业实践思考5.1 机器人操作领域的应用HER 最容易落地的场景就是机械臂操作抓取、推动、摆放、插孔。这些任务天然是目标条件化的目标可以定义成物体位置、末端姿态、夹爪开合状态甚至接触力。真实机器人训练成本极高一次抓取实验几秒钟但损坏风险大所以大家普遍先在仿真里用 HER 训练策略再迁移到真机。真机迁移时有个额外好处HER 重标记出的样本来自真实交互数据不依赖仿真环境的物理准确性。也就是说即使仿真和真机有差异HER 依然能在真机采集的数据上继续训练因为它从不额外要求“仿真给的奖励正确”只用到状态转移本身。这让 HER 成为 sim-to-real 流程里的可靠组件。5.2 从控制到决策的迁移HER 不止限于机器臂任何“目标可表达、环境可交互”的任务都可以套用。自动驾驶里目标可以是“到达某个路口”无人机导航里目标可以是“悬停在某个坐标”游戏 AI 里目标可以是“到达地图某个区域”。把终点位置当作目标输入HER 就能从失败的行驶轨迹中学习驾驶策略哪怕没有到达目的地也能学到“往某个方向开能到达哪里”。我见过把 HER 用在金融交易回测上的尝试把“最终收益达到某个值”当作目标交易过程记录状态和动作事后用真实达到的收益水平重标记目标让策略网络学到“在某种市场条件下怎么做能赚到当前水平的钱”。这个思路非常巧妙但需要谨慎设计目标和奖励函数因为金融市场的不可重复性比物理环境高得多。5.3 对“后见之明”的再思考HER 这个名字起得很贴切因为它的本质就是“结果已知后重新解读过程”。这种思想其实可以延伸到强化学习之外的领域。比如逆强化学习就是通过观察专家的行为轨迹反推奖励函数本质上也是用“事后结果”来解释“过程行为”。再比如课程学习如果智能体反复失败可以看成我们人给他设计的课程太难需要自动调整任务难度HER 提供了一种自动生成“难度适中课程”的方法。我在实际项目中体会到HER 更重要的价值在于改变了团队的“数据观”失败数据不是垃圾不是只能丢弃或减权的负样本它完全可以被重新解释、重新利用。这种思路在如今数据驱动的时代特别有价值。你手头积累的数据里有多少是因为“结果不理想”而被打入冷宫的换个目标定义它们可能立刻变成训练正样本。关于 HER 的更多扩展目前有研究者把它与数据增强结合在图像状态下做目标重标记也有工作把它用在多智能体协作场景让 agent 从“队友失败”中学习目标重标记。这些方向都还在快速迭代但核心思想始终是那四个字事后聪明。我在最初复现 HER 时犯过一个低级错误就是把虚拟目标和原始目标混合在一起存储却没有在状态里做区分导致网络分不清自己到底在追哪个目标训练结果一塌糊涂。后来我养成了一个好习惯所有经验元组统一格式为 (s, g, a, r, s, g)g 和 g 分开传网络输入显式拼接从此再没出过这类问题。最后再分享一个小技巧在你自己的任务里接入 HER 时别急着上物理引擎先用 Bit Flipping 这种迷你环境验证一遍目标重标记逻辑是否正确。这个环境跑一次只要几分钟却能帮你确认 k、strategy、buffer 这些超参数设置有没有低级错误。等迷你环境跑通了再切换到真实任务你会发现自己对 HER 的理解已经扎实很多。这也是我踩过无数次坑后总结出来的最稳妥的路线。