尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

HER算法:用“事后视角”破解强化学习稀疏奖励难题

HER算法:用“事后视角”破解强化学习稀疏奖励难题 hindsight 这个英文词翻译过来就是“后见之明”说难听点叫事后诸葛亮。但在强化学习圈子里提到它十有八九指的都是 OpenAI 2017 年那篇经典论文提出的 Hindsight Experience ReplayHER。我第一次看到这个思路时愣了一下把没有完成的目标直接替换成智能体实际到达的状态然后说这次尝试是成功的这不就是给自己找台阶下吗但正是这个“找台阶”的trick解决了一大批机器人连续控制任务长期以来的老大难问题——稀疏奖励。这篇文章想跟各位分享一下我对 Hindsight / HER 的理解和实操经验它到底在解决什么问题、核心机制在代码里怎么落地、调参时踩过哪些坑。适合正准备做强化学习、尤其是想做机器人操作或连续控制方向的朋友也适合已经跑通 DDPG / SAC 但卡在稀疏奖励任务里出不来的同学。我会尽量讲得直白公式只保留最必要的那一点剩下的都是可以直接抄走的实践细节。1. 先说清楚hindsight 到底在解决什么问题1.1 稀疏奖励——强化学习里那道过不去的坎强化学习有个让人又爱又恨的特点它不需要人工标注数据完全靠智能体和环境互动来学。但这句话只说对了一半——当奖励信号密集的时候确实这样一旦奖励变得稀疏互动一万次也未必能撞见一次正反馈。举个我经常跟朋友举的例子你要教一个机械臂抓取桌子上的水杯环境只给一个奖励信号抓住了给 1没抓住给 0。那么在训练刚开始的时候机械臂的动作基本是随机的它几乎不可能在几万次尝试里偶然正中目标。于是 replay buffer 里面装的几乎全是“没抓住”的轨迹每个 transition 的 reward 都是 0。DDPG 这类算法靠 TD 误差来更新 critic而当你采到一批 reward 全为 0 的数据时Q 网络的梯度信号弱到几乎无法更新策略自然也就永远不会变好。这就是稀疏奖励的“死锁”越没成功越没信号越没信号越不可能成功。很多人第一反应是做 reward shaping在中间过程加一些诱导奖励比如手指离目标越近就给越高的分。这个思路不能说错但 reward shaping 太容易引入设计者的主观偏见了。你费尽心思设计一个距离奖惩项智能体很可能学到的是“把手指挪到离目标 1 厘米的地方刷分”而不是真正学到如何稳定地完成任务。我在实际项目里见过太多次这种“刷分式学习”最后还得反过来调 shaping 权重非常痛苦。1.2 为什么“事后视角”能成为学习信号HER 的出发点其实特别朴素既然这次没抓到杯子但机械臂最终伸到了某个确定的位置那这段轨迹是不是至少教会了我们“如何把手臂移动到那个位置”答案是肯定的。假设原始目标用 g 表示比如“手指尖到达 (1.0, 0.5, 0.3) 这个点”。某次 episode 里机械臂一顿操作手指最后停在 (0.6, 0.4, 0.2)。对原始目标来说这毫无疑问是一次失败reward 0。但 HER 会做一件事把这条轨迹的 goal 重新标记成 (0.6, 0.4, 0.2)。由于手指确实在最后到达了这个位置这条轨迹瞬间变成了一条“成功轨迹”reward 1。同样的状态、同样的动作只是换了目标标签数据就从无用垃圾变成了高质量教材。这个操作的理论根基在于HER 面向的是 goal-conditioned 策略也就是以“目标”为输入、输出动作的策略网络。它学的不是“抓住那个特定杯子”而是“给定任意一个目标位置学会到达它”。所以用轨迹实际到达的状态来当备选目标完全合法。更直白地说我们欠智能体一个真相它在整个 episode 里并不只是在朝原始目标努力它同时也完成了无数个“隐式目标”只是我们没告诉它而已。1.3 HER 的适用场景与不适用场景HER 不是银弹它只适用于目标条件强化学习Goal-Conditioned Reinforcement Learning的场景。判断标准很简单你的状态里有没有一个东西可以明确地被当作“目标”并且能用来判定成功与否比如机械臂的末端位置、机器人的目的地坐标、迷宫里的出口位置这些都可以。哪怕目标是一个多维向量比如“物体在桌子上且机械爪张开”只要你能写一个判定函数HER 就能用。反过来如果任务是单目标、没有任何可替换的目标概念或者奖励本身已经足够稠密HER 的收益就很小。举两个例子训练智能体玩 Atari 游戏拿最高分目标就是“得分高”没有第二个有意义的目标可以替换HER 就用不上再比如倒立摆任务每一步都能拿到连续的距离奖励那本身就不缺学习信号HER 更多是锦上添花。还有一种情况也要注意如果环境状态不可完全观测比如你只能拿到部分信息而拿不到完整状态那“事后重标记”也没有可靠的对象。核心原则是目标空间必须可观测、可判定否则 HER 会变成瞎标数据。2. Hindsight Experience Replay 的核心原理拆解2.1 一次失败里藏着什么目标重标记的微观视角为了讲清原理我拿 OpenAI Gym 里经典的 FetchReach 任务来拆解。环境给智能体的 observation 是一个字典里面有两个关键字段achieved_goal当前实际到达的位置和desired_goal期望到达的目标。每一时刻智能体根据“当前状态 目标”输出一个动作环境随后返回新的状态和一个稀疏奖励如果achieved_goal和desired_goal的距离小于某个阈值奖励为 0或者 1否则为 -1或者 0。注意这个奖励函数本身简单到极致没有任何中间过程奖励。现在假设某条轨迹 T 有 50 步原始目标 g 是 A 点但整个轨迹走完末端位置一直停留在 B 点附近。HER 在把这条轨迹存进 replay buffer 的时候会额外生成 4 条“虚拟轨迹”每条都把 B 点或者轨迹中某个未来时刻的实际位置重新设定为 desired_goal。由于虚拟轨迹里每一步实际状态确实“到达”了虚拟目标这些 transition 的奖励全部变成了正样本。这样一来replay buffer 中不再是一潭死水而是充满了“成功到达某目标”的信号Q 网络终于有了可以反向传播的梯度。这里有件事值得强调重标记后的数据不是真实环境交互产生的而是我们事后改写的所以 HER 必须是 off-policy 算法才能用。像 PPO 这种 on-policy 算法如果用改写后的数据去算策略梯度会破坏重要性采样的前提训练曲线会非常诡异。我见过有新手直接把 HER 塞进 PPO 里结果 loss 一路乱跳最后连基础任务都学不会。记住HER 的本质是 replay buffer 层面的数据增强天然跟 DDPG、TD3、SAC 这类 off-policy 方法搭配。2.2 目标重标记的关键策略选择final 还是 future论文里提出了几种从轨迹中挑选“事后目标”的策略这个细节直接关系到训练效果值得单独说final直接把整条轨迹的最后一个状态当作事后目标。逻辑最简单但多样性差——一条轨迹只有一种事后目标。future从轨迹中某个时刻 i 之后的时间步里随机抽一个状态当作该时刻的事后目标。这是论文实验中最推荐的方案。episode从整条轨迹的任意时刻随机抽一个状态不要求它出现在当前时刻之后。random从所有收集到的状态里随机抽一个。我试下来future的效果确实最好原因也不难理解。对于轨迹中间时刻 t如果取一个在 t 之后才出现的状态作为目标那么这个目标的难度是“适中”的——它不是太远以至于这条轨迹完全没往那边走也不是太近以至于毫无学习价值。这种“逐步靠近目标”的数据分布能让策略学到一种平滑的到达能力。相比之下final只考虑终点早期时刻的目标距离太远学起来噪声很大random则可能选到完全无关的状态数据质量太差。具体实现里通常会为每条轨迹的每个 transition 额外生成 k 个 hindsight 目标k 一般取 4 到 8。注意这里不是把原始目标丢掉而是在保留原始目标的同时额外增加虚拟目标。这样才能保证智能体既知道真实任务是什么又能从失败经验中抓住信息。如果你把原始目标全换掉策略就会彻底跑偏只顾着追逐那些“容易达成的虚拟目标”真实任务的成功率会原地踏步。2.3 为什么它不用改公式也能加速学习对比一下传统的 reward shaping 和 HER你会发现一个很有意思的差异前者是在“奖励函数”上做文章企图让每一步都给出有意义的信号后者是在“数据”上做文章把失败轨迹重新解读成有效样本。所以 HER 的接入成本极低——你完全不需要改动 actor-critic 的更新公式不需要写复杂的辅助损失只需要在处理 replay buffer 时加一个重标记函数。从数学上看如果我们定义 goal-conditioned 的 Q 函数为 Q(s, a, g)那么 HER 做的事情就是把训练样本里的 g 替换成 g然后照常计算 TD 误差。因为 g 本身只是函数输入中的一个条件向量替换它并不会破坏函数拟合的目标反而让 Q 函数能利用到更多“到达了某状态”的真实数据。这有点像学一门新语言的时候你不仅听老师布置的“标准对话”还把平时日常聊天中无意说出的句子也当作学习材料反复琢磨。本质上这都是在从看似无用的经历里挖掘训练信号。3. 实操在代码里实现 HER3.1 环境搭建与基准选择真要动手复现 HER第一步是选环境。OpenAI Gym 里的 Fetch 系列FetchReach、FetchPush、FetchSlide、FetchPickAndPlace是论文的标准测试环境但依赖 MuJoCo需要申请 license。如果你不想折腾 license也可以换成 PyBullet 里类似的机械臂环境或者干脆自己写一个 2D 点导航环境来验证算法——反正 HER 的核心机制跟具体环境关系不大甚至在 2D 环境里更容易 debug。算法骨架我推荐用 DDPG不是因为 DDPG 好调而是因为它是 HER 论文的原生搭档参考资料多出了问题容易对照。如果你已经熟悉 TD3 或 SAC也可以直接把 HER 的 relabel 逻辑搬过去不会有任何冲突。我个人更建议新手从 DDPG HER 开始把机制跑通了再换更强的 base learner。3.2 核心代码逻辑与关键参数下面这段伪代码是 HER 最核心的部分也是我在项目里反复用到的一个模式。假设你已经在环境交互阶段收集了一条完整轨迹每条 transition 包含(obs, achieved_goal, desired_goal, action, reward, next_obs, next_achieved_goal, done)接下来要做的是重标记def hindsight_relabel(trajectory, k4, strategyfuture, relabel_prob0.5): trajectory: 一条完整 episode 的 transitions 列表 k: 每个 transition 额外生成的 hindsight 目标数量 strategy: future / final / episode / random relabel_prob: 对一个 transition 执行重标记的概率 relabeled [] horizon len(trajectory) for i, trans in enumerate(trajectory): # 保留原始目标确保真实任务不丢失 relabeled.append(trans) if np.random.rand() relabel_prob: continue for _ in range(k): if strategy final: j horizon - 1 elif strategy future: # 在 i 之后随机选一个时间步保证目标在“未来” j np.random.randint(i, horizon) elif strategy episode: j np.random.randint(0, horizon) else: # random j np.random.randint(0, horizon) new_goal trajectory[j][achieved_goal] # 重标记后的 reward 由环境自带的判定函数重新计算 new_reward compute_reward(new_goal, trans[achieved_goal]) new_trans dict(trans) new_trans[desired_goal] new_goal new_trans[reward] new_reward relabeled.append(new_trans) return relabeled这段代码有几个细节值得注意。第一compute_reward必须和环境的奖励函数保持一致一般是距离小于阈值就判成功这样重标记后的 reward 才可信。第二future策略里我取的j包括i本身也就是当前时刻的实际到达状态这在实操中也能用但多数情况下取j i效果更稳定。第三重标记后的done字段要谨慎处理尤其是稀疏奖励环境里虚拟轨迹很可能并没有真的结束所以通常把done强制设为 False避免触发 bootstrapping 的终止状态误判。训练主循环里每次从 replay buffer 采样一个 batch 时也要以一定概率对 batch 里的 transition 做同样的重标记而不是只在数据采集后处理一次。论文和大部分实现里这个概率取 0.5也就是说Q 网络每次更新大约一半数据来自原始目标另一半来自 hindsight 目标。这个比例我试过从 0.3 到 0.80.5 附近整体最稳过高会让策略偏向虚拟目标过低则稀疏奖励问题依旧。3.3 训练效果对比与参数调优心得用 DDPG HER 跑 FetchReach 这种相对简单的任务成功率通常在训练早期就能快速上升几千个 episode 之后就能冲到 0.9 以上。FetchPush 要难一些需要几万个 episode而 FetchSlide 和 PickAndPlace 则需要更长的时间和更细的超参调整。我自己的经验是如果某个任务在你的实现里跑了很久成功率还是 0优先检查重标记逻辑而不是急着调 actor 的学习率。超参建议直接抄我这份replay buffer 容量 1e6batch size 512actor 和 critic 的学习率都设 1e-3discount factor 0.98soft update 的 tau 取 0.05动作探索噪声用标准的高斯噪声并在训练中后期逐渐衰减。网络结构不需要太复杂两层 256 个神经元的 MLP 就够输入是observation和desired_goal拼接后的向量输出是动作。有一点很关键必须对状态和 goal 做归一化。Fetch 环境里机械臂关节角度和位置坐标的量纲不一样如果不归一化critic 的输入数值范围差好几个量级训练过程非常容易震荡。我在 PyBullet 环境里试过不归一化loss 直接飞上天。还有一个调参心得是HER 不是越使劲越好。k 值从 4 加到 16训练速度并没有线性提升反而 buffer 里虚拟目标占比太高增加了采样开销策略还容易陷入“盯着容易目标练手”的坏习惯。我的习惯是 k4 起步任务难度高再调到 8一般不会超过这个范围。4. 踩坑记录HER 不是万能药4.1 常见问题速查表我把自己和身边朋友踩过的坑整理成了一张表问题现象可能原因排查方向解决办法训练曲线完全不动成功率一直是 0重标记概率设成了 0或者 buffer 里原始轨迹占比过高检查 relabel 逻辑是否生效确认每个 transition 至少额外生成 4 个 hindsight 目标Q 值爆炸loss 出现 NaN状态和目标没做归一化打印 critic 输入数值范围对 observation 和 goal 分别做标准化策略只追虚拟目标真实任务成功率不涨relabel 概率太高或 k 太大统计 buffer 中虚拟目标占比把 relabel 概率降到 0.3~0.5k 控制在 4训练到中后期波动大已经学会的任务又忘了动作噪声没有衰减观察动作输出的方差训练进度过半后逐步线性降低噪声同一任务换随机种子后效果差异巨大稀疏奖励下随机性被放大多跑几个 seed 取中位数至少跑 5 个随机种子再做对比4.2 我踩过几次坑之后的体会第一次复现 HER 时我在一个小型 2D 导航任务上调试明明逻辑都对但智能体就是学不会。后来发现重标记后的 reward 我直接沿用了环境返回的reward没有重新调用compute_reward计算。因为环境返回的 reward 是针对原始目标的目标被替换后旧 reward 完全失效。这个问题特别隐蔽因为代码不报错训练也能跑就是成绩上不去。各位如果发现自己的 HER 实现“看起来没问题但就是没效果”第一个要去查的地方就是 reward 是否跟着目标一起重算。另一个让我印象很深的坑是done标志。在稀疏奖励任务里一旦智能体到达目标附近环境可能会判定 episode 结束。但重标记后的虚拟目标和真实轨迹的终止条件未必匹配如果你把原始的doneTrue直接带给虚拟 transitionQ 更新的 bootstrapping 就会出错导致价值估计严重偏差。我后来统一在重标记时把done置为 False只在最后一个真实目标达成时才置 True训练稳定性立刻好了不少。4.3 从 hindsight 到 forward thinkingHER 的边界与后续方向HER 再强也解决不了所有稀疏奖励问题。它最擅长的是这种场景智能体已经能在环境中“碰到”各种状态只是不知道这些状态可以作为目标来学习。但如果任务的探索空间实在太大比如机器人要从随机位置出发找到千里之外的稀有物体智能体连“碰运气碰不到”的时候HER 就无能为力了——你无法重标记根本不存在的状态。这种时候方向就变成了如何设计更聪明的探索策略比如在学习早期用课程学习逐渐提高目标难度或者用基于模型的 imagination 来生成虚拟数据。不过那已经是另一个故事了先把 HER 本身用好就已经能解决一大批实际问题。还有一个容易被人忽略的点HER 的思想并不仅限于目标条件强化学习。如果你在做一个多目标优化问题或者任何“同一个行为可以用多种标签描述”的场景都可以试着问一句我能不能从这次失败中提取出一个“成功”的角度这个思维模式才是 hindsight 留给我们的真正财富。我自己后来做项目时遇到某些稀疏信号的学习问题第一反应已经不是加奖励项而是先想想数据里有没有能重新解读的“事后目标”。写在最后的实操建议我个人在实际操作中的体会是HER 最厉害的地方不是它的公式而是它看待数据的态度。很多强化学习项目卡在稀疏奖励上第一反应是设计更精美的奖励函数结果越调越脏。其实不妨先停下来看看 replay buffer 里那些“失败”轨迹里面可能全是金子。最后再分享一个小技巧训练过程中定期打印一下 buffer 里原始目标和虚拟目标的比例如果虚拟目标占比超过 70%策略很可能已经失衡了。这时候不用着急清空 buffer只需把 relabel 概率调低一点比如从 0.5 降到 0.3然后继续训练真实任务的成功率通常会慢慢回涨。这个小技巧我在 FetchPush 和 PickAndPlace 上都验证过关键时刻能救回一个眼看要跑偏的训练过程。
返回列表