尧图精选

稀疏奖励困境下的HER:目标重标注如何将失败经验变废为宝

🕒 发布时间:2026/10/1 23:05:48 📁 来源:尧图网络
1. hindsight是什么把“事后聪明”变成训练信号我第一次被 hindsight 这个词击中是在调一个七自由度机械臂的推箱子任务。三百万步跑完成功率还趴在 1% 附近训练曲线抖得像心电图。奖励是稀疏的每步没碰到目标位置就给 -1碰到给 0一个 episode 最多五十步。我试过加旅程噪声、调学习率、换网络深度全部无效。直到翻到 Hindsight Experience Replay 那篇论文才反应过来问题根本不在网络容量而在“喂给 agent 的数据本身就没多少信息量”。hindsight 直译是后见之明在强化学习里它早被一个算法占用了名字Hindsight Experience Replay圈内直接叫 HER。HER 解决的问题一句话就能说清在稀疏奖励的目标条件强化学习goal-conditioned RL里agent 跑了半天全都失败数据里没有一条正反馈怎么还学得动它的办法听上去有点“不讲武德”——既然你没达成原定目标那你总能达成某个别的状态吧那我把这个状态重命名成目标一条失败轨迹立刻变成了一条“成功轨迹”。这就是典型的 hindsight事情发生之后重新定义一个合理的解释让损失变成收益。这篇文章不打算复读论文公式而是把我从读论文、复现、改代码、踩坑到调出可用模型的全过程梳理出来。想看原理推导的去翻原论文我这里重点讲三件事HER 为什么在数学上站得住、怎么写进你现有的 DDPG/SAC/TD3 训练循环、以及哪些参数和经验是论文里绝不会告诉你的。适合正在做机器人操纵、仿真导航、或者任何稀疏奖励任务的强化学习实践者哪怕你只是刚看完李宏毅的 RL 课程照着下面的代码也能把 HER 跑起来。2. 稀疏奖励问题HER要捅的到底是哪个马蜂窝2.1 稠密与稀疏为什么奖励设计总是两头受气做强化学习的人早晚会面对一个灵魂拷问奖励函数到底该给密的还是给稀的给稠密奖励你得像老中医一样把任务知识揉进 reward shaping 里而且形状稍歪一点就会出事。举个典型例子机械臂抓取你用“指尖到物体的距离”做负奖励agent 很快就学会把爪子怼到物体旁边但就是不抓——因为在你的奖励定义里“接近”比“抓住”得分更多这在业界有个专门说法reward hacking。你设计了一个自认为合理的梯度方向agent 顺着梯度找到了你没想到的捷径。给稀疏奖励问题换成另一个方向奖励只在任务成功时出现中间过程全是 -1agent 想优化都不知道往哪使劲。像 Fetch 系的机械臂任务一次 rollout 五十步五十步里全是 -1 就是“安静地失败”反向传播算出来的梯度要么为零要么是纯噪声。更麻烦的是时序信用分配五十步里到底哪一步导致失败稀疏奖励下你完全分不出来每步看起来都该担责等于每步都不该担责。我见过很多人在这个路口硬刚加奖励塑形、加课程学习、手动剪轨迹。这些路子不是不行而是又慢又费人。HER 的思路完全不同——不动奖励定义直接改造已经产生的经验数据让失败样本里也能挖出学习信号。这个视角切换非常本质它不问你“怎么让 agent 更容易成功”而问你“失败经验能不能换个角度变成有用经验”。2.2 高维空间里随机探索有多绝望一笔账算给你看稀疏奖励难难在探索。探索的本质是随机试错但高维空间里的随机试错效率低到超出直觉。算一笔账假设状态空间每个维度只分成 10 个格子一个六维状态就有 10^6 个格子目标区域只占其中一个。如果策略完全随机一次就命中目标的概率是百万分之一。而真实机械臂的状态空间哪止六维几十维起步碰撞、动力学全是连续量目标区域占整个空间的体积比可以小到 10^-20 级别。论文里那个 bit-flipping 例子更直观一个 50 位的二进制向量目标是指定向量只有全对才给 0 奖励否则 -1。随机猜对一次的概率是 2^-50约等于 10^-15。这个量级意味着你用随机策略跑一亿次也碰不到一次正反馈。普通 Q-learning 在这种情况下根本学不动因为价值函数没有任何一个非零样本可以 bootstrap——你没有任何“成功的邻居”可供泛化。这也是为什么单纯给 agent 增加探索噪声救不了它。噪声只会让行动更“随机”但随机性无法对抗维度爆炸。真正要解决的问题是在不依赖奖励信号的前提下如何让 agent 从失败中提取“哪些动作组合至少通往了某个可达状态”。HER 其实就是从这个角度切入的把“某个可达状态”变成训练时的替代目标让正的时序差分信号先流动起来。2.3 前提条件为什么HER必须有“目标条件”这个设定在你动手接 HER 之前先确认任务是不是目标条件形式。所谓目标条件就是你的策略和价值函数都要接收一个额外的目标 g 作为输入写成 π(a|s, g) 和 Q(s, a, g)。机械臂的 desired goal、导航的目的地、游戏里的目标坐标都属于这种设定。HER 之所以需要它是因为重标注操作本质上就是“换掉输入里的 g”如果网络根本不吃这个参数那你就没法给它换标签。用生活类比理解更顺你让一个新人“把杯子放到桌上标记位置 A”他没放到 A放到了 A 旁边的 B。普通训练会记一笔失败目标条件训练则允许你告诉他“那就把放到 B 当成一次成功来学”。下次再遇到类似初始局面他对“把东西放到某个指定位置”这个抽象技能的掌握就更深一层。积累多了他对各种目标位置都有了一定的价值估计哪怕你依然要求他放 A他也比只能区分“A 或失败”的策略聪明得多。所以 HER 不是万金油它解决的是“目标条件 稀疏奖励”这个组合拳下的问题。如果你的任务奖励本身已经稠密且设计良好HER 的收益有限如果你的策略压根不接收目标输入HER 则无从谈起。搞明白这个前提后面的代码才不会接错。3. HER的核心机制目标重标注3.1 三步完成目标重标注流程其实比想象中简单HER 的核心操作叫目标重标注goal relabeling完整流程只有三步。第一步正常跑一个 episode从头到尾agent 用原始目标 g 与环境交互收集每一步的观测、动作、奖励、下一观测。第二步episode 结束之后看整个轨迹里 agent 实际到达过哪些状态选其中一个作为替代目标 g最省事的做法是直接取轨迹末尾的 achieved_goal。第三步把原始轨迹里每一步的奖励按新目标 g 重新计算一遍然后把“原目标版本”和“新目标版本”的 transition 都塞进经验池。为什么重算奖励就行因为目标条件的设定下奖励函数本身就写成 r(s, g) 的形式我不管 step 怎么发生只看“当前状态离目标 g 近不近”。目标换掉了奖励自然跟着换。原来的失败轨迹里全程 -1现在按 g 算轨迹后半段全都是 0——失败了五十步现在里面有二三十步是“成功经验”。价值函数终于有了非零坐标可以立足。拿打篮球类比最贴切。你在三分线外投篮目标是篮筐结果球砸在篮板弹到了左侧。普通训练只记录“没进”。HER 告诉你把“球最终落到的那个点”当作目标刚才这次投篮就是一次标准的命中。练得多了你对“把球送到任意指定落点”这件事的掌控力逐渐提高其中当然也包括真正的篮筐。关键不在于这次投篮是否命中原目标而在于你一直在积累“把输入状态映射到目标状态”的真实样本。3.2 这样改标签不违反物理规律吗可行性背后三件事第一次听说重标注的人通常会问这不算造假吗动作 a_t 明明是在追目标 g凭什么叫它追 g 的成功样本这里有三件事可以打消这个疑虑。第一从环境动力学角度看(s_t, a_t, s_{t1}) 这条转移是环境真实产生的它不依赖目标变量。目标 g 只是价值函数的一个条件输入你把它换成 g并不改变转移本身的真实性。Q-learning 本来就要估计任意 (s, a) 组合的价值自然也要估计任意 (s, a, g) 组合的价值。新目标 g 下的 Q(s_t, a_t, g) 是一个完全合法的估计对象。第二这条样本不是策略“打算”去 g但它仍然携带着有用的因果信息在状态 s_t 执行 a_t结果来到了 s_{t1}而 s_{t1} 距离 g 很近。这等于告诉价值函数“从 s_t 出发执行 a_t 可以接近 g”。多次类似样本叠加价值函数慢慢学会“哪些动作能把状态推向哪些目标”。这正是通用价值函数的思想与其只学一个固定目标的 Q不如学一张覆盖所有可达目标的价值曲面。第三原始轨迹并没有被丢弃。重标注是在原始经验之外额外生成 k 份不同目标的副本不是拿新目标覆盖旧目标。这样做保证了训练数据里既有“原始真实目标”的样本又有“替代目标”的样本目标分布不会整体漂移到“太容易达成的区域”。如果你只留重标注版本agent 会变得只会追好达成的目标真正要解决的原始目标反而被忘了——这个坑我在第六节还会细讲。3.3 final / future / episode / random四种策略怎么选论文里给出了四种选择替代目标的策略实际工程里主要用前两种。我把它们放在一起对比方便你直接照着选。策略替代目标怎么选优点缺点/适用注意final当前 episode 最终 achieved_goal实现最简单短任务性能稳定长任务里目标信号集中在末尾中期样本提升有限future当前时刻之后的某个 achieved_goal保留时序顺序样本携带“从当前位置能到达该目标”的方向信息候选随 episode 长度变化训练早期候选少episode本 episode 内任意一个 achieved_goal目标覆盖整个轨迹实现简单可能选到当前时刻之前的点破坏时序直觉random全局经验池里任取一个 achieved_goal目标分布覆盖广泛化面大离当前状态太远时噪声大训练可能要更久我的经验是新任务一律先上 future。论文实验里 future 在多数环境表现最好或持平原因也说得通——它要求替代目标出现在当前时刻之后这等于告诉价值函数“沿这条轨迹走下去确实能接近这个目标”时序上不打架。final 适合那种轨迹短、成功率本身很低的入门验证任务比如 FetchReach。random 我一般不用它让目标分布太散训练初期的 Q 估计会非常毛糙如果你发现 agent 在一个简单任务上就是学不进去可以先考虑是不是候选目标离轨迹太远导致的信号稀释。4. 手把手实现HER从代码到训练循环4.1 重标注函数一份可直接落地的Python实现我直接给一份我一直在用的重标注实现代码不长但每个细节都踩过坑。这里的 transitions 是一个 list每个元素是包含 obs、action、achieved_goal、next_achieved_goal、done 等字段的 dict。import numpy as np def her_relabel(transitions, strategyfuture, k4, threshold0.05): transitions: 一个 episode 的 transition 列表 strategy: final / future / episode k: 每条原始 transition 额外生成 k 条重标样本 threshold: 判定目标是否达成的距离阈值 T len(transitions) achieved_goals [t[achieved_goal] for t in transitions] extra [] for t, tr in enumerate(transitions): for _ in range(k): if strategy final: g_prime achieved_goals[-1] elif strategy future: candidates list(range(t 1, T)) if not candidates: continue g_prime achieved_goals[np.random.choice(candidates)] elif strategy episode: g_prime achieved_goals[np.random.randint(0, T)] else: raise ValueError(funknown strategy: {strategy}) dist np.linalg.norm(tr[next_achieved_goal] - g_prime) new_reward 0.0 if dist threshold else -1.0 # 重标样本的 done 一律置 False原因见正文 extra.append({ obs: tr[obs], action: tr[action], goal: g_prime, reward: new_reward, next_obs: tr[next_obs], done: False, }) return extra注意这里“obs”和“next_obs”我刻意没有把 goal 塞进去因为不同框架的观测结构不一样。在 gym 的 GoalEnv 里整套观测其实是一个 dict包含 observation、achieved_goal、desired_goal。你在把 extra 写进经验池时要把 goal 填到 desired_goal 字段里做成一个完整的 obs dict否则采样时网络读不到目标。这个小接线很容易出错我第一次复现时就栽在这上面重标注样本的 obs 里 desired_goal 还是旧目标等于白标。4.2 和DDPG/SAC拼接数据流接错一步就白训HER 不是独立算法它是给离线策略off-policy算法用的数据增强器。你手上必须有 DDPG、SAC、TD3 这类带经验池的算法。数据流是这样接的环境跑完一个 episode 后把原始 transition 写进经验池紧接着调 her_relabel 生成额外样本同样写进经验池。训练时正常从经验池采 minibatch网络输入把目标拼接进状态其他什么都不用改。HER 能配对论策略吗基本不能。PPO、A3C 这类 on-policy 算法要求当前训练数据必须由当前策略采样而重标注改写了历史数据的奖励数据分布和策略新鲜度都对不上。硬要上你看到的就是 loss 乱跳、成功率上不去。如果你只会 PPO又确实想用 HER 的思想通常做法是换成离策略的 SAC——SAC 自带熵正则配上 HER 在很多机器人任务上比 DDPG 还稳。拼接时还有个容易搞错的地方网络的输入拼接方式。我见过有人把 state 和 goal 直接 concat也有人分别过编码层再相加两种都能用。差别在于concat 方式简单直接适合状态和 goal 维度都不高的场景如果你很在意泛化可以把 state 编码成 z_sgoal 编码成 z_g让 Q 网络吃(z_s, z_g, action)。做实验先用 concat 验证逻辑别一上来就用复杂结构排查问题会轻松很多。4.3 训练循环里三个经常被忽略的细节第一个细节原始 transition 和重标后的 transition 必须共存。前面说过只留重标版本会让目标分布偏向易达区域。实际操作里我习惯把 k 份重标样本和 1 份原始样本一起入池保持比例稳定。k4 意味着每个原始样本额外产生 4 份经验池里重标数据和原始数据的比例大概是 4:1这个比例是论文验证过比较合理的。第二个细节done 标志的处理。重标后的“成功”并不代表 episode 真的结束了所以 done 必须强制置 False。如果你沿用原始的 done不少复现代码就是这样偷懒价值函数会学到“看到目标就终止”——这在有提前终止逻辑的环境里会严重破坏 Q 的 Bootstrap训练曲线练到一半断崖式下跌。我建议在重标函数里显式写上 doneFalse宁可多写一行注释也别让后人误删。第三个细节目标归一化。Fetch 系任务的 goal 是三维坐标单位是米范围大概在 [-0.4, 0.4]但有些自定义环境的 goal 可能是角度、速度或者混合量尺度差异巨大。不归一化的后果是 Q 网络输入里某些维度的数值动辄几十梯度被大数值维度主导训练速度肉眼可见地变慢。我通常从经验池里统计 goal 的均值和方差在送入网络前做标准化。注意均值方差要持续更新最好放在训练主循环里而不是固定常数。5. 训练技巧与参数心得5.1 调参先看懂这几个参数k、策略、阈值、归一化HER 引入的超参数不多但每个都值得较真。k 控制数据增强强度k1 时的提升已经很明显但一般取 4 作为默认显存和时间够的话可以试 8收益会继续涨但不是线性的8 以上边际收益就很小了。k 太大的另一个隐患是经验池里重标样本过多原始目标样本被稀释训练初期看起来 loss 降得漂亮后期在真实目标上的成功率反而卡住。替代目标策略前面已经给了对比这里补充一点future 策略在长 horizon 任务里优势更明显因为它保留了“当前在 t 时刻、目标在 t 时刻”的先后关系。这个顺序对 Q 函数的时间差分学习很重要。如果你用 episode 策略可能选到当前时刻之前的目标那就等于告诉 agent“我能在时间上倒着接近目标”虽然数学上不致命但会引入额外的估计偏差。阈值 threshold 决定了“多远算成功”它和环境自带的判定阈值最好保持一致。Fetch 系给的是 0.05 米如果你想更严格就调到 0.03但这会让成功样本更稀疏训练难度上升。我的建议是先用环境默认值跑通确认 HER 生效后再收紧。5.2 一套可复现的基准配置直接抄作业版下面这套配置是我在 FetchPush、FetchPickAndPlace 这类任务上验证过的起点适合拿来当基线。算法选 SAC 或 DDPG 都行SAC 更稳DDPG 更快。参数建议值说明基础算法SAC / DDPG / TD3必须是离策略算法网络结构三层 MLP每层 256激活函数用 ReLU输出层看算法学习率Actor/Critic 均 1e-3SAC 可用 3e-4太大会震荡太小收敛慢折扣因子 γ0.95 ~ 0.98对应 50 步左右的 episode 长度批大小256小 batch 会让重标信号更吵经验池1e6尽量大重标样本也需要空间k4每条原始样本额外生成 4 条目标策略future默认值几乎不用改成功阈值0.05和环境判定一致探索噪声高斯 σ0.2训练后期可衰减到 0.1每个 epoch 的 rollout10 ~ 50 个 episode看你环境的采样成本这套配置下FetchPush 通常几十个 epoch 就能看到成功率明显抬头FetchPickAndPlace 会慢一些但也在可控范围。如果你一跑就是十几个 epoch 一点不动先别急着调参回头检查 4.3 节讲的三个细节是不是都处理对了——大多数“HER 无效”的案例都是接线问题不是参数问题。5.3 什么任务适合HER、什么任务别硬上适合 HER 的任务有三条硬指标目标条件化、离策略算法、稀疏奖励。存在一个“可达状态”集合agent 在失败时通常到达了和原目标不同的状态——机器人推箱子、机械臂抓取、迷宫到指定点、策略梯度做分子构象优化都是好靶子。这些任务的共同点是替代目标本身也充满学习意义agent 学的是“如何到达任意可达目标”这个通用技能而不是死记一条轨迹。反过来以下情况别硬上 HER。第一奖励已经很好塑形的连续控制任务比如用距离做稠密奖励即使加 HER 收益也有限反而浪费算力。第二任务的目标空间没有意义——比如环境的失败模式是“掉落悬崖、直接重置”整个 trajectory 里根本没有一个值得当作目标的中间状态。第三状态和动作空间里含着大量随机扰动导致 achieved_goal 本身不可重现重标目标会让 Q 学到噪声模式。第四on-policy 算法搭 HER 基本是白费功夫除非你把策略换成离策略版本。我还有一个更实用的判断标准先跑一个不加 HER 的基线如果成功率在数据量充足后依然长期为零说明稀疏奖励的探索信号彻底断了这时候 HER 是最值得上的招如果基线已经在稳步上升HER 可以锦上添花但优先级可以往后放。6. 踩坑记录与排查技巧6.1 六个典型翻车现场按症状排查先说我踩得最狠的一个坑训练初期正常中期突然失控。原因是 done 标志没有重置。我当时直接用环境返回的 done 写进重标样本环境在成功时确实返回 doneTrue于是 Q 函数学到了“看到任何目标都代表游戏结束”。一旦学到这个错误关联策略就会变得特别保守宁可推空也不想去碰目标区域。排查方式是打印经验池里重标样本的 done 分布发现成功样本全带 doneTrue 就基本实锤。第二个坑future 策略在短 episode 里候选太少。如果 episode 只有 20 步future 在 t19 时一个候选都没有整条轨迹能重标的样本就没几条等于 k 白设。我把一条 Fetch 系任务从 50 步缩短到 20 步做消融实验时HER 的收益瞬间缩水。这种时候要么把 episode 加长要么对末尾几步允许用 final 兜底。第三个坑目标没归一化。自定义环境里 goal 的三个分量尺度差一个数量级Q 网络的梯度被大尺度分量带着跑loss 曲线漂亮但成功率纹丝不动。解决办法是统计 goal 的均值和方差做标准化这个操作成本极低收益立竿见影。第四个坑原始目标数据占比过低。我用 k16 做了个极端实验经验池里 94% 都是重标样本结果 agent 在替代目标上花团锦簇在真实目标上几乎为零。原理前面讲过目标分布被“易达成状态”带偏agent 变成了“只挑软柿子捏”的策略。k 控制在 4-8 之间并且永远保留原始样本。第五个坑阈值设定和环境判定不一致。我把重标奖励的阈值设成 0.03环境自身判定成功用 0.05结果出现“环境认为成功但我给它打负分”的样本价值函数和真实奖励造假之间出现系统性偏差。这个纯属手滑但排查起来非常隐蔽因为表面上看奖励都对只有对比环境内建 reward 才会发现不一致。第六个坑在训练中期偷偷把探索噪声降没了。DDPG 本来就需要探索噪声来维持数据的多样性HER 再强也救不了完全贪心的行为策略。训练后期噪声太小时经验池里只有成功轨迹附近的样本重标目标的多样性大幅下降。我的经验是噪声从 0.2 慢慢衰减到 0.05 即可完全关掉会反噬。6.2 一张表定位问题症状、原因、操作症状可能原因建议操作前几轮正常中后期成功率塌方重标样本 done 误置为 True重标样本强制 doneFalse成功率长期为零loss 也不降目标没喂进网络obs dict 接错打印采样 batch 里的 desired_goal 是否等于重标目标多个维度差异大时收敛极慢goal 未归一化统计均值方差标准化后再喂网络原始目标成功率低但替代目标高k 太大或原始样本被稀释k 降到 4保留全部原始样本future 策略候选太少episode 过短加长 episode 或尾部用 final 兜底曲线抖得厉害用了 on-policy 算法接 HER换成 SAC/TD3/DDPG奖励分布看着对但学不动重标阈值与环境阈值不一致统一成功判定阈值这张表基本覆盖了我个人复现 HER 时遇到的 90% 问题。说实话HER 本身不复杂复杂的是工程接线。每一行都能对应到具体代码层面排查时不要靠猜多打印经验池里的实际数据多看一眼采样出来的 batch 长什么样问题很快就定位了。7. 一点个人体会最后聊点我自己的实操习惯。我现在接到任何目标条件稀疏奖励任务第一件事就是先把 HER 接上不接就浑身难受。但在大规模训练之前我一定会先跑一个冒烟测试找一个超小规模的目标条件任务比如十位的 bit-flip 或 FetchReach验证重标注逻辑、done 处理、目标拼接这三条数据流没接错。十五分钟能跑完的测试能省我后面一周的瞎调参时间。还有一件事值得多说一句HER 教会我的不只是一个算法套路而是一种看待失败数据的方式。失败轨迹不是废数据它只是贴错了标签的成功样本。这个思路后来也影响了我做其他任务的数据设计——与其花两周构思一个精巧的奖励塑形不如先让 agent 学会“从任何起点到达任何可达目标”往往后者才是任务真正需要的能力。如果你正准备在一个稀疏奖励项目里用 hindsight记住先保证接线正确再用默认参数跑起来最后才谈调优。顺序反了再好的算法也救不回来。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →