我在调机械臂抓取任务时最崩溃的不是写环境代码而是给自己挖了一个大坑——把任务奖励设成“抓取成功才有 1否则 0”。模型跑了大半天成功率一直趴在 1% 出头回放池里堆满了失败的 transition跟石头一样没有学习信号。后来我把hindsightHindsight Experience ReplayHER后见之明经验回放加进去问题开始松动。这一篇就聊聊这个让智能体把“失败”也算作“经验财富”的算法它到底是什么、为什么有效、落地的时候有哪些坑。名字本身就很有哲学味。心理学里有个概念叫 hindsight bias意思是“事后诸葛”——事情发生之后人会觉得自己早就知道是这个结果。HER 干的事情完全一样智能体没抓到目标但事后回头看它其实“到达”了另一个位置那这段轨迹就不是废的可以当成一次成功示范。这套思路在稀疏奖励场景下尤其能救命适合所有做强化学习落地、做机器人操作、做路径规划的人参考。1. 稀疏奖励问题强化学习里的“迷宫出口”1.1 为什么稀疏奖励能把模型饿死先说说我为什么一开始会写出那种坑爹奖励。那是个典型的 goal-conditioned 任务机械臂从任意位置出发目标是推动某个方块到指定位置。环境给的是最朴素的设计——只有最终没碰到目标就返回 0碰到才返回 1。这种奖励函数在数学上很简单但训练起来是灾难。原因是强化学习本质上靠奖励信号来引导策略信号太稀疏智能体绝大多数时间里获得的都是同一个常数 0它根本分不清哪些动作稍好、哪些动作稍差。你可以想象一个学生在没有任何考试反馈的情况下复习做完一百套模拟卷也不知道自己到底错在哪最后只能瞎蒙。具体到训练曲线上表现就是Q 值长时间不变化actor 的梯度方向几乎纯随机探索动作集中在原地抖动。我碰到过最夸张的情况是连续 80 万步成功率不到 2%后来检查了一下大部分采样到的 transition 的 reward 全是 0回放池根本没有有效梯度。换句话说不是模型笨是它在沙漠里找不到水。1.2 传统解决思路Reward Shaping 为什么治标不治本最早遇到这种情况大家的第一反应都是“给奖励做一下塑形”reward shaping。比如根据机械臂末端和目标点的距离给一个负的惩罚项离得越远扣得越多靠近了就加分。这个做法确实能让训练跑起来我当时也试了效果立竿见影成功率从 2% 涨到 30% 左右。但问题也很明显距离函数需要人工设计换一个物体、换一个任务就要重新调。距离函数可能会诱导策略去“钻空子”例如机械臂先走到一个折中点让数值上更逼近目标但物理上根本没在完成任务。稀疏奖励的本质是“目标是否达成”而塑形奖励改变的是“路径好坏”两者一旦冲突最终策略会变得特别拧巴。还有一个更致命的地方如果奖励塑形给得太强模型会过度拟合到奖励表面而不是真正理解“抓取”这个动作。换一个新的目标位置成功率立刻掉回解放前。后来我意识到问题的根源不是奖励函数不够密而是我们没有合理利用“失败数据”里隐含的目标信息。HER 就是在这一点上动手的。2. Hindsight 的核心思想既然够不着目标那就把目标挪近一点2.1 事后诸葛失败的经验也要有价值先回到机械臂任务最底层的逻辑。一个 episode 的完整轨迹里智能体从初始状态出发尝试去接近目标 g但因为策略很差最后可能停在离目标很远的地方。常规经验回放算法里这一整条轨迹都会被标记为“无价值”因为每一步的 reward 都是 0。但如果你把自己带入“事后视角”你会发现这条轨迹实际上完整地展示了另一个过程智能体从初始状态出发到达了某个最终状态 s_final。如果我们把这些失败数据的目标从 g 改写成 s_final那原本全是 0 的奖励序列就会变成一条“成功到达目标”的轨迹。这条轨迹包含真实的动作序列和环境反馈只是之前被错误地套在了一个不适合的目标上。这就是 HER 的关键一招目标重标记goal relabeling。它不是像 reward shaping 那样去修改奖励函数本身的形状而是保留原始的奖励计算逻辑但在存储经验时把一部分经验的“目标字段”替换成这条轨迹中实际到达过的状态然后重新计算奖励。这样原本稀疏到不行的奖励信号就被人为加密了模型终于能从失败里学到东西。2.2 目标重标记的正确姿势目标重标记不是把所有 transition 盲目改成以最终状态为目标而是要有选择地补充经验。最朴素的版本就是每个 episode 结束之后额外生成一条“虚拟的、以最终状态 g 为目标”的经验序列和原始经验一起放回回放池。同一条轨迹在池子里就有了两种解释一种是在挑战原始目标 g 时失败的经验另一种是在达成虚拟目标 g 时成功的经验。举个具体例子。假设机械臂任务的目标是“把红色方块推到桌面右上角”但这次实验里方块最终停在了左下角。原始经验记为(state_t, action_t, reward0, state_{t1}, goal右上角)重标记之后我们在回放池里额外放一条(state_t, action_t, reward1, state_{t1}, goal左下角)关键是第二条里的 reward1 不是骗出来的而是“如果目标本来就是左下角那么这个动作确实成功完成了任务”的真实反馈。模型同时看到大量类似数据后会慢慢理解怎么样的动作能够精准地击中一个目标位置并把这个泛化能力迁移到原始目标上。这里有一个非常容易被忽略的点重标记目标的奖励计算必须使用环境原始的 reward function而不是重新设计一个。HER 没有引入任何额外的奖励先验它的全部魔法只是改变了“我们让模型去学什么目标”而不是“如何评价动作好坏”。这是它和 reward shaping 最本质的区别。2.3 为什么 HER 能缓解稀疏奖励用信息论的角度理解稀疏奖励问题本质是“正样本太少”。HER 通过重标记把几乎所有失败的 transition 都变成了某个目标下的成功样本。假设一个 episode 有 T 步原本只有回到目标那一两步有正奖励重标记后整条轨迹都可以变成以最终状态为目标的正样本。这条路一打通模型就不再需要“瞎猫碰上死耗子”式的探索才能看到正奖励。它对状态-动作空间的覆盖效率会提升好几个量级。我自己的实验里加了 HER 之后机械臂推动任务大约 40 万步就能达到 70% 以上的成功率而之前跑 200 万步还在个位数徘徊。差距就是这么大。不过也要泼一盆冷水HER 不是万能药。它解决的问题是“目标达成型任务中的稀疏奖励”如果你面对的是没有明确目标的纯互动任务比如对话、游戏通关HER 并不能直接套用。它要求环境具备可描述的目标空间并且能够从状态中抽取或构造目标。这也是我在项目选型时最先确认的事。3. 算法实现细节从伪代码到可运行3.1 HER 训练流程全拆解很多博客讲到 HER 只会讲“目标重标记”这个抽象概念但落到代码上还是有一堆细节。我先给出一份我在项目里实际使用的伪代码再一个一个解释关键节点。# 伪代码HER off-policy 强化学习算法如 DDPG/SAC # 假设环境是 goal-conditioned def her_train(env, agent, replay_buffer, total_steps, her_strategyfuture, future_k4, her_ratio1): state, goal env.reset() # 返回当前状态和采样到的目标 for step in range(total_steps): action agent.select_action(state, goal) # 策略输入包含状态目标 next_state, reward, done, info env.step(action) # 保存原始 transition replay_buffer.add(state, action, reward, next_state, done, goal) if done: # 一条 episode 结束开始重标记 transitions collect_episode_transitions() # 从策略中选择一个虚拟目标 g if her_strategy final: her_goal transitions[-1].next_state elif her_strategy future: # 从当前 transition 之后 K 个状态里随机选一个 her_goal random.choice(future_states(transition, kfuture_k)) elif her_strategy episode: her_goal random.choice(all_states_in_episode) for transition in transitions: her_reward env.compute_reward(transition.next_state, her_goal, info) replay_buffer.add(transition.state, transition.action, her_reward, transition.next_state, done, her_goal) # 可选每一原始 transition 加一条重标记数据 state, goal env.reset() else: state next_state这个流程里有几个容易出问题的地方。第一goal的编码方式必须和策略输入对齐。我习惯把state和goal拼接成一个一维向量喂给网络但还有一种是让网络分别编码两部分再融合没有绝对标准。第二重标记的done标签怎么处理如果虚拟目标被“达成”了理论上环境应该结束但实际代码里不能直接给doneTrue否则会把轨迹截断导致训练不稳定。我一般直接把原始done复制过来只在奖励上做文章。3.2 关键模块与参数选择HER 本身不是一个独立算法它是一套经验生成策略需要配合 off-policy 算法使用。论文里用的是 DDPG后来的实践里 SAC、TD3 也都能套。选型时我建议优先考虑 SAC 或 TD3因为 DDPG 对超参太敏感尤其是探索噪声和网络初始化调试周期特别长。在模块层面真正需要写代码的只有两个地方目标采样函数和奖励计算函数。目标采样函数决定了重标记目标从哪来奖励计算函数必须与环境的原始 reward 保持完全一致否则重标记出的成功样本就是假的模型会被误导。参数上最值得关心的是her_ratio即“每条原始 transition 额外生成多少条重标记 transition”。论文里常见的是 1:1也就是一条原始经验配一条重标记经验。我在实操中试过 1:2 和 1:4发现比例太高会让回放池里成功样本过多策略会偏向虚拟目标而忽略原始目标反而不利于泛化。1:1 在我的任务里最稳。3.3 网络结构示意先直接给一个示例网络结构这是我在机械臂推方块任务上验证过的import torch import torch.nn as nn class Actor(nn.Module): def __init__(self, state_dim, goal_dim, action_dim, hidden256): super().__init__() input_dim state_dim goal_dim self.net nn.Sequential( nn.Linear(input_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, action_dim), nn.Tanh() # 假设动作归一化到 [-1, 1] ) def forward(self, state, goal): x torch.cat([state, goal], dim-1) return self.net(x)如果你把目标空间和状态空间做了特征化预处理也可以在拼接前分别过一个小 encoder这个不强制。重要的是在策略和价值网络的输入层把目标信息明显地区分出来否则网络很难学到“同一个状态下目标不同动作不同”的映射关系。还有个细节经验回放池最好把state、goal、action、reward、next_state、done都拆开存用字典形式也行但千万别只存transition对象。因为 HER 重标记的时候需要反复组合不同目标拆开存后写起来会省很多事。4. 实操心得与调参记录4.1 不同 goal 采样策略的对比final、future、episode、randomHER 论文里提出了四种目标采样方式我在实际项目里都跑过区别非常大。简单给一个横向对比采样策略做法我的实际体感适用场景final用 episode 最终状态作为虚拟目标实现最简单训练速度一般胜在稳定短 episode、目标近似可达的任务future从当前 transition 之后的 K 个状态里随机选一个效果最好训练速度和最终成功率都高推荐默认选择episode从整个 episode 的任意状态里随机选一个比 final 好但不如 futureepisode 较长、目标差异大的任务random从经验池里随机选一个状态效果最差基本等于乱标目标不推荐为什么future比final好因为final只把最终状态当作虚拟目标样本空间比较窄而future把轨迹中后半段的各种中间状态都纳入候选虚拟目标更多样奖励信号更密集。但future也有一个坑如果 K 值选得太大虚拟目标可能离当前状态太远导致一条原本“正在接近目标”的轨迹被错误重标成“已经失败”反而干扰学习。我在 50 步长度的任务里用 K4~8 比较合适K 不建议超过 episode 长度的一半。4.2 经验池怎么设计才不会拖垮训练HER 会翻倍甚至翻三倍地增加回放池里的数据量经验池的大小要提前规划。我以前用过 100 万条容量的池子加上重标记数据后很快就被填满了旧的高质量经验被挤掉。后来我把池子扩到 500 万条训练速度虽然慢了一些但成功率比之前高了将近 10 个百分点。经验池的采样策略也值得注意。我习惯给重标记数据打个特殊标记在采样时按比例混合原始数据和重标记数据各占一半 batch。如果让 batch 里全是重标记数据模型会过度关注虚拟目标分布导致在真实目标上的表现漂移。这个小细节在论文里没有明说是我自己在对比实验里发现的。另外每一条原始 transition 生成重标记数据时我建议在生成后立刻放入回放池不要等一个 episode 全部存完再重标。因为 episode 数据可能很大一次性做矩阵操作虽然快但内存占用会暴涨。按 transition 流式处理代码写起来更灵活。4.3 我在训练机器人臂任务时踩过的坑第一个大坑是目标归一化。机械臂任务的 state 是关节角度和末端坐标混合单位数值范围从 -3 到 3 不等goal 又是另一个量纲。我一开始直接把原始数值拼进网络导致 loss 在NaN附近横跳训练根本稳不住。后来把所有 state 和 goal 都做成了归一化统一放到 [-1, 1] 区间问题才消失。这个在论文里很少强调但在工程上是绕不过去的。第二个坑是虚拟目标的可达性。HER 保证虚拟目标一定来自轨迹中的真实状态所以“可达”没问题但要注意虚拟目标对应的 reward 是否合理。如果你的 reward function 不是简单的距离阈值而是带权重的复合指标重标记后计算出的虚拟 reward 可能会和虚拟目标不匹配。检查方法很简单随机抽 100 条重标记数据打印 reward 和对应目标人工看看是否符合直觉。第三个坑是critic 对虚拟目标的过拟合。我观察过一段时间模型在训练后期会出现一种症状critic 对虚拟目标的 Q 值普遍偏高但对真实目标的 Q 值偏低结果 actor 的策略偏向虚拟目标方向。解决办法是每隔固定步数在“真实目标分布”上做一次验证或者按 9:1 的比例在 batch 里保留少量“纯原始目标数据”不给重标记数据。5. 常见问题与排查技巧实录5.1 问题速查表下面这些问题都是我在 HER 落地过程中真实遇到过的整理成一张速查表方便大家直接对照。现象可能原因解决思路训练 loss 不降成功率长时间为 0重标记比例过低或 reward 阈值不合适提高 her_ratio检查 reward 计算策略只会在原地绕圈探索噪声太大或目标采样策略太随机降低动作噪声改用 future 策略训练初期效果很好后期变差回放池旧数据被冲掉或过拟合到虚拟目标扩大回放池加入原始目标数据混合采样模型对训练目标表现好但对新目标泛化差目标编码太原始缺少特征抽象归一化目标考虑用辅助特征编码训练时 loss 震荡特别剧烈DDPG 对超参敏感或网络层数太深换 SAC/TD3降低学习率并加 layer normalization重标记数据过多训练变慢her_ratio 太高回退到 1:1或者让重标记数据只占 batch 的一半5.2 几个排查技巧如果你在跑自己的 HER 实验我建议每一步都留好可复现的验证脚本。最简单的验证方式是固定一个初始状态手动指定一个目标然后看策略是否能够从任意状态逐步逼近。不需要跑完整训练几十步就能判断出目标编码、奖励阈值、网络结构是否合理。还有一个非常实用的技巧从单目标开始调参。我先关掉随机目标采样让环境每次 reset 都给出同一个固定目标调通整个训练流程后再放开多目标。单目标训练时如果成功率都上不去说明问题不在 HER而在基础 RL 算法、网络结构或者奖励函数。等单目标跑通了再加 HER 和多目标调试效率会高很多。最后分享一个我自己的独门经验HER 重标记后得到的“成功轨迹”不要只用来更新策略也可以用来做专家示范预训练。具体做法是先收集一批随机策略的轨迹重标记成“成功经验”用行为克隆或者类似的监督方式预训练 actor再进入强化学习阶段。这个 trick 在水下机器人路径规划项目里帮我省了大约 30% 的训练时间尤其是在二指机械爪这类高自由度任务中效果比从零开始强化学习明显更快。PR