拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

HER算法:用后见经验回放破解稀疏奖励难题

HER算法:用后见经验回放破解稀疏奖励难题

先说一个我实际遇到过的情况:用随机策略去控制一个六轴机械臂,让它在桌面推动积木,目标是到达某个红点位置。在连续动作空间里,这个“随机撞上目标”的概率低到可以忽略——六个关节角度连续取值,稍微偏一点落点就天差地别。于是整个训练过程陷入一个怪圈:没有任何成功样本,奖励全是0,策略网络收不到梯度,不管迭代多少轮都在原地打转。这个怪圈有个专门名字,叫稀疏奖励问题(sparse reward problem),而今天要聊的 hindsight——更准确地说是 Hindsight Experience Replay(HER,后见经验回放)——就是专门用来撬开这个怪圈的方法。

“hindsight”中文通常译作“后见之明”,通俗讲就是“事后诸葛亮”。在决策科学里,事后复盘常被认为带有认知偏差,但在深度强化学习里,把它变成一种系统化的训练信号后,效果出乎意料地好。这个思路最早来自 2017 年 OpenAI 团队的论文,之后迅速成为多目标强化学习、机器人操作任务里最常用的技巧之一。顺带说一句,hindsight 这个词在工程监控领域也有一款同名工具,但今天只聊强化学习里的 HER,不展开那个方向。

这篇内容适合谁看?一个是对强化学习有一点基础、正在做稀疏奖励任务的开发者;另一个是想在机器人仿真环境里快速跑通目标导向任务的工程同学。读完你至少能理解 HER 为什么有效,怎么把它接进自己的训练代码,以及哪些坑我替你先踩过了。

1. 为什么强化学习会在稀疏奖励里卡死

1.1 稀疏奖励不是“没奖励”,而是“没梯度”

先说清楚什么是稀疏奖励。以机械臂推积木为例:任务目标是“把积木推到红点中心”,状态是机械臂关节角度和积木坐标,动作是六个关节的目标角速度。奖励函数往往写得很简单:

  • 积木与目标点的距离小于阈值,r = +1;
  • 否则,r = 0。

这个奖励定义很清楚,没有任何歧义。但问题在于:在几十万步随机探索里,策略几乎一次都碰不到那个 +1。于是经验池里全是 r = 0 的样本,时间差分误差虽然在算,可目标值也都是 0,Q 网络学到的全是“无论怎么做都没好处”,策略自然也就学不到东西。

用生活话讲,这就像一个学生做了两个月的题,但所有题批改结果都是零分,而且老师不告诉他离及格差多少,只给“过”或“没过”。他连往哪个方向改都不知道。稀疏奖励的本质不是“奖励少”,而是“学习信号里没有梯度方向”。

我早期做过一个实验,在没有 HER 的情况下训练一个推积木任务,训练了 100 万步,成功率恒为 0。查看每个 epoch 的平均奖励时,那条曲线是一条贴地的直线,一点起伏都没有。那一刻你会真切理解什么叫“梯度消失”在非神经网络层面的体现。

1.2 常见的破解套路,为什么总差点意思

面对稀疏奖励,教科书里通常给出的方案有四类,每类都有它的代价:

奖励塑形(reward shaping)是最直接的思路:把 r = 0/1 改成 r = -distance_to_goal,这样每一步都有了连续梯度。但麻烦在于,如果你把“距离缩短”作为奖励,策略很容易停在“靠近但不能完全到达”的局部最优;而且塑形函数的系数极其敏感,调不好就会出现绕着目标点打转却拿不到最终奖励的怪象。

课程学习(curriculum learning)也很常见:先让目标出现在离机械臂很近的位置,训练成功后逐步拉远。听起来合理,但你需要手动为每个任务设计“从易到难”的序列,任务一换,课程也得跟着换。这属于人工先验成本很高的方案。

内在奖励(intrinsic motivation)是另一条路,典型代表是 ICM、RND 这类“好奇心”机制,鼓励智能体探索未见过的新状态。这类方法有效,但状态空间一大,计算开销明显上升,而且有时会鼓励智能体沉迷于随机噪声区域。

演示学习(learning from demonstration)思路是直接给一些专家轨迹,让模型模仿。问题是这需要额外获取专家数据,在很多物理环境里,采集一条可用的示教轨迹并不比训练策略便宜。

这四类方案都有一个共同点:它们都在“如何产生更多有效探索”上做文章。而 HER 的思路完全不同——它不改探索,也不改奖励函数,它改的是“已经发生的失败经验”的标签方式。这个视角差,是它最聪明的地方。

2. HER 的核心思想:用“后见之明”给经验重新贴标签

2.1 失败轨迹里其实藏着大量“成功片段”

让我用一个更具体的例子展开。机械臂推积木的任务中,一次完整的 episode 可能是这样的:

  • 目标位置 g = (0.3, 0.2, 0.05);
  • 策略从初始状态出发,推了 50 步;
  • 积木最终停在 (0.15, 0.4, 0.05),离目标点差距很大;
  • 整条轨迹没有获得任何奖励。

如果以“原始目标 g”来标记这条轨迹,它就是一条彻底的失败样本,对训练没有任何正向贡献。但如果换个角度:积木虽然没到 (0.3, 0.2),可它确实从初始位置被推到了 (0.15, 0.4)。那么请问,如果目标一开始就是 (0.15, 0.4),这条轨迹是不是一条成功的轨迹?最后一步是不是就应该拿到 r = +1?

HER 做的就是这个事:把轨迹里“实际达到的状态”拿出来,作为新的目标,重新给这条轨迹的每一步计算奖励。原来那条零奖励的失败轨迹,经过 relabel 之后,至少最后一段变成了有正奖励的成功经验。这就是“后见之明”——我事先不知道目标在哪里,但事后我可以说,“既然你到了这里,那这次的目标就是这里”。

实际算法里,HER 并不会把整条轨迹的每一步都变成成功经验,只有真正到达新目标的那个时刻以及之后的状态才是 +1。但哪怕只是给训练注入一个正样本,也足以让 Q 网络尝到“甜头”,开始往正确方向调整。

2.2 为什么可以随便改目标?因为目标本来就是输入

很多第一次接触 HER 的人会问一个很关键的问题:把目标从 g 改成 g’,相当于篡改训练数据,这真的没问题吗?这不会让策略学到了错误的目标吗?

答案在于强化学习任务的定义方式。传统单目标任务里,目标被写在奖励函数里,是环境的一部分,改不得。但 HER 适用于 goal-conditioned 任务,也就是“给定一个目标,输出对应动作”的设定。在这类设定中,目标 g 不是环境属性,而是作为网络输入的一维向量存在:

  • 状态空间:state = concat(observation, g);
  • 策略:π(a | observation, g);
  • 奖励:r = 1{φ(s_next) 与 g 的距离 < δ}。

也就是说,目标 g 是条件信号,同一个状态转移可以搭配不同的 g 使用。比如一条“从 A 点推到 B 点”的状态转移序列,在目标为 C 时是失败样本,在目标为 B 时就是成功样本。HER 只是把原来那条经验从“g=C 的失败样本”重新打包成“g=B 的成功样本”,并没有改变状态转移本身的物理逻辑。

这个设计真正学出来的能力,不是“推到某个特定点”,而是“给定一个目标点,我能学会把积木推向它”的通用映射。这就像刷题时,你不仅学习“解某一道题”,而是通过大量题目学习“解某一类题”的方法。HER 的 relabel 相当于变相扩充了训练题目集,而且这些题目都是策略自己“做出来”的,不需要人工标注。

2.3 关键细节:新目标从哪里采样

目标可以改,但改成什么是有讲究的。HER 原始论文里对比了几种新目标来源,这也是我后来才发现差别极大的地方:

  • final:把轨迹的终点状态作为新目标;
  • future:在轨迹中当前时刻 t 之后的状态里随机选一个作为新目标;
  • episode:在同一个 episode 的未来状态里随机选;
  • random:从整个经验池的任意状态里随机选。

读论文时我觉得 final 和 future 应该差不多,但实际测试下来差异不小。final 的缺点是:一条轨迹只有一个终点,整条轨迹 relabel 后,只有最后几步能拿到正奖励,中间大部分经验仍然是零奖励,信息增益有限。而 future 策略可以在轨迹的不同位置多次采样,把中段的状态也变成潜在目标,相当于把一整条轨迹里的“中途成就”都挖掘出来了。

论文推荐的默认配置是 future 策略 + K=4。K 的含义是:每一条真实经验,额外生成 4 条 relabel 经验存入 buffer。也就是说,经验池里原始经验只占 1/5,relabel 经验占 4/5。这个比例听起来激进,但实验下来确实效果最好。我个人的经验是,如果训练任务的目标空间特别大,K=4 是稳健起点;如果目标空间比较小,K 减到 2 也能快速收敛,还能省内存。

3. 从想法到代码:HER 的实现细节

3.1 整体架构:无需改探索,只改经验处理

HER 最大的工程优势是侵入性很小。它的前提是训练算法本身是 off-policy 的,也就是有经验池的算法,比如 DQN、DDPG、SAC 都行。你不需要改策略网络结构里的探索部分,也不需要改 env 的奖励函数定义,只需要在“数据入池”时多存一份信息,在“采样训练”时多做一次目标替换。

完整的数据流是这样的:

  1. 策略在环境中跑一个 episode;
  2. 每一步都保存 observation、action、reward、next_observation、achieved_goal、done;
  3. episode 结束后,把整条轨迹的原始经验存入 buffer;
  4. 同时按 K 值,为这条轨迹生成额外 K 份 relabel 数据,也存入 buffer;
  5. 训练时从 buffer 里随机采样 batch,正常算 Q 更新。

在这个流程里,探索策略本身完全没变。这意味着你可以把 HER 当成一个“后处理插件”挂在任何 off-policy 算法后面。我第一次接进 DDPG 的时候,整个改动大约只有几十行,训练效果完全是另一个量级。

3.2 核心代码逻辑:relabel 怎么实现

我用 Python 伪代码说明最核心的两个函数。第一个是“判断是否到达目标”的奖励函数:

def compute_reward(achieved_goal, desired_goal, threshold=0.05): # 用欧几里得距离判断是否到达目标 dist = np.linalg.norm(achieved_goal - desired_goal) return 1.0 if dist < threshold else 0.0

注意这里的 achieved_goal 是从 observation 里分离出来的子向量。在机械臂任务里,observation 通常包含机械臂关节角度、积木位置、目标位置等多个信息,其中积木位置就是 achieved_goal,目标位置就是 desired_goal。一定要在环境设计时把这两个字段独立出来,否则后面无法做 relabel。

第二个是 relabel 的核心逻辑:

def relabel_trajectory(trajectory, original_goal, k=4): # trajectory 是 [(obs, action, achieved_goal, next_obs, done), ...] relabeled = [] for step_idx, transition in enumerate(trajectory): obs, action, achieved_goal, next_obs, done = transition # 原始经验:保留一份 relabeled.append({ "obs": obs, "action": action, "goal": original_goal, "reward": compute_reward(achieved_goal, original_goal), "next_obs": next_obs, "done": done }) # 额外生成 k 份 relabel 经验 for _ in range(k): # future 采样:从当前步之后的状态里随机选一个新目标 future_idx = np.random.randint(step_idx, len(trajectory)) new_goal = trajectory[future_idx].achieved_goal relabeled.append({ "obs": obs, "action": action, "goal": new_goal, "reward": compute_reward(achieved_goal, new_goal), "next_obs": next_obs, "done": done }) return relabeled

这里有一个非常容易踩的坑:relabel 时不仅要改 goal,还要重新计算 reward。很多人第一次实现时只改了 goal,忘了改 reward,结果经验池里出现大量“目标已经换了但奖励还是老的”的错误数据,训练直接乱掉。

另外,done 标志也需要注意。在稀疏奖励任务里,如果新目标是轨迹终点,那最后一步确实到达了目标,done 可以设为 True,用来启动自举(boostrapping),给 Q 网络一个明确的终止信号。但如果新目标是从中间状态采样的,后续还有几步才到终点,那 done 应该保持 False。最简单稳妥的做法是:除了最后一步到达“新目标”的情况,其余一律 done=False。我甚至建议在早期验证阶段直接强制所有 relabel 数据的 done 都是 False,先确认其他环节没问题再说。

3.3 超参配置:照着抄也能稳的版本

我把自己在仿真环境里验证过的一组配置列出来,环境是 OpenAI Gym 的 Fetch 系列任务,算法是 DDPG+HER:

参数项我的配置说明
replay buffer 大小1,000,000必须大,relabel 会放大经验数量,buffer 太小容易覆盖早期经验
batch size256适中即可,太大并不会显著加速
relabel 比例 K4论文默认,大多数任务都合适
新目标采样策略future比 final 稳定,中段信息利用充分
actor 网络结构三层 MLP,每层 256目标条件策略,输入 concat(observation, goal)
critic 网络结构三层 MLP,每层 256同样输入 concat(observation, goal, action)
动作噪声OU 噪声或高斯噪声,标准差 0.2稀疏奖励任务需要较长时间探索,噪声不能太小
优化器Adam,学习率 1e-3(critic)、1e-4(actor)critic 学习率略高一点收敛更稳
目标网络软更新系数0.05DDPG 常用范围 0.01~0.1
折扣因子 γ0.98稀疏奖励任务中建议略低,避免长期信用分配压力过大

有几个细节值得展开说说。第一,buffer 容量一定要给足。relabel 会把经验放大到原来的 K+1 倍,意味着 1e5 的 buffer 实际能存的有效原始经验只有 2 万条左右,稀疏奖励任务本身又需要大量探索样本,buffer 太小会频繁覆盖掉稀有的正样本。第二,我在 relabel 时有意识地保留了原始经验,而不是全部替换。虽然 relabel 经验对学习“达成任意目标”很有帮助,但原始目标经验也不能完全丢,否则策略在真正需要推到指定点的时候,会缺少“朝指定目标优化”的信号。保留原始经验的比例我通常控制在 1/(K+1) 左右,正好和 K=4 的设定匹配。

还有一个容易忽视的细节:目标向量和 achieved_goal 向量的维度必须一致,而且物理量纲最好也一致。如果你的目标是用三维坐标表示,而 achieved_goal 是机器人末端的四元数姿态,这两个向量就不能直接替换。我在一个任务里吃过这个亏:目标空间是位置坐标,网络输入里却拼接了姿态角,结果 relabel 出来的新目标在语义上根本不对应,训练怎么跑都不收敛。后来把所有目标统一成“位置坐标”后才正常。

3.4 奖励函数与状态空间的匹配问题

HER 能工作的前提是:环境的状态里必须显式包含“当前任务达成到什么程度”的信息,也就是 achieved_goal 必须从 observation 中完整读取出来,且不能有歧义。

举个例子:判断一个积木是否到达目标位置,你需要知道积木当前的位置坐标,这在 observation 里通常是现成的。但如果你想让机械臂学习“把瓶盖拧紧”,achieved_goal 应该是“瓶盖当前旋转了多少弧度”,如果 observation 里只有一个 RGB 相机图像,那你就得先训练一个感知模块从图像里估计瓶盖状态,HER 的 relabel 才能做下去。感知误差会直接污染 relabel 后的目标,这是很多真实机器人任务里 HER 效果打折的原因之一。

所以在设计状态空间时,我的建议是:尽可能把任务相关的低维状态量直接暴露给策略。不要痴迷于端到端从像素学习,在大部分控制任务里,先用真值状态把控制逻辑跑通,再考虑感知部分,工程上要稳得多。

4. 实测效果与踩坑记录

4.1 加了 HER 之后,训练曲线到底长什么样

我在 MuJoCo 的 Fetch 系列环境里做过几组对比实验,不带 HER 的 DDPG 在 FetchReach 和 FetchPush 上基本不收敛,成功率曲线是一条贴地的直线。这话我说得很绝对,因为事实就是如此:随机探索撞不上目标,没有正样本,Q 网络学不到东西。

接入 HER 之后,曲线形态非常有意思,不是平滑上坡,而是“长平台+跳变”的结构。一开始很长一段时间成功率还是 0,但经验池里 relabel 出来的正样本在悄悄积累。然后某个时刻突然出现一些成功率大于 0 的试探性尖峰,接着成功率在几十个 epoch 内抬升到一个平台。这个“突然抬升”的阶段,正是 Q 网络第一次理解到“目标距离越近,价值越高”的时刻。

如果你画的曲线长时间没有任何跳变迹象,先别急着调网络结构,大概率是 relabel 逻辑有 bug,或者 achieved_goal 字段提取错了。我个人会先用 FetchReach 这种简单任务做代码验证,它只有单步到达目标,没有物体交互,跑通很快,能快速排除大部分实现问题。

4.2 六个常见问题速查表

我在反复折腾 HER 的过程中,整理了一张排查表,分享出来可能帮你省几天时间:

现象可能原因排查与解法
加了 HER 依然完全不收敛relabel 时没有重新计算 reward,或 achieved_goal 与 goal 维度不匹配单独写一个 test 函数,打印 relabel 前后某条数据的 goal 和 reward 是否匹配
训练前期震荡特别大buffer 太小,正样本被过早覆盖加大 buffer,优先保证原始经验越多越好
前期能涨但总是卡在某个成功率上不去K=4 的 relabel 经验太多,挤占了原始目标经验把 K 降到 2,或者保留更多原始经验的比例
偶尔出现一波成功率高,但随即跌回 0稀疏奖励天然会有较大的方差用滑动平均看趋势,不要用单次 epoch 的最高值判断效果
同样的配置换一个种子结果差异巨大稀疏奖励下随机种子影响非常敏感每个配置至少跑 5 个种子,修炼“多试几次”的心态
用 SAC+HER 时训练不稳定SAC 自动调节熵的机制和 relabel 后的目标分布有交互可以临时固定熵系数 α,先验证 HER 本身是否工作正常

第二个问题我特别有体会。在一组实验里我把 buffer 设成 1e5,结果训练前期出现了“刚刚学到一点、马上忘掉”的反复,后来把 buffer 加到 1e6 才稳住。经验池大小在普通连续控制任务里可能没那么敏感,但稀疏奖励任务里,正样本太稀缺,任何一点有效经验都非常宝贵。

4.3 关于实操心态的几个建议

第一,HER 不是万能药。它解决的是“经验标签稀疏”的问题,如果探索方向本身就完全错误——比如机械臂往远离目标的方向运动,而动作空间又限制了它无法回头——HER 也救不了你。这种情况下要回头检查状态表示、动作空间设计,或者引入一些先验知识来约束初始探索方向。

第二,验证 HER 代码时,不要一上来就跑大任务。先用一个你能用肉眼判断正误的简化环境:比如一个 2D 点形机器人,环境只有一个点需要到达目标位置。如果连这种环境都不收敛,说明代码有问题,而不是任务太难。我在一个高维任务上足足排查了两天,最后发现只是 future_idx 的采样范围写错了,在单点环境里这种 bug 一眼就能看出来。

第三,训练过程中建议定期保存模型,并且把每个阶段的 checkpoint 都留一份。HER relabel 后的经验池会让模型在训练中后期变化很快,有时候一个 checkpoint 看起来快收敛了,但继续训练一段后反而变差。有备份的话,可以回退到最好的版本继续微调,而不是从头再来。

我自己在机器人操作任务里的整体感受是:HER 是我遇到过的,性价比最高的稀疏奖励解决方案之一。它不要求你设计复杂的探索策略,也不依赖专家数据,只需在数据流上做一层“事后改写”,就能把大量看似无用的失败轨迹变成训练燃料。这种思路本身也值得记在心里:有时候问题解决不了,不是缺新数据,而是旧数据里有太多被浪费掉的信息。

返回列表