强化学习稀疏奖励的救星:HER事后经验回放实战指南
第一次看到 hindsight 这个词是在一份强化学习论文的标题里。当时我刚被一个稀疏奖励的机械臂抓取任务折磨了一周环境里每一条轨迹几乎都是零奖励网络训练跑了两天成功率纹丝不动TensorBoard 上的曲线像一条心电图直线。那种感觉用过一句话形容就是“RL 后期最难的不是网络结构不是目标函数而是怎么在满屏零奖励里硬挤出一点梯度信号”。而 hindsight对应的完整技术叫 Hindsight Experience ReplayHER事后经验回放正是专门治“智能体跑了半天奖励纹丝不动”这种病的。这篇文章不打算复述论文公式我想把我在实际项目里用 HER 处理稀疏奖励的经验、踩过的坑、以及它真正的适用范围一次讲清楚。适合正在调 RL 环境的同学、做机器人抓取和仿真导航的朋友以及所有被 reward0 逼疯、甚至开始怀疑人生的人。读完你至少能明白三件事HER 为什么有效、怎么把 HER 接到自己的训练代码里、以及哪些场景它救不了你。1. 为什么一张全是 0 的奖励表会把智能体逼疯1.1 稀疏奖励的本质99% 的样本都是无效样本先回到最基础的场景。假设你训练一个智能体走迷宫终点放一个奶酪只有碰到奶酪才给 1 奖励其他动作全部给 0。这条奖励函数看似简单但你要知道RL 优化的本质是“从数据里估计哪个动作更好”。如果一段 100 步的轨迹只有第 100 步有 1那么前面 99 步的 (state, action, 0, next_state) 样本对 actor 来说几乎不携带任何“该往哪走”的信息因为 TD 误差大部分时间都接近 0梯度传回去非常微弱。更致命的是探索问题。在连续动作空间里比如一个 7 自由度的机械臂动作取值范围是 [-1, 1]^7随机初始化策略能“恰好碰到目标”的概率基本是零。也就是说你采集到的绝大多数轨迹全部都是“无效轨迹”——奖励从头到尾都是 0。这种情况下无论用 DQN 还是 PPO智能体学到的东西都约等于“不动最安全”因为不动和乱动造成的后效完全一样。这里可以做个生活化类比让一个人蒙着眼睛在一个大房间里找一处开关如果每走一步都没有任何反馈他只能完全随机游走。但如果每次离开关近一点就听到“滴”一声他很快就能摸过去。稀疏奖励的本质就是省略了这声“滴”让智能体连“我是不是在变好”都无法判断。1.2 “事后诸葛亮”为什么是解药hindsight 这个词的本意是“事后看来”。这正是问题的突破口。你还记得追公交车的经历吗你拼命跑试图赶上 7 路车结果它还是开走了。从“追上车”这个目标看你失败了这一段跑步轨迹全是负面经验。但如果把目标改成“在 20 秒内跑到公交站”那么你刚才的行为就是一次完美的成功示范——你确实跑到了公交站只是晚了几秒。强化学习里的 HER 做的事是完全相同的一件事一条没有达到设定目标的失败轨迹我并不急着丢掉而是从这条轨迹的后续状态里挑一个“实际到达的状态”把它重新定义为新目标然后把整条轨迹贴上新标签当作一条成功轨迹放进回放缓冲区。这在目标条件强化学习中叫做目标重标定 goal relabeling。换句话说HER 的核心哲学是失败不等于没有信息量失败只是目标定错了。既然智能体来到了某个地方那“到达这个地方”这件事本身就是一个可以学习的技能。我们只要让它在回放中学这个技能它慢慢就能组合出一连串技能最终够到原来的目标。1.3 它的边界能解决什么解决不了什么我得先泼一盆冷水HER 不是银弹。它真正适用的场景必须同时满足三个条件任务是目标条件的goal-conditioned智能体的输入里包含一个明确的目标描述 g策略可以写成 π(a|s, g)。环境状态中能够提取出“实际达成目标”所需的量比如机械臂的末端位置、物体的最终位置或者游戏角色的最终坐标。距离目标越近行为就越接近成功路径换句话说任务存在“渐进路径”只是奖励信号太稀疏。如果任务压根没有目标概念比如纯探索问题、或者奖励只取决于一个隐变量HER 就很难发挥。比如训练一个智能体玩游戏目标是解开谜题但谜题的钥匙藏在随机房间状态里并不直接体现“有没有碰到钥匙”。这种时候 relabeling 很难做因为重标出来的目标可能根本不可达或者语义上无意义。所以别看到 HER 就以为能解决所有 reward 稀疏问题它解决的是“有目标但够不着”的问题。接下来我们看它具体是怎么改的。2. HER 到底在改什么目标重标定的原理拆解2.1 先搞清楚“目标”长什么样要理解 HER先要建立目标条件强化学习Goal-Conditioned RL的概念。普通 RL 的状态是 s动作是 a奖励是 r(s,a)。目标条件 RL 里多了一个目标变量 g策略变成 π(a|s,g)奖励也变成 r(s,g)——只有当前状态“符合”目标 g 时才有正奖励。举一个我经常用的例子。环境是 FetchReach机械臂末端去碰一个目标点状态 s 里包含机械臂各关节角度、末端位置、目标点位置可能还包含物体位置等。目标 g 是一个三维坐标期望的末端位置。奖励函数很常见如果末端和目标的欧氏距离小于阈值 0.05奖励为 1否则为 0。这里有个关键点目标 g 必须能跟状态 s 里的某个量直接对应起来relabeling 才能执行。你从状态里提出“实际末端位置”把它作为 g这件事才有意义。如果目标本身是抽象的“把水烧开”状态里没有任何可提取的中间量那 HER 就没有抓手。2.2 重标定流程给失败轨迹重新贴标签HER 的具体操作并不复杂上午看论文下午就能实现。假设你有一条轨迹τ (s_0, a_0, r_0, s_1, a_1, r_1, ..., s_T)这条轨迹的真实目标是 g但因为没达到奖励全部是 0。HER 的处理方法对轨迹中的每个时刻 t从时刻 t 之后的某个状态里选一个“未来状态” s_k其中 k t。从这个状态中提取一个新的目标 g f(s_k)比如 s_k 里的物体位置或者机械臂末端位置。把轨迹上每个转移的奖励重新算一遍r r(s_{t1}, g)。将新的转移样本 (s_t, a_t, r, s_{t1}, g) 投进回放缓冲区。因为 g 是从未来的状态提取的对于 t 到 k 这段区间来说智能体确实“成功达成了目标 g”所以后半个轨迹的奖励会从 0 变成 1。一条原本毫无价值的失败轨迹经过重标定之后变成了一段携带丰富正反馈的成功轨迹。这里最容易踩的误区是重标定时要用未来状态而不是用当前状态。如果用当前状态作为目标那智能体在 t 时刻“立刻就在目标上”这种样本同样没有学习意义。所以 HER 的标准做法是“从后续时刻采样目标”而且越靠后的状态越有价值。我当年实现的时候第一次写反了把目标设为当前状态结果训练出来一个只会原地发呆的策略K 值怎么调都没用。后来打印 buffer 里的 reward 分布才发现重标过的样本全部都是“原地成功”智能体被训练成了“站着别动就是满分”的废物。这个坑后面我会在排查部分再展开。2.3 重标过的样本为什么能帮助“真实目标”有人会问你换个目标让智能体“成功”但它学会的是“到达别的地方”这跟原来的目标 g 有什么关系关键在于策略是条件的π(a|s,g) 不是一个只会奔向某个固定点的策略而是一个给目标就能执行的策略。你训练它“让物体移动到位置 A”它学会了推的动作再训练它“让物体移动到位置 B”它又学了一个推的动作。这些动作内在机制是共享的——都要学会“怎么推”。HER 等于给你提供了一大批“各种位置的成功示范”这些示范覆盖了真实目标附近的状态空间。策略在这些重标目标上学会的感知和运动能力最终会迁移到真实目标 g 上。再打个比方你想训练一个球员射进门框右上角这种极高难度的死角球。如果只让他对着这个死角射他一天也进不了几个球教练也难以纠正动作。HER 的干法就是让他在多个不同角度的球门位置反复射门每个位置都允许他成功几次让他积累“调整脚法、瞄准、发力”的经验。当这些一般化能力足够强时再让他去射那个死角成功率就上来了。这也是 HER 为什么能跟 DQN、DDPG、TD3、SAC 这类 off-policy 算法完美配合的原因。回放缓冲区里除了原始轨迹还有大量重标轨迹它们一起参与了 Q 值和策略的更新。梯度不再稀缺Q 函数的估计也就更平稳。2.4 三种取目标策略到底该选哪个HER 论文里提出了几种策略来选择重标定的目标实际用得最多的是这两种但完整对比一下会让你理解更准策略做法优点缺点适用场景final只用轨迹最终状态作为新目标实现最简单目标离真实起点最远长程信息强最终状态往往离原始目标很远中后段才出现正奖励前半段梯度弱目标空间小、状态即目标的任务如点到达future对每个 t从 t 之后随机采一个状态作为新目标覆盖范围广整条轨迹都有正反馈训练稳定采样逻辑稍复杂目标数量大绝大多数连续控制任务首选random从整个回放缓冲区里随机抽一个状态作为目标目标分布多样大部分抽样目标与当前轨迹无关正奖励比例低不适合单独用一般做补充episode把整条轨迹所属 episode 的最终目标作为新目标语义贴近任务等价于不重标提升有限极少用多数情况不如 future我在实际项目中全部用 future并且 K 值取 4。也就是说每一条原始轨迹额外生成 4 条重标轨迹。这样缓冲区里真实目标样本和重标样本的比例基本是 1:4既保证了数据多样性又没有把真实目标淹没掉。如果 K 太大比如 16回放缓冲区里几乎全是重标样本智能体学出来的策略会变得“太擅长去各种地方”但对真实目标的定向能力反而弱。3. 亲手把 HER 接进训练流程从选型到跑通3.1 前置条件别拿 on-policy 算法硬上HER 依赖经验回放因此必须在 off-policy 算法上使用。DQN、DDPG、TD3、SAC 都没问题。如果你用的是 PPO、A3C 这类 on-policy 算法直接套 HER 会非常别扭on-policy 要求训练数据来自“当前策略”的采样分布而重标轨迹改变了分布策略梯度公式里的重要性权重根本算不准训练容易直接爆炸。这不是说你不能用 HER 的思想去改进 on-policy 算法而是说你需要走 EPO利用 off-policy 数据做 on-policy 修正这类更复杂的路径普通项目不值得折腾。我的建议很简单想用 HER就用 TD3 或 SAC 当基底亲测稳定省心。3.2 环境接入你的 env 需要暴露什么gym 里的 Fetch 系列环境已经把接口设计好了你自己写环境时至少要实现这几样东西reset() 返回的 obs 中必须包含与目标相关的字段通常是一个 dict例如 {observation: _, achieved_goal: _, desired_goal: _}。step(action) 返回 next_obs、reward、done、info其中 next_obs 里的 achieved_goal 会随状态变化。一个独立的 compute_reward(achieved_goal, desired_goal, info) 静态方法专门用来事后计算任意“已达成”状态相对任意目标的奖励。一个从状态里提取目标字段的函数 extract_goal(obs)我用它从未来状态里抽出 g。别小看 compute_reward 独立出来的意义。训练时你既要算真实目标的奖励又要算大量重标目标的奖励如果奖励函数藏在环境内部的某个私有方法里重标逻辑根本没法写。3.3 完整训练循环骨架下面给一个极简但完整的 HER 训练循环伪代码基于 DDPG 的思想但把细节剥开让你看到 HER 插在哪里import numpy as np import random def train_with_her(env, agent, replay_buffer, k4, epochs1000): for epoch in range(epochs): obs env.reset() episode [] # 保存一整条轨迹 goal obs[desired_goal] # 1. 用真实目标去环境里交互攒出一条轨迹 for t in range(env.max_steps): action agent.select_action(obs) next_obs, reward, done, info env.step(action) episode.append(Transition( obs[observation], action, reward, next_obs[observation], goal, obs[achieved_goal], float(done))) obs next_obs if done: break # 2. 原始轨迹直接进 buffer for transition in episode: replay_buffer.add(transition) # 3. HER 重标定对每个时刻额外生成 k 条重标样本 for t, trans in enumerate(episode): for _ in range(k): future_idx random.randint(t, len(episode) - 1) new_goal episode[future_idx].achieved_goal new_reward env.compute_reward( trans.achieved_goal, new_goal, None) replay_buffer.add(Transition( trans.state, trans.action, new_reward, trans.next_state, new_goal, trans.achieved_goal, trans.done)) # 4. 从 buffer 中采样并更新 actor / critic for _ in range(40): batch replay_buffer.sample(256) agent.update(batch)代码里第 3 步就是 HER 的全部秘密。注意我加了achieved_goal这个字段保存“当步实际上到达的状态”compute_reward 完全基于这个字段算奖励而不是基于原始目标。这也是我犯过的错——重标样本的 reward 还按真实目标去算那就等于没重标。3.4 我认为最关键的超参数光有代码还不够参数调不好照样白搭。HER 相关的关键参数我整理成一张表参数建议取值说明K重标轨迹数4论文消融结果K0 到 4 提升巨大4 到 8 趋于平缓超过 16 反而有害重标目标采样方式future绝大多数任务首选曲线上涨最稳定奖励阈值根据物理尺度定不要用 0.001 这种极高精度机械臂一般 0.03~0.1 就够了缓冲区容量50 万到 100 万HER 需要足够多样化的重标分布buffer 太小效果大打折扣每轮更新次数20~40重标样本增加后每轮多更新几次能明显加快收敛探索噪声 σ0.1~0.3探索太少buffer 里的状态覆盖不足重标样本也会缺乏多样性这里我想特别强调奖励阈值的选择。很多新手喜欢把稀疏奖励的阈值设得非常高精度觉得“这样才算正确”。但阈值设成 0.001 之后重标得到的“成功”样本几乎全集中在目标点附近极小区间奖励分布依然稀疏。我的经验是阈值应该接近任务本身的物理精度比如机械臂任务设 0.055 厘米这就已经足够区分“够没够着”。先用宽松阈值把技能学起来再逐步收紧比一步到位更容易成功。3.5 从简单到复杂的三步落地路线如果你从来没跑过 HER我强烈建议别一上来就碰真实机器人或者复杂仿真按下面三步走第一步跑通 OpenAI Gym 的 FetchReach。这是最简单的点到达任务目标空间只有三维HER 加持下通常几百个 epoch 就能达到 90% 以上成功率用来验证代码和参数没毛病。第二步换成 FetchPickAndPlace 或者 FetchPush。这两个任务引入了物体操作缓冲区里的重标目标变成了“物体最终位置”你会发现 HER 依然有效但需要的训练轮数明显上升此时开始学会看 buffer 里的 reward 比例。第三步再上你自己的定制环境。此时你已经知道 HER 的接口长什么样调试时可以直接复用我下面要讲的排查技巧。4. 调参路上的那些坑症状、原因、对策4.1 “train loss 降了成功率却不涨”这是我在多个任务上遇到频率最高的问题。先看是不是重标样本把真实目标淹没了——当你把 K 设成 20、30 的时候缓冲区里 95% 都是重标样本策略被训练成“到达随机地方”反而忘记主要任务。对策是把 K 回调到 4并且确保原始轨迹永远完整进入 buffer不要为了省空间丢原始样本。另一种可能是奖励函数被算错了。我建议你在训练过程中打印最近 100 条真实目标的 reward 分布如果发现真实目标的 reward 几乎全是 0说明策略学偏了但如果连重标样本的 reward 也都全是 0说明 extract_goal 提取的目标本身就不合理——很可能你从状态里提的是动作指令而非实际位置导致“新目标”根本不可达。4.2 Q 值爆炸、策略震荡HER 会显著增加正样本密度Q 值网络在大量“刚刚够到目标”的样本上容易高估。特别是在目标空间非常大、重标目标离当前状态很远的场景智能体偶尔“瞎猫碰到死耗子”成功了一次这个样本会被回放很多遍Q 值被过度放大之后策略开始抖。解决办法把 K 降到 1 或 2减少同一轨迹的重复重标样本。配合 TD3 的 target policy smoothing给目标 Q 加噪声缓解高估。如果还是震荡考虑把奖励阈值稍微放宽一点让“成功”不再那么苛刻降低方差。检查是否在同一轮训练里反复用同一条轨迹重标多次导致过拟合如果是把 HER 重标上限 K 调小。4.3 智能体变成一个“原地刷子”这是我的一个典型翻车现场。训练 FetchReach 时前端曲线看着挺正常loss 稳步下降但实际 rollout 成功率只有 2%策略就只会把末端移到一个固定位置然后原地转圈。原因是什么缓冲区里重标的“成功样本”几乎都集中在初始状态附近——因为未来采样会从轨迹后半段选目标但轨迹前半段的初始状态离这些目标很远所谓“成功”其实只是从很远的地方开始靠近。那为什么只动了那么一小下因为智能体发现只要稍微移动一点就会进入“下个状态离目标更近”的奖励区间于是它就把“稍微动一下”当成了最优策略不再敢大幅度移动。这种保守化在稀疏奖励任务里非常常见。对策是在环境交互阶段加大探索噪声或者用一个简单的内在奖励比如对状态访问次数计数鼓励它多逛不同区域。HER 负责从失败中挖掘成功探索负责产生足够多样的失败——两者缺一不可。如果探索度不够HER 就只能在一小片状态空间里打转。4.4 在复杂任务上没效果不是 HER 的错如果你的任务是“把抽屉里的螺丝拧到某个扭矩”这种多阶段任务HER 直接套用效果会很差。原因是目标空间不再是简单的几何坐标而是一个复合语义目标打开抽屉、拿起螺丝刀、对准、拧动。HER 重标出的子目标可能语义不连贯导致策略学出一堆互相矛盾的行为。我的建议是复杂任务先用分层思想拆解把每个子任务单独做成一个 goal-conditioned 环境各自套 HER或者采用课程学习先学第一阶段再学第二阶段。也可以关注 HER 的后续变体比如 CHERCurriculum-guided HER让重标目标的选择更智能从“随机选未来状态”变成“选择难度适中的目标”。但要做好心理准备这些变体的实现复杂度不低。4.5 我自己一直保留的排查清单最后分享一个我每次训练都会过一遍的检查清单看起来简单但救了我很多次症状排查方向对策成功率始终为 0奖励阈值是否太严格放宽阈值到物理尺度 0.05~0.1loss 下降但 rollout 不涨重标样本是否淹没真实样本K 回调到 4检查真实目标 reward 分布策略原地不动探索噪声太小状态覆盖不足增大噪声加 intrinsic rewardQ 值忽高忽低同轨迹重标过于集中K1开 target smoothing换任务后完全失效目标不可提取或语义复杂重新设计目标空间考虑分层训练极不稳定env 的 done 信号设计不合理检查 done 不要过早在半路触发导致轨迹截断我自己的习惯是每隔一段时间就打印一小批 buffer 里的重标样本看一眼 goals 的分布和 reward 的比例。如果 goal 分布太集中说明状态覆盖差得调探索如果 reward 的比例超过 80%说明重标目标太简单得调阈值。这种“看一眼原始数据”的土办法往往比盯着 loss 曲线有用得多。顺带说一句题外话HER 的思想后来也影响了我做项目复盘的方式。以前我总觉得跑失败的实验就是纯亏数据和日志都懒得再看。后来我养成了“给失败实验换指标重新看”的习惯——换一个评价维度失败样本里常常藏着成功路径。技术上这叫 relabeling工程上这叫复盘。两者本质上是一个道理别急着定义什么是失败先看看你能从这次经历里学到什么目标。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →