☰
事后经验回放HER:用失败轨迹破解强化学习稀疏奖励难题
2026/10/2 22:30:49 网站建设 项目流程

hindsight这个词,直译过来就是“后见之明”,说难听点叫“事后诸葛亮”。生活里到处都是它的影子:股票涨了有人喊“我早就看出来了”,考试出分有人懊恼“那道题我明明会做”。心理学家专门给这种倾向起过一个名字——“后见之明偏差”(hindsight bias),说的是人在结果揭晓之后,总会下意识高估自己事前预判的准确度。

但今天我想聊的不是心理学,而是“事后诸葛”这个概念如何被人工智能研究者捡起来,变成强化学习里一个非常好用的算法基石——Hindsight Experience Replay,简称HER,一般翻译成“事后经验回放”。如果你和我一样,第一次听到“把失败轨迹重新标成成功样本”这个思路时,脑子里冒出来的第一句话大概率是:这也能行?答案是,不仅能行,而且在多目标稀疏奖励任务里往往是最有效的解法之一。这篇内容我会从思路来源、算法机制、代码实现到调参踩坑,完整拆一遍。不管你是正在做机器人控制、游戏AI,还是单纯想搞懂强化学习里最巧妙的设计之一,这篇文章应该都能给你一点启发。

1. “hindsight”灵感从哪里来:认知偏差与算法思想的碰撞

1.1 人类认知里的后见之明偏差

在认知科学里,hindsight bias是个非常经典的课题。

研究者做过不少实验:先让参与者估一个事情的发生倾向,过段时间告诉ta真实结果,再让ta回忆自己当初是怎么判断的。结果大部分参与者都会“记错”,把自己当初的判断往真实结果的方向靠拢。这不是记忆力差,而是大脑在整理记忆时,会拿已知结果去重新“校准”过去的判断。

这个现象常年被当成认知缺陷来讨论,因为它会污染复盘的真实性:一旦结果揭晓,你就很难老老实实承认自己当时是猜的。但只要稍微换一个角度,这个“缺陷”背后藏着一个极其强大的能力——大脑天然善于利用结果信息来总结经验。不管过程多混乱,结果一出来,我们就能围绕结果快速组织出解释和记忆。这种“拿最终结果反哺过程经验”的机制,恰好是强化学习模型最初非常缺的东西。

我刚接触强化学习的时候,折腾过一个栅格找球问题:智能体在一个100x100的网格里,要走到一个随机位置。因为目标每次都在一个很远的角落,模型训练了很久,奖励曲线都是平的。当时第一反应是“加大学习率、换网络”,折腾好几天没什么用。后来才意识到,问题根本不是优化器不够强,而是模型从头到尾没收到过几次正反馈,它连“什么算是好”都没见过,怎么可能学得会?

1.2 强化学习里的稀疏奖励是训练启动的头号阻碍

强化学习的标准玩法,是让智能体通过和环境不断交互,最大化累计奖励。任务目标通常很直白:走到某个坐标、把物体推到指定位置、赢得一局对局。

问题在于,很多任务的目标达成条件极其苛刻。拿机器人抓取来举例,目标是机械臂末端到达某个精确坐标,坐标每回合随机变化。如果奖励函数写成“到达才算成功”,训练初期智能体会经历非常长的“双重黑”阶段:一是它不知道怎么动,二是就算动了,也不知道动得对不对,因为反正每一步都是0分。这种状态一旦持续几百上千个episode,标准的梯度下降几乎找不到任何有用的学习信号。

我试过在仿真环境里观察这个过程:随机策略跑出来的轨迹,回放池里塞满了“失败样本”,每一条的reward都是0。此时你去训练Q网络,它的loss其实很快就降下来了——因为所有目标值都是0,只要把预测输出都压到0附近,loss自然就小了。但这根本没有带来任何策略进步,预测值全部趋同,动作选择等于乱猜。这里最隐蔽的坑是:很多人看到loss下降就觉得模型在进步,实际上一丁点策略变化都没有。这也是稀疏奖励问题特别能“骗人”的地方。

1.3 反向用“事后视角”:失败轨迹里也能挖出成功样本

HER的出发点,是一条很朴素但反直觉的想法。这一局我定的目标是A,结果智能体从初始状态出发,折腾了半天也没到A,最后停在了B点。那么,这条轨迹对目标A来说是失败的,但换个视角,它对“目标B”来说,是不是刚好就是一条成功的完整轨迹?智能体从起点出发,一步步调整,最终落在了B点附近——这正是“到达B”这件事的标准示范。

那我为什么不把这条轨迹存进经验池时,顺手给它打上“目标B”的标签,把奖励改写成“成功到达B=1”呢?这样一来,一条原本毫无学习价值的失败轨迹,摇身一变就成了正样本。

这个过程用人类的“后见之明”来理解非常顺:虽然没达成原计划,但如果过程中意外完成了别的目标,那这段经历同样值得记下来。我自己练投篮就是这样,一开始怎么投都不进,每一次出手的落点其实都在告诉我“用力、角度和落点之间是什么关系”。只不过人脑会自动吸收这些隐含信息,而机器人需要研究者专门设计一个机制,把这些“落点信息”显式地标成目标喂给学习算法。

2. HER算法的机制拆解与选型逻辑

2.1 普通经验回放在零奖励环境里的困境

聊HER之前,必须把经验回放(Experience Replay)的基本机制说清楚。它是DQN时代引入的经典设计:把交互中产生的四元组(状态、动作、奖励、下一状态)存进回放池,训练时随机采样一小批,打破样本之间的时序关联。回放池就像一个错题本,模型每天随机翻几道旧题出来复习。

这个设计在奖励较密集的环境里很好用。但放到稀疏奖励场景,错题本里全是“0分题”。无论怎么随机抽样,样本给模型传递的信息都趋同:反正做什么都没奖励。Q值的更新方向会变得很平滑,却没有意义,模型根本无法建立起“状态-动作-结果”的因果关系。

有人会问:那把奖励函数改宽松点不就行了?比如把“到达才给1分”改成“距离目标越近分越高”。这确实是一种缓解办法,但它有一个代价:奖励塑形本身是一门玄学,尺度、函数形态、奖励范围都会影响训练收敛性。而且塑形奖励很容易带偏策略——智能体可能找到一堆刷分的捷径,而不是真正完成目标。HER提供的是另一种思路:不调环境、不改奖励,只改造经验池里的样本标签,把错题本里的一部分“废题”改写成“好题”。

2.2 目标重标记:HER的核心操作

HER的全称是Hindsight Experience Replay,核心操作就两步。

第一步,正常rollout一整条轨迹,把状态转移序列和动作序列完整记下来。第二步,在把这批样本写入经验池之前,用“事后视角”给轨迹指定一个新目标goal'。这个goal'通常取轨迹实际到达过的某个状态,比如终点状态,或者未来某一步的状态。随后用这个新目标重新计算每一步的奖励,得到一批“重标记样本”。

举例最直观:一个机械臂推木块,目标是推到坐标(1.2, 3.4),结果木块最后停在(2.0, 3.1)。原始轨迹里每步奖励都是0;但把目标改成(2.0, 3.1)后,轨迹末段的几个状态恰好落在这个目标的有效邻域内,于是那些步骤的奖励变成了1。整条轨迹从“废品”变成了“含金量不错的样本”。

需要注意一个前提:HER要求任务必须是多目标设定的。也就是说,环境里除了状态空间和动作空间,还存在一个可描述的goal space,奖励函数能表示为r(s, a, goal)。如果你是单一固定目标的任务,比如不管怎么重置小车永远只往同一个位置开,那轨迹实际到达状态就等于固定目标本身,重标记没有意义,甚至会把样本引向错误方向。

2.3 四种目标采样策略怎么选

HER论文里给出了四种选择新目标的方式,我直接用表整理出来:

策略重标记目标的来源适用情况
final取轨迹最终状态实现最简单,轨迹较短、目标空间较集中时够用
future取当前时刻未来k步内的某个状态最推荐,多样性和可达性平衡得最好
episode取本回合内任意随机状态样本多样性高,但可能造出难度过高的假目标
random从整个目标空间随机采样覆盖整个目标空间,但前期训练难度大

我自己在项目里最常用的是future策略加k=4,兼顾了样本的时间跨度和目标可达性。final策略的问题是会形成一个“取样偏差”:大量重标记样本的目标都集中在轨迹终点附近,导致目标空间里离起点近的区域被过度学习,远处的目标覆盖不足。episode策略虽然多样性上来了,但随机抽到的目标可能离当前状态太远,生成的样本学习价值不高。random就更极端了,相当于让模型一上来就学一张全目标空间的“世界地图”,对初始阶段的Q值估计来说负担偏重。

顺带说一个经验:k值不是越大越好。我试过k=8和k=16,某些环境里训练反而变慢。因为k越大,重标记目标与当前时刻的关联越弱,样本的有效性下降。具体k值建议从4起步,结合目标空间大小和单回合步数来调。

2.4 哪些任务适合HER,哪些不适合

HER的优点很突出,但它有明确的适用范围,硬上往往会踩坑。

适合的任务有三个硬性条件:第一,多目标设定,存在可描述的goal space;第二,奖励可以根据goal快速重算,否则重标记每一条样本的代价太高;第三,底层算法是off-policy的,比如DQN、DDPG、TD3、SAC,因为HER依赖经验回放池。on-policy算法(比如A2C、PPO的标准版本)每次采样策略都会变化,历史轨迹的重标意义会打折扣,需要额外改造才能用。

不适合的场景也要说清楚:如果目标空间极度不规则,或者真实目标分布和轨迹状态分布差得离谱,那重标记出来的“伪目标”可能并不反映真实任务分布,反而引入偏差。我遇到过一个真实案例:在仿真环境里HER效果非常好,但把同一套流程搬到真实机械臂上就崩了。原因很简单,仿真里的目标空间是连续平滑的,而真实平台的目标采样带一些离散约束,重标记出的目标在真实环境中根本不可达,策略学了一堆花架子。后来把重标记目标限制在轨迹实际访问过的状态集合里,才算稳定下来。这个细节论文里不明显,但工程上非常关键。

3. 手写一个HER最小demo:从零到出效果

3.1 一个随机目标的一维移动问题

理论讲再多,不如动手跑一个例子。我用一个极简环境演示HER的原理:一维坐标,长度101个格子,小球初始位置在50。每回合从0到100之间均匀抽11个离散目标之一,作为本回合的目标点。小球每步可以选择向左一格、原地不动、向右一格,如果小球位置与目标位置的距离不超过2格,奖励1分,否则0分。单回合最多100步。

这个环境把稀疏奖励和多目标两个痛点都浓缩了:成功条件苛刻,目标随机变化。随机策略下的成功率非常低,绝大多数轨迹完全拿不到正奖励。更妙的是,目标空间和状态空间在这个例子里是统一的,重标记的目标就是“一个真实存在过的位置”,可达性天然有保证。

这里把“目标”和“状态”设计成同一种数据,是HER最常见也最舒服的一种用法。实际机器人场景里,goal往往是“物体位置”或“末端位置”,而state里还会包含速度、关节角等额外变量,但核心逻辑一模一样。为了演示方便,我一维空间里让它们重合了。

3.2 不带HER:奖励全零,loss下降但策略不动

先用普通DQN在这个环境上跑,注意观察两个指标:一个是loss,一个是成功率。

我先把核心网络定义列出来。状态是一个位置的标量,目标也是一个标量,输入神经网络前并成一个两维向量,网络输出三个动作(左、停、右)各自的Q值。

import torch import torch.nn as nn class QNet(nn.Module): def __init__(self): super().__init__() self.net = nn.Sequential( nn.Linear(2, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 3), ) def forward(self, state, goal): x = torch.cat([state.float(), goal.float()], dim=-1) return self.net(x)

训练循环里,每回合正常采样轨迹,把(s, goal, a, r, s_next)存入回放池,随机抽batch更新Q网络。在强化学习流程里写得多了以后,你会发现代码结构都差不多,真正改变命运的往往就是“样本进池子之前做了什么”。

不带HER跑到3000回合,最典型的现象是:loss很快就降到一个贴近0的低位,但成功率始终上不去,甚至因为探索率衰减,策略开始固定在某个无意义动作上,成功率比随机策略还要差。原因我在2.1已经说过——回放池里全是零奖样本,模型唯一学到的事情就是“把所有Q值输出到接近0”。这里给大家提个醒:训练曲线里loss下降不等于策略变好,一定要同时看成功率/累计奖励这类更有意义的指标。

3.3 HER重标记函数:成败就在这几十行

接下来是HER的核心,重标记函数。我在完整demo里抽出的关键代码是这样:

import numpy as np def reward_for(x, goal): return 1.0 if abs(x - goal) <= 2 else 0.0 def her_relabel(transitions, original_goal, strategy="future", k=4): """ transitions: 列表,每个元素是 (s, a, s_next) original_goal: 本回合真实目标 """ T = len(transitions) samples = [] for i, (s, a, s_next) in enumerate(transitions): # 原始目标样本:保留 samples.append((s, original_goal, a, reward_for(s_next, original_goal), s_next)) # 事后重标记样本:另选一个目标并重算奖励 if strategy == "final": g_new = transitions[-1][2] # 终点状态 elif strategy == "future": upper = min(T, i + 1 + k) # 从未来k条转移里抽 j = int(np.random.randint(i + 1, upper)) if i + 1 < upper else T - 1 g_new = transitions[j][0] # 取该转移的起点状态 elif strategy == "episode": j = int(np.random.randint(0, T)) g_new = transitions[j][0] # 本回合内任意状态 else: g_new = float(np.random.choice([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100])) samples.append((s, g_new, a, reward_for(s_next, g_new), s_next)) return samples

这几十行代码,就是HER的全部灵魂。注意这里的每个episode都会产出两倍数量的样本:一份保留原始目标,一份用重标记后的目标生成。让模型同时看到“原任务的失败”和“变体任务的成功”,它才能在对比中学到真正的因果结构。

future策略里,我取的是未来某条转移的起点状态作为新目标。当抽到i+1这条转移时,g_new恰好等于s_{i+1},那么当前样本的奖励立刻变成1,正样本就这样无中生有了。整条轨迹越往后,越容易命中这种“短距离目标”,所以轨迹末端会生成一片高质量正样本,这正是HER能带动稀疏奖励训练的关键。

采集轨迹时记得记录完整的转移序列,而不要只记录奖励。因为在重标记时要重新算奖励,原始奖励已经没用了,状态转移才是原材料。

3.4 训练曲线与我的参数建议

加上HER之后,同一套DQN,只在样本入池前多调用一次her_relabel,训练行为会发生肉眼可见的变化。我用这个demo跑出来的典型趋势是:前一两百回合正样本比例逐渐增加,随后Q值开始出现明显的分化,成功率曲线从底部慢慢抬起,最终稳定在一个远高于随机策略的水平。整个过程没有改动任何奖励函数,也没有增加任何手工知识,只靠“改标签”就把训练盘活了。

这里给出一组我调试时踩出来的参数基线,适合这类小规模离散环境作为起点:

参数建议值备注
经验池容量50000用collections.deque实现,满了从左侧弹出
batch size256太小不稳定
k值4目标空间100格时比较稳
探索率1.0到0.02线性衰减衰减周期约2000回合
目标网络更新每500步软更新或硬拷贝DQN必备
HER重标记比例每条轨迹生成1倍重标记样本和原始样本等比例入池

还有一个常被问到的问题:既然重标记生成的样本这么好,干脆只存重标记样本,不存原始样本行不行?我的建议是不要。原始样本保留了“真实目标”的分布信息,如果只重标记,模型会忽略真实目标空间中那些远离轨迹的区域,造成目标覆盖偏差。两倍样本、两边都要,是HER论文及大多数工程实现默认的方案,这个平衡不建议打破。

4. 实战中容易踩的坑与排障经验

4.1 HER不收敛,先检查这三件事

如果加了HER训练还是不动,先不要怀疑算法本身,按照下面顺序排查:

第一,确认环境是不是真的多目标。很多任务看起来有随机目标,但奖励函数里并没有显式使用goal来计算,重标记后奖励根本不变,那HER等于白加。快速验证方法:随机取一条轨迹,把目标改成轨迹终点,手动重算奖励,看最后几步是不是变成了1,如果不是,说明goal和reward的关联没接好。

第二,确认采样策略和k值是否得体。如果重标记后正样本比例仍然极低,你可以在训练代码里加一个计数器,统计每次采样时reward为1的样本占比。我见过有些项目里这个比例连1%都不到,那说明目标空间离轨迹可达区域太远,此时可以换final策略,或者增大k值。反过来,如果正样本比例一开始就高达30%以上,说明任务其实没那么稀疏,优先检查是不是奖励阈值设得太宽了。

第三,确认on-policy/off-policy的匹配。如果你用的是REINFORCE或者A2C这类on-policy算法,HER的重标记目标会和当前策略的行为分布不一致,效果大打折扣。这时候要么换成DQN/DDPG/SAC等off-policy算法,要么做额外的importance sampling修正,但后者实现成本很高,我一般不推荐新手碰。

4.2 采样策略、目标合理性带来的隐蔽问题

HER看起来简单,但工程实现里有几个隐蔽的坑,我踩过,也帮别人排查过。

第一个隐蔽坑是“重标记目标不等于实际可达目标”。在仿真环境里,目标空间和状态空间通常完全重合,重标记出的任何位置理论上都可达。但真实系统里,goal往往是“物体的期望位置”,而状态里的物体实际位置会受到物理约束、障碍物、关节限位的影响。如果你把轨迹终点直接当新目标,这个目标在物理上可能并不可达,模型学出来的策略就会带着“虚拟目标”的偏见,部署时立刻失效。解决方法就是我在2.4提到的:把重标记目标限制在轨迹实际访问过的状态集合内,或者加一个物理可行性检查。

第二个隐蔽坑是“重标记样本和原始样本在回放池里的比例失衡”。如果你的her_relabel函数被调用了两次,比如在一段完整episode里又对折半子序列分别重标了一次,那么重标记样本可能占主导,模型会被“事后视角”带跑偏。建议固定比例,比如每条轨迹原始样本和重标记样本1:1,不要贪多。

第三个坑和目标编码方式有关。很多任务里goal只是状态的一部分,比如状态是(物体位置, 物体速度, 机械臂关节角),目标却只关心物体位置。要是直接把状态整体当goal,模型要学的东西是“整个高维状态的轨迹分布”,难度陡然上升,效果反而不如只取目标子空间。我在项目里一般会单独写一个字典/映射来区分state和goal,防止数据维度揉在一起。

4.3 从仿真到真实平台:重标记目标必须可达到

前面我已经提过仿真和真实平台的差别,这里再展开讲一次,因为这是我个人印象最深的一次翻车经历。

当时做一个双臂协作推箱子的项目,仿真阶段一切顺利,成功率做到了90%以上。迁移到真实平台后,头几百次部署测试就暴露了问题:机械臂经常摆出一些奇奇怪怪的姿态,明明是在往一个看似正确的目标移动,但轨迹末端总会卡在关节极限附近,要么就是目标点被另一个臂挡住了。后来定位到根因,真机训练时经验池里很多重标记样本的目标来自仿真离线轨迹,这些目标在真实环境中由于臂间碰撞而不可达,策略却在努力朝这些“鬼目标”靠近。

处理办法是加一条约束:重标记目标必须满足真实环境的碰撞检测和关节限位检查,不满足的直接丢弃。同时把future策略的k值从4降到2,因为真实平台轨迹更嘈杂,远期状态作为目标的可靠性下降。这两处调整之后,真机成功率重新回到85%以上。这个案例给我的教训是:HER在仿真里表现好,不代表在真实环境里直接照搬就安全,凡是重标记目标,都要做“可达性复审”。

4.4 调试技巧:监控正样本比例

最后分享一个调试技巧,也是我每次跑HER必做的一件事:在训练循环里加一个指标,实时统计每个批次中reward为1的样本占比。

这个比例是杠杆点。如果它长期接近0,说明训练信号没起来,问题大概率在采样策略或目标空间设计;如果这个比例在训练中后期逐渐走高,说明模型确实在学会“靠近目标”的技能,重标记机制在起作用。我一般把它和成功率画在同一张图里,两个曲线的先后关系能帮我看清楚HER是“先产生信号再带动成功率”,还是“信号不产生、成功率也一直被压着”。

另外,我还习惯把重标记出来的正样本单独打一个logging标记,抽样打印出对应的s、goal'和s_next。用肉眼确认这些正样本不是“贴脸生成”的假样本,而是有意义的中间状态。这一步看起来笨,但在复杂环境里往往能救回好几个小时的调试时间。

这篇文章写到这里,我最后想说的是,HER真正教会我的其实不只是那一套重标记函数,而是一种处理“失败数据”的思维方式:不要急着扔掉没有目标的轨迹,先想一想,这些数据在什么目标下可能是有价值的。这种思路现在已经延伸到许多方向,比如模仿学习里做轨迹标注、离线强化学习里做伪目标数据增强,甚至在一些推荐系统的冷启动策略里也能看到“事后视角”的影子。hindsight这个词在心理学里带着点自嘲的意味,但在强化学习这条技术路线上,它正经是一个能把稀疏奖励盘活的利器。如果你手头正好有一个“多目标+奖励稀疏”的任务卡着没进展,不妨按这篇文章的步骤,先实现一个最小demo,再逐步加大环境复杂度。我自己的体验是:第一次亲眼看到假样本数量从0涨起来的时候,你真的会相信,失败里面确实藏着金子。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询